diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 0000000000000..1171d75845cba --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,6 @@ +# Apply php-cs-fixer fix --rules nullable_type_declaration_for_default_null_value +f4118e110a46de3ffb799e7d79bf15128d1646ea +9519b54417c09c49496a4a6be238e63be9a73465 +ae0a783425b80b78376488619bf9106e69193fa4 +9c1e36257c4df0929179462d6b2bdd00453ac8aa +6ae74d38e3d20d0ffcc66c7c3d28767fab76bdfb diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index be833bfec1a14..00a24cbcfc13c 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,6 +1,6 @@ | Q | A | ------------- | --- -| Branch? | 7.1 for features / 5.4, 6.3, 6.4, or 7.0 for bug fixes +| Branch? | 7.1 for features / 5.4, 6.4, or 7.0 for bug fixes | Bug fix? | yes/no | New feature? | yes/no | Deprecations? | yes/no diff --git a/.github/expected-missing-return-types.diff b/.github/expected-missing-return-types.diff index e477abe0fa71b..36cf1d6177550 100644 --- a/.github/expected-missing-return-types.diff +++ b/.github/expected-missing-return-types.diff @@ -22,7 +22,7 @@ diff --git a/src/Symfony/Component/BrowserKit/AbstractBrowser.php b/src/Symfony/ */ - abstract protected function doRequest(object $request); + abstract protected function doRequest(object $request): object; - + /** @@ -451,5 +451,5 @@ abstract class AbstractBrowser * @throws LogicException When this abstract class is not implemented @@ -146,21 +146,21 @@ diff --git a/src/Symfony/Component/DependencyInjection/Extension/ExtensionInterf */ - public function load(array $configs, ContainerBuilder $container); + public function load(array $configs, ContainerBuilder $container): void; - + /** @@ -37,5 +37,5 @@ interface ExtensionInterface * @return string */ - public function getNamespace(); + public function getNamespace(): string; - + /** @@ -44,5 +44,5 @@ interface ExtensionInterface * @return string|false */ - public function getXsdValidationBasePath(); + public function getXsdValidationBasePath(): string|false; - + /** @@ -53,4 +53,4 @@ interface ExtensionInterface * @return string @@ -249,35 +249,35 @@ diff --git a/src/Symfony/Component/Form/FormTypeInterface.php b/src/Symfony/Comp */ - public function getParent(); + public function getParent(): ?string; - + /** @@ -34,5 +34,5 @@ interface FormTypeInterface * @return void */ - public function configureOptions(OptionsResolver $resolver); + public function configureOptions(OptionsResolver $resolver): void; - + /** @@ -48,5 +48,5 @@ interface FormTypeInterface * @see FormTypeExtensionInterface::buildForm() */ - public function buildForm(FormBuilderInterface $builder, array $options); + public function buildForm(FormBuilderInterface $builder, array $options): void; - + /** @@ -66,5 +66,5 @@ interface FormTypeInterface * @see FormTypeExtensionInterface::buildView() */ - public function buildView(FormView $view, FormInterface $form, array $options); + public function buildView(FormView $view, FormInterface $form, array $options): void; - + /** @@ -85,5 +85,5 @@ interface FormTypeInterface * @see FormTypeExtensionInterface::finishView() */ - public function finishView(FormView $view, FormInterface $form, array $options); + public function finishView(FormView $view, FormInterface $form, array $options): void; - + /** @@ -95,4 +95,4 @@ interface FormTypeInterface * @return string @@ -324,21 +324,21 @@ diff --git a/src/Symfony/Component/HttpKernel/Bundle/BundleInterface.php b/src/S */ - public function boot(); + public function boot(): void; - + /** @@ -35,5 +35,5 @@ interface BundleInterface * @return void */ - public function shutdown(); + public function shutdown(): void; - + /** @@ -44,5 +44,5 @@ interface BundleInterface * @return void */ - public function build(ContainerBuilder $container); + public function build(ContainerBuilder $container): void; - + /** diff --git a/src/Symfony/Component/HttpKernel/DataCollector/DataCollector.php b/src/Symfony/Component/HttpKernel/DataCollector/DataCollector.php --- a/src/Symfony/Component/HttpKernel/DataCollector/DataCollector.php @@ -356,9 +356,9 @@ diff --git a/src/Symfony/Component/HttpKernel/DataCollector/DataCollectorInterfa @@ -28,5 +28,5 @@ interface DataCollectorInterface extends ResetInterface * @return void */ -- public function collect(Request $request, Response $response, \Throwable $exception = null); -+ public function collect(Request $request, Response $response, \Throwable $exception = null): void; - +- public function collect(Request $request, Response $response, ?\Throwable $exception = null); ++ public function collect(Request $request, Response $response, ?\Throwable $exception = null): void; + /** @@ -35,4 +35,4 @@ interface DataCollectorInterface extends ResetInterface * @return string @@ -383,21 +383,21 @@ diff --git a/src/Symfony/Component/HttpKernel/KernelInterface.php b/src/Symfony/ */ - public function registerContainerConfiguration(LoaderInterface $loader); + public function registerContainerConfiguration(LoaderInterface $loader): void; - + /** @@ -44,5 +44,5 @@ interface KernelInterface extends HttpKernelInterface * @return void */ - public function boot(); + public function boot(): void; - + /** @@ -53,5 +53,5 @@ interface KernelInterface extends HttpKernelInterface * @return void */ - public function shutdown(); + public function shutdown(): void; - + /** diff --git a/src/Symfony/Component/Routing/Loader/AttributeClassLoader.php b/src/Symfony/Component/Routing/Loader/AttributeClassLoader.php --- a/src/Symfony/Component/Routing/Loader/AttributeClassLoader.php @@ -414,7 +414,7 @@ diff --git a/src/Symfony/Component/Routing/Loader/AttributeClassLoader.php b/src */ - abstract protected function configureRoute(Route $route, \ReflectionClass $class, \ReflectionMethod $method, object $annot); + abstract protected function configureRoute(Route $route, \ReflectionClass $class, \ReflectionMethod $method, object $annot): void; - + /** diff --git a/src/Symfony/Component/Security/Core/Authentication/RememberMe/TokenProviderInterface.php b/src/Symfony/Component/Security/Core/Authentication/RememberMe/TokenProviderInterface.php --- a/src/Symfony/Component/Security/Core/Authentication/RememberMe/TokenProviderInterface.php @@ -424,21 +424,21 @@ diff --git a/src/Symfony/Component/Security/Core/Authentication/RememberMe/Token */ - public function loadTokenBySeries(string $series); + public function loadTokenBySeries(string $series): PersistentTokenInterface; - + /** @@ -35,5 +35,5 @@ interface TokenProviderInterface * @return void */ - public function deleteTokenBySeries(string $series); + public function deleteTokenBySeries(string $series): void; - + /** @@ -44,5 +44,5 @@ interface TokenProviderInterface * @throws TokenNotFoundException if the token is not found */ - public function updateToken(string $series, #[\SensitiveParameter] string $tokenValue, \DateTimeInterface $lastUsed); + public function updateToken(string $series, #[\SensitiveParameter] string $tokenValue, \DateTimeInterface $lastUsed): void; - + /** @@ -51,4 +51,4 @@ interface TokenProviderInterface * @return void @@ -485,7 +485,7 @@ diff --git a/src/Symfony/Component/Translation/Extractor/ExtractorInterface.php */ - public function extract(string|iterable $resource, MessageCatalogue $catalogue); + public function extract(string|iterable $resource, MessageCatalogue $catalogue): void; - + /** @@ -36,4 +36,4 @@ interface ExtractorInterface * @return void @@ -501,7 +501,7 @@ diff --git a/src/Symfony/Component/Validator/ConstraintValidatorInterface.php b/ */ - public function initialize(ExecutionContextInterface $context); + public function initialize(ExecutionContextInterface $context): void; - + /** @@ -31,4 +31,4 @@ interface ConstraintValidatorInterface * @return void @@ -526,7 +526,7 @@ diff --git a/src/Symfony/Contracts/Translation/LocaleAwareInterface.php b/src/Sy */ - public function setLocale(string $locale); + public function setLocale(string $locale): void; - + /** diff --git a/src/Symfony/Contracts/Translation/TranslatorTrait.php b/src/Symfony/Contracts/Translation/TranslatorTrait.php --- a/src/Symfony/Contracts/Translation/TranslatorTrait.php diff --git a/.github/sync-translations.php b/.github/sync-translations.php index b1f7c237c39c0..eb3f8e840ab4a 100644 --- a/.github/sync-translations.php +++ b/.github/sync-translations.php @@ -33,6 +33,9 @@ function dumpXliff1(string $defaultLocale, MessageCatalogue $messages, string $d $metadata = $messages->getMetadata($source, $domain); $translation->setAttribute('id', $metadata['id']); + if (isset($metadata['resname'])) { + $translation->setAttribute('resname', $metadata['resname']); + } $s = $translation->appendChild($dom->createElement('source')); $s->appendChild($dom->createTextNode($source)); @@ -64,23 +67,32 @@ function dumpXliff1(string $defaultLocale, MessageCatalogue $messages, string $d $dir = __DIR__.'/../src/Symfony/Component/'.$component.'/Resources/translations'; $enCatalogue = (new XliffFileLoader())->load($dir.'/'.$domain.'.en.xlf', 'en', $domain); + file_put_contents($dir.'/'.$domain.'.en.xlf', dumpXliff1('en', $enCatalogue, $domain)); + $finder = new Finder(); foreach ($finder->files()->in($dir)->name('*.xlf') as $file) { $locale = substr($file->getBasename(), 1 + strlen($domain), -4); + if ('en' === $locale) { + continue; + } + $catalogue = (new XliffFileLoader())->load($file, $locale, $domain); $localeCatalogue = new MessageCatalogue($locale); - foreach ($enCatalogue->all($domain) as $id => $translation) { + foreach ($enCatalogue->all($domain) as $resname => $source) { $metadata = []; - if ($catalogue->defines($id, $domain)) { - $translation = $catalogue->get($id, $domain); - $metadata = $catalogue->getMetadata($id, $domain); + if ($catalogue->defines($resname, $domain)) { + $translation = $catalogue->get($resname, $domain); + $metadata = $catalogue->getMetadata($resname, $domain); + } + $metadata['id'] = $enCatalogue->getMetadata($resname, $domain)['id']; + if ($resname !== $source) { + $metadata['resname'] = $resname; } - $metadata['id'] = $enCatalogue->getMetadata($id, $domain)['id']; - $localeCatalogue->set($id, $translation, $domain); - $localeCatalogue->setMetadata($id, $metadata, $domain); + $localeCatalogue->set($source, $translation, $domain); + $localeCatalogue->setMetadata($source, $metadata, $domain); } file_put_contents($file, dumpXliff1('en', $localeCatalogue, $domain)); diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index 71182fcbaaa32..9ef1476bd7bd7 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -95,9 +95,9 @@ jobs: - 8094:8094 - 11210:11210 sqs: - image: asyncaws/testing-sqs + image: localstack/localstack:3.0.2 ports: - - 9494:9494 + - 4566:4566 zookeeper: image: wurstmeister/zookeeper:3.4.6 kafka: @@ -182,8 +182,8 @@ jobs: REDIS_SENTINEL_SERVICE: redis_sentinel MESSENGER_REDIS_DSN: redis://127.0.0.1:7006/messages MESSENGER_AMQP_DSN: amqp://localhost/%2f/messages - MESSENGER_SQS_DSN: "sqs://localhost:9494/messages?sslmode=disable&poll_timeout=0.01" - MESSENGER_SQS_FIFO_QUEUE_DSN: "sqs://localhost:9494/messages.fifo?sslmode=disable&poll_timeout=0.01" + MESSENGER_SQS_DSN: "sqs://localhost:4566/messages?sslmode=disable&poll_timeout=0.01" + MESSENGER_SQS_FIFO_QUEUE_DSN: "sqs://localhost:4566/messages.fifo?sslmode=disable&poll_timeout=0.01" KAFKA_BROKER: 127.0.0.1:9092 POSTGRES_HOST: localhost diff --git a/.github/workflows/intl-data-tests.yml b/.github/workflows/intl-data-tests.yml index 01401fedc232f..a02bd73ac5b8f 100644 --- a/.github/workflows/intl-data-tests.yml +++ b/.github/workflows/intl-data-tests.yml @@ -1,8 +1,11 @@ -name: Intl data +name: Intl/Emoji data on: push: paths: + - 'src/Symfony/Component/Emoji/*.php' + - 'src/Symfony/Component/Emoji/Resources/data/**' + - 'src/Symfony/Component/Emoji/Tests/*Test.php' - 'src/Symfony/Component/Intl/*.php' - 'src/Symfony/Component/Intl/Util/GitRepository.php' - 'src/Symfony/Component/Intl/Resources/data/**' @@ -10,6 +13,9 @@ on: - 'src/Symfony/Component/Intl/Tests/Util/GitRepositoryTest.php' pull_request: paths: + - 'src/Symfony/Component/Emoji/*.php' + - 'src/Symfony/Component/Emoji/Resources/data/**' + - 'src/Symfony/Component/Emoji/Tests/*Test.php' - 'src/Symfony/Component/Intl/*.php' - 'src/Symfony/Component/Intl/Util/GitRepository.php' - 'src/Symfony/Component/Intl/Resources/data/**' @@ -29,7 +35,7 @@ permissions: jobs: tests: - name: Intl data + name: Intl/Emoji data runs-on: Ubuntu-20.04 steps: @@ -80,15 +86,23 @@ jobs: - name: Run intl-data tests run: ./phpunit --group intl-data -v - - name: Test with compressed data + - name: Test intl-data with compressed data run: | [ -f src/Symfony/Component/Intl/Resources/data/locales/en.php ] [ ! -f src/Symfony/Component/Intl/Resources/data/locales/en.php.gz ] - [ -f src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-en.php ] - [ ! -f src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-en.php.gz ] src/Symfony/Component/Intl/Resources/bin/compress [ ! -f src/Symfony/Component/Intl/Resources/data/locales/en.php ] [ -f src/Symfony/Component/Intl/Resources/data/locales/en.php.gz ] - [ ! -f src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-en.php ] - [ -f src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-en.php.gz ] ./phpunit src/Symfony/Component/Intl + + - name: Run Emoji tests + run: ./phpunit src/Symfony/Component/Emoji -v + + - name: Test Emoji with compressed data + run: | + [ -f src/Symfony/Component/Emoji/Resources/data/emoji-en.php ] + [ ! -f src/Symfony/Component/Emoji/Resources/data/emoji-en.php.gz ] + src/Symfony/Component/Emoji/Resources/bin/compress + [ ! -f src/Symfony/Component/Emoji/Resources/data/emoji-en.php ] + [ -f src/Symfony/Component/Emoji/Resources/data/emoji-en.php.gz ] + ./phpunit src/Symfony/Component/Emoji diff --git a/.github/workflows/package-tests.yml b/.github/workflows/package-tests.yml index 96b7451b7f945..bc6f8eec683c7 100644 --- a/.github/workflows/package-tests.yml +++ b/.github/workflows/package-tests.yml @@ -21,7 +21,7 @@ jobs: - name: Find packages id: find-packages - run: echo "packages=$(php .github/get-modified-packages.php $(find src/Symfony -mindepth 2 -type f -name composer.json -printf '%h\n' | grep -v src/Symfony/Component/Intl/Resources/emoji |jq -R -s -c 'split("\n")[:-1]') $(git diff --name-only origin/${{ github.base_ref }} HEAD | grep src/ | jq -R -s -c 'split("\n")[:-1]'))" >> $GITHUB_OUTPUT + run: echo "packages=$(php .github/get-modified-packages.php $(find src/Symfony -mindepth 2 -type f -name composer.json -printf '%h\n' | grep -v src/Symfony/Component/Emoji/Resources/bin |jq -R -s -c 'split("\n")[:-1]') $(git diff --name-only origin/${{ github.base_ref }} HEAD | grep src/ | jq -R -s -c 'split("\n")[:-1]'))" >> $GITHUB_OUTPUT - name: Verify meta files are correct run: | diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index 414408098f091..08d67bfa91b7f 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -95,7 +95,7 @@ jobs: echo SYMFONY_DEPRECATIONS_HELPER=weak >> $GITHUB_ENV cp composer.json composer.json.orig echo -e '{\n"require":{'"$(grep phpunit-bridge composer.json)"'"php":"*"},"minimum-stability":"dev"}' > composer.json - php .github/build-packages.php HEAD^ $SYMFONY_VERSION $(find src/Symfony -mindepth 2 -type f -name composer.json -printf '%h\n' | grep -v src/Symfony/Component/Intl/Resources/emoji) + php .github/build-packages.php HEAD^ $SYMFONY_VERSION $(find src/Symfony -mindepth 2 -type f -name composer.json -printf '%h\n' | grep -v src/Symfony/Component/Emoji/Resources/bin) mv composer.json composer.json.phpunit mv composer.json.orig composer.json fi @@ -129,7 +129,7 @@ jobs: [[ "${{ matrix.mode }}" = *-deps ]] && mv composer.json.phpunit composer.json || true if [[ "${{ matrix.mode }}" = low-deps ]]; then - echo SYMFONY_PHPUNIT_REQUIRE="nikic/php-parser:^4.16" >> $GITHUB_ENV + echo SYMFONY_PHPUNIT_REQUIRE="nikic/php-parser:^4.18" >> $GITHUB_ENV fi - name: Install dependencies diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php index 86c5b0f1b7371..5ae76ab4312d6 100644 --- a/.php-cs-fixer.dist.php +++ b/.php-cs-fixer.dist.php @@ -34,10 +34,12 @@ 'remove_inheritdoc' => true, 'allow_unused_params' => true, // for future-ready params, to be replaced with https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues/7377 ], + 'nullable_type_declaration_for_default_null_value' => true, 'header_comment' => ['header' => $fileHeaderComment], 'modernize_strpos' => true, 'get_class_to_class_keyword' => true, 'nullable_type_declaration' => true, + 'ordered_types' => ['null_adjustment' => 'always_last', 'sort_algorithm' => 'none'], 'trailing_comma_in_multiline' => ['elements' => ['arrays', 'match', 'parameters']], ]) ->setRiskyAllowed(true) @@ -54,6 +56,7 @@ 'Symfony/Bundle/FrameworkBundle/Resources/views/Form', // explicit trigger_error tests 'Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/', + 'Symfony/Component/Emoji/Resources/', 'Symfony/Component/Intl/Resources/data/', ]) // explicit tests for ommited @param type, against `no_superfluous_phpdoc_tags` diff --git a/CHANGELOG-7.0.md b/CHANGELOG-7.0.md index 17289415f1144..6edcfe85bf1f4 100644 --- a/CHANGELOG-7.0.md +++ b/CHANGELOG-7.0.md @@ -7,6 +7,65 @@ in 7.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/v7.0.0...v7.0.1 +* 7.0.3 (2024-01-31) + + * bug #52913 [Routing] Fixed priority getting lost when setting localized prefix (pritasil) + * bug #53681 [DoctrineBridge]  Fix detection of Xml/Yaml driver in DoctrineExtension (GromNaN) + * bug #53183 [Messenger] PhpSerializer: TypeError should throw `MessageDecodingFailedException` (B-Galati) + * bug #52131 [HttpKernel] Fix `RequestPayloadValueResolver` handling error with no ExpectedTypes (Jeroeny) + * bug #51559 [DependencyInjection] `#[Autowire]` attribute should have precedence over bindings (HypeMC) + * bug #53678 [Mime] Fix serializing uninitialized `RawMessage::$message` to null (nicolas-grekas) + * bug #53634 [Notifer][Smsapi] Set messageId of SentMessage (tomasz-kusy) + * bug #53501 [DependencyInjection] support lazy evaluated exception messages with Xdebug 3 (xabbuh) + * bug #53672 [FrameworkBundle] `ConfigBuilderCacheWarmer` should be non-optional (nicolas-grekas) + * bug #52994 [MonologBridge] Fix context data and display extra data (louismariegaborit) + * bug #53671 [HttpClient] Fix pausing responses before they start when using curl (nicolas-grekas) + * bug #53594 [Notifier] Updated the NTFY notifier to run without a user parameter (lostfocus) + * bug #53620 [Validator] Fix option filenameMaxLength to the File constraint (Image) (mindaugasvcs) + * bug #53624 [Translation] Fix constant domain resolution in PhpAstExtractor (VincentLanglet) + * bug #53663 [TwigBridge] separate child and parent context in NotificationEmail on writes (xabbuh) + * bug #53667 [Mailer] [Mailgun] Fix sender header encoding (spajxo) + * bug #53631 [DependencyInjection] Fix loading all env vars from secrets when only a subset is needed (nicolas-grekas) + * bug #53656 [Form] Use self-closing `` syntax again, reverting #47715 (mpdude) + * bug #53653 [Mailer] [Scaleway] Fix attachment handling (madbob) + * bug #53157 [Mailer] Throw `TransportException` when unable to read from socket (xdanik) + * bug #53361 [Serializer] Take unnamed variadic parameters into account when denormalizing (thijsBreker) + * bug #53530 [Serializer] Rewrite `AbstractObjectNormalizer::createChildContext()` to use the provided `cache_key` from original context when creating child contexts (amne) + * bug #53506 [HttpClient] Fix error chunk creation in passthru (rmikalkenas) + * bug #53260 [AssetMapper] Handle assets with non-ascii characters in dev server (fbourigault) + * bug #53357 [Translation] Fix `TranslationNodeVisitor` with constant domain (VincentLanglet) + * bug #53525 [Messenger] [AMQP] Throw exception on `nack` callback (kvrushifa) + * bug #53432 [HttpFoundation] Request without content-type or content-length header should result in null values, not empty strings (priyadi) + * bug #53593 [Cache] Fix possible infinite loop in `CachePoolPass` (HypeMC) + * bug #53588 [Translation] fix multi-byte code area to convert (xabbuh) + * bug #53572 [FrameworkBundle] grab a service from the container only if it exists (xabbuh) + * bug #53565 [Mime] Fix undefined array key 0 when empty sender (0x346e3730) + * bug #53516 [Console] Allow '0' as a $shortcut in InputOption.php (lawsonjl-ornl) + * bug #53576 [Console] Only execute additional checks for color support if the output (theofidry) + * bug #53582 [TwigBundle] Fix configuration when "paths" is null (smnandre) + * bug #53575 [Mailer] register the MailPaceTransportFactory (xabbuh) + * bug #53581 [String] fix aircraft inflection (renanbr) + * bug #53509 [Security] Fix `AuthenticationUtils::getLastUsername()` returning null (alexandre-daubois) + * bug #53529 [Ldap] Use `{user_identifier}` over deprecated `{username}` in factories (tcitworld) + * bug #53567 [String] Correct inflection of axis (Vladislav Iurciuc) + * bug #53537 [VarDumper] Fix missing colors initialization in `CliDumper` (nicolas-grekas) + * bug #53521 [VarDumper] Fixes `Typed property Symfony\Component\VarDumper\Dumper\CliDumper::$colors must not be accessed before initialization` (crynobone) + * bug #53481 [Process] Fix executable finder when the command starts with a dash (kayw-geek) + * bug #53006 [ErrorHandler] Don't format binary strings (aleho) + * bug #53453 [Translation] add support for nikic/php-parser 5.0 (xabbuh) + * bug #53434 [ErrorHandler] fix rendering exception pages without the HttpKernel component (xabbuh) + * bug #53441 [Messenger] Amazon SQS Delay has a max of 15 minutes (alamirault) + * bug #53414 [Serializer] `GetSetMethodNormalizer`: fix BC break with `#[Ignore]` attribute (nikophil) + * bug #53383 [Validator] re-allow an empty list of fields (xabbuh) + * bug #53418 [FrameworkBundle][Notifier] Fix service registration (MessageBird + TurboSms) (smnandre) + * bug #53381 [Form] Fix assigning data in `PostSetDataEvent` and `PostSubmitEvent` (fancyweb) + * bug #53350 [Validator] fix the exception being thrown (xabbuh) + * bug #52930 [Messenger] Fix Redis messenger scheme comparison (freswa) + * bug #52874 [Scheduler] Separate id and description in message providers (valtzu) + * bug #53341 [FrameworkBundle] append instead of replacing potentially non-existent named-arguments (xabbuh) + * bug #53320 [Cache][DependencyInjection][Lock][Mailer][Messenger][Notifier][Translation] Url decode username and passwords from `parse_url()` results (alexandre-daubois) + * bug #53108 [Serializer] Fix using deserialization path 5.4 (HypeMC) + * 7.0.2 (2023-12-30) * bug #53282 [RateLimiter] Fix RateLimit->getRetryAfter() return value when consuming 0 or last tokens (wouterj, ERuban) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index ca870dd304464..8ae25e554297b 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -15,9 +15,9 @@ The Symfony Connect username in parenthesis allows to get more information - Thomas Calvet (fancyweb) - Christophe Coevoet (stof) - Wouter de Jong (wouterj) + - Alexandre Daubois (alexandre-daubois) - Jordi Boggiano (seldaek) - Maxime Steinhausser (ogizanagi) - - Alexandre Daubois (alexandre-daubois) - Kévin Dunglas (dunglas) - Victor Berchet (victor) - Ryan Weaver (weaverryan) @@ -26,9 +26,9 @@ The Symfony Connect username in parenthesis allows to get more information - Jules Pietri (heah) - Roland Franssen - Johannes S (johannes) + - Oskar Stark (oskarstark) - Kris Wallsmith (kriswallsmith) - Jakub Zalas (jakubzalas) - - Oskar Stark (oskarstark) - Yonel Ceruto (yonelceruto) - Hugo Hamon (hhamon) - Tobias Nyholm (tobias) @@ -38,11 +38,11 @@ The Symfony Connect username in parenthesis allows to get more information - Romain Neutron - Antoine Lamirault (alamirault) - Joseph Bielawski (stloyd) + - HypeMC (hypemc) - Drak (drak) - Abdellatif Ait boudad (aitboudad) - - HypeMC (hypemc) - - Lukas Kahwe Smith (lsmith) - Kevin Bond (kbond) + - Lukas Kahwe Smith (lsmith) - Hamza Amrouche (simperfit) - Martin Hasoň (hason) - Jeremy Mikola (jmikola) @@ -64,25 +64,26 @@ The Symfony Connect username in parenthesis allows to get more information - Diego Saint Esteben (dosten) - stealth35 ‏ (stealth35) - Alexander Mols (asm89) + - Gábor Egyed (1ed) - Francis Besset (francisbesset) - Titouan Galopin (tgalopin) - Pierre du Plessis (pierredup) - - Gábor Egyed (1ed) - David Maicher (dmaicher) - Bulat Shakirzyanov (avalanche123) - - Iltar van der Berg - Vincent Langlet (deviling) + - Iltar van der Berg - Miha Vrhovnik (mvrhov) - Gary PEGEOT (gary-p) - Saša Stamenković (umpirsky) - Allison Guilhem (a_guilhem) - Mathieu Piot (mpiot) + - Mathieu Santostefano (welcomattic) - Alexander Schranz (alexander-schranz) - Vasilij Duško (staff) - - Mathieu Santostefano (welcomattic) - Sarah Khalil (saro0h) - Laurent VOULLEMIER (lvo) - Konstantin Kudryashov (everzet) + - Tomasz Kowalczyk (thunderer) - Guilhem N (guilhemn) - Bilal Amarni (bamarni) - Eriksen Costa @@ -91,10 +92,11 @@ The Symfony Connect username in parenthesis allows to get more information - Peter Rehm (rpet) - Henrik Bjørnskov (henrikbjorn) - Mathias Arlaud (mtarld) + - Dariusz Ruminski - Andrej Hudec (pulzarraider) - Jáchym Toušek (enumag) + - Simon André (simonandre) - David Buchmann (dbu) - - Dariusz Ruminski - Christian Raue - Eric Clemmons (ericclemmons) - Denis (yethee) @@ -103,36 +105,36 @@ The Symfony Connect username in parenthesis allows to get more information - Douglas Greenshields (shieldo) - Frank A. Fiebig (fafiebig) - Baldini + - Ruud Kamphuis (ruudk) - Alex Pott - Fran Moreno (franmomu) - Arnout Boks (aboks) - - Simon André (simonandre) - Charles Sarrazin (csarrazi) - - Ruud Kamphuis (ruudk) - Henrik Westphal (snc) - Dariusz Górecki (canni) - Ener-Getick - Graham Campbell (graham) + - Tomas Norkūnas (norkunas) - Tugdual Saunier (tucksaun) - Lee McDermott - Brandon Turner + - Massimiliano Arione (garak) - Luis Cordova (cordoval) - Antoine Makdessi (amakdessi) - Konstantin Myakshin (koc) - Hubert Lenoir (hubert_lenoir) - Daniel Holmes (dholmes) - Julien Falque (julienfalque) - - Tomas Norkūnas (norkunas) - Toni Uebernickel (havvg) - Bart van den Burg (burgov) - Vasilij Dusko | CREATION - Jordan Alliot (jalliot) - - Massimiliano Arione (garak) - John Wards (johnwards) - Phil E. Taylor (philetaylor) - Antoine Hérault (herzult) - Konstantin.Myakshin - Yanick Witschi (toflar) + - Théo FIDRY - Arnaud Le Blanc (arnaud-lb) - Joel Wurtz (brouznouf) - Sebastiaan Stok (sstok) @@ -140,15 +142,14 @@ The Symfony Connect username in parenthesis allows to get more information - gnito-org - Jeroen Spee (jeroens) - Tim Nagel (merk) - - Théo FIDRY - Chris Wilkinson (thewilkybarkid) - Jérôme Vasseur (jvasseur) + - Rokas Mikalkėnas (rokasm) - Peter Kokot (peterkokot) - Brice BERNARD (brikou) - Tac Tacelosky (tacman1123) - Michal Piotrowski - marc.weistroff - - Rokas Mikalkėnas (rokasm) - Lars Strojny (lstrojny) - lenar - Vladimir Tsykun (vtsykun) @@ -178,6 +179,7 @@ The Symfony Connect username in parenthesis allows to get more information - François-Xavier de Guillebon (de-gui_f) - noniagriconomie - Eric GELOEN (gelo) + - Nicolas Philippe (nikophil) - Gabriel Caruso - Stefano Sala (stefano.sala) - Ion Bazan (ionbazan) @@ -187,7 +189,6 @@ The Symfony Connect username in parenthesis allows to get more information - Gregor Harlan (gharlan) - Michael Babker (mbabker) - Anthony MARTIN - - Nicolas Philippe (nikophil) - Sebastian Hörl (blogsh) - Tigran Azatyan (tigranazatyan) - Christopher Hertel (chertel) @@ -207,17 +208,19 @@ The Symfony Connect username in parenthesis allows to get more information - Andreas Braun - Hugo Alliaume (kocal) - Pablo Godel (pgodel) + - Florent Mata (fmata) - Alessandro Chitolina (alekitto) - - Tomasz Kowalczyk (thunderer) + - Dāvis Zālītis (k0d3r1s) - Rafael Dohms (rdohms) - jwdeitch + - David Prévot (taffit) - Jérôme Parmentier (lctrs) - Ahmed TAILOULOUTE (ahmedtai) - Simon Berger - Jérémy Derussé + - Valtteri R (valtzu) - Matthieu Napoli (mnapoli) - Tomas Votruba (tomas_votruba) - - Florent Mata (fmata) - Arman Hosseini (arman) - Sokolov Evgeniy (ewgraf) - Andréia Bohner (andreia) @@ -227,15 +230,15 @@ The Symfony Connect username in parenthesis allows to get more information - George Mponos (gmponos) - Roman Martinuk (a2a4) - Richard Shank (iampersistent) - - David Prévot (taffit) + - Thomas Landauer (thomas-landauer) - Romain Monteil (ker0x) - Sergey (upyx) - Marco Pivetta (ocramius) - Antonio Pauletich (x-coder264) - Vincent Touzet (vincenttouzet) + - Fabien Bourigault (fbourigault) - Olivier Dolbeau (odolbeau) - Rouven Weßling (realityking) - - Valtteri R (valtzu) - Ben Davies (bendavies) - YaFou - Clemens Tolboom @@ -248,7 +251,6 @@ The Symfony Connect username in parenthesis allows to get more information - Matthieu Ouellette-Vachon (maoueh) - Michał Pipa (michal.pipa) - Dawid Nowak - - Dāvis Zālītis (k0d3r1s) - Jannik Zschiesche - Amal Raghav (kertz) - Jonathan Ingram @@ -259,7 +261,6 @@ The Symfony Connect username in parenthesis allows to get more information - GDIBass - Samuel NELA (snela) - Vincent AUBERT (vincent) - - Fabien Bourigault (fbourigault) - Michael Voříšek - zairig imad (zairigimad) - Colin O'Dell (colinodell) @@ -282,6 +283,7 @@ The Symfony Connect username in parenthesis allows to get more information - Martin Hujer (martinhujer) - Sergey Linnik (linniksa) - Richard Miller + - Aleksandar Jakovljevic (ajakov) - Mario A. Alvarez Garcia (nomack84) - Thomas Rabaix (rande) - D (denderello) @@ -310,11 +312,9 @@ The Symfony Connect username in parenthesis allows to get more information - sun (sun) - Larry Garfield (crell) - Leo Feyer - - Thomas Landauer (thomas-landauer) - Philipp Wahala (hifi) - Victor Bocharsky (bocharsky_bw) - Nikolay Labinskiy (e-moe) - - Aleksandar Jakovljevic (ajakov) - Martin Schuhfuß (usefulthink) - apetitpa - Guilliam Xavier @@ -366,6 +366,7 @@ The Symfony Connect username in parenthesis allows to get more information - Florent Morselli (spomky_) - dFayet - Rob Frawley 2nd (robfrawley) + - Renan (renanbr) - Nikita Konstantinov (unkind) - Dariusz - Francois Zaninotto @@ -402,6 +403,7 @@ The Symfony Connect username in parenthesis allows to get more information - Sullivan SENECHAL (soullivaneuh) - Loick Piera (pyrech) - Uwe Jäger (uwej711) + - W0rma - Lynn van der Berg (kjarli) - Michaël Perrin (michael.perrin) - Eugene Leonovich (rybakit) @@ -425,7 +427,6 @@ The Symfony Connect username in parenthesis allows to get more information - Frank de Jonge - Andrii Bodnar - Dane Powell - - Renan (renanbr) - Sebastien Morel (plopix) - Christopher Davis (chrisguitarguy) - Karoly Gossler (connorhu) @@ -473,7 +474,6 @@ The Symfony Connect username in parenthesis allows to get more information - Chris Smith (cs278) - Thomas Bisignani (toma) - Florian Klein (docteurklein) - - W0rma - Damien Alexandre (damienalexandre) - Manuel Kießling (manuelkiessling) - Alexey Kopytko (sanmai) @@ -544,6 +544,7 @@ The Symfony Connect username in parenthesis allows to get more information - Pavel Kirpitsov (pavel-kirpichyov) - Robert Meijers - Artur Eshenbrener + - Priyadi Iman Nurcahyo (priyadi) - Harm van Tilborg (hvt) - Thomas Perez (scullwm) - Cédric Anne @@ -588,6 +589,7 @@ The Symfony Connect username in parenthesis allows to get more information - Greg Thornton (xdissent) - Alex Bowers - Michel Roca (mroca) + - Asis Pattisahusiwa - Costin Bereveanu (schniper) - Andrii Dembitskyi - Gasan Guseynov (gassan) @@ -604,9 +606,11 @@ The Symfony Connect username in parenthesis allows to get more information - Saif Eddin G - Endre Fejes - Tobias Naumann (tna) + - Mathieu Rochette (mathroc) - Daniel Beyer - flack (flack) - Shein Alexey + - Joppe De Cuyper (joppedc) - Joe Lencioni - Daniel Tschinder - Diego Agulló (aeoris) @@ -683,6 +687,7 @@ The Symfony Connect username in parenthesis allows to get more information - vagrant - Matthias Krauser (mkrauser) - Benjamin Cremer (bcremer) + - Alex Hofbauer (alexhofbauer) - Maarten de Boer (mdeboer) - Asier Illarramendi (doup) - AKeeman (akeeman) @@ -690,7 +695,6 @@ The Symfony Connect username in parenthesis allows to get more information - Restless-ET - Vlad Gregurco (vgregurco) - Artem Stepin (astepin) - - Priyadi Iman Nurcahyo (priyadi) - Boris Vujicic (boris.vujicic) - Dries Vints - Judicaël RUFFIEUX (axanagor) @@ -709,6 +713,7 @@ The Symfony Connect username in parenthesis allows to get more information - Vitaliy Tverdokhlib (vitaliytv) - Ariel Ferrandini (aferrandini) - BASAK Semih (itsemih) + - Kai Dederichs - Dirk Pahl (dirkaholic) - Cédric Lombardot (cedriclombardot) - Jérémy REYNAUD (babeuloula) @@ -779,7 +784,6 @@ The Symfony Connect username in parenthesis allows to get more information - Eduardo Oliveira (entering) - Oleksii Zhurbytskyi - Bilge - - Asis Pattisahusiwa - Anatoly Pashin (b1rdex) - Jonathan Johnson (jrjohnson) - Eugene Wissner @@ -787,6 +791,7 @@ The Symfony Connect username in parenthesis allows to get more information - Roy Van Ginneken (rvanginneken) - ondrowan - Barry vd. Heuvel (barryvdh) + - Antonin CLAUZIER (0x346e3730) - Chad Sikorra (chadsikorra) - Evan S Kaufman (evanskaufman) - mcben @@ -803,7 +808,6 @@ The Symfony Connect username in parenthesis allows to get more information - Leevi Graham (leevigraham) - Anthony Ferrara - tim - - Mathieu Rochette (mathroc) - Ioan Negulescu - Greg ORIOL - Jakub Škvára (jskvara) @@ -813,7 +817,6 @@ The Symfony Connect username in parenthesis allows to get more information - alexpods - Adam Szaraniec - Dariusz Ruminski - - Joppe De Cuyper (joppedc) - Romain Gautier (mykiwi) - Matthieu Bontemps - Erik Trapman @@ -905,6 +908,7 @@ The Symfony Connect username in parenthesis allows to get more information - julien57 - Mátyás Somfai (smatyas) - Bastien DURAND (deamon) + - Nicolas Rigaud - Dmitry Simushev - alcaeus - Ahmed Ghanem (ahmedghanem00) @@ -976,7 +980,6 @@ The Symfony Connect username in parenthesis allows to get more information - Dustin Dobervich (dustin10) - Luis Tacón (lutacon) - Dmitrii Tarasov (dtarasov) - - Alex Hofbauer (alexhofbauer) - dantleech - Philipp Kolesnikov - Jack Worman (jworman) @@ -1086,7 +1089,6 @@ The Symfony Connect username in parenthesis allows to get more information - Tiago Brito (blackmx) - Gintautas Miselis (naktibalda) - Richard van den Brand (ricbra) - - Kai Dederichs - Toon Verwerft (veewee) - develop - flip111 @@ -1100,6 +1102,7 @@ The Symfony Connect username in parenthesis allows to get more information - Mark Sonnabaum - Chris Jones (magikid) - Massimiliano Braglia (massimilianobraglia) + - Thijs-jan Veldhuizen (tjveldhuizen) - Richard Quadling - James Hudson (mrthehud) - Raphaëll Roussel @@ -1123,6 +1126,7 @@ The Symfony Connect username in parenthesis allows to get more information - Wybren Koelmans (wybren_koelmans) - Roberto Nygaard - victor-prdh + - Kev - Davide Borsatto (davide.borsatto) - Florian Hermann (fhermann) - zenas1210 @@ -1156,6 +1160,7 @@ The Symfony Connect username in parenthesis allows to get more information - Tarjei Huse (tarjei) - Besnik Br - Issam Raouf (iraouf) + - Simon Mönch - Jose Gonzalez - Jonathan (jlslew) - Claudio Zizza @@ -1222,6 +1227,7 @@ The Symfony Connect username in parenthesis allows to get more information - Evan C - buffcode - Glodzienski + - Natsuki Ikeguchi - Krzysztof Łabuś (crozin) - Xavier Lacot (xavier) - Jon Dufresne @@ -1229,7 +1235,6 @@ The Symfony Connect username in parenthesis allows to get more information - Denis Zunke (donalberto) - Adrien Roches (neirda24) - _sir_kane (waly) - - Antonin CLAUZIER (0x346e3730) - Olivier Maisonneuve - Andrei C. (moldman) - Mike Meier (mykon) @@ -1309,6 +1314,7 @@ The Symfony Connect username in parenthesis allows to get more information - Maksim Kotlyar (makasim) - Neil Ferreira - Julie Hourcade (juliehde) + - Marc Biorklund (mbiork) - Dmitry Parnas (parnas) - Loïc Beurlet - Ana Raro @@ -1412,6 +1418,7 @@ The Symfony Connect username in parenthesis allows to get more information - Daniel Cestari - Matt Janssen - Stéphane Delprat + - Mior Muhammad Zaki (crynobone) - Elan Ruusamäe (glen) - Brunet Laurent (lbrunet) - Florent Viel (luxifer) @@ -1487,6 +1494,7 @@ The Symfony Connect username in parenthesis allows to get more information - Jules Matsounga (hyoa) - Yewhen Khoptynskyi (khoptynskyi) - Jérôme Nadaud (jnadaud) + - Frank Naegler - Sam Malone - Ha Phan (haphan) - Chris Jones (leek) @@ -1495,6 +1503,7 @@ The Symfony Connect username in parenthesis allows to get more information - xaav - Jean-Christophe Cuvelier [Artack] - Mahmoud Mostafa (mahmoud) + - Ninos - Alexandre Tranchant (alexandre_t) - Anthony Moutte - Ahmed Abdou @@ -1514,6 +1523,7 @@ The Symfony Connect username in parenthesis allows to get more information - Grégoire Hébert (gregoirehebert) - Franz Wilding (killerpoke) - Ferenczi Krisztian (fchris82) + - Ioan Ovidiu Enache (ionutenache) - Artyum Petrov - Oleg Golovakhin (doc_tr) - Guillaume Smolders (guillaumesmo) @@ -1839,7 +1849,6 @@ The Symfony Connect username in parenthesis allows to get more information - Gustavo Adrian - Jorrit Schippers (jorrit) - Matthias Neid - - Kev - Yannick - Kuzia - Vladimir Luchaninov (luchaninov) @@ -2055,6 +2064,7 @@ The Symfony Connect username in parenthesis allows to get more information - Maxime THIRY - Norman Soetbeer - Ludek Stepan + - Frederik Schwan - Mark van den Berg - Aaron Stephens (astephens) - Craig Menning (cmenning) @@ -2181,6 +2191,7 @@ The Symfony Connect username in parenthesis allows to get more information - Aurélien Fontaine - ncou - Ian Carroll + - Dennis Fehr - caponica - jdcook - Daniel Kay (danielkay-cp) @@ -2197,6 +2208,7 @@ The Symfony Connect username in parenthesis allows to get more information - Martin Pärtel - Daniel Rotter (danrot) - Frédéric Bouchery (fbouchery) + - Jacek Kobus (jackks) - Patrick Daley (padrig) - Phillip Look (plook) - Foxprodev @@ -2215,6 +2227,7 @@ The Symfony Connect username in parenthesis allows to get more information - mfettig - Oleksii Bulba - Ramon Cuñat + - mboultoureau - Raphaëll Roussel - Vitalii - Tadcka @@ -2231,7 +2244,6 @@ The Symfony Connect username in parenthesis allows to get more information - parinz1234 - Romain Geissler - Adrien Moiruad - - Natsuki Ikeguchi - Viktoriia Zolotova - Tomaz Ahlin - Nasim @@ -2298,6 +2310,7 @@ The Symfony Connect username in parenthesis allows to get more information - Jos Elstgeest - Kirill Lazarev - Thomas Counsell + - Joe - BilgeXA - mmokhi - Serhii Smirnov @@ -2401,6 +2414,7 @@ The Symfony Connect username in parenthesis allows to get more information - izenin - Mephistofeles - Oleh Korneliuk + - Evgeny Ruban - Hoffmann András - LubenZA - Victor Garcia @@ -2481,7 +2495,6 @@ The Symfony Connect username in parenthesis allows to get more information - Anton Sukhachev (mrsuh) - Pavlo Pelekh (pelekh) - Stefan Kleff (stefanxl) - - Thijs-jan Veldhuizen (tjveldhuizen) - Vitaliy Zhuk (zhukv) - Marcel Siegert - ryunosuke @@ -2635,7 +2648,6 @@ The Symfony Connect username in parenthesis allows to get more information - Grayson Koonce - Ruben Jansen - Wissame MEKHILEF - - Marc Biorklund - shreypuranik - NanoSector - Thibaut Salanon @@ -2686,6 +2698,7 @@ The Symfony Connect username in parenthesis allows to get more information - downace - Aarón Nieves Fernández - Mikolaj Czajkowski + - Ahto Türkson - Ph3nol - Kirill Saksin - Shiro @@ -2737,6 +2750,7 @@ The Symfony Connect username in parenthesis allows to get more information - Mohammad Ali Sarbanha (sarbanha) - Sergii Dolgushev (sergii-swds) - Steeve Titeca (stiteca) + - Thomas Citharel (tcit) - Artem Lopata (bumz) - alex - evgkord @@ -2804,6 +2818,7 @@ The Symfony Connect username in parenthesis allows to get more information - Shamimul Alam - Cyril HERRERA - dropfen + - RAHUL K JHA - Andrey Chernykh - Edvinas Klovas - Drew Butler @@ -2901,6 +2916,7 @@ The Symfony Connect username in parenthesis allows to get more information - John Nickell (jrnickell) - Martin Mayer (martin) - Grzegorz Łukaszewicz (newicz) + - Nico Müller (nicomllr) - Omar Yepez (oyepez003) - Jonny Schmid (schmidjon) - Toby Griffiths (tog) @@ -2912,6 +2928,7 @@ The Symfony Connect username in parenthesis allows to get more information - Ernest Hymel - Andrea Civita - Nicolás Alonso + - Roman Tyshyk - LoginovIlya - andreyserdjuk - Nick Chiu @@ -2975,6 +2992,7 @@ The Symfony Connect username in parenthesis allows to get more information - NothingWeAre - Storkeus - goabonga + - Vladislav Iurciuc - Alan Chen - Anton Zagorskii - ging-dev @@ -2995,7 +3013,6 @@ The Symfony Connect username in parenthesis allows to get more information - Matheus Gontijo - Gerrit Drost - Linnaea Von Lavia - - Simon Mönch - Javan Eskander - Lenar Lõhmus - Cristian Gonzalez @@ -3169,6 +3186,7 @@ The Symfony Connect username in parenthesis allows to get more information - helmi - Michael Steininger - Nardberjean + - Dylan - ghazy ben ahmed - Karolis - Myke79 @@ -3190,6 +3208,7 @@ The Symfony Connect username in parenthesis allows to get more information - Steffen Keuper - Kai Eichinger - Antonio Angelino + - Kay Wei - Jens Schulze - Tema Yud - Matt Fields @@ -3424,6 +3443,7 @@ The Symfony Connect username in parenthesis allows to get more information - Andreas Forsblom (aforsblo) - Alex Olmos (alexolmos) - Cedric BERTOLINI (alsciende) + - Cornel Cruceru (amne) - Robin Kanters (anddarerobin) - Antoine (antoinela_adveris) - Juan Ases García (ases) @@ -3443,7 +3463,6 @@ The Symfony Connect username in parenthesis allows to get more information - Bermon Clément (chou666) - Kousuke Ebihara (co3k) - Loïc Vernet (coil) - - Mior Muhammad Zaki (crynobone) - Christoph Vincent Schaefer (cvschaefer) - Kamil Piwowarski (cyklista) - Damon Jones (damon__jones) diff --git a/README.md b/README.md index f7b8a37a30284..6f69c1c7e6f2b 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@

- + Symfony Logo

[Symfony][1] is a **PHP framework** for web and console applications and a set @@ -17,26 +17,16 @@ Installation Sponsor ------- -Symfony 7.0 is [backed][27] by -- [Shopware][28] -- [Sulu][29] -- [Les-Tilleuls.coop][30] +Symfony 7.1 is [backed][27] by +- [Rector][29] -**Shopware** is an open headless commerce platform powered by Symfony and Vue.js -that is used by thousands of shops and supported by a huge, worldwide community -of developers, agencies and merchants. +**Rector** helps successful and growing companies to get the most of the code +they already have. Including upgrading to the latest Symfony LTS. They deliver +automated refactoring, reduce maintenance costs, speed up feature delivery, and +transform legacy code into a strategic asset. They can handle the dirty work, +so you can focus on the features. -**Sulu** is the CMS for Symfony developers. It provides pre-built content-management -features while giving developers the freedom to build, deploy, and maintain custom -solutions using full-stack Symfony. Sulu is ideal for creating complex websites, -integrating external tools, and building custom-built solutions. - -**Les-Tilleuls.coop** is a team of 70+ Symfony experts who can help you design, -develop and fix your projects. We provide a wide range of professional services -including development, consulting, coaching, training and audits. We also are -highly skilled in JS, Go and DevOps. We are a worker cooperative! - -Help Symfony by [sponsoring][31] its development! +Help Symfony by [sponsoring][28] its development! Documentation ------------- @@ -99,7 +89,5 @@ and supported by [Symfony contributors][19]. [25]: https://symfony.com/doc/current/contributing/code_of_conduct/care_team.html [26]: https://symfony.com/book [27]: https://symfony.com/backers -[28]: https://www.shopware.com -[29]: https://sulu.io -[30]: https://les-tilleuls.coop/ -[31]: https://symfony.com/sponsor +[28]: https://symfony.com/sponsor +[29]: https://getrector.com diff --git a/UPGRADE-7.0.md b/UPGRADE-7.0.md index dc4b8bca419b7..d7833e13492d3 100644 --- a/UPGRADE-7.0.md +++ b/UPGRADE-7.0.md @@ -429,6 +429,8 @@ SecurityBundle -------------- * Enabling SecurityBundle and not configuring it is not allowed, either remove the bundle or configure at least one firewall + * Remove the `enable_authenticator_manager` config option + * Remove the `security.firewalls.logout.csrf_token_generator` config option, use `security.firewalls.logout.csrf_token_manager` instead * Remove the `require_previous_session` config option from authenticators Serializer diff --git a/UPGRADE-7.1.md b/UPGRADE-7.1.md index ce3a26a180664..34a816fb78412 100644 --- a/UPGRADE-7.1.md +++ b/UPGRADE-7.1.md @@ -16,6 +16,11 @@ Messenger * Make `#[AsMessageHandler]` final +PropertyInfo +------------ + + * The `PropertyTypeExtractorInterface::getTypes` method is deprecated, use `PropertyTypeExtractorInterface::getType` instead + SecurityBundle -------------- diff --git a/composer.json b/composer.json index e9b9ae71d3ee3..5918ff73ae31d 100644 --- a/composer.json +++ b/composer.json @@ -71,6 +71,7 @@ "symfony/doctrine-bridge": "self.version", "symfony/dom-crawler": "self.version", "symfony/dotenv": "self.version", + "symfony/emoji": "self.version", "symfony/error-handler": "self.version", "symfony/event-dispatcher": "self.version", "symfony/expression-language": "self.version", @@ -125,7 +126,7 @@ "amphp/http-client": "^4.2.1", "amphp/http-tunnel": "^1.0", "async-aws/ses": "^1.0", - "async-aws/sqs": "^1.0", + "async-aws/sqs": "^1.0|^2.0", "async-aws/sns": "^1.0", "cache/integration-tests": "dev-master", "doctrine/collections": "^1.0|^2.0", @@ -139,7 +140,7 @@ "league/uri": "^6.5|^7.0", "masterminds/html5": "^2.7.2", "monolog/monolog": "^3.0", - "nikic/php-parser": "^4.16|^5.0", + "nikic/php-parser": "^4.18|^5.0", "nyholm/psr7": "^1.0", "pda/pheanstalk": "^4.0", "php-http/discovery": "^1.15", diff --git a/psalm.xml b/psalm.xml index a21be22fe248f..f5f9c5b4c4e88 100644 --- a/psalm.xml +++ b/psalm.xml @@ -17,7 +17,8 @@ - + + diff --git a/src/Symfony/Bridge/Doctrine/ArgumentResolver/EntityValueResolver.php b/src/Symfony/Bridge/Doctrine/ArgumentResolver/EntityValueResolver.php index bdf975b32befd..c1ede525e0f2e 100644 --- a/src/Symfony/Bridge/Doctrine/ArgumentResolver/EntityValueResolver.php +++ b/src/Symfony/Bridge/Doctrine/ArgumentResolver/EntityValueResolver.php @@ -73,7 +73,7 @@ public function resolve(Request $request, ArgumentMetadata $argument): array } if (null === $object && !$argument->isNullable()) { - throw new NotFoundHttpException(sprintf('"%s" object not found by "%s".', $options->class, self::class).$message); + throw new NotFoundHttpException($options->message ?? (sprintf('"%s" object not found by "%s".', $options->class, self::class).$message)); } return [$object]; diff --git a/src/Symfony/Bridge/Doctrine/Attribute/MapEntity.php b/src/Symfony/Bridge/Doctrine/Attribute/MapEntity.php index 73bb1aa398f37..0c3ab0cd0ed9f 100644 --- a/src/Symfony/Bridge/Doctrine/Attribute/MapEntity.php +++ b/src/Symfony/Bridge/Doctrine/Attribute/MapEntity.php @@ -44,6 +44,7 @@ public function __construct( public ?bool $evictCache = null, bool $disabled = false, string $resolver = EntityValueResolver::class, + public ?string $message = null, ) { parent::__construct($resolver, $disabled); } @@ -59,6 +60,7 @@ public function withDefaults(self $defaults, ?string $class): static $clone->stripNull ??= $defaults->stripNull ?? false; $clone->id ??= $defaults->id; $clone->evictCache ??= $defaults->evictCache ?? false; + $clone->message ??= $defaults->message; return $clone; } diff --git a/src/Symfony/Bridge/Doctrine/CacheWarmer/ProxyCacheWarmer.php b/src/Symfony/Bridge/Doctrine/CacheWarmer/ProxyCacheWarmer.php index 62e1836fbd268..ddf222e2940d2 100644 --- a/src/Symfony/Bridge/Doctrine/CacheWarmer/ProxyCacheWarmer.php +++ b/src/Symfony/Bridge/Doctrine/CacheWarmer/ProxyCacheWarmer.php @@ -39,7 +39,7 @@ public function isOptional(): bool return false; } - public function warmUp(string $cacheDir, string $buildDir = null): array + public function warmUp(string $cacheDir, ?string $buildDir = null): array { $files = []; foreach ($this->registry->getManagers() as $em) { diff --git a/src/Symfony/Bridge/Doctrine/ContainerAwareEventManager.php b/src/Symfony/Bridge/Doctrine/ContainerAwareEventManager.php index 26d31a2247802..3d331ac010e1b 100644 --- a/src/Symfony/Bridge/Doctrine/ContainerAwareEventManager.php +++ b/src/Symfony/Bridge/Doctrine/ContainerAwareEventManager.php @@ -44,7 +44,7 @@ public function __construct(ContainerInterface $container, array $listeners = [] $this->listeners = $listeners; } - public function dispatchEvent(string $eventName, EventArgs $eventArgs = null): void + public function dispatchEvent(string $eventName, ?EventArgs $eventArgs = null): void { if (!$this->initializedSubscribers) { $this->initializeSubscribers(); diff --git a/src/Symfony/Bridge/Doctrine/DataCollector/DoctrineDataCollector.php b/src/Symfony/Bridge/Doctrine/DataCollector/DoctrineDataCollector.php index 7b01e98712f8c..ef0a369db9f95 100644 --- a/src/Symfony/Bridge/Doctrine/DataCollector/DoctrineDataCollector.php +++ b/src/Symfony/Bridge/Doctrine/DataCollector/DoctrineDataCollector.php @@ -39,7 +39,7 @@ public function __construct( $this->managers = $registry->getManagerNames(); } - public function collect(Request $request, Response $response, \Throwable $exception = null): void + public function collect(Request $request, Response $response, ?\Throwable $exception = null): void { $this->data = [ 'queries' => $this->collectQueries(), diff --git a/src/Symfony/Bridge/Doctrine/DependencyInjection/AbstractDoctrineExtension.php b/src/Symfony/Bridge/Doctrine/DependencyInjection/AbstractDoctrineExtension.php index 54b6c8bf924f2..94b99d8d7e925 100644 --- a/src/Symfony/Bridge/Doctrine/DependencyInjection/AbstractDoctrineExtension.php +++ b/src/Symfony/Bridge/Doctrine/DependencyInjection/AbstractDoctrineExtension.php @@ -134,7 +134,7 @@ protected function setMappingDriverConfig(array $mappingConfig, string $mappingN * * Returns false when autodetection failed, an array of the completed information otherwise. */ - protected function getMappingDriverBundleConfigDefaults(array $bundleConfig, \ReflectionClass $bundle, ContainerBuilder $container, string $bundleDir = null): array|false + protected function getMappingDriverBundleConfigDefaults(array $bundleConfig, \ReflectionClass $bundle, ContainerBuilder $container, ?string $bundleDir = null): array|false { $bundleClassDir = \dirname($bundle->getFileName()); $bundleDir ??= $bundleClassDir; @@ -193,7 +193,9 @@ protected function registerMappingDrivers(array $objectManager, ContainerBuilder array_values($driverPaths), ]); } - if (str_contains($mappingDriverDef->getClass(), 'yml') || str_contains($mappingDriverDef->getClass(), 'xml')) { + if (str_contains($mappingDriverDef->getClass(), 'yml') || str_contains($mappingDriverDef->getClass(), 'xml') + || str_contains($mappingDriverDef->getClass(), 'Yaml') || str_contains($mappingDriverDef->getClass(), 'Xml') + ) { $mappingDriverDef->setArguments([array_flip($driverPaths)]); $mappingDriverDef->addMethodCall('setGlobalBasename', ['mapping']); } @@ -386,7 +388,7 @@ abstract protected function getMappingObjectDefaultName(): string; /** * Relative path from the bundle root to the directory where mapping files reside. */ - abstract protected function getMappingResourceConfigDirectory(string $bundleDir = null): string; + abstract protected function getMappingResourceConfigDirectory(?string $bundleDir = null): string; /** * Extension used by the mapping files. diff --git a/src/Symfony/Bridge/Doctrine/Form/ChoiceList/IdReader.php b/src/Symfony/Bridge/Doctrine/Form/ChoiceList/IdReader.php index b03c832ac13e6..1baed3b718d1c 100644 --- a/src/Symfony/Bridge/Doctrine/Form/ChoiceList/IdReader.php +++ b/src/Symfony/Bridge/Doctrine/Form/ChoiceList/IdReader.php @@ -76,7 +76,7 @@ public function isIntId(): bool * * This method assumes that the object has a single-column ID. */ - public function getIdValue(object $object = null): string + public function getIdValue(?object $object = null): string { if (!$object) { return ''; diff --git a/src/Symfony/Bridge/Doctrine/Form/DoctrineOrmTypeGuesser.php b/src/Symfony/Bridge/Doctrine/Form/DoctrineOrmTypeGuesser.php index 19b683b3e14b8..6ef7b258808ec 100644 --- a/src/Symfony/Bridge/Doctrine/Form/DoctrineOrmTypeGuesser.php +++ b/src/Symfony/Bridge/Doctrine/Form/DoctrineOrmTypeGuesser.php @@ -14,6 +14,7 @@ use Doctrine\DBAL\Types\Types; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Mapping\ClassMetadataInfo; +use Doctrine\ORM\Mapping\JoinColumnMapping; use Doctrine\ORM\Mapping\MappingException as LegacyMappingException; use Doctrine\Persistence\ManagerRegistry; use Doctrine\Persistence\Mapping\MappingException; @@ -110,13 +111,13 @@ public function guessRequired(string $class, string $property): ?ValueGuess if ($classMetadata->isAssociationWithSingleJoinColumn($property)) { $mapping = $classMetadata->getAssociationMapping($property); - if (!isset($mapping['joinColumns'][0]['nullable'])) { + if (null === self::getMappingValue($mapping['joinColumns'][0], 'nullable')) { // The "nullable" option defaults to true, in that case the // field should not be required. return new ValueGuess(false, Guess::HIGH_CONFIDENCE); } - return new ValueGuess(!$mapping['joinColumns'][0]['nullable'], Guess::HIGH_CONFIDENCE); + return new ValueGuess(!self::getMappingValue($mapping['joinColumns'][0], 'nullable'), Guess::HIGH_CONFIDENCE); } return null; @@ -190,4 +191,13 @@ private static function getRealClass(string $class): string return substr($class, $pos + Proxy::MARKER_LENGTH + 2); } + + private static function getMappingValue(array|JoinColumnMapping $mapping, string $key): mixed + { + if ($mapping instanceof JoinColumnMapping) { + return $mapping->$key; + } + + return $mapping[$key] ?? null; + } } diff --git a/src/Symfony/Bridge/Doctrine/IdGenerator/UuidGenerator.php b/src/Symfony/Bridge/Doctrine/IdGenerator/UuidGenerator.php index 408b1e19af995..72bab54129e28 100644 --- a/src/Symfony/Bridge/Doctrine/IdGenerator/UuidGenerator.php +++ b/src/Symfony/Bridge/Doctrine/IdGenerator/UuidGenerator.php @@ -26,7 +26,7 @@ final class UuidGenerator extends AbstractIdGenerator private UuidFactory|NameBasedUuidFactory|RandomBasedUuidFactory|TimeBasedUuidFactory $factory; private ?string $entityGetter = null; - public function __construct(UuidFactory $factory = null) + public function __construct(?UuidFactory $factory = null) { $this->protoFactory = $this->factory = $factory ?? new UuidFactory(); } @@ -52,7 +52,7 @@ public function generateId(EntityManagerInterface $em, $entity): Uuid return $this->factory->create(); } - public function nameBased(string $entityGetter, Uuid|string $namespace = null): static + public function nameBased(string $entityGetter, Uuid|string|null $namespace = null): static { $clone = clone $this; $clone->factory = $clone->protoFactory->nameBased($namespace); @@ -70,7 +70,7 @@ public function randomBased(): static return $clone; } - public function timeBased(Uuid|string $node = null): static + public function timeBased(Uuid|string|null $node = null): static { $clone = clone $this; $clone->factory = $clone->protoFactory->timeBased($node); diff --git a/src/Symfony/Bridge/Doctrine/Messenger/AbstractDoctrineMiddleware.php b/src/Symfony/Bridge/Doctrine/Messenger/AbstractDoctrineMiddleware.php index 95fcf21d210bb..649a19716f16f 100644 --- a/src/Symfony/Bridge/Doctrine/Messenger/AbstractDoctrineMiddleware.php +++ b/src/Symfony/Bridge/Doctrine/Messenger/AbstractDoctrineMiddleware.php @@ -28,7 +28,7 @@ abstract class AbstractDoctrineMiddleware implements MiddlewareInterface protected ManagerRegistry $managerRegistry; protected ?string $entityManagerName; - public function __construct(ManagerRegistry $managerRegistry, string $entityManagerName = null) + public function __construct(ManagerRegistry $managerRegistry, ?string $entityManagerName = null) { $this->managerRegistry = $managerRegistry; $this->entityManagerName = $entityManagerName; diff --git a/src/Symfony/Bridge/Doctrine/Messenger/DoctrineOpenTransactionLoggerMiddleware.php b/src/Symfony/Bridge/Doctrine/Messenger/DoctrineOpenTransactionLoggerMiddleware.php index 78e6af93792b1..75187a1e490c5 100644 --- a/src/Symfony/Bridge/Doctrine/Messenger/DoctrineOpenTransactionLoggerMiddleware.php +++ b/src/Symfony/Bridge/Doctrine/Messenger/DoctrineOpenTransactionLoggerMiddleware.php @@ -28,7 +28,7 @@ class DoctrineOpenTransactionLoggerMiddleware extends AbstractDoctrineMiddleware public function __construct( ManagerRegistry $managerRegistry, - string $entityManagerName = null, + ?string $entityManagerName = null, private readonly ?LoggerInterface $logger = null, ) { parent::__construct($managerRegistry, $entityManagerName); diff --git a/src/Symfony/Bridge/Doctrine/PropertyInfo/DoctrineExtractor.php b/src/Symfony/Bridge/Doctrine/PropertyInfo/DoctrineExtractor.php index 89edf1ce2d2ff..fbe25db53766f 100644 --- a/src/Symfony/Bridge/Doctrine/PropertyInfo/DoctrineExtractor.php +++ b/src/Symfony/Bridge/Doctrine/PropertyInfo/DoctrineExtractor.php @@ -16,12 +16,17 @@ use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Mapping\AssociationMapping; use Doctrine\ORM\Mapping\ClassMetadata; +use Doctrine\ORM\Mapping\EmbeddedClassMapping; +use Doctrine\ORM\Mapping\FieldMapping; +use Doctrine\ORM\Mapping\JoinColumnMapping; use Doctrine\ORM\Mapping\MappingException as OrmMappingException; use Doctrine\Persistence\Mapping\MappingException; use Symfony\Component\PropertyInfo\PropertyAccessExtractorInterface; use Symfony\Component\PropertyInfo\PropertyListExtractorInterface; use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface; -use Symfony\Component\PropertyInfo\Type; +use Symfony\Component\PropertyInfo\Type as LegacyType; +use Symfony\Component\TypeInfo\Type; +use Symfony\Component\TypeInfo\TypeIdentifier; /** * Extracts data using Doctrine ORM and ODM metadata. @@ -52,8 +57,149 @@ public function getProperties(string $class, array $context = []): ?array return $properties; } + public function getType(string $class, string $property, array $context = []): ?Type + { + if (null === $metadata = $this->getMetadata($class)) { + return null; + } + + if ($metadata->hasAssociation($property)) { + $class = $metadata->getAssociationTargetClass($property); + + if ($metadata->isSingleValuedAssociation($property)) { + if ($metadata instanceof ClassMetadata) { + $associationMapping = $metadata->getAssociationMapping($property); + $nullable = $this->isAssociationNullable($associationMapping); + } else { + $nullable = false; + } + + $t = Type::object($class); + + return $nullable ? Type::nullable($t) : $t; + } + + $collectionKeyType = TypeIdentifier::INT; + + if ($metadata instanceof ClassMetadata) { + $associationMapping = $metadata->getAssociationMapping($property); + + if (self::getMappingValue($associationMapping, 'indexBy')) { + $subMetadata = $this->entityManager->getClassMetadata(self::getMappingValue($associationMapping, 'targetEntity')); + + // Check if indexBy value is a property + $fieldName = self::getMappingValue($associationMapping, 'indexBy'); + if (null === ($typeOfField = $subMetadata->getTypeOfField($fieldName))) { + $fieldName = $subMetadata->getFieldForColumn(self::getMappingValue($associationMapping, 'indexBy')); + // Not a property, maybe a column name? + if (null === ($typeOfField = $subMetadata->getTypeOfField($fieldName))) { + // Maybe the column name is the association join column? + $associationMapping = $subMetadata->getAssociationMapping($fieldName); + + $indexProperty = $subMetadata->getSingleAssociationReferencedJoinColumnName($fieldName); + $subMetadata = $this->entityManager->getClassMetadata(self::getMappingValue($associationMapping, 'targetEntity')); + + // Not a property, maybe a column name? + if (null === ($typeOfField = $subMetadata->getTypeOfField($indexProperty))) { + $fieldName = $subMetadata->getFieldForColumn($indexProperty); + $typeOfField = $subMetadata->getTypeOfField($fieldName); + } + } + } + + if (!$collectionKeyType = $this->getPhpType($typeOfField)) { + return null; + } + } + } + + return Type::collection(Type::object(Collection::class), Type::object($class), Type::builtin($collectionKeyType)); + } + + if ($metadata instanceof ClassMetadata && isset($metadata->embeddedClasses[$property])) { + return Type::object(self::getMappingValue($metadata->embeddedClasses[$property], 'class')); + } + + if ($metadata->hasField($property)) { + $typeOfField = $metadata->getTypeOfField($property); + + if (!$builtinType = $this->getPhpType($typeOfField)) { + return null; + } + + $nullable = $metadata instanceof ClassMetadata && $metadata->isNullable($property); + $enumType = null; + if (null !== $enumClass = self::getMappingValue($metadata->getFieldMapping($property), 'enumType') ?? null) { + $enumType = Type::enum($enumClass); + $enumType = $nullable ? Type::nullable($enumType) : $enumType; + } + + switch ($builtinType) { + case TypeIdentifier::OBJECT: + switch ($typeOfField) { + case Types::DATE_MUTABLE: + case Types::DATETIME_MUTABLE: + case Types::DATETIMETZ_MUTABLE: + case 'vardatetime': + case Types::TIME_MUTABLE: + $t = Type::object(\DateTime::class); + + return $nullable ? Type::nullable($t) : $t; + + case Types::DATE_IMMUTABLE: + case Types::DATETIME_IMMUTABLE: + case Types::DATETIMETZ_IMMUTABLE: + case Types::TIME_IMMUTABLE: + $t = Type::object(\DateTimeImmutable::class); + + return $nullable ? Type::nullable($t) : $t; + + case Types::DATEINTERVAL: + $t = Type::object(\DateInterval::class); + + return $nullable ? Type::nullable($t) : $t; + } + + break; + case TypeIdentifier::ARRAY: + switch ($typeOfField) { + case 'array': // DBAL < 4 + case 'json_array': // DBAL < 3 + // return null if $enumType is set, because we can't determine if collectionKeyType is string or int + if ($enumType) { + return null; + } + + $t = Type::array(); + + return $nullable ? Type::nullable($t) : $t; + + case Types::SIMPLE_ARRAY: + $t = Type::list($enumType ?? Type::string()); + + return $nullable ? Type::nullable($t) : $t; + } + break; + case TypeIdentifier::INT: + case TypeIdentifier::STRING: + if ($enumType) { + return $enumType; + } + break; + } + + $t = Type::builtin($builtinType); + + return $nullable ? Type::nullable($t) : $t; + } + + return null; + } + public function getTypes(string $class, string $property, array $context = []): ?array { + trigger_deprecation('symfony/doctrine-bridge', '7.1', 'The "%s()" method is deprecated, use "%s::getType()" instead.', __METHOD__, self::class); + if (null === $metadata = $this->getMetadata($class)) { return null; } @@ -70,10 +216,10 @@ public function getTypes(string $class, string $property, array $context = []): $nullable = false; } - return [new Type(Type::BUILTIN_TYPE_OBJECT, $nullable, $class)]; + return [new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, $nullable, $class)]; } - $collectionKeyType = Type::BUILTIN_TYPE_INT; + $collectionKeyType = LegacyType::BUILTIN_TYPE_INT; if ($metadata instanceof ClassMetadata) { $associationMapping = $metadata->getAssociationMapping($property); @@ -101,61 +247,61 @@ public function getTypes(string $class, string $property, array $context = []): } } - if (!$collectionKeyType = $this->getPhpType($typeOfField)) { + if (!$collectionKeyType = $this->getPhpType($typeOfField)?->value) { return null; } } } - return [new Type( - Type::BUILTIN_TYPE_OBJECT, + return [new LegacyType( + LegacyType::BUILTIN_TYPE_OBJECT, false, Collection::class, true, - new Type($collectionKeyType), - new Type(Type::BUILTIN_TYPE_OBJECT, false, $class) + new LegacyType($collectionKeyType), + new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, $class) )]; } if ($metadata instanceof ClassMetadata && isset($metadata->embeddedClasses[$property])) { - return [new Type(Type::BUILTIN_TYPE_OBJECT, false, $metadata->embeddedClasses[$property]['class'])]; + return [new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, $metadata->embeddedClasses[$property]['class'])]; } if ($metadata->hasField($property)) { $typeOfField = $metadata->getTypeOfField($property); - if (!$builtinType = $this->getPhpType($typeOfField)) { + if (!$builtinType = $this->getPhpType($typeOfField)?->value) { return null; } $nullable = $metadata instanceof ClassMetadata && $metadata->isNullable($property); $enumType = null; if (null !== $enumClass = $metadata->getFieldMapping($property)['enumType'] ?? null) { - $enumType = new Type(Type::BUILTIN_TYPE_OBJECT, $nullable, $enumClass); + $enumType = new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, $nullable, $enumClass); } switch ($builtinType) { - case Type::BUILTIN_TYPE_OBJECT: + case LegacyType::BUILTIN_TYPE_OBJECT: switch ($typeOfField) { case Types::DATE_MUTABLE: case Types::DATETIME_MUTABLE: case Types::DATETIMETZ_MUTABLE: case 'vardatetime': case Types::TIME_MUTABLE: - return [new Type(Type::BUILTIN_TYPE_OBJECT, $nullable, 'DateTime')]; + return [new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, $nullable, 'DateTime')]; case Types::DATE_IMMUTABLE: case Types::DATETIME_IMMUTABLE: case Types::DATETIMETZ_IMMUTABLE: case Types::TIME_IMMUTABLE: - return [new Type(Type::BUILTIN_TYPE_OBJECT, $nullable, 'DateTimeImmutable')]; + return [new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, $nullable, 'DateTimeImmutable')]; case Types::DATEINTERVAL: - return [new Type(Type::BUILTIN_TYPE_OBJECT, $nullable, 'DateInterval')]; + return [new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, $nullable, 'DateInterval')]; } break; - case Type::BUILTIN_TYPE_ARRAY: + case LegacyType::BUILTIN_TYPE_ARRAY: switch ($typeOfField) { case 'array': // DBAL < 4 case 'json_array': // DBAL < 3 @@ -164,21 +310,21 @@ public function getTypes(string $class, string $property, array $context = []): return null; } - return [new Type(Type::BUILTIN_TYPE_ARRAY, $nullable, null, true)]; + return [new LegacyType(LegacyType::BUILTIN_TYPE_ARRAY, $nullable, null, true)]; case Types::SIMPLE_ARRAY: - return [new Type(Type::BUILTIN_TYPE_ARRAY, $nullable, null, true, new Type(Type::BUILTIN_TYPE_INT), $enumType ?? new Type(Type::BUILTIN_TYPE_STRING))]; + return [new LegacyType(LegacyType::BUILTIN_TYPE_ARRAY, $nullable, null, true, new LegacyType(LegacyType::BUILTIN_TYPE_INT), $enumType ?? new LegacyType(LegacyType::BUILTIN_TYPE_STRING))]; } break; - case Type::BUILTIN_TYPE_INT: - case Type::BUILTIN_TYPE_STRING: + case LegacyType::BUILTIN_TYPE_INT: + case LegacyType::BUILTIN_TYPE_STRING: if ($enumType) { return [$enumType]; } break; } - return [new Type($builtinType, $nullable)]; + return [new LegacyType($builtinType, $nullable)]; } return null; @@ -220,17 +366,17 @@ private function getMetadata(string $class): ?ClassMetadata */ private function isAssociationNullable(array|AssociationMapping $associationMapping): bool { - if (isset($associationMapping['id']) && $associationMapping['id']) { + if (self::getMappingValue($associationMapping, 'id')) { return false; } - if (!isset($associationMapping['joinColumns'])) { + if (!self::getMappingValue($associationMapping, 'joinColumns')) { return true; } - $joinColumns = $associationMapping['joinColumns']; + $joinColumns = self::getMappingValue($associationMapping, 'joinColumns'); foreach ($joinColumns as $joinColumn) { - if (isset($joinColumn['nullable']) && !$joinColumn['nullable']) { + if (false === self::getMappingValue($joinColumn, 'nullable')) { return false; } } @@ -241,20 +387,20 @@ private function isAssociationNullable(array|AssociationMapping $associationMapp /** * Gets the corresponding built-in PHP type. */ - private function getPhpType(string $doctrineType): ?string + private function getPhpType(string $doctrineType): ?TypeIdentifier { return match ($doctrineType) { Types::SMALLINT, - Types::INTEGER => Type::BUILTIN_TYPE_INT, - Types::FLOAT => Type::BUILTIN_TYPE_FLOAT, + Types::INTEGER => TypeIdentifier::INT, + Types::FLOAT => TypeIdentifier::FLOAT, Types::BIGINT, Types::STRING, Types::TEXT, Types::GUID, - Types::DECIMAL => Type::BUILTIN_TYPE_STRING, - Types::BOOLEAN => Type::BUILTIN_TYPE_BOOL, + Types::DECIMAL => TypeIdentifier::STRING, + Types::BOOLEAN => TypeIdentifier::BOOL, Types::BLOB, - Types::BINARY => Type::BUILTIN_TYPE_RESOURCE, + Types::BINARY => TypeIdentifier::RESOURCE, 'object', // DBAL < 4 Types::DATE_MUTABLE, Types::DATETIME_MUTABLE, @@ -265,11 +411,20 @@ private function getPhpType(string $doctrineType): ?string Types::DATETIME_IMMUTABLE, Types::DATETIMETZ_IMMUTABLE, Types::TIME_IMMUTABLE, - Types::DATEINTERVAL => Type::BUILTIN_TYPE_OBJECT, + Types::DATEINTERVAL => TypeIdentifier::OBJECT, 'array', // DBAL < 4 'json_array', // DBAL < 3 - Types::SIMPLE_ARRAY => Type::BUILTIN_TYPE_ARRAY, + Types::SIMPLE_ARRAY => TypeIdentifier::ARRAY, default => null, }; } + + private static function getMappingValue(array|AssociationMapping|EmbeddedClassMapping|FieldMapping|JoinColumnMapping $mapping, string $key): mixed + { + if ($mapping instanceof AssociationMapping || $mapping instanceof EmbeddedClassMapping || $mapping instanceof FieldMapping || $mapping instanceof JoinColumnMapping) { + return $mapping->$key; + } + + return $mapping[$key] ?? null; + } } diff --git a/src/Symfony/Bridge/Doctrine/Tests/ArgumentResolver/EntityValueResolverTest.php b/src/Symfony/Bridge/Doctrine/Tests/ArgumentResolver/EntityValueResolverTest.php index f45c8b6d27f66..64ccc1656d75c 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/ArgumentResolver/EntityValueResolverTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/ArgumentResolver/EntityValueResolverTest.php @@ -153,7 +153,7 @@ public function testResolveWithConversionFailedException() $request = new Request(); $request->attributes->set('id', 'test'); - $argument = $this->createArgument('stdClass', new MapEntity(id: 'id')); + $argument = $this->createArgument('stdClass', new MapEntity(id: 'id', message: 'Test')); $repository = $this->getMockBuilder(ObjectRepository::class)->getMock(); $repository->expects($this->once()) @@ -167,6 +167,7 @@ public function testResolveWithConversionFailedException() ->willReturn($repository); $this->expectException(NotFoundHttpException::class); + $this->expectExceptionMessage('Test'); $resolver->resolve($request, $argument); } @@ -388,12 +389,12 @@ public function testAlreadyResolved() $this->assertSame([], $resolver->resolve($request, $argument)); } - private function createArgument(string $class = null, MapEntity $entity = null, string $name = 'arg', bool $isNullable = false): ArgumentMetadata + private function createArgument(?string $class = null, ?MapEntity $entity = null, string $name = 'arg', bool $isNullable = false): ArgumentMetadata { return new ArgumentMetadata($name, $class ?? \stdClass::class, false, false, null, $isNullable, $entity ? [$entity] : []); } - private function createRegistry(ObjectManager $manager = null): ManagerRegistry&MockObject + private function createRegistry(?ObjectManager $manager = null): ManagerRegistry&MockObject { $registry = $this->getMockBuilder(ManagerRegistry::class)->getMock(); diff --git a/src/Symfony/Bridge/Doctrine/Tests/DoctrineTestHelper.php b/src/Symfony/Bridge/Doctrine/Tests/DoctrineTestHelper.php index 509d250e12afc..e8d36c892b942 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/DoctrineTestHelper.php +++ b/src/Symfony/Bridge/Doctrine/Tests/DoctrineTestHelper.php @@ -33,7 +33,7 @@ final class DoctrineTestHelper /** * Returns an entity manager for testing. */ - public static function createTestEntityManager(Configuration $config = null): EntityManager + public static function createTestEntityManager(?Configuration $config = null): EntityManager { if (!\extension_loaded('pdo_sqlite')) { TestCase::markTestSkipped('Extension pdo_sqlite is required.'); diff --git a/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypeTest.php b/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypeTest.php index a3946c624f85d..783cf5a5524e9 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypeTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypeTest.php @@ -766,7 +766,7 @@ public function testOverrideChoicesValuesWithCallable() 'em' => 'default', 'class' => self::ITEM_GROUP_CLASS, 'choice_label' => 'name', - 'choice_value' => function (GroupableEntity $entity = null) { + 'choice_value' => function (?GroupableEntity $entity = null) { if (null === $entity) { return ''; } diff --git a/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/DoctrineExtractorTest.php b/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/DoctrineExtractorTest.php index a4e254da35307..35919529be459 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/DoctrineExtractorTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/DoctrineExtractorTest.php @@ -29,7 +29,8 @@ use Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\DoctrineWithEmbedded; use Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\EnumInt; use Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\EnumString; -use Symfony\Component\PropertyInfo\Type; +use Symfony\Component\PropertyInfo\Type as LegacyType; +use Symfony\Component\TypeInfo\Type; /** * @author Kévin Dunglas @@ -106,17 +107,22 @@ public function testTestGetPropertiesWithEmbedded() } /** - * @dataProvider typesProvider + * @group legacy + * + * @dataProvider legacyTypesProvider */ - public function testExtract(string $property, array $type = null) + public function testExtractLegacy(string $property, ?array $type = null) { $this->assertEquals($type, $this->createExtractor()->getTypes(DoctrineDummy::class, $property, [])); } - public function testExtractWithEmbedded() + /** + * @group legacy + */ + public function testExtractWithEmbeddedLegacy() { - $expectedTypes = [new Type( - Type::BUILTIN_TYPE_OBJECT, + $expectedTypes = [new LegacyType( + LegacyType::BUILTIN_TYPE_OBJECT, false, DoctrineEmbeddable::class )]; @@ -130,97 +136,103 @@ public function testExtractWithEmbedded() $this->assertEquals($expectedTypes, $actualTypes); } - public function testExtractEnum() + /** + * @group legacy + */ + public function testExtractEnumLegacy() { - $this->assertEquals([new Type(Type::BUILTIN_TYPE_OBJECT, false, EnumString::class)], $this->createExtractor()->getTypes(DoctrineEnum::class, 'enumString', [])); - $this->assertEquals([new Type(Type::BUILTIN_TYPE_OBJECT, false, EnumInt::class)], $this->createExtractor()->getTypes(DoctrineEnum::class, 'enumInt', [])); + $this->assertEquals([new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, EnumString::class)], $this->createExtractor()->getTypes(DoctrineEnum::class, 'enumString', [])); + $this->assertEquals([new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, EnumInt::class)], $this->createExtractor()->getTypes(DoctrineEnum::class, 'enumInt', [])); $this->assertNull($this->createExtractor()->getTypes(DoctrineEnum::class, 'enumStringArray', [])); - $this->assertEquals([new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_OBJECT, false, EnumInt::class))], $this->createExtractor()->getTypes(DoctrineEnum::class, 'enumIntArray', [])); + $this->assertEquals([new LegacyType(LegacyType::BUILTIN_TYPE_ARRAY, false, null, true, new LegacyType(LegacyType::BUILTIN_TYPE_INT), new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, EnumInt::class))], $this->createExtractor()->getTypes(DoctrineEnum::class, 'enumIntArray', [])); $this->assertNull($this->createExtractor()->getTypes(DoctrineEnum::class, 'enumCustom', [])); } - public static function typesProvider(): array + /** + * @group legacy + */ + public static function legacyTypesProvider(): array { return [ - ['id', [new Type(Type::BUILTIN_TYPE_INT)]], - ['guid', [new Type(Type::BUILTIN_TYPE_STRING)]], - ['bigint', [new Type(Type::BUILTIN_TYPE_STRING)]], - ['time', [new Type(Type::BUILTIN_TYPE_OBJECT, false, 'DateTime')]], - ['timeImmutable', [new Type(Type::BUILTIN_TYPE_OBJECT, false, 'DateTimeImmutable')]], - ['dateInterval', [new Type(Type::BUILTIN_TYPE_OBJECT, false, 'DateInterval')]], - ['float', [new Type(Type::BUILTIN_TYPE_FLOAT)]], - ['decimal', [new Type(Type::BUILTIN_TYPE_STRING)]], - ['bool', [new Type(Type::BUILTIN_TYPE_BOOL)]], - ['binary', [new Type(Type::BUILTIN_TYPE_RESOURCE)]], - ['jsonArray', [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true)]], - ['foo', [new Type(Type::BUILTIN_TYPE_OBJECT, true, 'Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\DoctrineRelation')]], - ['bar', [new Type( - Type::BUILTIN_TYPE_OBJECT, + ['id', [new LegacyType(LegacyType::BUILTIN_TYPE_INT)]], + ['guid', [new LegacyType(LegacyType::BUILTIN_TYPE_STRING)]], + ['bigint', [new LegacyType(LegacyType::BUILTIN_TYPE_STRING)]], + ['time', [new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, 'DateTime')]], + ['timeImmutable', [new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, 'DateTimeImmutable')]], + ['dateInterval', [new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, 'DateInterval')]], + ['float', [new LegacyType(LegacyType::BUILTIN_TYPE_FLOAT)]], + ['decimal', [new LegacyType(LegacyType::BUILTIN_TYPE_STRING)]], + ['bool', [new LegacyType(LegacyType::BUILTIN_TYPE_BOOL)]], + ['binary', [new LegacyType(LegacyType::BUILTIN_TYPE_RESOURCE)]], + ['jsonArray', [new LegacyType(LegacyType::BUILTIN_TYPE_ARRAY, false, null, true)]], + ['foo', [new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, true, 'Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\DoctrineRelation')]], + ['bar', [new LegacyType( + LegacyType::BUILTIN_TYPE_OBJECT, false, 'Doctrine\Common\Collections\Collection', true, - new Type(Type::BUILTIN_TYPE_INT), - new Type(Type::BUILTIN_TYPE_OBJECT, false, 'Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\DoctrineRelation') + new LegacyType(LegacyType::BUILTIN_TYPE_INT), + new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, 'Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\DoctrineRelation') )]], - ['indexedRguid', [new Type( - Type::BUILTIN_TYPE_OBJECT, + ['indexedRguid', [new LegacyType( + LegacyType::BUILTIN_TYPE_OBJECT, false, 'Doctrine\Common\Collections\Collection', true, - new Type(Type::BUILTIN_TYPE_STRING), - new Type(Type::BUILTIN_TYPE_OBJECT, false, 'Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\DoctrineRelation') + new LegacyType(LegacyType::BUILTIN_TYPE_STRING), + new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, 'Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\DoctrineRelation') )]], - ['indexedBar', [new Type( - Type::BUILTIN_TYPE_OBJECT, + ['indexedBar', [new LegacyType( + LegacyType::BUILTIN_TYPE_OBJECT, false, 'Doctrine\Common\Collections\Collection', true, - new Type(Type::BUILTIN_TYPE_STRING), - new Type(Type::BUILTIN_TYPE_OBJECT, false, 'Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\DoctrineRelation') + new LegacyType(LegacyType::BUILTIN_TYPE_STRING), + new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, 'Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\DoctrineRelation') )]], - ['indexedFoo', [new Type( - Type::BUILTIN_TYPE_OBJECT, + ['indexedFoo', [new LegacyType( + LegacyType::BUILTIN_TYPE_OBJECT, false, 'Doctrine\Common\Collections\Collection', true, - new Type(Type::BUILTIN_TYPE_STRING), - new Type(Type::BUILTIN_TYPE_OBJECT, false, 'Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\DoctrineRelation') + new LegacyType(LegacyType::BUILTIN_TYPE_STRING), + new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, 'Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\DoctrineRelation') )]], - ['indexedBaz', [new Type( - Type::BUILTIN_TYPE_OBJECT, + ['indexedBaz', [new LegacyType( + LegacyType::BUILTIN_TYPE_OBJECT, false, Collection::class, true, - new Type(Type::BUILTIN_TYPE_INT), - new Type(Type::BUILTIN_TYPE_OBJECT, false, DoctrineRelation::class) + new LegacyType(LegacyType::BUILTIN_TYPE_INT), + new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, DoctrineRelation::class) )]], - ['simpleArray', [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_STRING))]], + ['simpleArray', [new LegacyType(LegacyType::BUILTIN_TYPE_ARRAY, false, null, true, new LegacyType(LegacyType::BUILTIN_TYPE_INT), new LegacyType(LegacyType::BUILTIN_TYPE_STRING))]], ['customFoo', null], ['notMapped', null], - ['indexedByDt', [new Type( - Type::BUILTIN_TYPE_OBJECT, + ['indexedByDt', [new LegacyType( + LegacyType::BUILTIN_TYPE_OBJECT, false, Collection::class, true, - new Type(Type::BUILTIN_TYPE_OBJECT), - new Type(Type::BUILTIN_TYPE_OBJECT, false, DoctrineRelation::class) + new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT), + new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, DoctrineRelation::class) )]], ['indexedByCustomType', null], - ['indexedBuz', [new Type( - Type::BUILTIN_TYPE_OBJECT, + ['indexedBuz', [new LegacyType( + LegacyType::BUILTIN_TYPE_OBJECT, false, Collection::class, true, - new Type(Type::BUILTIN_TYPE_STRING), - new Type(Type::BUILTIN_TYPE_OBJECT, false, DoctrineRelation::class) + new LegacyType(LegacyType::BUILTIN_TYPE_STRING), + new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, DoctrineRelation::class) )]], - ['dummyGeneratedValueList', [new Type( - Type::BUILTIN_TYPE_OBJECT, + ['dummyGeneratedValueList', [new LegacyType( + LegacyType::BUILTIN_TYPE_OBJECT, false, 'Doctrine\Common\Collections\Collection', true, - new Type(Type::BUILTIN_TYPE_INT), - new Type(Type::BUILTIN_TYPE_OBJECT, false, DoctrineRelation::class) + new LegacyType(LegacyType::BUILTIN_TYPE_INT), + new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, DoctrineRelation::class) )]], ['json', null], ]; @@ -231,7 +243,10 @@ public function testGetPropertiesCatchException() $this->assertNull($this->createExtractor()->getProperties('Not\Exist')); } - public function testGetTypesCatchException() + /** + * @group legacy + */ + public function testGetTypesCatchExceptionLegacy() { $this->assertNull($this->createExtractor()->getTypes('Not\Exist', 'baz')); } @@ -244,4 +259,66 @@ public function testGeneratedValueNotWritable() $this->assertNull($extractor->isWritable(DoctrineGeneratedValue::class, 'foo')); $this->assertNull($extractor->isReadable(DoctrineGeneratedValue::class, 'foo')); } + + public function testExtractWithEmbedded() + { + $this->assertEquals( + Type::object(DoctrineEmbeddable::class), + $this->createExtractor()->getType(DoctrineWithEmbedded::class, 'embedded'), + ); + } + + public function testExtractEnum() + { + $this->assertEquals(Type::enum(EnumString::class), $this->createExtractor()->getType(DoctrineEnum::class, 'enumString')); + $this->assertEquals(Type::enum(EnumInt::class), $this->createExtractor()->getType(DoctrineEnum::class, 'enumInt')); + $this->assertNull($this->createExtractor()->getType(DoctrineEnum::class, 'enumStringArray')); + $this->assertEquals(Type::list(Type::enum(EnumInt::class)), $this->createExtractor()->getType(DoctrineEnum::class, 'enumIntArray')); + $this->assertNull($this->createExtractor()->getType(DoctrineEnum::class, 'enumCustom')); + } + + /** + * @dataProvider typeProvider + */ + public function testExtract(string $property, ?Type $type) + { + $this->assertEquals($type, $this->createExtractor()->getType(DoctrineDummy::class, $property, [])); + } + + /** + * @return iterable + */ + public static function typeProvider(): iterable + { + yield ['id', Type::int()]; + yield ['guid', Type::string()]; + yield ['bigint', Type::string()]; + yield ['time', Type::object(\DateTime::class)]; + yield ['timeImmutable', Type::object(\DateTimeImmutable::class)]; + yield ['dateInterval', Type::object(\DateInterval::class)]; + yield ['float', Type::float()]; + yield ['decimal', Type::string()]; + yield ['bool', Type::bool()]; + yield ['binary', Type::resource()]; + yield ['jsonArray', Type::array()]; + yield ['foo', Type::nullable(Type::object(DoctrineRelation::class))]; + yield ['bar', Type::collection(Type::object(Collection::class), Type::object(DoctrineRelation::class), Type::int())]; + yield ['indexedRguid', Type::collection(Type::object(Collection::class), Type::object(DoctrineRelation::class), Type::string())]; + yield ['indexedBar', Type::collection(Type::object(Collection::class), Type::object(DoctrineRelation::class), Type::string())]; + yield ['indexedFoo', Type::collection(Type::object(Collection::class), Type::object(DoctrineRelation::class), Type::string())]; + yield ['indexedBaz', Type::collection(Type::object(Collection::class), Type::object(DoctrineRelation::class), Type::int())]; + yield ['simpleArray', Type::list(Type::string())]; + yield ['customFoo', null]; + yield ['notMapped', null]; + yield ['indexedByDt', Type::collection(Type::object(Collection::class), Type::object(DoctrineRelation::class), Type::object())]; + yield ['indexedByCustomType', null]; + yield ['indexedBuz', Type::collection(Type::object(Collection::class), Type::object(DoctrineRelation::class), Type::string())]; + yield ['dummyGeneratedValueList', Type::collection(Type::object(Collection::class), Type::object(DoctrineRelation::class), Type::int())]; + yield ['json', null]; + } + + public function testGetTypeCatchException() + { + $this->assertNull($this->createExtractor()->getType('Not\Exist', 'baz')); + } } diff --git a/src/Symfony/Bridge/Doctrine/Tests/Validator/DoctrineLoaderTest.php b/src/Symfony/Bridge/Doctrine/Tests/Validator/DoctrineLoaderTest.php index 8d63457a9406d..ef304114be0c4 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Validator/DoctrineLoaderTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Validator/DoctrineLoaderTest.php @@ -178,7 +178,7 @@ public function testFieldMappingsConfiguration() /** * @dataProvider regexpProvider */ - public function testClassValidator(bool $expected, string $classValidatorRegexp = null) + public function testClassValidator(bool $expected, ?string $classValidatorRegexp = null) { $doctrineLoader = new DoctrineLoader(DoctrineTestHelper::createTestEntityManager(), $classValidatorRegexp, false); diff --git a/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntity.php b/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntity.php index 36b372476a9fc..02e853016d202 100644 --- a/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntity.php +++ b/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntity.php @@ -47,14 +47,14 @@ class UniqueEntity extends Constraint */ public function __construct( array|string $fields, - string $message = null, - string $service = null, - string $em = null, - string $entityClass = null, - string $repositoryMethod = null, - string $errorPath = null, - bool|string|array $ignoreNull = null, - array $groups = null, + ?string $message = null, + ?string $service = null, + ?string $em = null, + ?string $entityClass = null, + ?string $repositoryMethod = null, + ?string $errorPath = null, + bool|string|array|null $ignoreNull = null, + ?array $groups = null, $payload = null, array $options = [], ) { diff --git a/src/Symfony/Bridge/Doctrine/Validator/DoctrineLoader.php b/src/Symfony/Bridge/Doctrine/Validator/DoctrineLoader.php index e3a939955ca84..fdaec0c72ce30 100644 --- a/src/Symfony/Bridge/Doctrine/Validator/DoctrineLoader.php +++ b/src/Symfony/Bridge/Doctrine/Validator/DoctrineLoader.php @@ -13,10 +13,10 @@ use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Mapping\ClassMetadata as OrmClassMetadata; +use Doctrine\ORM\Mapping\FieldMapping; use Doctrine\ORM\Mapping\MappingException as OrmMappingException; use Doctrine\Persistence\Mapping\MappingException; use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; -use Symfony\Component\PropertyInfo\Type; use Symfony\Component\Validator\Constraints\Length; use Symfony\Component\Validator\Constraints\Valid; use Symfony\Component\Validator\Mapping\AutoMappingStrategy; @@ -65,11 +65,11 @@ public function loadClassMetadata(ClassMetadata $metadata): bool */ $existingUniqueFields = $this->getExistingUniqueFields($metadata); - // Type and nullable aren't handled here, use the PropertyInfo Loader instead. + // type and nullable aren't handled here, use the PropertyInfo Loader instead. foreach ($doctrineMetadata->fieldMappings as $mapping) { $enabledForProperty = $enabledForClass; $lengthConstraint = null; - foreach ($metadata->getPropertyMetadata($mapping['fieldName']) as $propertyMetadata) { + foreach ($metadata->getPropertyMetadata(self::getFieldMappingValue($mapping, 'fieldName')) as $propertyMetadata) { // Enabling or disabling auto-mapping explicitly always takes precedence if (AutoMappingStrategy::DISABLED === $propertyMetadata->getAutoMappingStrategy()) { continue 2; @@ -89,26 +89,26 @@ public function loadClassMetadata(ClassMetadata $metadata): bool continue; } - if (true === ($mapping['unique'] ?? false) && !isset($existingUniqueFields[$mapping['fieldName']])) { - $metadata->addConstraint(new UniqueEntity(['fields' => $mapping['fieldName']])); + if (true === (self::getFieldMappingValue($mapping, 'unique') ?? false) && !isset($existingUniqueFields[self::getFieldMappingValue($mapping, 'fieldName')])) { + $metadata->addConstraint(new UniqueEntity(['fields' => self::getFieldMappingValue($mapping, 'fieldName')])); $loaded = true; } - if (null === ($mapping['length'] ?? null) || null !== ($mapping['enumType'] ?? null) || !\in_array($mapping['type'], ['string', 'text'], true)) { + if (null === (self::getFieldMappingValue($mapping, 'length') ?? null) || null !== (self::getFieldMappingValue($mapping, 'enumType') ?? null) || !\in_array(self::getFieldMappingValue($mapping, 'type'), ['string', 'text'], true)) { continue; } if (null === $lengthConstraint) { - if (isset($mapping['originalClass']) && !str_contains($mapping['declaredField'], '.')) { - $metadata->addPropertyConstraint($mapping['declaredField'], new Valid()); + if (self::getFieldMappingValue($mapping, 'originalClass') && !str_contains(self::getFieldMappingValue($mapping, 'declaredField'), '.')) { + $metadata->addPropertyConstraint(self::getFieldMappingValue($mapping, 'declaredField'), new Valid()); $loaded = true; - } elseif (property_exists($className, $mapping['fieldName']) && (!$doctrineMetadata->isMappedSuperclass || $metadata->getReflectionClass()->getProperty($mapping['fieldName'])->isPrivate())) { - $metadata->addPropertyConstraint($mapping['fieldName'], new Length(['max' => $mapping['length']])); + } elseif (property_exists($className, self::getFieldMappingValue($mapping, 'fieldName')) && (!$doctrineMetadata->isMappedSuperclass || $metadata->getReflectionClass()->getProperty(self::getFieldMappingValue($mapping, 'fieldName'))->isPrivate())) { + $metadata->addPropertyConstraint(self::getFieldMappingValue($mapping, 'fieldName'), new Length(['max' => self::getFieldMappingValue($mapping, 'length')])); $loaded = true; } } elseif (null === $lengthConstraint->max) { // If a Length constraint exists and no max length has been explicitly defined, set it - $lengthConstraint->max = $mapping['length']; + $lengthConstraint->max = self::getFieldMappingValue($mapping, 'length'); } } @@ -132,4 +132,13 @@ private function getExistingUniqueFields(ClassMetadata $metadata): array return $fields; } + + private static function getFieldMappingValue(array|FieldMapping $mapping, string $key): mixed + { + if ($mapping instanceof FieldMapping) { + return $mapping->$key; + } + + return $mapping[$key] ?? null; + } } diff --git a/src/Symfony/Bridge/Doctrine/composer.json b/src/Symfony/Bridge/Doctrine/composer.json index a5d1e1e09ba0c..e616e817e16c3 100644 --- a/src/Symfony/Bridge/Doctrine/composer.json +++ b/src/Symfony/Bridge/Doctrine/composer.json @@ -19,6 +19,7 @@ "php": ">=8.2", "doctrine/event-manager": "^2", "doctrine/persistence": "^3.1", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-ctype": "~1.8", "symfony/polyfill-mbstring": "~1.0", "symfony/service-contracts": "^2.5|^3" @@ -58,7 +59,7 @@ "symfony/http-kernel": "<6.4", "symfony/lock": "<6.4", "symfony/messenger": "<6.4", - "symfony/property-info": "<6.4", + "symfony/property-info": "<7.1", "symfony/security-bundle": "<6.4", "symfony/security-core": "<6.4", "symfony/validator": "<6.4" diff --git a/src/Symfony/Bridge/Monolog/Formatter/ConsoleFormatter.php b/src/Symfony/Bridge/Monolog/Formatter/ConsoleFormatter.php index a1d55e18fce9a..b8f797e34d1ed 100644 --- a/src/Symfony/Bridge/Monolog/Formatter/ConsoleFormatter.php +++ b/src/Symfony/Bridge/Monolog/Formatter/ConsoleFormatter.php @@ -176,7 +176,7 @@ private function replacePlaceHolder(LogRecord $record): LogRecord return $record->with(message: strtr($message, $replacements)); } - private function dumpData(mixed $data, bool $colors = null): string + private function dumpData(mixed $data, ?bool $colors = null): string { if (!isset($this->dumper)) { return ''; diff --git a/src/Symfony/Bridge/Monolog/Formatter/VarDumperFormatter.php b/src/Symfony/Bridge/Monolog/Formatter/VarDumperFormatter.php index 08af56ebbc0a8..b7c0a01e8d069 100644 --- a/src/Symfony/Bridge/Monolog/Formatter/VarDumperFormatter.php +++ b/src/Symfony/Bridge/Monolog/Formatter/VarDumperFormatter.php @@ -22,7 +22,7 @@ final class VarDumperFormatter implements FormatterInterface { private VarCloner $cloner; - public function __construct(VarCloner $cloner = null) + public function __construct(?VarCloner $cloner = null) { $this->cloner = $cloner ?? new VarCloner(); } diff --git a/src/Symfony/Bridge/Monolog/Handler/ConsoleHandler.php b/src/Symfony/Bridge/Monolog/Handler/ConsoleHandler.php index 6fcd9e88a940d..3f5df8bbed7b0 100644 --- a/src/Symfony/Bridge/Monolog/Handler/ConsoleHandler.php +++ b/src/Symfony/Bridge/Monolog/Handler/ConsoleHandler.php @@ -61,7 +61,7 @@ final class ConsoleHandler extends AbstractProcessingHandler implements EventSub * @param array $verbosityLevelMap Array that maps the OutputInterface verbosity to a minimum logging * level (leave empty to use the default mapping) */ - public function __construct(OutputInterface $output = null, bool $bubble = true, array $verbosityLevelMap = [], array $consoleFormatterOptions = []) + public function __construct(?OutputInterface $output = null, bool $bubble = true, array $verbosityLevelMap = [], array $consoleFormatterOptions = []) { parent::__construct(Level::Debug, $bubble); $this->output = $output; diff --git a/src/Symfony/Bridge/Monolog/Handler/ElasticsearchLogstashHandler.php b/src/Symfony/Bridge/Monolog/Handler/ElasticsearchLogstashHandler.php index a86ac18229178..004a68ccedb16 100644 --- a/src/Symfony/Bridge/Monolog/Handler/ElasticsearchLogstashHandler.php +++ b/src/Symfony/Bridge/Monolog/Handler/ElasticsearchLogstashHandler.php @@ -56,7 +56,7 @@ final class ElasticsearchLogstashHandler extends AbstractHandler */ private \SplObjectStorage $responses; - public function __construct(string $endpoint = 'http://127.0.0.1:9200', string $index = 'monolog', HttpClientInterface $client = null, string|int|Level $level = Level::Debug, bool $bubble = true, string $elasticsearchVersion = '1.0.0') + public function __construct(string $endpoint = 'http://127.0.0.1:9200', string $index = 'monolog', ?HttpClientInterface $client = null, string|int|Level $level = Level::Debug, bool $bubble = true, string $elasticsearchVersion = '1.0.0') { if (!interface_exists(HttpClientInterface::class)) { throw new \LogicException(sprintf('The "%s" handler needs an HTTP client. Try running "composer require symfony/http-client".', __CLASS__)); diff --git a/src/Symfony/Bridge/Monolog/Processor/DebugProcessor.php b/src/Symfony/Bridge/Monolog/Processor/DebugProcessor.php index a551ac5fa25b2..df9182becada9 100644 --- a/src/Symfony/Bridge/Monolog/Processor/DebugProcessor.php +++ b/src/Symfony/Bridge/Monolog/Processor/DebugProcessor.php @@ -24,7 +24,7 @@ class DebugProcessor implements DebugLoggerInterface, ResetInterface private array $errorCount = []; private ?RequestStack $requestStack; - public function __construct(RequestStack $requestStack = null) + public function __construct(?RequestStack $requestStack = null) { $this->requestStack = $requestStack; } @@ -54,7 +54,7 @@ public function __invoke(LogRecord $record): LogRecord return $record; } - public function getLogs(Request $request = null): array + public function getLogs(?Request $request = null): array { if (null !== $request) { return $this->records[spl_object_id($request)] ?? []; @@ -67,7 +67,7 @@ public function getLogs(Request $request = null): array return array_merge(...array_values($this->records)); } - public function countErrors(Request $request = null): int + public function countErrors(?Request $request = null): int { if (null !== $request) { return $this->errorCount[spl_object_id($request)] ?? 0; diff --git a/src/Symfony/Bridge/Monolog/Processor/WebProcessor.php b/src/Symfony/Bridge/Monolog/Processor/WebProcessor.php index e34219b97cc4c..8e5b6e7bd9e83 100644 --- a/src/Symfony/Bridge/Monolog/Processor/WebProcessor.php +++ b/src/Symfony/Bridge/Monolog/Processor/WebProcessor.php @@ -25,7 +25,7 @@ */ class WebProcessor extends BaseWebProcessor implements EventSubscriberInterface { - public function __construct(array $extraFields = null) + public function __construct(?array $extraFields = null) { // Pass an empty array as the default null value would access $_SERVER parent::__construct([], $extraFields); diff --git a/src/Symfony/Bridge/Monolog/Tests/Processor/ClassThatInheritDebugProcessor.php b/src/Symfony/Bridge/Monolog/Tests/Processor/ClassThatInheritDebugProcessor.php index bc87c724c9d31..697b5872cb579 100644 --- a/src/Symfony/Bridge/Monolog/Tests/Processor/ClassThatInheritDebugProcessor.php +++ b/src/Symfony/Bridge/Monolog/Tests/Processor/ClassThatInheritDebugProcessor.php @@ -16,12 +16,12 @@ class ClassThatInheritDebugProcessor extends DebugProcessor { - public function getLogs(Request $request = null): array + public function getLogs(?Request $request = null): array { return parent::getLogs($request); } - public function countErrors(Request $request = null): int + public function countErrors(?Request $request = null): int { return parent::countErrors($request); } diff --git a/src/Symfony/Bridge/PhpUnit/CoverageListener.php b/src/Symfony/Bridge/PhpUnit/CoverageListener.php index 766252b8728b7..65d6aa9dc9dcc 100644 --- a/src/Symfony/Bridge/PhpUnit/CoverageListener.php +++ b/src/Symfony/Bridge/PhpUnit/CoverageListener.php @@ -26,7 +26,7 @@ class CoverageListener implements TestListener private $sutFqcnResolver; private $warningOnSutNotFound; - public function __construct(callable $sutFqcnResolver = null, bool $warningOnSutNotFound = false) + public function __construct(?callable $sutFqcnResolver = null, bool $warningOnSutNotFound = false) { $this->sutFqcnResolver = $sutFqcnResolver ?? static function (Test $test): ?string { $class = \get_class($test); diff --git a/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler/Configuration.php b/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler/Configuration.php index d9162e81e328e..000deca6f2e6c 100644 --- a/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler/Configuration.php +++ b/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler/Configuration.php @@ -70,7 +70,7 @@ class Configuration * @param string $baselineFile The path to the baseline file * @param string|null $logFile The path to the log file */ - private function __construct(array $thresholds = [], string $regex = '', array $verboseOutput = [], string $ignoreFile = '', bool $generateBaseline = false, string $baselineFile = '', string $logFile = null) + private function __construct(array $thresholds = [], string $regex = '', array $verboseOutput = [], string $ignoreFile = '', bool $generateBaseline = false, string $baselineFile = '', ?string $logFile = null) { $groups = ['total', 'indirect', 'direct', 'self']; diff --git a/src/Symfony/Bridge/PsrHttpMessage/EventListener/PsrResponseListener.php b/src/Symfony/Bridge/PsrHttpMessage/EventListener/PsrResponseListener.php index e709ef9de2343..dd7ef6cbc5521 100644 --- a/src/Symfony/Bridge/PsrHttpMessage/EventListener/PsrResponseListener.php +++ b/src/Symfony/Bridge/PsrHttpMessage/EventListener/PsrResponseListener.php @@ -28,7 +28,7 @@ final class PsrResponseListener implements EventSubscriberInterface { private readonly HttpFoundationFactoryInterface $httpFoundationFactory; - public function __construct(HttpFoundationFactoryInterface $httpFoundationFactory = null) + public function __construct(?HttpFoundationFactoryInterface $httpFoundationFactory = null) { $this->httpFoundationFactory = $httpFoundationFactory ?? new HttpFoundationFactory(); } diff --git a/src/Symfony/Bridge/PsrHttpMessage/Factory/PsrHttpFactory.php b/src/Symfony/Bridge/PsrHttpMessage/Factory/PsrHttpFactory.php index 76b6a5a07d5f5..78a36ded1d709 100644 --- a/src/Symfony/Bridge/PsrHttpMessage/Factory/PsrHttpFactory.php +++ b/src/Symfony/Bridge/PsrHttpMessage/Factory/PsrHttpFactory.php @@ -41,10 +41,10 @@ class PsrHttpFactory implements HttpMessageFactoryInterface private readonly ResponseFactoryInterface $responseFactory; public function __construct( - ServerRequestFactoryInterface $serverRequestFactory = null, - StreamFactoryInterface $streamFactory = null, - UploadedFileFactoryInterface $uploadedFileFactory = null, - ResponseFactoryInterface $responseFactory = null, + ?ServerRequestFactoryInterface $serverRequestFactory = null, + ?StreamFactoryInterface $streamFactory = null, + ?UploadedFileFactoryInterface $uploadedFileFactory = null, + ?ResponseFactoryInterface $responseFactory = null, ) { if (null === $serverRequestFactory || null === $streamFactory || null === $uploadedFileFactory || null === $responseFactory) { $psr17Factory = match (true) { diff --git a/src/Symfony/Bridge/PsrHttpMessage/Factory/UploadedFile.php b/src/Symfony/Bridge/PsrHttpMessage/Factory/UploadedFile.php index c6da856376614..f680dd5ab5040 100644 --- a/src/Symfony/Bridge/PsrHttpMessage/Factory/UploadedFile.php +++ b/src/Symfony/Bridge/PsrHttpMessage/Factory/UploadedFile.php @@ -48,7 +48,7 @@ public function __construct( ); } - public function move(string $directory, string $name = null): File + public function move(string $directory, ?string $name = null): File { if (!$this->isValid() || $this->test) { return parent::move($directory, $name); diff --git a/src/Symfony/Bridge/Twig/AppVariable.php b/src/Symfony/Bridge/Twig/AppVariable.php index 600362fd28f15..e7b976e3eacf8 100644 --- a/src/Symfony/Bridge/Twig/AppVariable.php +++ b/src/Symfony/Bridge/Twig/AppVariable.php @@ -165,7 +165,7 @@ public function getEnabled_locales(): array * * getFlashes('notice') returns a simple array with flash messages of that type * * getFlashes(['notice', 'error']) returns a nested array of type => messages. */ - public function getFlashes(string|array $types = null): array + public function getFlashes(string|array|null $types = null): array { try { $session = $this->getSession(); diff --git a/src/Symfony/Bridge/Twig/Command/DebugCommand.php b/src/Symfony/Bridge/Twig/Command/DebugCommand.php index 1e1c446dbdbf3..43fab03ca37da 100644 --- a/src/Symfony/Bridge/Twig/Command/DebugCommand.php +++ b/src/Symfony/Bridge/Twig/Command/DebugCommand.php @@ -48,7 +48,7 @@ class DebugCommand extends Command private ?FileLinkFormatter $fileLinkFormatter; - public function __construct(Environment $twig, string $projectDir = null, array $bundlesMetadata = [], string $twigDefaultPath = null, FileLinkFormatter $fileLinkFormatter = null) + public function __construct(Environment $twig, ?string $projectDir = null, array $bundlesMetadata = [], ?string $twigDefaultPath = null, ?FileLinkFormatter $fileLinkFormatter = null) { parent::__construct(); @@ -214,7 +214,7 @@ private function displayPathsJson(SymfonyStyle $io, string $name): void $io->writeln(json_encode($data)); } - private function displayGeneralText(SymfonyStyle $io, string $filter = null): void + private function displayGeneralText(SymfonyStyle $io, ?string $filter = null): void { $decorated = $io->isDecorated(); $types = ['functions', 'filters', 'tests', 'globals']; @@ -276,7 +276,7 @@ private function displayGeneralJson(SymfonyStyle $io, ?string $filter): void $io->writeln($decorated ? OutputFormatter::escape($data) : $data); } - private function getLoaderPaths(string $name = null): array + private function getLoaderPaths(?string $name = null): array { $loaderPaths = []; foreach ($this->getFilesystemLoaders() as $loader) { diff --git a/src/Symfony/Bridge/Twig/Command/LintCommand.php b/src/Symfony/Bridge/Twig/Command/LintCommand.php index 77427920d3ed7..14c00ba112659 100644 --- a/src/Symfony/Bridge/Twig/Command/LintCommand.php +++ b/src/Symfony/Bridge/Twig/Command/LintCommand.php @@ -39,6 +39,7 @@ #[AsCommand(name: 'lint:twig', description: 'Lint a Twig template and outputs encountered errors')] class LintCommand extends Command { + private array $excludes; private string $format; public function __construct( @@ -54,6 +55,7 @@ protected function configure(): void ->addOption('format', null, InputOption::VALUE_REQUIRED, sprintf('The output format ("%s")', implode('", "', $this->getAvailableFormatOptions()))) ->addOption('show-deprecations', null, InputOption::VALUE_NONE, 'Show deprecations as errors') ->addArgument('filename', InputArgument::IS_ARRAY, 'A file, a directory or "-" for reading from STDIN') + ->addOption('excludes', null, InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, 'Excluded directories', []) ->setHelp(<<<'EOF' The %command.name% command lints a template and outputs to STDOUT the first encountered syntax error. @@ -81,6 +83,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $io = new SymfonyStyle($input, $output); $filenames = $input->getArgument('filename'); $showDeprecations = $input->getOption('show-deprecations'); + $this->excludes = $input->getOption('excludes'); $this->format = $input->getOption('format') ?? (GithubActionReporter::isGithubActionEnvironment() ? 'github' : 'txt'); if (['-'] === $filenames) { @@ -145,7 +148,7 @@ protected function findFiles(string $filename): iterable if (is_file($filename)) { return [$filename]; } elseif (is_dir($filename)) { - return Finder::create()->files()->in($filename)->name($this->namePatterns); + return Finder::create()->files()->in($filename)->name($this->namePatterns)->exclude($this->excludes); } throw new RuntimeException(sprintf('File or directory "%s" is not readable.', $filename)); @@ -221,7 +224,7 @@ private function displayJson(OutputInterface $output, array $filesInfo): int return min($errors, 1); } - private function renderException(SymfonyStyle $output, string $template, Error $exception, string $file = null, GithubActionReporter $githubReporter = null): void + private function renderException(SymfonyStyle $output, string $template, Error $exception, ?string $file = null, ?GithubActionReporter $githubReporter = null): void { $line = $exception->getTemplateLine(); diff --git a/src/Symfony/Bridge/Twig/DataCollector/TwigDataCollector.php b/src/Symfony/Bridge/Twig/DataCollector/TwigDataCollector.php index 3994cbe30e495..a5786d2f82f6c 100644 --- a/src/Symfony/Bridge/Twig/DataCollector/TwigDataCollector.php +++ b/src/Symfony/Bridge/Twig/DataCollector/TwigDataCollector.php @@ -32,13 +32,13 @@ class TwigDataCollector extends DataCollector implements LateDataCollectorInterf private ?Environment $twig; private array $computed; - public function __construct(Profile $profile, Environment $twig = null) + public function __construct(Profile $profile, ?Environment $twig = null) { $this->profile = $profile; $this->twig = $twig; } - public function collect(Request $request, Response $response, \Throwable $exception = null): void + public function collect(Request $request, Response $response, ?\Throwable $exception = null): void { } diff --git a/src/Symfony/Bridge/Twig/ErrorRenderer/TwigErrorRenderer.php b/src/Symfony/Bridge/Twig/ErrorRenderer/TwigErrorRenderer.php index 9086c22d5c3cb..50d8b44d2a742 100644 --- a/src/Symfony/Bridge/Twig/ErrorRenderer/TwigErrorRenderer.php +++ b/src/Symfony/Bridge/Twig/ErrorRenderer/TwigErrorRenderer.php @@ -32,7 +32,7 @@ class TwigErrorRenderer implements ErrorRendererInterface /** * @param bool|callable $debug The debugging mode as a boolean or a callable that should return it */ - public function __construct(Environment $twig, HtmlErrorRenderer $fallbackErrorRenderer = null, bool|callable $debug = false) + public function __construct(Environment $twig, ?HtmlErrorRenderer $fallbackErrorRenderer = null, bool|callable $debug = false) { $this->twig = $twig; $this->fallbackErrorRenderer = $fallbackErrorRenderer ?? new HtmlErrorRenderer(); diff --git a/src/Symfony/Bridge/Twig/Extension/AssetExtension.php b/src/Symfony/Bridge/Twig/Extension/AssetExtension.php index feb25ed5c2062..7a7aba0d69148 100644 --- a/src/Symfony/Bridge/Twig/Extension/AssetExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/AssetExtension.php @@ -43,7 +43,7 @@ public function getFunctions(): array * If the package used to generate the path is an instance of * UrlPackage, you will always get a URL and not a path. */ - public function getAssetUrl(string $path, string $packageName = null): string + public function getAssetUrl(string $path, ?string $packageName = null): string { return $this->packages->getUrl($path, $packageName); } @@ -51,7 +51,7 @@ public function getAssetUrl(string $path, string $packageName = null): string /** * Returns the version of an asset. */ - public function getAssetVersion(string $path, string $packageName = null): string + public function getAssetVersion(string $path, ?string $packageName = null): string { return $this->packages->getVersion($path, $packageName); } diff --git a/src/Symfony/Bridge/Twig/Extension/DumpExtension.php b/src/Symfony/Bridge/Twig/Extension/DumpExtension.php index c84e1e751a0f8..1bf2beeed5d1c 100644 --- a/src/Symfony/Bridge/Twig/Extension/DumpExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/DumpExtension.php @@ -29,7 +29,7 @@ final class DumpExtension extends AbstractExtension private ClonerInterface $cloner; private ?HtmlDumper $dumper; - public function __construct(ClonerInterface $cloner, HtmlDumper $dumper = null) + public function __construct(ClonerInterface $cloner, ?HtmlDumper $dumper = null) { $this->cloner = $cloner; $this->dumper = $dumper; diff --git a/src/Symfony/Bridge/Twig/Extension/FormExtension.php b/src/Symfony/Bridge/Twig/Extension/FormExtension.php index 827145963a8e6..673f8199f9a2a 100644 --- a/src/Symfony/Bridge/Twig/Extension/FormExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/FormExtension.php @@ -35,7 +35,7 @@ final class FormExtension extends AbstractExtension { private ?TranslatorInterface $translator; - public function __construct(TranslatorInterface $translator = null) + public function __construct(?TranslatorInterface $translator = null) { $this->translator = $translator; } diff --git a/src/Symfony/Bridge/Twig/Extension/HtmlSanitizerExtension.php b/src/Symfony/Bridge/Twig/Extension/HtmlSanitizerExtension.php index bec5ceb94e34e..9549c2a36e1bd 100644 --- a/src/Symfony/Bridge/Twig/Extension/HtmlSanitizerExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/HtmlSanitizerExtension.php @@ -33,7 +33,7 @@ public function getFilters(): array ]; } - public function sanitize(string $html, string $sanitizer = null): string + public function sanitize(string $html, ?string $sanitizer = null): string { return $this->sanitizers->get($sanitizer ?? $this->defaultSanitizer)->sanitize($html); } diff --git a/src/Symfony/Bridge/Twig/Extension/HttpKernelRuntime.php b/src/Symfony/Bridge/Twig/Extension/HttpKernelRuntime.php index b059bf1aae4c3..5456de33d2b6a 100644 --- a/src/Symfony/Bridge/Twig/Extension/HttpKernelRuntime.php +++ b/src/Symfony/Bridge/Twig/Extension/HttpKernelRuntime.php @@ -25,7 +25,7 @@ final class HttpKernelRuntime private FragmentHandler $handler; private ?FragmentUriGeneratorInterface $fragmentUriGenerator; - public function __construct(FragmentHandler $handler, FragmentUriGeneratorInterface $fragmentUriGenerator = null) + public function __construct(FragmentHandler $handler, ?FragmentUriGeneratorInterface $fragmentUriGenerator = null) { $this->handler = $handler; $this->fragmentUriGenerator = $fragmentUriGenerator; diff --git a/src/Symfony/Bridge/Twig/Extension/LogoutUrlExtension.php b/src/Symfony/Bridge/Twig/Extension/LogoutUrlExtension.php index abced287f999c..a576a6dd6b152 100644 --- a/src/Symfony/Bridge/Twig/Extension/LogoutUrlExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/LogoutUrlExtension.php @@ -42,7 +42,7 @@ public function getFunctions(): array * * @param string|null $key The firewall key or null to use the current firewall key */ - public function getLogoutPath(string $key = null): string + public function getLogoutPath(?string $key = null): string { return $this->generator->getLogoutPath($key); } @@ -52,7 +52,7 @@ public function getLogoutPath(string $key = null): string * * @param string|null $key The firewall key or null to use the current firewall key */ - public function getLogoutUrl(string $key = null): string + public function getLogoutUrl(?string $key = null): string { return $this->generator->getLogoutUrl($key); } diff --git a/src/Symfony/Bridge/Twig/Extension/ProfilerExtension.php b/src/Symfony/Bridge/Twig/Extension/ProfilerExtension.php index f63aa41cf2738..ab56f22a1efd6 100644 --- a/src/Symfony/Bridge/Twig/Extension/ProfilerExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/ProfilerExtension.php @@ -28,7 +28,7 @@ final class ProfilerExtension extends BaseProfilerExtension */ private \SplObjectStorage $events; - public function __construct(Profile $profile, Stopwatch $stopwatch = null) + public function __construct(Profile $profile, ?Stopwatch $stopwatch = null) { parent::__construct($profile); diff --git a/src/Symfony/Bridge/Twig/Extension/SecurityExtension.php b/src/Symfony/Bridge/Twig/Extension/SecurityExtension.php index 3c3881ad00d04..c94912e35f683 100644 --- a/src/Symfony/Bridge/Twig/Extension/SecurityExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/SecurityExtension.php @@ -28,13 +28,13 @@ final class SecurityExtension extends AbstractExtension private ?AuthorizationCheckerInterface $securityChecker; private ?ImpersonateUrlGenerator $impersonateUrlGenerator; - public function __construct(AuthorizationCheckerInterface $securityChecker = null, ImpersonateUrlGenerator $impersonateUrlGenerator = null) + public function __construct(?AuthorizationCheckerInterface $securityChecker = null, ?ImpersonateUrlGenerator $impersonateUrlGenerator = null) { $this->securityChecker = $securityChecker; $this->impersonateUrlGenerator = $impersonateUrlGenerator; } - public function isGranted(mixed $role, mixed $object = null, string $field = null): bool + public function isGranted(mixed $role, mixed $object = null, ?string $field = null): bool { if (null === $this->securityChecker) { return false; @@ -51,7 +51,7 @@ public function isGranted(mixed $role, mixed $object = null, string $field = nul } } - public function getImpersonateExitUrl(string $exitTo = null): string + public function getImpersonateExitUrl(?string $exitTo = null): string { if (null === $this->impersonateUrlGenerator) { return ''; @@ -60,7 +60,7 @@ public function getImpersonateExitUrl(string $exitTo = null): string return $this->impersonateUrlGenerator->generateExitUrl($exitTo); } - public function getImpersonateExitPath(string $exitTo = null): string + public function getImpersonateExitPath(?string $exitTo = null): string { if (null === $this->impersonateUrlGenerator) { return ''; diff --git a/src/Symfony/Bridge/Twig/Extension/StopwatchExtension.php b/src/Symfony/Bridge/Twig/Extension/StopwatchExtension.php index 972cd1acda44c..49df52cff7e58 100644 --- a/src/Symfony/Bridge/Twig/Extension/StopwatchExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/StopwatchExtension.php @@ -26,7 +26,7 @@ final class StopwatchExtension extends AbstractExtension private ?Stopwatch $stopwatch; private bool $enabled; - public function __construct(Stopwatch $stopwatch = null, bool $enabled = true) + public function __construct(?Stopwatch $stopwatch = null, bool $enabled = true) { $this->stopwatch = $stopwatch; $this->enabled = $enabled; diff --git a/src/Symfony/Bridge/Twig/Extension/TranslationExtension.php b/src/Symfony/Bridge/Twig/Extension/TranslationExtension.php index 67835e2b87e75..ba5758f3f1bfc 100644 --- a/src/Symfony/Bridge/Twig/Extension/TranslationExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/TranslationExtension.php @@ -37,7 +37,7 @@ final class TranslationExtension extends AbstractExtension private ?TranslatorInterface $translator; private ?TranslationNodeVisitor $translationNodeVisitor; - public function __construct(TranslatorInterface $translator = null, TranslationNodeVisitor $translationNodeVisitor = null) + public function __construct(?TranslatorInterface $translator = null, ?TranslationNodeVisitor $translationNodeVisitor = null) { $this->translator = $translator; $this->translationNodeVisitor = $translationNodeVisitor; @@ -96,7 +96,7 @@ public function getTranslationNodeVisitor(): TranslationNodeVisitor /** * @param array|string $arguments Can be the locale as a string when $message is a TranslatableInterface */ - public function trans(string|\Stringable|TranslatableInterface|null $message, array|string $arguments = [], string $domain = null, string $locale = null, int $count = null): string + public function trans(string|\Stringable|TranslatableInterface|null $message, array|string $arguments = [], ?string $domain = null, ?string $locale = null, ?int $count = null): string { if ($message instanceof TranslatableInterface) { if ([] !== $arguments && !\is_string($arguments)) { @@ -125,7 +125,7 @@ public function trans(string|\Stringable|TranslatableInterface|null $message, ar return $this->getTranslator()->trans($message, $arguments, $domain, $locale); } - public function createTranslatable(string $message, array $parameters = [], string $domain = null): TranslatableMessage + public function createTranslatable(string $message, array $parameters = [], ?string $domain = null): TranslatableMessage { if (!class_exists(TranslatableMessage::class)) { throw new \LogicException(sprintf('You cannot use the "%s" as the Translation Component is not installed. Try running "composer require symfony/translation".', __CLASS__)); diff --git a/src/Symfony/Bridge/Twig/Extension/WorkflowExtension.php b/src/Symfony/Bridge/Twig/Extension/WorkflowExtension.php index 661a063f98e61..b50130ccbc5a9 100644 --- a/src/Symfony/Bridge/Twig/Extension/WorkflowExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/WorkflowExtension.php @@ -48,7 +48,7 @@ public function getFunctions(): array /** * Returns true if the transition is enabled. */ - public function canTransition(object $subject, string $transitionName, string $name = null): bool + public function canTransition(object $subject, string $transitionName, ?string $name = null): bool { return $this->workflowRegistry->get($subject, $name)->can($subject, $transitionName); } @@ -58,12 +58,12 @@ public function canTransition(object $subject, string $transitionName, string $n * * @return Transition[] */ - public function getEnabledTransitions(object $subject, string $name = null): array + public function getEnabledTransitions(object $subject, ?string $name = null): array { return $this->workflowRegistry->get($subject, $name)->getEnabledTransitions($subject); } - public function getEnabledTransition(object $subject, string $transition, string $name = null): ?Transition + public function getEnabledTransition(object $subject, string $transition, ?string $name = null): ?Transition { return $this->workflowRegistry->get($subject, $name)->getEnabledTransition($subject, $transition); } @@ -71,7 +71,7 @@ public function getEnabledTransition(object $subject, string $transition, string /** * Returns true if the place is marked. */ - public function hasMarkedPlace(object $subject, string $placeName, string $name = null): bool + public function hasMarkedPlace(object $subject, string $placeName, ?string $name = null): bool { return $this->workflowRegistry->get($subject, $name)->getMarking($subject)->has($placeName); } @@ -81,7 +81,7 @@ public function hasMarkedPlace(object $subject, string $placeName, string $name * * @return string[]|int[] */ - public function getMarkedPlaces(object $subject, bool $placesNameOnly = true, string $name = null): array + public function getMarkedPlaces(object $subject, bool $placesNameOnly = true, ?string $name = null): array { $places = $this->workflowRegistry->get($subject, $name)->getMarking($subject)->getPlaces(); @@ -99,7 +99,7 @@ public function getMarkedPlaces(object $subject, bool $placesNameOnly = true, st * Use a string (the place name) to get place metadata * Use a Transition instance to get transition metadata */ - public function getMetadata(object $subject, string $key, string|Transition $metadataSubject = null, string $name = null): mixed + public function getMetadata(object $subject, string $key, string|Transition|null $metadataSubject = null, ?string $name = null): mixed { return $this ->workflowRegistry @@ -109,7 +109,7 @@ public function getMetadata(object $subject, string $key, string|Transition $met ; } - public function buildTransitionBlockerList(object $subject, string $transitionName, string $name = null): TransitionBlockerList + public function buildTransitionBlockerList(object $subject, string $transitionName, ?string $name = null): TransitionBlockerList { $workflow = $this->workflowRegistry->get($subject, $name); diff --git a/src/Symfony/Bridge/Twig/Mime/BodyRenderer.php b/src/Symfony/Bridge/Twig/Mime/BodyRenderer.php index 18e0eb1f86693..d5b6d14c139a0 100644 --- a/src/Symfony/Bridge/Twig/Mime/BodyRenderer.php +++ b/src/Symfony/Bridge/Twig/Mime/BodyRenderer.php @@ -31,7 +31,7 @@ final class BodyRenderer implements BodyRendererInterface private HtmlToTextConverterInterface $converter; private ?LocaleSwitcher $localeSwitcher = null; - public function __construct(Environment $twig, array $context = [], HtmlToTextConverterInterface $converter = null, LocaleSwitcher $localeSwitcher = null) + public function __construct(Environment $twig, array $context = [], ?HtmlToTextConverterInterface $converter = null, ?LocaleSwitcher $localeSwitcher = null) { $this->twig = $twig; $this->context = $context; diff --git a/src/Symfony/Bridge/Twig/Mime/NotificationEmail.php b/src/Symfony/Bridge/Twig/Mime/NotificationEmail.php index 5bd54e64463e5..6e33d33dfa89a 100644 --- a/src/Symfony/Bridge/Twig/Mime/NotificationEmail.php +++ b/src/Symfony/Bridge/Twig/Mime/NotificationEmail.php @@ -42,7 +42,7 @@ class NotificationEmail extends TemplatedEmail ]; private bool $rendered = false; - public function __construct(Headers $headers = null, AbstractPart $body = null) + public function __construct(?Headers $headers = null, ?AbstractPart $body = null) { $missingPackages = []; if (!class_exists(CssInlinerExtension::class)) { @@ -63,7 +63,7 @@ public function __construct(Headers $headers = null, AbstractPart $body = null) /** * Creates a NotificationEmail instance that is appropriate to send to normal (non-admin) users. */ - public static function asPublicEmail(Headers $headers = null, AbstractPart $body = null): self + public static function asPublicEmail(?Headers $headers = null, ?AbstractPart $body = null): self { $email = new static($headers, $body); $email->markAsPublic(); @@ -174,6 +174,26 @@ public function getHtmlTemplate(): ?string return '@email/'.$this->theme.'/notification/body.html.twig'; } + /** + * @return $this + */ + public function context(array $context): static + { + $parentContext = []; + + foreach ($context as $key => $value) { + if (\array_key_exists($key, $this->context)) { + $this->context[$key] = $value; + } else { + $parentContext[$key] = $value; + } + } + + parent::context($parentContext); + + return $this; + } + public function getContext(): array { return array_merge($this->context, parent::getContext()); diff --git a/src/Symfony/Bridge/Twig/Mime/WrappedTemplatedEmail.php b/src/Symfony/Bridge/Twig/Mime/WrappedTemplatedEmail.php index c4e0003fac1a4..e72335a5ececd 100644 --- a/src/Symfony/Bridge/Twig/Mime/WrappedTemplatedEmail.php +++ b/src/Symfony/Bridge/Twig/Mime/WrappedTemplatedEmail.php @@ -43,7 +43,7 @@ public function toName(): string * @param string|null $contentType The media type (i.e. MIME type) of the image file (e.g. 'image/png'). * Some email clients require this to display embedded images. */ - public function image(string $image, string $contentType = null): string + public function image(string $image, ?string $contentType = null): string { $file = $this->twig->getLoader()->getSourceContext($image); $body = $file->getPath() ? new File($file->getPath()) : $file->getCode(); @@ -59,7 +59,7 @@ public function image(string $image, string $contentType = null): string * @param string|null $contentType The media type (i.e. MIME type) of the file (e.g. 'application/pdf'). * Some email clients require this to display attached files. */ - public function attach(string $file, string $name = null, string $contentType = null): void + public function attach(string $file, ?string $name = null, ?string $contentType = null): void { $file = $this->twig->getLoader()->getSourceContext($file); $body = $file->getPath() ? new File($file->getPath()) : $file->getCode(); diff --git a/src/Symfony/Bridge/Twig/Node/DumpNode.php b/src/Symfony/Bridge/Twig/Node/DumpNode.php index 8ce2bd8c4fa51..086ff7a68aaea 100644 --- a/src/Symfony/Bridge/Twig/Node/DumpNode.php +++ b/src/Symfony/Bridge/Twig/Node/DumpNode.php @@ -21,7 +21,7 @@ final class DumpNode extends Node { private string $varPrefix; - public function __construct(string $varPrefix, ?Node $values, int $lineno, string $tag = null) + public function __construct(string $varPrefix, ?Node $values, int $lineno, ?string $tag = null) { $nodes = []; if (null !== $values) { diff --git a/src/Symfony/Bridge/Twig/Node/FormThemeNode.php b/src/Symfony/Bridge/Twig/Node/FormThemeNode.php index e37311267bb17..2d4659ae7bb61 100644 --- a/src/Symfony/Bridge/Twig/Node/FormThemeNode.php +++ b/src/Symfony/Bridge/Twig/Node/FormThemeNode.php @@ -20,7 +20,7 @@ */ final class FormThemeNode extends Node { - public function __construct(Node $form, Node $resources, int $lineno, string $tag = null, bool $only = false) + public function __construct(Node $form, Node $resources, int $lineno, ?string $tag = null, bool $only = false) { parent::__construct(['form' => $form, 'resources' => $resources], ['only' => $only], $lineno, $tag); } diff --git a/src/Symfony/Bridge/Twig/Node/StopwatchNode.php b/src/Symfony/Bridge/Twig/Node/StopwatchNode.php index cfa4d8a197f9b..796ee4dab8d16 100644 --- a/src/Symfony/Bridge/Twig/Node/StopwatchNode.php +++ b/src/Symfony/Bridge/Twig/Node/StopwatchNode.php @@ -22,7 +22,7 @@ */ final class StopwatchNode extends Node { - public function __construct(Node $name, Node $body, AssignNameExpression $var, int $lineno = 0, string $tag = null) + public function __construct(Node $name, Node $body, AssignNameExpression $var, int $lineno = 0, ?string $tag = null) { parent::__construct(['body' => $body, 'name' => $name, 'var' => $var], [], $lineno, $tag); } diff --git a/src/Symfony/Bridge/Twig/Node/TransDefaultDomainNode.php b/src/Symfony/Bridge/Twig/Node/TransDefaultDomainNode.php index df29f0a19931f..5a96d7420122f 100644 --- a/src/Symfony/Bridge/Twig/Node/TransDefaultDomainNode.php +++ b/src/Symfony/Bridge/Twig/Node/TransDefaultDomainNode.php @@ -20,7 +20,7 @@ */ final class TransDefaultDomainNode extends Node { - public function __construct(AbstractExpression $expr, int $lineno = 0, string $tag = null) + public function __construct(AbstractExpression $expr, int $lineno = 0, ?string $tag = null) { parent::__construct(['expr' => $expr], [], $lineno, $tag); } diff --git a/src/Symfony/Bridge/Twig/Node/TransNode.php b/src/Symfony/Bridge/Twig/Node/TransNode.php index 8a126ba569172..881104c8cc3fd 100644 --- a/src/Symfony/Bridge/Twig/Node/TransNode.php +++ b/src/Symfony/Bridge/Twig/Node/TransNode.php @@ -24,7 +24,7 @@ */ final class TransNode extends Node { - public function __construct(Node $body, Node $domain = null, AbstractExpression $count = null, AbstractExpression $vars = null, AbstractExpression $locale = null, int $lineno = 0, string $tag = null) + public function __construct(Node $body, ?Node $domain = null, ?AbstractExpression $count = null, ?AbstractExpression $vars = null, ?AbstractExpression $locale = null, int $lineno = 0, ?string $tag = null) { $nodes = ['body' => $body]; if (null !== $domain) { diff --git a/src/Symfony/Bridge/Twig/NodeVisitor/Scope.php b/src/Symfony/Bridge/Twig/NodeVisitor/Scope.php index efa354d03feac..66904b09b5303 100644 --- a/src/Symfony/Bridge/Twig/NodeVisitor/Scope.php +++ b/src/Symfony/Bridge/Twig/NodeVisitor/Scope.php @@ -20,7 +20,7 @@ class Scope private array $data = []; private bool $left = false; - public function __construct(self $parent = null) + public function __construct(?self $parent = null) { $this->parent = $parent; } diff --git a/src/Symfony/Bridge/Twig/NodeVisitor/TranslationNodeVisitor.php b/src/Symfony/Bridge/Twig/NodeVisitor/TranslationNodeVisitor.php index d5e95040d6bf2..3a7ea67cac90b 100644 --- a/src/Symfony/Bridge/Twig/NodeVisitor/TranslationNodeVisitor.php +++ b/src/Symfony/Bridge/Twig/NodeVisitor/TranslationNodeVisitor.php @@ -149,6 +149,22 @@ private function getReadDomainFromNode(Node $node): ?string return $node->getAttribute('value'); } + if ( + $node instanceof FunctionExpression + && 'constant' === $node->getAttribute('name') + ) { + $nodeArguments = $node->getNode('arguments'); + if ($nodeArguments->getIterator()->current() instanceof ConstantExpression) { + $constantName = $nodeArguments->getIterator()->current()->getAttribute('value'); + if (\defined($constantName)) { + $value = \constant($constantName); + if (\is_string($value)) { + return $value; + } + } + } + } + return self::UNDEFINED_DOMAIN; } 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 d57a4e1197e1e..1e421d5f9f5a9 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 @@ -14,7 +14,7 @@ {# Attribute "required" is not supported #} {%- set required = false -%} {%- endif -%} - + {%- endblock form_widget_simple -%} {%- block form_widget_compound -%} @@ -95,11 +95,11 @@ {%- endblock choice_widget_options -%} {%- block checkbox_widget -%} - + {%- endblock checkbox_widget -%} {%- block radio_widget -%} - + {%- endblock radio_widget -%} {%- block datetime_widget -%} @@ -406,7 +406,7 @@ {%- endif -%} {%- if form_method != method -%} - + {%- endif -%} {%- endblock form_start -%} @@ -444,7 +444,7 @@ {%- endif -%} {%- if form_method != method -%} - + {%- endif -%} {% endif -%} {% endblock form_rest %} diff --git a/src/Symfony/Bridge/Twig/Test/FormLayoutTestCase.php b/src/Symfony/Bridge/Twig/Test/FormLayoutTestCase.php index 71a71530831eb..1fdd83c95beba 100644 --- a/src/Symfony/Bridge/Twig/Test/FormLayoutTestCase.php +++ b/src/Symfony/Bridge/Twig/Test/FormLayoutTestCase.php @@ -52,8 +52,6 @@ protected function assertMatchesXpath($html, $expression, $count = 1): void { $dom = new \DOMDocument('UTF-8'); - $html = preg_replace('/(]+)(?/', '$1/>', $html); - try { // Wrap in node so we can load HTML with multiple tags at // the top level diff --git a/src/Symfony/Bridge/Twig/Tests/Command/DebugCommandTest.php b/src/Symfony/Bridge/Twig/Tests/Command/DebugCommandTest.php index 3d61d6eed7458..8a67932fe3b94 100644 --- a/src/Symfony/Bridge/Twig/Tests/Command/DebugCommandTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Command/DebugCommandTest.php @@ -317,7 +317,7 @@ public static function provideCompletionSuggestions(): iterable yield 'option --format' => [['--format', ''], ['text', 'json']]; } - private function createCommandTester(array $paths = [], array $bundleMetadata = [], string $defaultPath = null, bool $useChainLoader = false, array $globals = []): CommandTester + private function createCommandTester(array $paths = [], array $bundleMetadata = [], ?string $defaultPath = null, bool $useChainLoader = false, array $globals = []): CommandTester { $projectDir = \dirname(__DIR__).\DIRECTORY_SEPARATOR.'Fixtures'; $loader = new FilesystemLoader([], $projectDir); diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/AbstractBootstrap3LayoutTestCase.php b/src/Symfony/Bridge/Twig/Tests/Extension/AbstractBootstrap3LayoutTestCase.php index 5b02b69576e6d..08a026fe7d114 100644 --- a/src/Symfony/Bridge/Twig/Tests/Extension/AbstractBootstrap3LayoutTestCase.php +++ b/src/Symfony/Bridge/Twig/Tests/Extension/AbstractBootstrap3LayoutTestCase.php @@ -2797,7 +2797,7 @@ public function testWidgetAttributes() $html = $this->renderWidget($form->createView()); // compare plain HTML to check the whitespace - $this->assertSame('', $html); + $this->assertSame('', $html); } public function testWidgetAttributeNameRepeatedIfTrue() @@ -2809,7 +2809,7 @@ public function testWidgetAttributeNameRepeatedIfTrue() $html = $this->renderWidget($form->createView()); // foo="foo" - $this->assertSame('', $html); + $this->assertSame('', $html); } public function testButtonAttributes() diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/AbstractLayoutTestCase.php b/src/Symfony/Bridge/Twig/Tests/Extension/AbstractLayoutTestCase.php index fc9eff09a375b..f340b066ed884 100644 --- a/src/Symfony/Bridge/Twig/Tests/Extension/AbstractLayoutTestCase.php +++ b/src/Symfony/Bridge/Twig/Tests/Extension/AbstractLayoutTestCase.php @@ -2448,7 +2448,7 @@ public function testWidgetAttributes() $html = $this->renderWidget($form->createView()); // compare plain HTML to check the whitespace - $this->assertSame('', $html); + $this->assertSame('', $html); } public function testWidgetAttributeNameRepeatedIfTrue() @@ -2460,7 +2460,7 @@ public function testWidgetAttributeNameRepeatedIfTrue() $html = $this->renderWidget($form->createView()); // foo="foo" - $this->assertSame('', $html); + $this->assertSame('', $html); } public function testWidgetAttributeHiddenIfFalse() @@ -2672,7 +2672,7 @@ public function testHelpWithTranslatableMessage() public function testHelpWithTranslatableInterface() { $message = new class() implements TranslatableInterface { - public function trans(TranslatorInterface $translator, string $locale = null): string + public function trans(TranslatorInterface $translator, ?string $locale = null): string { return $translator->trans('foo'); } diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/Fixtures/templates/form/theme.html.twig b/src/Symfony/Bridge/Twig/Tests/Extension/Fixtures/templates/form/theme.html.twig index 3ec513a467978..e8816be96e54e 100644 --- a/src/Symfony/Bridge/Twig/Tests/Extension/Fixtures/templates/form/theme.html.twig +++ b/src/Symfony/Bridge/Twig/Tests/Extension/Fixtures/templates/form/theme.html.twig @@ -1,4 +1,4 @@ {% block form_widget_simple %} {%- set type = type|default('text') -%} - + {%- endblock form_widget_simple %} diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/Fixtures/templates/form/theme_extends.html.twig b/src/Symfony/Bridge/Twig/Tests/Extension/Fixtures/templates/form/theme_extends.html.twig index 2b9118a20ce28..501b555efc59f 100644 --- a/src/Symfony/Bridge/Twig/Tests/Extension/Fixtures/templates/form/theme_extends.html.twig +++ b/src/Symfony/Bridge/Twig/Tests/Extension/Fixtures/templates/form/theme_extends.html.twig @@ -2,5 +2,5 @@ {% block form_widget_simple %} {%- set type = type|default('text') -%} - + {%- endblock form_widget_simple %} diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/Fixtures/templates/form/theme_use.html.twig b/src/Symfony/Bridge/Twig/Tests/Extension/Fixtures/templates/form/theme_use.html.twig index e05de5ac3b2a7..37150734a4698 100644 --- a/src/Symfony/Bridge/Twig/Tests/Extension/Fixtures/templates/form/theme_use.html.twig +++ b/src/Symfony/Bridge/Twig/Tests/Extension/Fixtures/templates/form/theme_use.html.twig @@ -2,5 +2,5 @@ {% block form_widget_simple %} {%- set type = type|default('text') -%} - + {%- endblock form_widget_simple %} diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionBootstrap3LayoutTest.php b/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionBootstrap3LayoutTest.php index 90a1756361d9d..7c3742a7409b2 100644 --- a/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionBootstrap3LayoutTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionBootstrap3LayoutTest.php @@ -71,7 +71,7 @@ public function testMoneyWidgetInIso() $this->assertSame(<<<'HTML'
-
+ HTML , trim($this->renderWidget($view))); } diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionBootstrap4LayoutTest.php b/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionBootstrap4LayoutTest.php index bffebe3f6425f..5fdec71db05e9 100644 --- a/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionBootstrap4LayoutTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionBootstrap4LayoutTest.php @@ -76,7 +76,7 @@ public function testMoneyWidgetInIso() $this->assertSame(<<<'HTML'
-
+ HTML , trim($this->renderWidget($view))); } diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionBootstrap5LayoutTest.php b/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionBootstrap5LayoutTest.php index e7e537ac5ae49..ced0fe607174e 100644 --- a/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionBootstrap5LayoutTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionBootstrap5LayoutTest.php @@ -75,7 +75,7 @@ public function testMoneyWidgetInIso() ->createView(); self::assertSame(<<<'HTML' -
+
HTML , trim($this->renderWidget($view))); } diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionDivLayoutTest.php b/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionDivLayoutTest.php index cf76f9ee291b8..ad2627a238a18 100644 --- a/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionDivLayoutTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionDivLayoutTest.php @@ -156,7 +156,7 @@ public function testMoneyWidgetInIso() ->createView() ; - $this->assertSame('€ ', $this->renderWidget($view)); + $this->assertSame('€ ', $this->renderWidget($view)); } public function testHelpAttr() diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/TranslationExtensionTest.php b/src/Symfony/Bridge/Twig/Tests/Extension/TranslationExtensionTest.php index 12d6bc5e2a000..96f707cdfdf2c 100644 --- a/src/Symfony/Bridge/Twig/Tests/Extension/TranslationExtensionTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Extension/TranslationExtensionTest.php @@ -206,7 +206,7 @@ public function testDefaultTranslationDomainWithNamedArguments() $this->assertEquals('foo (custom)foo (foo)foo (custom)foo (custom)foo (fr)foo (custom)foo (fr)', trim($template->render([]))); } - private function getTemplate($template, TranslatorInterface $translator = null): TemplateWrapper + private function getTemplate($template, ?TranslatorInterface $translator = null): TemplateWrapper { $translator ??= new Translator('en'); diff --git a/src/Symfony/Bridge/Twig/Tests/Mime/BodyRendererTest.php b/src/Symfony/Bridge/Twig/Tests/Mime/BodyRendererTest.php index 4ffea6b8135e7..f5d37e7d45c4e 100644 --- a/src/Symfony/Bridge/Twig/Tests/Mime/BodyRendererTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Mime/BodyRendererTest.php @@ -145,7 +145,7 @@ public function testRenderWithLocale() $this->assertEquals('Locale: fr', $email->getHtmlBody()); } - private function prepareEmail(?string $text, ?string $html, array $context = [], HtmlToTextConverterInterface $converter = null, LocaleSwitcher $localeSwitcher = null, string $locale = null): TemplatedEmail + private function prepareEmail(?string $text, ?string $html, array $context = [], ?HtmlToTextConverterInterface $converter = null, ?LocaleSwitcher $localeSwitcher = null, ?string $locale = null): TemplatedEmail { $twig = new Environment(new ArrayLoader([ 'text' => $text, diff --git a/src/Symfony/Bridge/Twig/Tests/Mime/NotificationEmailTest.php b/src/Symfony/Bridge/Twig/Tests/Mime/NotificationEmailTest.php index 6e48f2b4a5d61..979f2791d11b9 100644 --- a/src/Symfony/Bridge/Twig/Tests/Mime/NotificationEmailTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Mime/NotificationEmailTest.php @@ -128,4 +128,54 @@ public function testPublicMailSubject() $headers = $email->getPreparedHeaders(); $this->assertSame('Foo', $headers->get('Subject')->getValue()); } + + public function testContext() + { + $email = new NotificationEmail(); + $email->context(['some' => 'context']); + + $this->assertSame([ + 'importance' => NotificationEmail::IMPORTANCE_LOW, + 'content' => '', + 'exception' => false, + 'action_text' => null, + 'action_url' => null, + 'markdown' => false, + 'raw' => false, + 'footer_text' => 'Notification email sent by Symfony', + 'some' => 'context', + ], $email->getContext()); + + $context = $email->getContext(); + $context['foo'] = 'bar'; + $email->context($context); + + $this->assertSame([ + 'importance' => NotificationEmail::IMPORTANCE_LOW, + 'content' => '', + 'exception' => false, + 'action_text' => null, + 'action_url' => null, + 'markdown' => false, + 'raw' => false, + 'footer_text' => 'Notification email sent by Symfony', + 'some' => 'context', + 'foo' => 'bar', + ], $email->getContext()); + + $email->action('Action Text', 'Action URL'); + + $this->assertSame([ + 'importance' => NotificationEmail::IMPORTANCE_LOW, + 'content' => '', + 'exception' => false, + 'action_text' => 'Action Text', + 'action_url' => 'Action URL', + 'markdown' => false, + 'raw' => false, + 'footer_text' => 'Notification email sent by Symfony', + 'some' => 'context', + 'foo' => 'bar', + ], $email->getContext()); + } } diff --git a/src/Symfony/Bridge/Twig/Tests/Mime/TemplatedEmailTest.php b/src/Symfony/Bridge/Twig/Tests/Mime/TemplatedEmailTest.php index 81f0edb6870ea..f77b3ad4b5337 100644 --- a/src/Symfony/Bridge/Twig/Tests/Mime/TemplatedEmailTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Mime/TemplatedEmailTest.php @@ -99,8 +99,7 @@ public function testSymfonySerialize() } ] }, - "body": null, - "message": null + "body": null } EOF; diff --git a/src/Symfony/Bridge/Twig/Tests/Translation/TwigExtractorTest.php b/src/Symfony/Bridge/Twig/Tests/Translation/TwigExtractorTest.php index 7d7d65adbdbef..f9ae8c348e0fb 100644 --- a/src/Symfony/Bridge/Twig/Tests/Translation/TwigExtractorTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Translation/TwigExtractorTest.php @@ -22,6 +22,8 @@ class TwigExtractorTest extends TestCase { + public const CUSTOM_DOMAIN = 'domain'; + /** * @dataProvider getExtractData */ @@ -76,6 +78,11 @@ public static function getExtractData() // make sure this works with twig's named arguments ['{{ "new key" | trans(domain="domain") }}', ['new key' => 'domain']], + // make sure this works with const domain + ['{{ "new key" | trans({}, constant(\'Symfony\\\\Bridge\\\\Twig\\\\Tests\\\\Translation\\\\TwigExtractorTest::CUSTOM_DOMAIN\')) }}', ['new key' => self::CUSTOM_DOMAIN]], + ['{% trans from constant(\'Symfony\\\\Bridge\\\\Twig\\\\Tests\\\\Translation\\\\TwigExtractorTest::CUSTOM_DOMAIN\') %}new key{% endtrans %}', ['new key' => self::CUSTOM_DOMAIN]], + ['{{ t("new key", {}, constant(\'Symfony\\\\Bridge\\\\Twig\\\\Tests\\\\Translation\\\\TwigExtractorTest::CUSTOM_DOMAIN\')) | trans() }}', ['new key' => self::CUSTOM_DOMAIN]], + // concat translations ['{{ ("new" ~ " key") | trans() }}', ['new key' => 'messages']], ['{{ ("another " ~ "new " ~ "key") | trans() }}', ['another new key' => 'messages']], diff --git a/src/Symfony/Bridge/Twig/UndefinedCallableHandler.php b/src/Symfony/Bridge/Twig/UndefinedCallableHandler.php index ede634e196fcf..0ae2893cb8adf 100644 --- a/src/Symfony/Bridge/Twig/UndefinedCallableHandler.php +++ b/src/Symfony/Bridge/Twig/UndefinedCallableHandler.php @@ -25,6 +25,7 @@ class UndefinedCallableHandler private const FILTER_COMPONENTS = [ 'humanize' => 'form', 'form_encode_currency' => 'form', + 'serialize' => 'serializer', 'trans' => 'translation', 'sanitize_html' => 'html-sanitizer', 'yaml_encode' => 'yaml', @@ -59,6 +60,11 @@ class UndefinedCallableHandler 'logout_url' => 'security-http', 'logout_path' => 'security-http', 'is_granted' => 'security-core', + 'impersonation_path' => 'security-http', + 'impersonation_url' => 'security-http', + 'impersonation_exit_path' => 'security-http', + 'impersonation_exit_url' => 'security-http', + 't' => 'translation', 'link' => 'web-link', 'preload' => 'web-link', 'dns_prefetch' => 'web-link', diff --git a/src/Symfony/Bridge/Twig/composer.json b/src/Symfony/Bridge/Twig/composer.json index c4c0479a7ce16..1610ec547cd88 100644 --- a/src/Symfony/Bridge/Twig/composer.json +++ b/src/Symfony/Bridge/Twig/composer.json @@ -43,7 +43,7 @@ "symfony/security-core": "^6.4|^7.0", "symfony/security-csrf": "^6.4|^7.0", "symfony/security-http": "^6.4|^7.0", - "symfony/serializer": "^6.4|^7.0", + "symfony/serializer": "^6.4.3|^7.0.3", "symfony/stopwatch": "^6.4|^7.0", "symfony/console": "^6.4|^7.0", "symfony/expression-language": "^6.4|^7.0", diff --git a/src/Symfony/Bundle/DebugBundle/Command/ServerDumpPlaceholderCommand.php b/src/Symfony/Bundle/DebugBundle/Command/ServerDumpPlaceholderCommand.php index 44023876fbf07..69ee83d18ba1a 100644 --- a/src/Symfony/Bundle/DebugBundle/Command/ServerDumpPlaceholderCommand.php +++ b/src/Symfony/Bundle/DebugBundle/Command/ServerDumpPlaceholderCommand.php @@ -31,7 +31,7 @@ class ServerDumpPlaceholderCommand extends Command { private ServerDumpCommand $replacedCommand; - public function __construct(DumpServer $server = null, array $descriptors = []) + public function __construct(?DumpServer $server = null, array $descriptors = []) { $this->replacedCommand = new ServerDumpCommand((new \ReflectionClass(DumpServer::class))->newInstanceWithoutConstructor(), $descriptors); diff --git a/src/Symfony/Bundle/DebugBundle/DebugBundle.php b/src/Symfony/Bundle/DebugBundle/DebugBundle.php index bed1ee86412b7..605f3579c96c6 100644 --- a/src/Symfony/Bundle/DebugBundle/DebugBundle.php +++ b/src/Symfony/Bundle/DebugBundle/DebugBundle.php @@ -32,10 +32,10 @@ public function boot(): void // The dump data collector is used by default, so dump output is sent to // the WDT. In a CLI context, if dump is used too soon, the data collector // will buffer it, and release it at the end of the script. - VarDumper::setHandler(function ($var, string $label = null) use ($container) { + VarDumper::setHandler(function ($var, ?string $label = null) use ($container) { $dumper = $container->get('data_collector.dump'); $cloner = $container->get('var_dumper.cloner'); - $handler = function ($var, string $label = null) use ($dumper, $cloner) { + $handler = function ($var, ?string $label = null) use ($dumper, $cloner) { $var = $cloner->cloneVar($var); if (null !== $label) { $var = $var->withContext(['label' => $label]); diff --git a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md index 43d7f62950e83..6fe797e3a3152 100644 --- a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md @@ -9,6 +9,8 @@ CHANGELOG * Move the Router `cache_dir` to `kernel.build_dir` * Deprecate the `router.cache_dir` config option * Add `rate_limiter` tags to rate limiter services + * Add `secrets:reveal` command + * Add `rate_limiter` option to `http_client.default_options` and `http_client.scoped_clients` 7.0 --- diff --git a/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/AbstractPhpFileCacheWarmer.php b/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/AbstractPhpFileCacheWarmer.php index f6c259bbbb708..3d7c99e4faa6c 100644 --- a/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/AbstractPhpFileCacheWarmer.php +++ b/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/AbstractPhpFileCacheWarmer.php @@ -34,7 +34,7 @@ public function isOptional(): bool return true; } - public function warmUp(string $cacheDir, string $buildDir = null): array + public function warmUp(string $cacheDir, ?string $buildDir = null): array { $arrayAdapter = new ArrayAdapter(); @@ -77,5 +77,5 @@ final protected function ignoreAutoloadException(string $class, \Exception $exce /** * @return bool false if there is nothing to warm-up */ - abstract protected function doWarmUp(string $cacheDir, ArrayAdapter $arrayAdapter, string $buildDir = null): bool; + abstract protected function doWarmUp(string $cacheDir, ArrayAdapter $arrayAdapter, ?string $buildDir = null): bool; } diff --git a/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/CachePoolClearerCacheWarmer.php b/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/CachePoolClearerCacheWarmer.php index 7498a82d1f3a3..ae27502cfb3c5 100644 --- a/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/CachePoolClearerCacheWarmer.php +++ b/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/CachePoolClearerCacheWarmer.php @@ -37,7 +37,7 @@ public function __construct(Psr6CacheClearer $poolClearer, array $pools = []) $this->pools = $pools; } - public function warmUp(string $cacheDir, string $buildDir = null): array + public function warmUp(string $cacheDir, ?string $buildDir = null): array { foreach ($this->pools as $pool) { if ($this->poolClearer->hasPool($pool)) { diff --git a/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/ConfigBuilderCacheWarmer.php b/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/ConfigBuilderCacheWarmer.php index 6c93797c75669..44e5699012bb5 100644 --- a/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/ConfigBuilderCacheWarmer.php +++ b/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/ConfigBuilderCacheWarmer.php @@ -33,13 +33,13 @@ class ConfigBuilderCacheWarmer implements CacheWarmerInterface private KernelInterface $kernel; private ?LoggerInterface $logger; - public function __construct(KernelInterface $kernel, LoggerInterface $logger = null) + public function __construct(KernelInterface $kernel, ?LoggerInterface $logger = null) { $this->kernel = $kernel; $this->logger = $logger; } - public function warmUp(string $cacheDir, string $buildDir = null): array + public function warmUp(string $cacheDir, ?string $buildDir = null): array { if (!$buildDir) { return []; @@ -82,6 +82,6 @@ private function dumpExtension(ExtensionInterface $extension, ConfigBuilderGener public function isOptional(): bool { - return true; + return false; } } diff --git a/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/RouterCacheWarmer.php b/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/RouterCacheWarmer.php index 9dfa71c2c542f..eed548046b88b 100644 --- a/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/RouterCacheWarmer.php +++ b/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/RouterCacheWarmer.php @@ -34,7 +34,7 @@ public function __construct(ContainerInterface $container) $this->container = $container; } - public function warmUp(string $cacheDir, string $buildDir = null): array + public function warmUp(string $cacheDir, ?string $buildDir = null): array { if (!$buildDir) { return []; diff --git a/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/SerializerCacheWarmer.php b/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/SerializerCacheWarmer.php index 662c2136d3e93..228a763fed78a 100644 --- a/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/SerializerCacheWarmer.php +++ b/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/SerializerCacheWarmer.php @@ -40,7 +40,7 @@ public function __construct(array $loaders, string $phpArrayFile) $this->loaders = $loaders; } - protected function doWarmUp(string $cacheDir, ArrayAdapter $arrayAdapter, string $buildDir = null): bool + protected function doWarmUp(string $cacheDir, ArrayAdapter $arrayAdapter, ?string $buildDir = null): bool { if (!$this->loaders) { return true; diff --git a/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/TranslationsCacheWarmer.php b/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/TranslationsCacheWarmer.php index 37fa98aa7d312..19b2725c93d4c 100644 --- a/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/TranslationsCacheWarmer.php +++ b/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/TranslationsCacheWarmer.php @@ -35,7 +35,7 @@ public function __construct(ContainerInterface $container) $this->container = $container; } - public function warmUp(string $cacheDir, string $buildDir = null): array + public function warmUp(string $cacheDir, ?string $buildDir = null): array { $this->translator ??= $this->container->get('translator'); diff --git a/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/ValidatorCacheWarmer.php b/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/ValidatorCacheWarmer.php index 3d9349a5b0487..abf3505622d2f 100644 --- a/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/ValidatorCacheWarmer.php +++ b/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/ValidatorCacheWarmer.php @@ -40,7 +40,7 @@ public function __construct(ValidatorBuilder $validatorBuilder, string $phpArray $this->validatorBuilder = $validatorBuilder; } - protected function doWarmUp(string $cacheDir, ArrayAdapter $arrayAdapter, string $buildDir = null): bool + protected function doWarmUp(string $cacheDir, ArrayAdapter $arrayAdapter, ?string $buildDir = null): bool { $loaders = $this->validatorBuilder->getLoaders(); $metadataFactory = new LazyLoadingMetadataFactory(new LoaderChain($loaders), $arrayAdapter); diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/CacheClearCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/CacheClearCommand.php index d8f23f7de4264..40880df4760ac 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/CacheClearCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/CacheClearCommand.php @@ -40,7 +40,7 @@ class CacheClearCommand extends Command private CacheClearerInterface $cacheClearer; private Filesystem $filesystem; - public function __construct(CacheClearerInterface $cacheClearer, Filesystem $filesystem = null) + public function __construct(CacheClearerInterface $cacheClearer, ?Filesystem $filesystem = null) { parent::__construct(); diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/CachePoolClearCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/CachePoolClearCommand.php index fcd70ca0e93a8..8b8b9cd35e51e 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/CachePoolClearCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/CachePoolClearCommand.php @@ -38,7 +38,7 @@ final class CachePoolClearCommand extends Command /** * @param string[]|null $poolNames */ - public function __construct(Psr6CacheClearer $poolClearer, array $poolNames = null) + public function __construct(Psr6CacheClearer $poolClearer, ?array $poolNames = null) { parent::__construct(); @@ -72,7 +72,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $poolNames = $input->getArgument('pools'); $excludedPoolNames = $input->getOption('exclude'); - if ($input->getOption('all')) { + if ($clearAll = $input->getOption('all')) { if (!$this->poolNames) { throw new InvalidArgumentException('Could not clear all cache pools, try specifying a specific pool or cache clearer.'); } @@ -91,7 +91,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int foreach ($poolNames as $id) { if ($this->poolClearer->hasPool($id)) { $pools[$id] = $id; - } else { + } elseif (!$clearAll || $kernel->getContainer()->has($id)) { $pool = $kernel->getContainer()->get($id); if ($pool instanceof CacheItemPoolInterface) { diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/CachePoolDeleteCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/CachePoolDeleteCommand.php index 7b53ceb8ba94d..dfa307bc0b73c 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/CachePoolDeleteCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/CachePoolDeleteCommand.php @@ -35,7 +35,7 @@ final class CachePoolDeleteCommand extends Command /** * @param string[]|null $poolNames */ - public function __construct(Psr6CacheClearer $poolClearer, array $poolNames = null) + public function __construct(Psr6CacheClearer $poolClearer, ?array $poolNames = null) { parent::__construct(); diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/DebugAutowiringCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/DebugAutowiringCommand.php index ab4793b685f8a..f6efd8bef8ce1 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/DebugAutowiringCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/DebugAutowiringCommand.php @@ -35,7 +35,7 @@ class DebugAutowiringCommand extends ContainerDebugCommand { private ?FileLinkFormatter $fileLinkFormatter; - public function __construct(string $name = null, FileLinkFormatter $fileLinkFormatter = null) + public function __construct(?string $name = null, ?FileLinkFormatter $fileLinkFormatter = null) { $this->fileLinkFormatter = $fileLinkFormatter; parent::__construct($name); diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/RouterDebugCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/RouterDebugCommand.php index 8d4e38a29438f..9318b46be50d7 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/RouterDebugCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/RouterDebugCommand.php @@ -42,7 +42,7 @@ class RouterDebugCommand extends Command private RouterInterface $router; private ?FileLinkFormatter $fileLinkFormatter; - public function __construct(RouterInterface $router, FileLinkFormatter $fileLinkFormatter = null) + public function __construct(RouterInterface $router, ?FileLinkFormatter $fileLinkFormatter = null) { parent::__construct(); diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/SecretsDecryptToLocalCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/SecretsDecryptToLocalCommand.php index 22be8950244de..ac711e3dbd850 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/SecretsDecryptToLocalCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/SecretsDecryptToLocalCommand.php @@ -31,7 +31,7 @@ final class SecretsDecryptToLocalCommand extends Command private AbstractVault $vault; private ?AbstractVault $localVault; - public function __construct(AbstractVault $vault, AbstractVault $localVault = null) + public function __construct(AbstractVault $vault, ?AbstractVault $localVault = null) { $this->vault = $vault; $this->localVault = $localVault; diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/SecretsEncryptFromLocalCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/SecretsEncryptFromLocalCommand.php index 4c613ef7b27b6..46e0baffc9242 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/SecretsEncryptFromLocalCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/SecretsEncryptFromLocalCommand.php @@ -30,7 +30,7 @@ final class SecretsEncryptFromLocalCommand extends Command private AbstractVault $vault; private ?AbstractVault $localVault; - public function __construct(AbstractVault $vault, AbstractVault $localVault = null) + public function __construct(AbstractVault $vault, ?AbstractVault $localVault = null) { $this->vault = $vault; $this->localVault = $localVault; diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/SecretsGenerateKeysCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/SecretsGenerateKeysCommand.php index 761f6c260cd7b..989eff9fd2977 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/SecretsGenerateKeysCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/SecretsGenerateKeysCommand.php @@ -33,7 +33,7 @@ final class SecretsGenerateKeysCommand extends Command private AbstractVault $vault; private ?AbstractVault $localVault; - public function __construct(AbstractVault $vault, AbstractVault $localVault = null) + public function __construct(AbstractVault $vault, ?AbstractVault $localVault = null) { $this->vault = $vault; $this->localVault = $localVault; diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/SecretsListCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/SecretsListCommand.php index de8a7e7722cee..9a24f4a90fbb6 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/SecretsListCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/SecretsListCommand.php @@ -34,7 +34,7 @@ final class SecretsListCommand extends Command private AbstractVault $vault; private ?AbstractVault $localVault; - public function __construct(AbstractVault $vault, AbstractVault $localVault = null) + public function __construct(AbstractVault $vault, ?AbstractVault $localVault = null) { $this->vault = $vault; $this->localVault = $localVault; diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/SecretsRemoveCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/SecretsRemoveCommand.php index e03afcd0cf902..1789f2981b11b 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/SecretsRemoveCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/SecretsRemoveCommand.php @@ -35,7 +35,7 @@ final class SecretsRemoveCommand extends Command private AbstractVault $vault; private ?AbstractVault $localVault; - public function __construct(AbstractVault $vault, AbstractVault $localVault = null) + public function __construct(AbstractVault $vault, ?AbstractVault $localVault = null) { $this->vault = $vault; $this->localVault = $localVault; diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/SecretsRevealCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/SecretsRevealCommand.php new file mode 100644 index 0000000000000..bcbdea11f079c --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Command/SecretsRevealCommand.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\Bundle\FrameworkBundle\Command; + +use Symfony\Bundle\FrameworkBundle\Secrets\AbstractVault; +use Symfony\Component\Console\Attribute\AsCommand; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\ConsoleOutputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; + +/** + * @internal + */ +#[AsCommand(name: 'secrets:reveal', description: 'Reveal the value of a secret')] +final class SecretsRevealCommand extends Command +{ + public function __construct( + private readonly AbstractVault $vault, + private readonly ?AbstractVault $localVault = null, + ) { + parent::__construct(); + } + + protected function configure(): void + { + $this + ->addArgument('name', InputArgument::REQUIRED, 'The name of the secret to reveal', null, fn () => array_keys($this->vault->list())) + ->setHelp(<<<'EOF' +The %command.name% command reveals a stored secret. + + %command.full_name% +EOF + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $io = new SymfonyStyle($input, $output instanceof ConsoleOutputInterface ? $output->getErrorOutput() : $output); + + $secrets = $this->vault->list(true); + $localSecrets = $this->localVault?->list(true); + + $name = (string) $input->getArgument('name'); + + if (null !== $localSecrets && \array_key_exists($name, $localSecrets)) { + $io->writeln($localSecrets[$name]); + } else { + if (!\array_key_exists($name, $secrets)) { + $io->error(sprintf('The secret "%s" does not exist.', $name)); + + return self::INVALID; + } + + $io->writeln($secrets[$name]); + } + + return self::SUCCESS; + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/SecretsSetCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/SecretsSetCommand.php index 0e831a343d2f7..2d2b8c5cb6b42 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/SecretsSetCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/SecretsSetCommand.php @@ -36,7 +36,7 @@ final class SecretsSetCommand extends Command private AbstractVault $vault; private ?AbstractVault $localVault; - public function __construct(AbstractVault $vault, AbstractVault $localVault = null) + public function __construct(AbstractVault $vault, ?AbstractVault $localVault = null) { $this->vault = $vault; $this->localVault = $localVault; diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/TranslationDebugCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/TranslationDebugCommand.php index 15544a90c74f2..7095d70abb129 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/TranslationDebugCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/TranslationDebugCommand.php @@ -59,7 +59,7 @@ class TranslationDebugCommand extends Command private array $codePaths; private array $enabledLocales; - public function __construct(TranslatorInterface $translator, TranslationReaderInterface $reader, ExtractorInterface $extractor, string $defaultTransPath = null, string $defaultViewsPath = null, array $transPaths = [], array $codePaths = [], array $enabledLocales = []) + public function __construct(TranslatorInterface $translator, TranslationReaderInterface $reader, ExtractorInterface $extractor, ?string $defaultTransPath = null, ?string $defaultViewsPath = null, array $transPaths = [], array $codePaths = [], array $enabledLocales = []) { parent::__construct(); diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/TranslationUpdateCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/TranslationUpdateCommand.php index bf2c0ec8fd179..fc95e0217908a 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/TranslationUpdateCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/TranslationUpdateCommand.php @@ -60,7 +60,7 @@ class TranslationUpdateCommand extends Command private array $codePaths; private array $enabledLocales; - public function __construct(TranslationWriterInterface $writer, TranslationReaderInterface $reader, ExtractorInterface $extractor, string $defaultLocale, string $defaultTransPath = null, string $defaultViewsPath = null, array $transPaths = [], array $codePaths = [], array $enabledLocales = []) + public function __construct(TranslationWriterInterface $writer, TranslationReaderInterface $reader, ExtractorInterface $extractor, string $defaultLocale, ?string $defaultTransPath = null, ?string $defaultViewsPath = null, array $transPaths = [], array $codePaths = [], array $enabledLocales = []) { parent::__construct(); diff --git a/src/Symfony/Bundle/FrameworkBundle/Console/Application.php b/src/Symfony/Bundle/FrameworkBundle/Console/Application.php index daa6c7026291a..14a907e2e9fb7 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Console/Application.php +++ b/src/Symfony/Bundle/FrameworkBundle/Console/Application.php @@ -149,7 +149,7 @@ public function get(string $name): Command return parent::get($name); } - public function all(string $namespace = null): array + public function all(?string $namespace = null): array { $this->registerCommands(); diff --git a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/Descriptor.php b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/Descriptor.php index ba500adb2bbca..8541f71bbe765 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/Descriptor.php +++ b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/Descriptor.php @@ -96,7 +96,7 @@ abstract protected function describeContainerTags(ContainerBuilder $container, a * * @param Definition|Alias|object $service */ - abstract protected function describeContainerService(object $service, array $options = [], ContainerBuilder $container = null): void; + abstract protected function describeContainerService(object $service, array $options = [], ?ContainerBuilder $container = null): void; /** * Describes container services. @@ -108,9 +108,9 @@ abstract protected function describeContainerServices(ContainerBuilder $containe abstract protected function describeContainerDeprecations(ContainerBuilder $container, array $options = []): void; - abstract protected function describeContainerDefinition(Definition $definition, array $options = [], ContainerBuilder $container = null): void; + abstract protected function describeContainerDefinition(Definition $definition, array $options = [], ?ContainerBuilder $container = null): void; - abstract protected function describeContainerAlias(Alias $alias, array $options = [], ContainerBuilder $container = null): void; + abstract protected function describeContainerAlias(Alias $alias, array $options = [], ?ContainerBuilder $container = null): void; abstract protected function describeContainerParameter(mixed $parameter, ?array $deprecation, array $options = []): void; @@ -278,7 +278,7 @@ protected function getReverseAliases(RouteCollection $routes): array return $reverseAliases; } - public static function getClassDescription(string $class, string &$resolvedClass = null): string + public static function getClassDescription(string $class, ?string &$resolvedClass = null): string { $resolvedClass = $class; try { diff --git a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/JsonDescriptor.php b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/JsonDescriptor.php index 9562dc500c37f..88cf4162c6c83 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/JsonDescriptor.php +++ b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/JsonDescriptor.php @@ -70,7 +70,7 @@ protected function describeContainerTags(ContainerBuilder $container, array $opt $this->writeData($data, $options); } - protected function describeContainerService(object $service, array $options = [], ContainerBuilder $container = null): void + protected function describeContainerService(object $service, array $options = [], ?ContainerBuilder $container = null): void { if (!isset($options['id'])) { throw new \InvalidArgumentException('An "id" option must be provided.'); @@ -121,12 +121,12 @@ protected function describeContainerServices(ContainerBuilder $container, array $this->writeData($data, $options); } - protected function describeContainerDefinition(Definition $definition, array $options = [], ContainerBuilder $container = null): void + protected function describeContainerDefinition(Definition $definition, array $options = [], ?ContainerBuilder $container = null): void { $this->writeData($this->getContainerDefinitionData($definition, isset($options['omit_tags']) && $options['omit_tags'], isset($options['show_arguments']) && $options['show_arguments'], $container, $options['id'] ?? null), $options); } - protected function describeContainerAlias(Alias $alias, array $options = [], ContainerBuilder $container = null): void + protected function describeContainerAlias(Alias $alias, array $options = [], ?ContainerBuilder $container = null): void { if (!$container) { $this->writeData($this->getContainerAliasData($alias), $options); @@ -245,7 +245,7 @@ protected function sortParameters(ParameterBag $parameters): array return $sortedParameters; } - private function getContainerDefinitionData(Definition $definition, bool $omitTags = false, bool $showArguments = false, ContainerBuilder $container = null, string $id = null): array + private function getContainerDefinitionData(Definition $definition, bool $omitTags = false, bool $showArguments = false, ?ContainerBuilder $container = null, ?string $id = null): array { $data = [ 'class' => (string) $definition->getClass(), @@ -418,7 +418,7 @@ private function getCallableData(mixed $callable): array throw new \InvalidArgumentException('Callable is not describable.'); } - private function describeValue($value, bool $omitTags, bool $showArguments, ContainerBuilder $container = null, string $id = null): mixed + private function describeValue($value, bool $omitTags, bool $showArguments, ?ContainerBuilder $container = null, ?string $id = null): mixed { if (\is_array($value)) { $data = []; diff --git a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/MarkdownDescriptor.php b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/MarkdownDescriptor.php index 924951b5de397..7965990bdf207 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/MarkdownDescriptor.php +++ b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/MarkdownDescriptor.php @@ -98,7 +98,7 @@ protected function describeContainerTags(ContainerBuilder $container, array $opt } } - protected function describeContainerService(object $service, array $options = [], ContainerBuilder $container = null): void + protected function describeContainerService(object $service, array $options = [], ?ContainerBuilder $container = null): void { if (!isset($options['id'])) { throw new \InvalidArgumentException('An "id" option must be provided.'); @@ -206,7 +206,7 @@ protected function describeContainerServices(ContainerBuilder $container, array } } - protected function describeContainerDefinition(Definition $definition, array $options = [], ContainerBuilder $container = null): void + protected function describeContainerDefinition(Definition $definition, array $options = [], ?ContainerBuilder $container = null): void { $output = ''; @@ -276,7 +276,7 @@ protected function describeContainerDefinition(Definition $definition, array $op $this->write(isset($options['id']) ? sprintf("### %s\n\n%s\n", $options['id'], $output) : $output); } - protected function describeContainerAlias(Alias $alias, array $options = [], ContainerBuilder $container = null): void + protected function describeContainerAlias(Alias $alias, array $options = [], ?ContainerBuilder $container = null): void { $output = '- Service: `'.$alias.'`' ."\n".'- Public: '.($alias->isPublic() && !$alias->isPrivate() ? 'yes' : 'no'); diff --git a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/TextDescriptor.php b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/TextDescriptor.php index 4e243c45ef042..1ce0539da6963 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/TextDescriptor.php +++ b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/TextDescriptor.php @@ -40,7 +40,7 @@ class TextDescriptor extends Descriptor { private ?FileLinkFormatter $fileLinkFormatter; - public function __construct(FileLinkFormatter $fileLinkFormatter = null) + public function __construct(?FileLinkFormatter $fileLinkFormatter = null) { $this->fileLinkFormatter = $fileLinkFormatter; } @@ -159,7 +159,7 @@ protected function describeContainerTags(ContainerBuilder $container, array $opt } } - protected function describeContainerService(object $service, array $options = [], ContainerBuilder $container = null): void + protected function describeContainerService(object $service, array $options = [], ?ContainerBuilder $container = null): void { if (!isset($options['id'])) { throw new \InvalidArgumentException('An "id" option must be provided.'); @@ -281,7 +281,7 @@ protected function describeContainerServices(ContainerBuilder $container, array $options['output']->table($tableHeaders, $tableRows); } - protected function describeContainerDefinition(Definition $definition, array $options = [], ContainerBuilder $container = null): void + protected function describeContainerDefinition(Definition $definition, array $options = [], ?ContainerBuilder $container = null): void { if (isset($options['id'])) { $options['output']->title(sprintf('Information for Service "%s"', $options['id'])); @@ -420,7 +420,7 @@ protected function describeContainerDeprecations(ContainerBuilder $container, ar $options['output']->listing($formattedLogs); } - protected function describeContainerAlias(Alias $alias, array $options = [], ContainerBuilder $container = null): void + protected function describeContainerAlias(Alias $alias, array $options = [], ?ContainerBuilder $container = null): void { if ($alias->isPublic() && !$alias->isPrivate()) { $options['output']->comment(sprintf('This service is a public alias for the service %s', (string) $alias)); @@ -579,7 +579,7 @@ private function formatRouterConfig(array $config): string return trim($configAsString); } - private function formatControllerLink(mixed $controller, string $anchorText, callable $getContainer = null): string + private function formatControllerLink(mixed $controller, string $anchorText, ?callable $getContainer = null): string { if (null === $this->fileLinkFormatter) { return $anchorText; diff --git a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/XmlDescriptor.php b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/XmlDescriptor.php index 14435f9e0b8fd..c52b196674364 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/XmlDescriptor.php +++ b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/XmlDescriptor.php @@ -53,7 +53,7 @@ protected function describeContainerTags(ContainerBuilder $container, array $opt $this->writeDocument($this->getContainerTagsDocument($container, isset($options['show_hidden']) && $options['show_hidden'])); } - protected function describeContainerService(object $service, array $options = [], ContainerBuilder $container = null): void + protected function describeContainerService(object $service, array $options = [], ?ContainerBuilder $container = null): void { if (!isset($options['id'])) { throw new \InvalidArgumentException('An "id" option must be provided.'); @@ -67,12 +67,12 @@ protected function describeContainerServices(ContainerBuilder $container, array $this->writeDocument($this->getContainerServicesDocument($container, $options['tag'] ?? null, isset($options['show_hidden']) && $options['show_hidden'], isset($options['show_arguments']) && $options['show_arguments'], $options['filter'] ?? null, $options['id'] ?? null)); } - protected function describeContainerDefinition(Definition $definition, array $options = [], ContainerBuilder $container = null): void + protected function describeContainerDefinition(Definition $definition, array $options = [], ?ContainerBuilder $container = null): void { $this->writeDocument($this->getContainerDefinitionDocument($definition, $options['id'] ?? null, isset($options['omit_tags']) && $options['omit_tags'], isset($options['show_arguments']) && $options['show_arguments'], $container)); } - protected function describeContainerAlias(Alias $alias, array $options = [], ContainerBuilder $container = null): void + protected function describeContainerAlias(Alias $alias, array $options = [], ?ContainerBuilder $container = null): void { $dom = new \DOMDocument('1.0', 'UTF-8'); $dom->appendChild($dom->importNode($this->getContainerAliasDocument($alias, $options['id'] ?? null)->childNodes->item(0), true)); @@ -162,7 +162,7 @@ private function getRouteCollectionDocument(RouteCollection $routes, array $opti return $dom; } - private function getRouteDocument(Route $route, string $name = null): \DOMDocument + private function getRouteDocument(Route $route, ?string $name = null): \DOMDocument { $dom = new \DOMDocument('1.0', 'UTF-8'); $dom->appendChild($routeXML = $dom->createElement('route')); @@ -268,7 +268,7 @@ private function getContainerTagsDocument(ContainerBuilder $container, bool $sho return $dom; } - private function getContainerServiceDocument(object $service, string $id, ContainerBuilder $container = null, bool $showArguments = false): \DOMDocument + private function getContainerServiceDocument(object $service, string $id, ?ContainerBuilder $container = null, bool $showArguments = false): \DOMDocument { $dom = new \DOMDocument('1.0', 'UTF-8'); @@ -288,7 +288,7 @@ private function getContainerServiceDocument(object $service, string $id, Contai return $dom; } - private function getContainerServicesDocument(ContainerBuilder $container, string $tag = null, bool $showHidden = false, bool $showArguments = false, callable $filter = null, string $id = null): \DOMDocument + private function getContainerServicesDocument(ContainerBuilder $container, ?string $tag = null, bool $showHidden = false, bool $showArguments = false, ?callable $filter = null, ?string $id = null): \DOMDocument { $dom = new \DOMDocument('1.0', 'UTF-8'); $dom->appendChild($containerXML = $dom->createElement('container')); @@ -318,7 +318,7 @@ private function getContainerServicesDocument(ContainerBuilder $container, strin return $dom; } - private function getContainerDefinitionDocument(Definition $definition, string $id = null, bool $omitTags = false, bool $showArguments = false, ContainerBuilder $container = null): \DOMDocument + private function getContainerDefinitionDocument(Definition $definition, ?string $id = null, bool $omitTags = false, bool $showArguments = false, ?ContainerBuilder $container = null): \DOMDocument { $dom = new \DOMDocument('1.0', 'UTF-8'); $dom->appendChild($serviceXML = $dom->createElement('definition')); @@ -418,7 +418,7 @@ private function getContainerDefinitionDocument(Definition $definition, string $ /** * @return \DOMNode[] */ - private function getArgumentNodes(array $arguments, \DOMDocument $dom, ContainerBuilder $container = null): array + private function getArgumentNodes(array $arguments, \DOMDocument $dom, ?ContainerBuilder $container = null): array { $nodes = []; @@ -466,7 +466,7 @@ private function getArgumentNodes(array $arguments, \DOMDocument $dom, Container return $nodes; } - private function getContainerAliasDocument(Alias $alias, string $id = null): \DOMDocument + private function getContainerAliasDocument(Alias $alias, ?string $id = null): \DOMDocument { $dom = new \DOMDocument('1.0', 'UTF-8'); $dom->appendChild($aliasXML = $dom->createElement('alias')); diff --git a/src/Symfony/Bundle/FrameworkBundle/Console/Helper/DescriptorHelper.php b/src/Symfony/Bundle/FrameworkBundle/Console/Helper/DescriptorHelper.php index 47d69fef46cb6..b12a8d86215d6 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Console/Helper/DescriptorHelper.php +++ b/src/Symfony/Bundle/FrameworkBundle/Console/Helper/DescriptorHelper.php @@ -25,7 +25,7 @@ */ class DescriptorHelper extends BaseDescriptorHelper { - public function __construct(FileLinkFormatter $fileLinkFormatter = null) + public function __construct(?FileLinkFormatter $fileLinkFormatter = null) { $this ->register('txt', new TextDescriptor($fileLinkFormatter)) diff --git a/src/Symfony/Bundle/FrameworkBundle/Controller/AbstractController.php b/src/Symfony/Bundle/FrameworkBundle/Controller/AbstractController.php index dd65afce1f67e..f0c1d98ee01be 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Controller/AbstractController.php +++ b/src/Symfony/Bundle/FrameworkBundle/Controller/AbstractController.php @@ -160,7 +160,7 @@ protected function json(mixed $data, int $status = 200, array $headers = [], arr /** * Returns a BinaryFileResponse object with original or customized file name and disposition header. */ - protected function file(\SplFileInfo|string $file, string $fileName = null, string $disposition = ResponseHeaderBag::DISPOSITION_ATTACHMENT): BinaryFileResponse + protected function file(\SplFileInfo|string $file, ?string $fileName = null, string $disposition = ResponseHeaderBag::DISPOSITION_ATTACHMENT): BinaryFileResponse { $response = new BinaryFileResponse($file); $response->setContentDisposition($disposition, $fileName ?? $response->getFile()->getFilename()); @@ -245,7 +245,7 @@ protected function renderBlockView(string $view, string $block, array $parameter * If an invalid form is found in the list of parameters, a 422 status code is returned. * Forms found in parameters are auto-cast to form views. */ - protected function render(string $view, array $parameters = [], Response $response = null): Response + protected function render(string $view, array $parameters = [], ?Response $response = null): Response { return $this->doRender($view, null, $parameters, $response, __FUNCTION__); } @@ -256,7 +256,7 @@ protected function render(string $view, array $parameters = [], Response $respon * If an invalid form is found in the list of parameters, a 422 status code is returned. * Forms found in parameters are auto-cast to form views. */ - protected function renderBlock(string $view, string $block, array $parameters = [], Response $response = null): Response + protected function renderBlock(string $view, string $block, array $parameters = [], ?Response $response = null): Response { return $this->doRender($view, $block, $parameters, $response, __FUNCTION__); } @@ -264,7 +264,7 @@ protected function renderBlock(string $view, string $block, array $parameters = /** * Streams a view. */ - protected function stream(string $view, array $parameters = [], StreamedResponse $response = null): StreamedResponse + protected function stream(string $view, array $parameters = [], ?StreamedResponse $response = null): StreamedResponse { if (!$this->container->has('twig')) { throw new \LogicException('You cannot use the "stream" method if the Twig Bundle is not available. Try running "composer require symfony/twig-bundle".'); @@ -292,7 +292,7 @@ protected function stream(string $view, array $parameters = [], StreamedResponse * * throw $this->createNotFoundException('Page not found!'); */ - protected function createNotFoundException(string $message = 'Not Found', \Throwable $previous = null): NotFoundHttpException + protected function createNotFoundException(string $message = 'Not Found', ?\Throwable $previous = null): NotFoundHttpException { return new NotFoundHttpException($message, $previous); } @@ -306,7 +306,7 @@ protected function createNotFoundException(string $message = 'Not Found', \Throw * * @throws \LogicException If the Security component is not available */ - protected function createAccessDeniedException(string $message = 'Access Denied.', \Throwable $previous = null): AccessDeniedException + protected function createAccessDeniedException(string $message = 'Access Denied.', ?\Throwable $previous = null): AccessDeniedException { if (!class_exists(AccessDeniedException::class)) { throw new \LogicException('You cannot use the "createAccessDeniedException" method if the Security component is not available. Try running "composer require symfony/security-bundle".'); @@ -389,7 +389,7 @@ protected function addLink(Request $request, LinkInterface $link): void /** * @param LinkInterface[] $links */ - protected function sendEarlyHints(iterable $links = [], Response $response = null): Response + protected function sendEarlyHints(iterable $links = [], ?Response $response = null): Response { if (!$this->container->has('web_link.http_header_serializer')) { throw new \LogicException('You cannot use the "sendEarlyHints" method if the WebLink component is not available. Try running "composer require symfony/web-link".'); diff --git a/src/Symfony/Bundle/FrameworkBundle/Controller/RedirectController.php b/src/Symfony/Bundle/FrameworkBundle/Controller/RedirectController.php index f5f42a7f77c8c..fbb52ead7507d 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Controller/RedirectController.php +++ b/src/Symfony/Bundle/FrameworkBundle/Controller/RedirectController.php @@ -31,7 +31,7 @@ class RedirectController private ?int $httpPort; private ?int $httpsPort; - public function __construct(UrlGeneratorInterface $router = null, int $httpPort = null, int $httpsPort = null) + public function __construct(?UrlGeneratorInterface $router = null, ?int $httpPort = null, ?int $httpsPort = null) { $this->router = $router; $this->httpPort = $httpPort; @@ -107,7 +107,7 @@ public function redirectAction(Request $request, string $route, bool $permanent * * @throws HttpException In case the path is empty */ - public function urlRedirectAction(Request $request, string $path, bool $permanent = false, string $scheme = null, int $httpPort = null, int $httpsPort = null, bool $keepRequestMethod = false): Response + public function urlRedirectAction(Request $request, string $path, bool $permanent = false, ?string $scheme = null, ?int $httpPort = null, ?int $httpsPort = null, bool $keepRequestMethod = false): Response { if ('' == $path) { throw new HttpException($permanent ? 410 : 404); diff --git a/src/Symfony/Bundle/FrameworkBundle/Controller/TemplateController.php b/src/Symfony/Bundle/FrameworkBundle/Controller/TemplateController.php index 437458a499255..97631572c9c62 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Controller/TemplateController.php +++ b/src/Symfony/Bundle/FrameworkBundle/Controller/TemplateController.php @@ -25,7 +25,7 @@ class TemplateController { private ?Environment $twig; - public function __construct(Environment $twig = null) + public function __construct(?Environment $twig = null) { $this->twig = $twig; } @@ -40,7 +40,7 @@ public function __construct(Environment $twig = null) * @param array $context The context (arguments) of the template * @param int $statusCode The HTTP status code to return with the response (200 "OK" by default) */ - public function templateAction(string $template, int $maxAge = null, int $sharedAge = null, bool $private = null, array $context = [], int $statusCode = 200): Response + public function templateAction(string $template, ?int $maxAge = null, ?int $sharedAge = null, ?bool $private = null, array $context = [], int $statusCode = 200): Response { if (null === $this->twig) { throw new \LogicException('You cannot use the TemplateController if the Twig Bundle is not available. Try running "composer require symfony/twig-bundle".'); @@ -68,7 +68,7 @@ public function templateAction(string $template, int $maxAge = null, int $shared /** * @param int $statusCode The HTTP status code (200 "OK" by default) */ - public function __invoke(string $template, int $maxAge = null, int $sharedAge = null, bool $private = null, array $context = [], int $statusCode = 200): Response + public function __invoke(string $template, ?int $maxAge = null, ?int $sharedAge = null, ?bool $private = null, array $context = [], int $statusCode = 200): Response { return $this->templateAction($template, $maxAge, $sharedAge, $private, $context, $statusCode); } diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php index 81ee3e06e0546..e5b15e88fff01 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php @@ -138,7 +138,7 @@ public function getConfigTreeBuilder(): TreeBuilder ->end() ; - $willBeAvailable = static function (string $package, string $class, string $parentPackage = null) { + $willBeAvailable = static function (string $package, string $class, ?string $parentPackage = null) { $parentPackages = (array) $parentPackage; $parentPackages[] = 'symfony/framework-bundle'; @@ -902,6 +902,10 @@ private function addAssetMapperSection(ArrayNodeDefinition $rootNode, callable $ ->end() ->scalarNode('importmap_polyfill') ->info('The importmap name that will be used to load the polyfill. Set to false to disable.') + ->validate() + ->ifTrue() + ->thenInvalid('Invalid "importmap_polyfill" value. Must be either an importmap name or false.') + ->end() ->defaultValue('es-module-shims') ->end() ->arrayNode('importmap_script_attributes') @@ -1118,7 +1122,6 @@ private function addSerializerSection(ArrayNodeDefinition $rootNode, callable $e ->end() ->arrayNode('default_context') ->normalizeKeys(false) - ->useAttributeAsKey('name') ->beforeNormalization() ->ifTrue(fn () => $this->debug && class_exists(JsonParser::class)) ->then(fn (array $v) => $v + [JsonDecode::DETAILED_ERROR_MESSAGES => true]) @@ -1730,17 +1733,32 @@ private function addHttpClientSection(ArrayNodeDefinition $rootNode, callable $e ->fixXmlConfig('scoped_client') ->beforeNormalization() ->always(function ($config) { - if (empty($config['scoped_clients']) || !\is_array($config['default_options']['retry_failed'] ?? null)) { + if (empty($config['scoped_clients'])) { + return $config; + } + + $hasDefaultRateLimiter = isset($config['default_options']['rate_limiter']); + $hasDefaultRetryFailed = \is_array($config['default_options']['retry_failed'] ?? null); + + if (!$hasDefaultRateLimiter && !$hasDefaultRetryFailed) { return $config; } foreach ($config['scoped_clients'] as &$scopedConfig) { - if (!isset($scopedConfig['retry_failed']) || true === $scopedConfig['retry_failed']) { - $scopedConfig['retry_failed'] = $config['default_options']['retry_failed']; - continue; + if ($hasDefaultRateLimiter) { + if (!isset($scopedConfig['rate_limiter']) || true === $scopedConfig['rate_limiter']) { + $scopedConfig['rate_limiter'] = $config['default_options']['rate_limiter']; + } elseif (false === $scopedConfig['rate_limiter']) { + $scopedConfig['rate_limiter'] = null; + } } - if (\is_array($scopedConfig['retry_failed'])) { - $scopedConfig['retry_failed'] += $config['default_options']['retry_failed']; + + if ($hasDefaultRetryFailed) { + if (!isset($scopedConfig['retry_failed']) || true === $scopedConfig['retry_failed']) { + $scopedConfig['retry_failed'] = $config['default_options']['retry_failed']; + } elseif (\is_array($scopedConfig['retry_failed'])) { + $scopedConfig['retry_failed'] += $config['default_options']['retry_failed']; + } } } @@ -1845,6 +1863,10 @@ private function addHttpClientSection(ArrayNodeDefinition $rootNode, callable $e ->normalizeKeys(false) ->variablePrototype()->end() ->end() + ->scalarNode('rate_limiter') + ->defaultNull() + ->info('Rate limiter name to use for throttling requests') + ->end() ->append($this->createHttpClientRetrySection()) ->end() ->end() @@ -1993,6 +2015,10 @@ private function addHttpClientSection(ArrayNodeDefinition $rootNode, callable $e ->normalizeKeys(false) ->variablePrototype()->end() ->end() + ->scalarNode('rate_limiter') + ->defaultNull() + ->info('Rate limiter name to use for throttling requests') + ->end() ->append($this->createHttpClientRetrySection()) ->end() ->end() diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index 933c141e10fed..23f000f9026a9 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -86,6 +86,7 @@ use Symfony\Component\HttpClient\Retry\GenericRetryStrategy; use Symfony\Component\HttpClient\RetryableHttpClient; use Symfony\Component\HttpClient\ScopingHttpClient; +use Symfony\Component\HttpClient\ThrottlingHttpClient; use Symfony\Component\HttpClient\UriTemplateHttpClient; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Attribute\AsController; @@ -348,6 +349,7 @@ public function load(array $configs, ContainerBuilder $container): void } if ($this->readConfigEnabled('http_client', $container, $config['http_client'])) { + $this->readConfigEnabled('rate_limiter', $container, $config['rate_limiter']); // makes sure that isInitializedConfigEnabled() will work $this->registerHttpClientConfiguration($config['http_client'], $container, $loader); } @@ -1780,6 +1782,7 @@ private function registerSecretsConfiguration(array $config, ContainerBuilder $c if (!$this->readConfigEnabled('secrets', $container, $config)) { $container->removeDefinition('console.command.secrets_set'); $container->removeDefinition('console.command.secrets_list'); + $container->removeDefinition('console.command.secrets_reveal'); $container->removeDefinition('console.command.secrets_remove'); $container->removeDefinition('console.command.secrets_generate_key'); $container->removeDefinition('console.command.secrets_decrypt_to_local'); @@ -2434,6 +2437,8 @@ private function registerHttpClientConfiguration(array $config, ContainerBuilder $loader->load('http_client.php'); $options = $config['default_options'] ?? []; + $rateLimiter = $options['rate_limiter'] ?? null; + unset($options['rate_limiter']); $retryOptions = $options['retry_failed'] ?? ['enabled' => false]; unset($options['retry_failed']); $defaultUriTemplateVars = $options['vars'] ?? []; @@ -2455,6 +2460,10 @@ private function registerHttpClientConfiguration(array $config, ContainerBuilder $container->removeAlias(HttpClient::class); } + if (null !== $rateLimiter) { + $this->registerThrottlingHttpClient($rateLimiter, 'http_client', $container); + } + if ($this->readConfigEnabled('http_client.retry_failed', $container, $retryOptions)) { $this->registerRetryableHttpClient($retryOptions, 'http_client', $container); } @@ -2476,6 +2485,8 @@ private function registerHttpClientConfiguration(array $config, ContainerBuilder $scope = $scopeConfig['scope'] ?? null; unset($scopeConfig['scope']); + $rateLimiter = $scopeConfig['rate_limiter'] ?? null; + unset($scopeConfig['rate_limiter']); $retryOptions = $scopeConfig['retry_failed'] ?? ['enabled' => false]; unset($scopeConfig['retry_failed']); @@ -2495,6 +2506,10 @@ private function registerHttpClientConfiguration(array $config, ContainerBuilder ; } + if (null !== $rateLimiter) { + $this->registerThrottlingHttpClient($rateLimiter, $name, $container); + } + if ($this->readConfigEnabled('http_client.scoped_clients.'.$name.'.retry_failed', $container, $retryOptions)) { $this->registerRetryableHttpClient($retryOptions, $name, $container); } @@ -2532,6 +2547,25 @@ private function registerHttpClientConfiguration(array $config, ContainerBuilder } } + private function registerThrottlingHttpClient(string $rateLimiter, string $name, ContainerBuilder $container): void + { + if (!class_exists(ThrottlingHttpClient::class)) { + throw new LogicException('Rate limiter support cannot be enabled as version 7.1+ of the HttpClient component is required.'); + } + + if (!$this->isInitializedConfigEnabled('rate_limiter')) { + throw new LogicException('Rate limiter cannot be used within HttpClient as the RateLimiter component is not enabled.'); + } + + $container->register($name.'.throttling.limiter', LimiterInterface::class) + ->setFactory([new Reference('limiter.'.$rateLimiter), 'create']); + + $container + ->register($name.'.throttling', ThrottlingHttpClient::class) + ->setDecoratedService($name, null, 15) // higher priority than RetryableHttpClient (10) + ->setArguments([new Reference($name.'.throttling.inner'), new Reference($name.'.throttling.limiter')]); + } + private function registerRetryableHttpClient(array $options, string $name, ContainerBuilder $container): void { if (null !== $options['retry_strategy']) { @@ -2598,6 +2632,7 @@ private function registerMailerConfiguration(array $config, ContainerBuilder $co MailerBridge\MailPace\Transport\MailPaceTransportFactory::class => 'mailer.transport_factory.mailpace', MailerBridge\Mailchimp\Transport\MandrillTransportFactory::class => 'mailer.transport_factory.mailchimp', MailerBridge\Postmark\Transport\PostmarkTransportFactory::class => 'mailer.transport_factory.postmark', + MailerBridge\Resend\Transport\ResendTransportFactory::class => 'mailer.transport_factory.resend', MailerBridge\Scaleway\Transport\ScalewayTransportFactory::class => 'mailer.transport_factory.scaleway', MailerBridge\Sendgrid\Transport\SendgridTransportFactory::class => 'mailer.transport_factory.sendgrid', MailerBridge\Amazon\Transport\SesTransportFactory::class => 'mailer.transport_factory.amazon', @@ -2614,9 +2649,11 @@ private function registerMailerConfiguration(array $config, ContainerBuilder $co if ($webhookEnabled) { $webhookRequestParsers = [ MailerBridge\Brevo\Webhook\BrevoRequestParser::class => 'mailer.webhook.request_parser.brevo', + MailerBridge\MailerSend\Webhook\MailerSendRequestParser::class => 'mailer.webhook.request_parser.mailersend', MailerBridge\Mailgun\Webhook\MailgunRequestParser::class => 'mailer.webhook.request_parser.mailgun', MailerBridge\Mailjet\Webhook\MailjetRequestParser::class => 'mailer.webhook.request_parser.mailjet', MailerBridge\Postmark\Webhook\PostmarkRequestParser::class => 'mailer.webhook.request_parser.postmark', + MailerBridge\Resend\Webhook\ResendRequestParser::class => 'mailer.webhook.request_parser.resend', MailerBridge\Sendgrid\Webhook\SendgridRequestParser::class => 'mailer.webhook.request_parser.sendgrid', ]; diff --git a/src/Symfony/Bundle/FrameworkBundle/HttpCache/HttpCache.php b/src/Symfony/Bundle/FrameworkBundle/HttpCache/HttpCache.php index ab8cae6b74d47..38eee7361392c 100644 --- a/src/Symfony/Bundle/FrameworkBundle/HttpCache/HttpCache.php +++ b/src/Symfony/Bundle/FrameworkBundle/HttpCache/HttpCache.php @@ -37,7 +37,7 @@ class HttpCache extends BaseHttpCache /** * @param $cache The cache directory (default used if null) or the storage instance */ - public function __construct(KernelInterface $kernel, string|StoreInterface $cache = null, SurrogateInterface $surrogate = null, array $options = null) + public function __construct(KernelInterface $kernel, string|StoreInterface|null $cache = null, ?SurrogateInterface $surrogate = null, ?array $options = null) { $this->kernel = $kernel; $this->surrogate = $surrogate; @@ -60,7 +60,7 @@ public function __construct(KernelInterface $kernel, string|StoreInterface $cach parent::__construct($kernel, $this->createStore(), $this->createSurrogate(), array_merge($this->options, $this->getOptions())); } - protected function forward(Request $request, bool $catch = false, Response $entry = null): Response + protected function forward(Request $request, bool $catch = false, ?Response $entry = null): Response { $this->getKernel()->boot(); $this->getKernel()->getContainer()->set('cache', $this); diff --git a/src/Symfony/Bundle/FrameworkBundle/KernelBrowser.php b/src/Symfony/Bundle/FrameworkBundle/KernelBrowser.php index 0c13746338258..e06b9a056a727 100644 --- a/src/Symfony/Bundle/FrameworkBundle/KernelBrowser.php +++ b/src/Symfony/Bundle/FrameworkBundle/KernelBrowser.php @@ -34,7 +34,7 @@ class KernelBrowser extends HttpKernelBrowser private bool $profiler = false; private bool $reboot = true; - public function __construct(KernelInterface $kernel, array $server = [], History $history = null, CookieJar $cookieJar = null) + public function __construct(KernelInterface $kernel, array $server = [], ?History $history = null, ?CookieJar $cookieJar = null) { parent::__construct($kernel, $server, $history, $cookieJar); } diff --git a/src/Symfony/Bundle/FrameworkBundle/README.md b/src/Symfony/Bundle/FrameworkBundle/README.md index 14c600facfd71..220d72a8a1386 100644 --- a/src/Symfony/Bundle/FrameworkBundle/README.md +++ b/src/Symfony/Bundle/FrameworkBundle/README.md @@ -17,4 +17,4 @@ Resources [send Pull Requests](https://github.com/symfony/symfony/pulls) in the [main Symfony repository](https://github.com/symfony/symfony) -[3]: https://symfony.com/sponsor +[1]: https://symfony.com/sponsor diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/asset_mapper.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/asset_mapper.php index f41574d3b58da..b7ce65f030345 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/asset_mapper.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/asset_mapper.php @@ -75,6 +75,7 @@ param('kernel.project_dir'), abstract_arg('array of excluded path patterns'), abstract_arg('exclude dot files'), + param('kernel.debug'), ]) ->set('asset_mapper.public_assets_path_resolver', PublicAssetsPathResolver::class) diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/console.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/console.php index 334d20426c68c..b4f7dfcf3ea5e 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/console.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/console.php @@ -33,6 +33,7 @@ use Symfony\Bundle\FrameworkBundle\Command\SecretsGenerateKeysCommand; use Symfony\Bundle\FrameworkBundle\Command\SecretsListCommand; use Symfony\Bundle\FrameworkBundle\Command\SecretsRemoveCommand; +use Symfony\Bundle\FrameworkBundle\Command\SecretsRevealCommand; use Symfony\Bundle\FrameworkBundle\Command\SecretsSetCommand; use Symfony\Bundle\FrameworkBundle\Command\TranslationDebugCommand; use Symfony\Bundle\FrameworkBundle\Command\TranslationUpdateCommand; @@ -355,6 +356,13 @@ ]) ->tag('console.command') + ->set('console.command.secrets_reveal', SecretsRevealCommand::class) + ->args([ + service('secrets.vault'), + service('secrets.local_vault')->ignoreOnInvalid(), + ]) + ->tag('console.command') + ->set('console.command.secrets_decrypt_to_local', SecretsDecryptToLocalCommand::class) ->args([ service('secrets.vault'), diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/mailer_transports.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/mailer_transports.php index f95fc6d640c12..5434b4c56e6b2 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/mailer_transports.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/mailer_transports.php @@ -22,6 +22,7 @@ use Symfony\Component\Mailer\Bridge\Mailjet\Transport\MailjetTransportFactory; use Symfony\Component\Mailer\Bridge\MailPace\Transport\MailPaceTransportFactory; use Symfony\Component\Mailer\Bridge\Postmark\Transport\PostmarkTransportFactory; +use Symfony\Component\Mailer\Bridge\Resend\Transport\ResendTransportFactory; use Symfony\Component\Mailer\Bridge\Scaleway\Transport\ScalewayTransportFactory; use Symfony\Component\Mailer\Bridge\Sendgrid\Transport\SendgridTransportFactory; use Symfony\Component\Mailer\Transport\AbstractTransportFactory; @@ -55,6 +56,7 @@ 'native' => NativeTransportFactory::class, 'null' => NullTransportFactory::class, 'postmark' => PostmarkTransportFactory::class, + 'resend' => ResendTransportFactory::class, 'scaleway' => ScalewayTransportFactory::class, 'sendgrid' => SendgridTransportFactory::class, 'sendmail' => SendmailTransportFactory::class, diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/mailer_webhook.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/mailer_webhook.php index bb487b36c0f21..f9d2b9686ff03 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/mailer_webhook.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/mailer_webhook.php @@ -13,12 +13,16 @@ use Symfony\Component\Mailer\Bridge\Brevo\RemoteEvent\BrevoPayloadConverter; use Symfony\Component\Mailer\Bridge\Brevo\Webhook\BrevoRequestParser; +use Symfony\Component\Mailer\Bridge\MailerSend\RemoteEvent\MailerSendPayloadConverter; +use Symfony\Component\Mailer\Bridge\MailerSend\Webhook\MailerSendRequestParser; use Symfony\Component\Mailer\Bridge\Mailgun\RemoteEvent\MailgunPayloadConverter; use Symfony\Component\Mailer\Bridge\Mailgun\Webhook\MailgunRequestParser; use Symfony\Component\Mailer\Bridge\Mailjet\RemoteEvent\MailjetPayloadConverter; use Symfony\Component\Mailer\Bridge\Mailjet\Webhook\MailjetRequestParser; use Symfony\Component\Mailer\Bridge\Postmark\RemoteEvent\PostmarkPayloadConverter; use Symfony\Component\Mailer\Bridge\Postmark\Webhook\PostmarkRequestParser; +use Symfony\Component\Mailer\Bridge\Resend\RemoteEvent\ResendPayloadConverter; +use Symfony\Component\Mailer\Bridge\Resend\Webhook\ResendRequestParser; use Symfony\Component\Mailer\Bridge\Sendgrid\RemoteEvent\SendgridPayloadConverter; use Symfony\Component\Mailer\Bridge\Sendgrid\Webhook\SendgridRequestParser; @@ -29,6 +33,11 @@ ->args([service('mailer.payload_converter.brevo')]) ->alias(BrevoRequestParser::class, 'mailer.webhook.request_parser.brevo') + ->set('mailer.payload_converter.mailersend', MailerSendPayloadConverter::class) + ->set('mailer.webhook.request_parser.mailersend', MailerSendRequestParser::class) + ->args([service('mailer.payload_converter.mailersend')]) + ->alias(MailerSendRequestParser::class, 'mailer.webhook.request_parser.mailersend') + ->set('mailer.payload_converter.mailgun', MailgunPayloadConverter::class) ->set('mailer.webhook.request_parser.mailgun', MailgunRequestParser::class) ->args([service('mailer.payload_converter.mailgun')]) @@ -44,6 +53,11 @@ ->args([service('mailer.payload_converter.postmark')]) ->alias(PostmarkRequestParser::class, 'mailer.webhook.request_parser.postmark') + ->set('mailer.payload_converter.resend', ResendPayloadConverter::class) + ->set('mailer.webhook.request_parser.resend', ResendRequestParser::class) + ->args([service('mailer.payload_converter.resend')]) + ->alias(ResendRequestParser::class, 'mailer.webhook.request_parser.resend') + ->set('mailer.payload_converter.sendgrid', SendgridPayloadConverter::class) ->set('mailer.webhook.request_parser.sendgrid', SendgridRequestParser::class) ->args([service('mailer.payload_converter.sendgrid')]) 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 6ebcfedbf7ad8..f1169e630caf1 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 @@ -666,6 +666,7 @@ + @@ -696,6 +697,7 @@ + diff --git a/src/Symfony/Bundle/FrameworkBundle/Routing/Attribute/AsRoutingConditionService.php b/src/Symfony/Bundle/FrameworkBundle/Routing/Attribute/AsRoutingConditionService.php index d1f1a5f34a654..5e481d73aa626 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Routing/Attribute/AsRoutingConditionService.php +++ b/src/Symfony/Bundle/FrameworkBundle/Routing/Attribute/AsRoutingConditionService.php @@ -41,8 +41,12 @@ #[\Attribute(\Attribute::TARGET_CLASS)] class AsRoutingConditionService extends AutoconfigureTag { + /** + * @param string|null $alias The alias of the service to use it in routing condition expressions + * @param int $priority Defines a priority that allows the routing condition service to override a service with the same alias + */ public function __construct( - string $alias = null, + ?string $alias = null, int $priority = 0, ) { parent::__construct('routing.condition_service', ['alias' => $alias, 'priority' => $priority]); diff --git a/src/Symfony/Bundle/FrameworkBundle/Routing/DelegatingLoader.php b/src/Symfony/Bundle/FrameworkBundle/Routing/DelegatingLoader.php index df7e9348c3f1c..3239d1094bba5 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Routing/DelegatingLoader.php +++ b/src/Symfony/Bundle/FrameworkBundle/Routing/DelegatingLoader.php @@ -40,7 +40,7 @@ public function __construct(LoaderResolverInterface $resolver, array $defaultOpt parent::__construct($resolver); } - public function load(mixed $resource, string $type = null): RouteCollection + public function load(mixed $resource, ?string $type = null): RouteCollection { if ($this->loading) { // This can happen if a fatal error occurs in parent::load(). diff --git a/src/Symfony/Bundle/FrameworkBundle/Routing/RedirectableCompiledUrlMatcher.php b/src/Symfony/Bundle/FrameworkBundle/Routing/RedirectableCompiledUrlMatcher.php index 538427aae6cf2..609502bcafad2 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Routing/RedirectableCompiledUrlMatcher.php +++ b/src/Symfony/Bundle/FrameworkBundle/Routing/RedirectableCompiledUrlMatcher.php @@ -21,7 +21,7 @@ */ class RedirectableCompiledUrlMatcher extends CompiledUrlMatcher implements RedirectableUrlMatcherInterface { - public function redirect(string $path, string $route, string $scheme = null): array + public function redirect(string $path, string $route, ?string $scheme = null): array { return [ '_controller' => 'Symfony\\Bundle\\FrameworkBundle\\Controller\\RedirectController::urlRedirectAction', diff --git a/src/Symfony/Bundle/FrameworkBundle/Routing/Router.php b/src/Symfony/Bundle/FrameworkBundle/Routing/Router.php index 6712817eba128..4dfb71e747487 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Routing/Router.php +++ b/src/Symfony/Bundle/FrameworkBundle/Routing/Router.php @@ -42,7 +42,7 @@ class Router extends BaseRouter implements WarmableInterface, ServiceSubscriberI /** * @param mixed $resource The main resource to load */ - public function __construct(ContainerInterface $container, mixed $resource, array $options = [], RequestContext $context = null, ContainerInterface $parameters = null, LoggerInterface $logger = null, string $defaultLocale = null) + public function __construct(ContainerInterface $container, mixed $resource, array $options = [], ?RequestContext $context = null, ?ContainerInterface $parameters = null, ?LoggerInterface $logger = null, ?string $defaultLocale = null) { $this->container = $container; $this->resource = $resource; @@ -82,7 +82,7 @@ public function getRouteCollection(): RouteCollection return $this->collection; } - public function warmUp(string $cacheDir, string $buildDir = null): array + public function warmUp(string $cacheDir, ?string $buildDir = null): array { if (!$buildDir) { return []; diff --git a/src/Symfony/Bundle/FrameworkBundle/Secrets/SodiumVault.php b/src/Symfony/Bundle/FrameworkBundle/Secrets/SodiumVault.php index b6bb058b3f170..dcf79869f6cf5 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Secrets/SodiumVault.php +++ b/src/Symfony/Bundle/FrameworkBundle/Secrets/SodiumVault.php @@ -12,6 +12,7 @@ namespace Symfony\Bundle\FrameworkBundle\Secrets; use Symfony\Component\DependencyInjection\EnvVarLoaderInterface; +use Symfony\Component\String\LazyString; use Symfony\Component\VarExporter\VarExporter; /** @@ -30,7 +31,7 @@ class SodiumVault extends AbstractVault implements EnvVarLoaderInterface * @param $decryptionKey A string or a stringable object that defines the private key to use to decrypt the vault * or null to store generated keys in the provided $secretsDir */ - public function __construct(string $secretsDir, #[\SensitiveParameter] string|\Stringable $decryptionKey = null) + public function __construct(string $secretsDir, #[\SensitiveParameter] string|\Stringable|null $decryptionKey = null) { $this->pathPrefix = rtrim(strtr($secretsDir, '/', \DIRECTORY_SEPARATOR), \DIRECTORY_SEPARATOR).\DIRECTORY_SEPARATOR.basename($secretsDir).'.'; $this->decryptionKey = $decryptionKey; @@ -169,7 +170,14 @@ public function list(bool $reveal = false): array public function loadEnvVars(): array { - return $this->list(true); + $envs = []; + $reveal = $this->reveal(...); + + foreach ($this->list() as $name => $value) { + $envs[$name] = LazyString::fromCallable($reveal, $name); + } + + return $envs; } private function loadKeys(): void diff --git a/src/Symfony/Bundle/FrameworkBundle/Test/BrowserKitAssertionsTrait.php b/src/Symfony/Bundle/FrameworkBundle/Test/BrowserKitAssertionsTrait.php index a6d4fed3377a4..125aa45a74c01 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Test/BrowserKitAssertionsTrait.php +++ b/src/Symfony/Bundle/FrameworkBundle/Test/BrowserKitAssertionsTrait.php @@ -43,7 +43,7 @@ public static function assertResponseFormatSame(?string $expectedFormat, string self::assertThatForResponse(new ResponseConstraint\ResponseFormatSame(self::getRequest(), $expectedFormat), $message); } - public static function assertResponseRedirects(string $expectedLocation = null, int $expectedCode = null, string $message = ''): void + public static function assertResponseRedirects(?string $expectedLocation = null, ?int $expectedCode = null, string $message = ''): void { $constraint = new ResponseConstraint\ResponseIsRedirected(); if ($expectedLocation) { @@ -82,17 +82,17 @@ public static function assertResponseHeaderNotSame(string $headerName, string $e self::assertThatForResponse(new LogicalNot(new ResponseConstraint\ResponseHeaderSame($headerName, $expectedValue)), $message); } - public static function assertResponseHasCookie(string $name, string $path = '/', string $domain = null, string $message = ''): void + public static function assertResponseHasCookie(string $name, string $path = '/', ?string $domain = null, string $message = ''): void { self::assertThatForResponse(new ResponseConstraint\ResponseHasCookie($name, $path, $domain), $message); } - public static function assertResponseNotHasCookie(string $name, string $path = '/', string $domain = null, string $message = ''): void + public static function assertResponseNotHasCookie(string $name, string $path = '/', ?string $domain = null, string $message = ''): void { self::assertThatForResponse(new LogicalNot(new ResponseConstraint\ResponseHasCookie($name, $path, $domain)), $message); } - public static function assertResponseCookieValueSame(string $name, string $expectedValue, string $path = '/', string $domain = null, string $message = ''): void + public static function assertResponseCookieValueSame(string $name, string $expectedValue, string $path = '/', ?string $domain = null, string $message = ''): void { self::assertThatForResponse(LogicalAnd::fromConstraints( new ResponseConstraint\ResponseHasCookie($name, $path, $domain), @@ -105,17 +105,17 @@ public static function assertResponseIsUnprocessable(string $message = ''): void self::assertThatForResponse(new ResponseConstraint\ResponseIsUnprocessable(), $message); } - public static function assertBrowserHasCookie(string $name, string $path = '/', string $domain = null, string $message = ''): void + public static function assertBrowserHasCookie(string $name, string $path = '/', ?string $domain = null, string $message = ''): void { self::assertThatForClient(new BrowserKitConstraint\BrowserHasCookie($name, $path, $domain), $message); } - public static function assertBrowserNotHasCookie(string $name, string $path = '/', string $domain = null, string $message = ''): void + public static function assertBrowserNotHasCookie(string $name, string $path = '/', ?string $domain = null, string $message = ''): void { self::assertThatForClient(new LogicalNot(new BrowserKitConstraint\BrowserHasCookie($name, $path, $domain)), $message); } - public static function assertBrowserCookieValueSame(string $name, string $expectedValue, bool $raw = false, string $path = '/', string $domain = null, string $message = ''): void + public static function assertBrowserCookieValueSame(string $name, string $expectedValue, bool $raw = false, string $path = '/', ?string $domain = null, string $message = ''): void { self::assertThatForClient(LogicalAnd::fromConstraints( new BrowserKitConstraint\BrowserHasCookie($name, $path, $domain), @@ -162,7 +162,7 @@ public static function assertThatForClient(Constraint $constraint, string $messa self::assertThat(self::getClient(), $constraint, $message); } - protected static function getClient(AbstractBrowser $newClient = null): ?AbstractBrowser + protected static function getClient(?AbstractBrowser $newClient = null): ?AbstractBrowser { static $client; diff --git a/src/Symfony/Bundle/FrameworkBundle/Test/HttpClientAssertionsTrait.php b/src/Symfony/Bundle/FrameworkBundle/Test/HttpClientAssertionsTrait.php index bed835fa1e14a..20c64608e9dde 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Test/HttpClientAssertionsTrait.php +++ b/src/Symfony/Bundle/FrameworkBundle/Test/HttpClientAssertionsTrait.php @@ -20,7 +20,7 @@ trait HttpClientAssertionsTrait { - public static function assertHttpClientRequest(string $expectedUrl, string $expectedMethod = 'GET', string|array $expectedBody = null, array $expectedHeaders = [], string $httpClientId = 'http_client'): void + public static function assertHttpClientRequest(string $expectedUrl, string $expectedMethod = 'GET', string|array|null $expectedBody = null, array $expectedHeaders = [], string $httpClientId = 'http_client'): void { /** @var KernelBrowser $client */ $client = static::getClient(); diff --git a/src/Symfony/Bundle/FrameworkBundle/Test/MailerAssertionsTrait.php b/src/Symfony/Bundle/FrameworkBundle/Test/MailerAssertionsTrait.php index 83643421ef880..2308c3e2fd1cd 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Test/MailerAssertionsTrait.php +++ b/src/Symfony/Bundle/FrameworkBundle/Test/MailerAssertionsTrait.php @@ -20,12 +20,12 @@ trait MailerAssertionsTrait { - public static function assertEmailCount(int $count, string $transport = null, string $message = ''): void + public static function assertEmailCount(int $count, ?string $transport = null, string $message = ''): void { self::assertThat(self::getMessageMailerEvents(), new MailerConstraint\EmailCount($count, $transport), $message); } - public static function assertQueuedEmailCount(int $count, string $transport = null, string $message = ''): void + public static function assertQueuedEmailCount(int $count, ?string $transport = null, string $message = ''): void { self::assertThat(self::getMessageMailerEvents(), new MailerConstraint\EmailCount($count, $transport, true), $message); } @@ -103,12 +103,12 @@ public static function assertEmailSubjectNotContains(RawMessage $email, string $ /** * @return MessageEvent[] */ - public static function getMailerEvents(string $transport = null): array + public static function getMailerEvents(?string $transport = null): array { return self::getMessageMailerEvents()->getEvents($transport); } - public static function getMailerEvent(int $index = 0, string $transport = null): ?MessageEvent + public static function getMailerEvent(int $index = 0, ?string $transport = null): ?MessageEvent { return self::getMailerEvents($transport)[$index] ?? null; } @@ -116,12 +116,12 @@ public static function getMailerEvent(int $index = 0, string $transport = null): /** * @return RawMessage[] */ - public static function getMailerMessages(string $transport = null): array + public static function getMailerMessages(?string $transport = null): array { return self::getMessageMailerEvents()->getMessages($transport); } - public static function getMailerMessage(int $index = 0, string $transport = null): ?RawMessage + public static function getMailerMessage(int $index = 0, ?string $transport = null): ?RawMessage { return self::getMailerMessages($transport)[$index] ?? null; } diff --git a/src/Symfony/Bundle/FrameworkBundle/Test/NotificationAssertionsTrait.php b/src/Symfony/Bundle/FrameworkBundle/Test/NotificationAssertionsTrait.php index 53d24cb12853b..b68473561eb4d 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Test/NotificationAssertionsTrait.php +++ b/src/Symfony/Bundle/FrameworkBundle/Test/NotificationAssertionsTrait.php @@ -22,12 +22,12 @@ */ trait NotificationAssertionsTrait { - public static function assertNotificationCount(int $count, string $transportName = null, string $message = ''): void + public static function assertNotificationCount(int $count, ?string $transportName = null, string $message = ''): void { self::assertThat(self::getNotificationEvents(), new NotifierConstraint\NotificationCount($count, $transportName), $message); } - public static function assertQueuedNotificationCount(int $count, string $transportName = null, string $message = ''): void + public static function assertQueuedNotificationCount(int $count, ?string $transportName = null, string $message = ''): void { self::assertThat(self::getNotificationEvents(), new NotifierConstraint\NotificationCount($count, $transportName, true), $message); } @@ -52,12 +52,12 @@ public static function assertNotificationSubjectNotContains(MessageInterface $no self::assertThat($notification, new LogicalNot(new NotifierConstraint\NotificationSubjectContains($text)), $message); } - public static function assertNotificationTransportIsEqual(MessageInterface $notification, string $transportName = null, string $message = ''): void + public static function assertNotificationTransportIsEqual(MessageInterface $notification, ?string $transportName = null, string $message = ''): void { self::assertThat($notification, new NotifierConstraint\NotificationTransportIsEqual($transportName), $message); } - public static function assertNotificationTransportIsNotEqual(MessageInterface $notification, string $transportName = null, string $message = ''): void + public static function assertNotificationTransportIsNotEqual(MessageInterface $notification, ?string $transportName = null, string $message = ''): void { self::assertThat($notification, new LogicalNot(new NotifierConstraint\NotificationTransportIsEqual($transportName)), $message); } @@ -65,12 +65,12 @@ public static function assertNotificationTransportIsNotEqual(MessageInterface $n /** * @return MessageEvent[] */ - public static function getNotifierEvents(string $transportName = null): array + public static function getNotifierEvents(?string $transportName = null): array { return self::getNotificationEvents()->getEvents($transportName); } - public static function getNotifierEvent(int $index = 0, string $transportName = null): ?MessageEvent + public static function getNotifierEvent(int $index = 0, ?string $transportName = null): ?MessageEvent { return self::getNotifierEvents($transportName)[$index] ?? null; } @@ -78,12 +78,12 @@ public static function getNotifierEvent(int $index = 0, string $transportName = /** * @return MessageInterface[] */ - public static function getNotifierMessages(string $transportName = null): array + public static function getNotifierMessages(?string $transportName = null): array { return self::getNotificationEvents()->getMessages($transportName); } - public static function getNotifierMessage(int $index = 0, string $transportName = null): ?MessageInterface + public static function getNotifierMessage(int $index = 0, ?string $transportName = null): ?MessageInterface { return self::getNotifierMessages($transportName)[$index] ?? null; } diff --git a/src/Symfony/Bundle/FrameworkBundle/Test/TestBrowserToken.php b/src/Symfony/Bundle/FrameworkBundle/Test/TestBrowserToken.php index 8bf365eb06380..25d71d084a25b 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Test/TestBrowserToken.php +++ b/src/Symfony/Bundle/FrameworkBundle/Test/TestBrowserToken.php @@ -23,7 +23,7 @@ class TestBrowserToken extends AbstractToken { private string $firewallName; - public function __construct(array $roles = [], UserInterface $user = null, string $firewallName = 'main') + public function __construct(array $roles = [], ?UserInterface $user = null, string $firewallName = 'main') { parent::__construct($roles); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/CacheWarmer/RouterCacheWarmerTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/CacheWarmer/RouterCacheWarmerTest.php index 7686b139f28f9..615010a47be53 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/CacheWarmer/RouterCacheWarmerTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/CacheWarmer/RouterCacheWarmerTest.php @@ -12,8 +12,8 @@ namespace Symfony\Bundle\FrameworkBundle\Tests\CacheWarmer; use PHPUnit\Framework\TestCase; -use Psr\Container\ContainerInterface; use Symfony\Bundle\FrameworkBundle\CacheWarmer\RouterCacheWarmer; +use Symfony\Component\DependencyInjection\Container; use Symfony\Component\HttpKernel\CacheWarmer\WarmableInterface; use Symfony\Component\Routing\RouterInterface; @@ -21,11 +21,11 @@ class RouterCacheWarmerTest extends TestCase { public function testWarmUpWithWarmableInterfaceWithBuildDir() { - $containerMock = $this->getMockBuilder(ContainerInterface::class)->onlyMethods(['get', 'has'])->getMock(); + $container = new Container(); $routerMock = $this->getMockBuilder(testRouterInterfaceWithWarmableInterface::class)->onlyMethods(['match', 'generate', 'getContext', 'setContext', 'getRouteCollection', 'warmUp'])->getMock(); - $containerMock->expects($this->any())->method('get')->with('router')->willReturn($routerMock); - $routerCacheWarmer = new RouterCacheWarmer($containerMock); + $container->set('router', $routerMock); + $routerCacheWarmer = new RouterCacheWarmer($container); $routerCacheWarmer->warmUp('/tmp/cache', '/tmp/build'); $routerMock->expects($this->any())->method('warmUp')->with('/tmp/cache', '/tmp/build')->willReturn([]); @@ -34,11 +34,11 @@ public function testWarmUpWithWarmableInterfaceWithBuildDir() public function testWarmUpWithoutWarmableInterfaceWithBuildDir() { - $containerMock = $this->getMockBuilder(ContainerInterface::class)->onlyMethods(['get', 'has'])->getMock(); + $container = new Container(); $routerMock = $this->getMockBuilder(testRouterInterfaceWithoutWarmableInterface::class)->onlyMethods(['match', 'generate', 'getContext', 'setContext', 'getRouteCollection'])->getMock(); - $containerMock->expects($this->any())->method('get')->with('router')->willReturn($routerMock); - $routerCacheWarmer = new RouterCacheWarmer($containerMock); + $container->set('router', $routerMock); + $routerCacheWarmer = new RouterCacheWarmer($container); $this->expectException(\LogicException::class); $this->expectExceptionMessage('cannot be warmed up because it does not implement "Symfony\Component\HttpKernel\CacheWarmer\WarmableInterface"'); $routerCacheWarmer->warmUp('/tmp/cache', '/tmp/build'); @@ -46,11 +46,11 @@ public function testWarmUpWithoutWarmableInterfaceWithBuildDir() public function testWarmUpWithWarmableInterfaceWithoutBuildDir() { - $containerMock = $this->getMockBuilder(ContainerInterface::class)->onlyMethods(['get', 'has'])->getMock(); + $container = new Container(); $routerMock = $this->getMockBuilder(testRouterInterfaceWithWarmableInterface::class)->onlyMethods(['match', 'generate', 'getContext', 'setContext', 'getRouteCollection', 'warmUp'])->getMock(); - $containerMock->expects($this->any())->method('get')->with('router')->willReturn($routerMock); - $routerCacheWarmer = new RouterCacheWarmer($containerMock); + $container->set('router', $routerMock); + $routerCacheWarmer = new RouterCacheWarmer($container); $preload = $routerCacheWarmer->warmUp('/tmp'); $routerMock->expects($this->never())->method('warmUp'); @@ -60,11 +60,11 @@ public function testWarmUpWithWarmableInterfaceWithoutBuildDir() public function testWarmUpWithoutWarmableInterfaceWithoutBuildDir() { - $containerMock = $this->getMockBuilder(ContainerInterface::class)->onlyMethods(['get', 'has'])->getMock(); + $container = new Container(); $routerMock = $this->getMockBuilder(testRouterInterfaceWithoutWarmableInterface::class)->onlyMethods(['match', 'generate', 'getContext', 'setContext', 'getRouteCollection'])->getMock(); - $containerMock->expects($this->any())->method('get')->with('router')->willReturn($routerMock); - $routerCacheWarmer = new RouterCacheWarmer($containerMock); + $container->set('router', $routerMock); + $routerCacheWarmer = new RouterCacheWarmer($container); $preload = $routerCacheWarmer->warmUp('/tmp'); self::assertSame([], $preload); } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Command/CacheClearCommand/Fixture/TestAppKernel.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Command/CacheClearCommand/Fixture/TestAppKernel.php index 7992c4f9d599b..58a40cd58b417 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Command/CacheClearCommand/Fixture/TestAppKernel.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Command/CacheClearCommand/Fixture/TestAppKernel.php @@ -52,7 +52,7 @@ public function isOptional(): bool return false; } - public function warmUp(string $cacheDir, string $buildDir = null): array + public function warmUp(string $cacheDir, ?string $buildDir = null): array { file_put_contents($cacheDir.'/dummy.txt', 'Hello'); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Command/CachePoolClearCommandTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Command/CachePoolClearCommandTest.php index fb73588319cda..3a927f217874d 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Command/CachePoolClearCommandTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Command/CachePoolClearCommandTest.php @@ -17,7 +17,7 @@ use Symfony\Bundle\FrameworkBundle\Console\Application; use Symfony\Bundle\FrameworkBundle\Tests\TestCase; use Symfony\Component\Console\Tester\CommandCompletionTester; -use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\DependencyInjection\Container; use Symfony\Component\HttpKernel\CacheClearer\Psr6CacheClearer; use Symfony\Component\HttpKernel\KernelInterface; @@ -54,13 +54,11 @@ public static function provideCompletionSuggestions(): iterable private function getKernel(): MockObject&KernelInterface { - $container = $this->createMock(ContainerInterface::class); - $kernel = $this->createMock(KernelInterface::class); $kernel ->expects($this->any()) ->method('getContainer') - ->willReturn($container); + ->willReturn(new Container()); $kernel ->expects($this->once()) diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Command/CachePoolDeleteCommandTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Command/CachePoolDeleteCommandTest.php index caa7eb550f543..3db39e12173e6 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Command/CachePoolDeleteCommandTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Command/CachePoolDeleteCommandTest.php @@ -18,7 +18,7 @@ use Symfony\Bundle\FrameworkBundle\Tests\TestCase; use Symfony\Component\Console\Tester\CommandCompletionTester; use Symfony\Component\Console\Tester\CommandTester; -use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\DependencyInjection\Container; use Symfony\Component\HttpKernel\CacheClearer\Psr6CacheClearer; use Symfony\Component\HttpKernel\KernelInterface; @@ -108,13 +108,11 @@ public static function provideCompletionSuggestions(): iterable private function getKernel(): MockObject&KernelInterface { - $container = $this->createMock(ContainerInterface::class); - $kernel = $this->createMock(KernelInterface::class); $kernel ->expects($this->any()) ->method('getContainer') - ->willReturn($container); + ->willReturn(new Container()); $kernel ->expects($this->once()) diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Command/CachePruneCommandTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Command/CachePruneCommandTest.php index 54467f1efe879..a2d0ad7fef8f6 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Command/CachePruneCommandTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Command/CachePruneCommandTest.php @@ -18,7 +18,7 @@ use Symfony\Component\Cache\PruneableInterface; use Symfony\Component\Console\Tester\CommandTester; use Symfony\Component\DependencyInjection\Argument\RewindableGenerator; -use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\DependencyInjection\Container; use Symfony\Component\HttpKernel\KernelInterface; class CachePruneCommandTest extends TestCase @@ -50,13 +50,11 @@ private function getEmptyRewindableGenerator(): RewindableGenerator private function getKernel(): MockObject&KernelInterface { - $container = $this->createMock(ContainerInterface::class); - $kernel = $this->createMock(KernelInterface::class); $kernel ->expects($this->any()) ->method('getContainer') - ->willReturn($container); + ->willReturn(new Container()); $kernel ->expects($this->once()) diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Command/RouterMatchCommandTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Command/RouterMatchCommandTest.php index 7dab41991b1b1..b6b6771f928ab 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Command/RouterMatchCommandTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Command/RouterMatchCommandTest.php @@ -16,7 +16,7 @@ use Symfony\Bundle\FrameworkBundle\Command\RouterMatchCommand; use Symfony\Bundle\FrameworkBundle\Console\Application; use Symfony\Component\Console\Tester\CommandTester; -use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\DependencyInjection\Container; use Symfony\Component\HttpKernel\KernelInterface; use Symfony\Component\Routing\RequestContext; use Symfony\Component\Routing\Route; @@ -72,24 +72,11 @@ private function getRouter() private function getKernel() { - $container = $this->createMock(ContainerInterface::class); - $container - ->expects($this->atLeastOnce()) - ->method('has') - ->willReturnCallback(fn ($id) => 'console.command_loader' !== $id) - ; - $container - ->expects($this->any()) - ->method('get') - ->with('router') - ->willReturn($this->getRouter()) - ; - $kernel = $this->createMock(KernelInterface::class); $kernel ->expects($this->any()) ->method('getContainer') - ->willReturn($container) + ->willReturn(new Container()) ; $kernel ->expects($this->once()) diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Command/SecretsRevealCommandTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Command/SecretsRevealCommandTest.php new file mode 100644 index 0000000000000..94643db2c92c5 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Command/SecretsRevealCommandTest.php @@ -0,0 +1,86 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Tests\Command; + +use PHPUnit\Framework\TestCase; +use Symfony\Bundle\FrameworkBundle\Command\SecretsRevealCommand; +use Symfony\Bundle\FrameworkBundle\Secrets\AbstractVault; +use Symfony\Bundle\FrameworkBundle\Secrets\DotenvVault; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Tester\CommandTester; + +class SecretsRevealCommandTest extends TestCase +{ + public function testExecute() + { + $vault = $this->createMock(AbstractVault::class); + $vault->method('list')->willReturn(['secretKey' => 'secretValue']); + + $command = new SecretsRevealCommand($vault); + + $tester = new CommandTester($command); + $this->assertSame(Command::SUCCESS, $tester->execute(['name' => 'secretKey'])); + + $this->assertEquals('secretValue', trim($tester->getDisplay(true))); + } + + public function testInvalidName() + { + $vault = $this->createMock(AbstractVault::class); + $vault->method('list')->willReturn(['secretKey' => 'secretValue']); + + $command = new SecretsRevealCommand($vault); + + $tester = new CommandTester($command); + $this->assertSame(Command::INVALID, $tester->execute(['name' => 'undefinedKey'])); + + $this->assertStringContainsString('The secret "undefinedKey" does not exist.', trim($tester->getDisplay(true))); + } + + /** + * @backupGlobals enabled + */ + public function testLocalVaultOverride() + { + $vault = $this->createMock(AbstractVault::class); + $vault->method('list')->willReturn(['secretKey' => 'secretValue']); + + $_ENV = ['secretKey' => 'newSecretValue']; + $localVault = new DotenvVault('/not/a/path'); + + $command = new SecretsRevealCommand($vault, $localVault); + + $tester = new CommandTester($command); + $this->assertSame(Command::SUCCESS, $tester->execute(['name' => 'secretKey'])); + + $this->assertEquals('newSecretValue', trim($tester->getDisplay(true))); + } + + /** + * @backupGlobals enabled + */ + public function testOnlyLocalVaultContainsName() + { + $vault = $this->createMock(AbstractVault::class); + $vault->method('list')->willReturn(['otherKey' => 'secretValue']); + + $_ENV = ['secretKey' => 'secretValue']; + $localVault = new DotenvVault('/not/a/path'); + + $command = new SecretsRevealCommand($vault, $localVault); + + $tester = new CommandTester($command); + $this->assertSame(Command::SUCCESS, $tester->execute(['name' => 'secretKey'])); + + $this->assertEquals('secretValue', trim($tester->getDisplay(true))); + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Command/TranslationDebugCommandTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Command/TranslationDebugCommandTest.php index 251d2fa6ad8f0..dcff845a31cfa 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Command/TranslationDebugCommandTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Command/TranslationDebugCommandTest.php @@ -160,12 +160,12 @@ protected function tearDown(): void $this->fs->remove($this->translationDir); } - private function createCommandTester(array $extractedMessages = [], array $loadedMessages = [], KernelInterface $kernel = null, array $transPaths = [], array $codePaths = []): CommandTester + private function createCommandTester(array $extractedMessages = [], array $loadedMessages = [], ?KernelInterface $kernel = null, array $transPaths = [], array $codePaths = []): CommandTester { return new CommandTester($this->createCommand($extractedMessages, $loadedMessages, $kernel, $transPaths, $codePaths)); } - private function createCommand(array $extractedMessages = [], array $loadedMessages = [], KernelInterface $kernel = null, array $transPaths = [], array $codePaths = [], ExtractorInterface $extractor = null, array $bundles = [], array $enabledLocales = []): TranslationDebugCommand + private function createCommand(array $extractedMessages = [], array $loadedMessages = [], ?KernelInterface $kernel = null, array $transPaths = [], array $codePaths = [], ?ExtractorInterface $extractor = null, array $bundles = [], array $enabledLocales = []): TranslationDebugCommand { $translator = $this->createMock(Translator::class); $translator diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Command/TranslationUpdateCommandCompletionTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Command/TranslationUpdateCommandCompletionTest.php index ee80e1932a56e..1b11a6111d0b3 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Command/TranslationUpdateCommandCompletionTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Command/TranslationUpdateCommandCompletionTest.php @@ -67,7 +67,7 @@ protected function tearDown(): void $this->fs->remove($this->translationDir); } - private function createCommandCompletionTester($extractedMessages = [], $loadedMessages = [], KernelInterface $kernel = null, array $transPaths = [], array $codePaths = []): CommandCompletionTester + private function createCommandCompletionTester($extractedMessages = [], $loadedMessages = [], ?KernelInterface $kernel = null, array $transPaths = [], array $codePaths = []): CommandCompletionTester { $translator = $this->createMock(Translator::class); $translator diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Command/TranslationUpdateCommandTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Command/TranslationUpdateCommandTest.php index 789f68376f551..d9f142e8f8aa0 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Command/TranslationUpdateCommandTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Command/TranslationUpdateCommandTest.php @@ -184,7 +184,7 @@ protected function tearDown(): void $this->fs->remove($this->translationDir); } - private function createCommandTester($extractedMessages = [], $loadedMessages = [], KernelInterface $kernel = null, array $transPaths = [], array $codePaths = []): CommandTester + private function createCommandTester($extractedMessages = [], $loadedMessages = [], ?KernelInterface $kernel = null, array $transPaths = [], array $codePaths = []): CommandTester { $translator = $this->createMock(Translator::class); $translator diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Console/ApplicationTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Console/ApplicationTest.php index 42691c75b0890..9afb5a2fd85f6 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Console/ApplicationTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Console/ApplicationTest.php @@ -22,11 +22,11 @@ use Symfony\Component\Console\Output\NullOutput; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Tester\ApplicationTester; +use Symfony\Component\DependencyInjection\Container; use Symfony\Component\DependencyInjection\ContainerBuilder; -use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; use Symfony\Component\EventDispatcher\EventDispatcher; use Symfony\Component\EventDispatcher\EventDispatcherInterface; -use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\HttpKernel\Bundle\Bundle; use Symfony\Component\HttpKernel\Bundle\BundleInterface; use Symfony\Component\HttpKernel\KernelInterface; @@ -241,7 +241,10 @@ private function createEventForSuggestingPackages(string $command, array $altern private function getKernel(array $bundles, $useDispatcher = false) { - $container = $this->createMock(ContainerInterface::class); + $container = new Container(new ParameterBag([ + 'console.command.ids' => [], + 'console.lazy_command.ids' => [], + ])); if ($useDispatcher) { $dispatcher = $this->createMock(EventDispatcherInterface::class); @@ -250,45 +253,9 @@ private function getKernel(array $bundles, $useDispatcher = false) ->method('dispatch') ; - $container->expects($this->atLeastOnce()) - ->method('get') - ->willReturnMap([ - ['.virtual_request_stack', 2, new RequestStack()], - ['event_dispatcher', 1, $dispatcher], - ]) - ; + $container->set('event_dispatcher', $dispatcher); } - $container - ->expects($this->exactly(2)) - ->method('hasParameter') - ->willReturnCallback(function (...$args) { - static $series = [ - ['console.command.ids'], - ['console.lazy_command.ids'], - ]; - - $this->assertSame(array_shift($series), $args); - - return true; - }) - ; - - $container - ->expects($this->exactly(2)) - ->method('getParameter') - ->willReturnCallback(function (...$args) { - static $series = [ - ['console.lazy_command.ids'], - ['console.command.ids'], - ]; - - $this->assertSame(array_shift($series), $args); - - return []; - }) - ; - $kernel = $this->createMock(KernelInterface::class); $kernel->expects($this->once())->method('boot'); $kernel diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/ControllerResolverTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/ControllerResolverTest.php index 0c51924a4aed0..7c7398fd32331 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/ControllerResolverTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/ControllerResolverTest.php @@ -16,7 +16,6 @@ use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Bundle\FrameworkBundle\Controller\ControllerResolver; use Symfony\Component\DependencyInjection\Container; -use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Tests\Controller\ContainerControllerResolverTest; @@ -104,10 +103,10 @@ class_exists(AbstractControllerTest::class); $this->assertSame($controllerContainer, $controller->getContainer()); } - protected function createControllerResolver(LoggerInterface $logger = null, Psr11ContainerInterface $container = null) + protected function createControllerResolver(?LoggerInterface $logger = null, ?Psr11ContainerInterface $container = null) { if (!$container) { - $container = $this->createMockContainer(); + $container = new Container(); } return new ControllerResolver($container, $logger); @@ -117,11 +116,6 @@ protected function createMockParser() { return $this->createMock(ControllerNameParser::class); } - - protected function createMockContainer() - { - return $this->createMock(ContainerInterface::class); - } } class DummyController extends AbstractController diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/ProfilerPassTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/ProfilerPassTest.php index 46982578227ef..1b699d4d15069 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/ProfilerPassTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/ProfilerPassTest.php @@ -67,7 +67,7 @@ public function testValidCollector() public static function provideValidCollectorWithTemplateUsingAutoconfigure(): \Generator { yield [new class() implements TemplateAwareDataCollectorInterface { - public function collect(Request $request, Response $response, \Throwable $exception = null): void + public function collect(Request $request, Response $response, ?\Throwable $exception = null): void { } @@ -87,7 +87,7 @@ public static function getTemplate(): string }]; yield [new class() extends AbstractDataCollector { - public function collect(Request $request, Response $response, \Throwable $exception = null): void + public function collect(Request $request, Response $response, ?\Throwable $exception = null): void { } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php index 5a4005bbf2722..b32d8681b43b3 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php @@ -144,6 +144,42 @@ public function testAssetMapperCanBeEnabled() $this->assertEquals($defaultConfig, $config['asset_mapper']); } + /** + * @dataProvider provideImportmapPolyfillTests + */ + public function testAssetMapperPolyfillValue(mixed $polyfillValue, bool $isValid, mixed $expected) + { + $processor = new Processor(); + $configuration = new Configuration(true); + + if (!$isValid) { + $this->expectException(InvalidConfigurationException::class); + $this->expectExceptionMessage($expected); + } + + $config = $processor->processConfiguration($configuration, [[ + 'http_method_override' => false, + 'handle_all_throwables' => true, + 'php_errors' => ['log' => true], + 'asset_mapper' => null === $polyfillValue ? [] : [ + 'importmap_polyfill' => $polyfillValue, + ], + ]]); + + if ($isValid) { + $this->assertEquals($expected, $config['asset_mapper']['importmap_polyfill']); + } + } + + public static function provideImportmapPolyfillTests() + { + yield [true, false, 'Must be either an importmap name or false.']; + yield [null, true, 'es-module-shims']; + yield ['es-module-shims', true, 'es-module-shims']; + yield ['foo', true, 'foo']; + yield [false, true, false]; + } + /** * @dataProvider provideValidAssetsPackageNameConfigurationTests */ @@ -531,6 +567,46 @@ public function testEnabledLockNeedsResources() ]); } + public function testScopedHttpClientsInheritRateLimiterAndRetryFailedConfiguration() + { + $processor = new Processor(); + $configuration = new Configuration(true); + + $config = $processor->processConfiguration($configuration, [[ + 'http_client' => [ + 'default_options' => ['rate_limiter' => 'default_limiter', 'retry_failed' => ['max_retries' => 77]], + 'scoped_clients' => [ + 'foo' => ['base_uri' => 'http://example.com'], + 'bar' => ['base_uri' => 'http://example.com', 'rate_limiter' => true, 'retry_failed' => true], + 'baz' => ['base_uri' => 'http://example.com', 'rate_limiter' => false, 'retry_failed' => false], + 'qux' => ['base_uri' => 'http://example.com', 'rate_limiter' => 'foo_limiter', 'retry_failed' => ['max_retries' => 88, 'delay' => 999]], + ], + ], + ]]); + + $scopedClients = $config['http_client']['scoped_clients']; + + $this->assertSame('default_limiter', $scopedClients['foo']['rate_limiter']); + $this->assertTrue($scopedClients['foo']['retry_failed']['enabled']); + $this->assertSame(77, $scopedClients['foo']['retry_failed']['max_retries']); + $this->assertSame(1000, $scopedClients['foo']['retry_failed']['delay']); + + $this->assertSame('default_limiter', $scopedClients['bar']['rate_limiter']); + $this->assertTrue($scopedClients['bar']['retry_failed']['enabled']); + $this->assertSame(77, $scopedClients['bar']['retry_failed']['max_retries']); + $this->assertSame(1000, $scopedClients['bar']['retry_failed']['delay']); + + $this->assertNull($scopedClients['baz']['rate_limiter']); + $this->assertFalse($scopedClients['baz']['retry_failed']['enabled']); + $this->assertSame(3, $scopedClients['baz']['retry_failed']['max_retries']); + $this->assertSame(1000, $scopedClients['baz']['retry_failed']['delay']); + + $this->assertSame('foo_limiter', $scopedClients['qux']['rate_limiter']); + $this->assertTrue($scopedClients['qux']['retry_failed']['enabled']); + $this->assertSame(88, $scopedClients['qux']['retry_failed']['max_retries']); + $this->assertSame(999, $scopedClients['qux']['retry_failed']['delay']); + } + protected static function getBundleDefaultConfig() { return [ diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/http_client_rate_limiter.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/http_client_rate_limiter.php new file mode 100644 index 0000000000000..c8256d91348d6 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/http_client_rate_limiter.php @@ -0,0 +1,27 @@ +loadFromExtension('framework', [ + 'annotations' => false, + 'http_method_override' => false, + 'handle_all_throwables' => true, + 'php_errors' => ['log' => true], + 'rate_limiter' => [ + 'foo_limiter' => [ + 'lock_factory' => null, + 'policy' => 'token_bucket', + 'limit' => 10, + 'rate' => ['interval' => '5 seconds', 'amount' => 10], + ], + ], + 'http_client' => [ + 'default_options' => [ + 'rate_limiter' => 'default_limiter', + ], + 'scoped_clients' => [ + 'foo' => [ + 'base_uri' => 'http://example.com', + 'rate_limiter' => 'foo_limiter', + ], + ], + ], +]); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/http_client_rate_limiter.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/http_client_rate_limiter.xml new file mode 100644 index 0000000000000..8c9dbcdad40a5 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/http_client_rate_limiter.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/http_client_rate_limiter.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/http_client_rate_limiter.yml new file mode 100644 index 0000000000000..6376192b76182 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/http_client_rate_limiter.yml @@ -0,0 +1,19 @@ +framework: + annotations: false + http_method_override: false + handle_all_throwables: true + php_errors: + log: true + rate_limiter: + foo_limiter: + lock_factory: null + policy: token_bucket + limit: 10 + rate: { interval: '5 seconds', amount: 10 } + http_client: + default_options: + rate_limiter: default_limiter + scoped_clients: + foo: + base_uri: http://example.com + rate_limiter: foo_limiter diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTestCase.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTestCase.php index 8c4711c641c91..7bbbaf2d24cf5 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTestCase.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTestCase.php @@ -52,6 +52,7 @@ use Symfony\Component\HttpClient\MockHttpClient; use Symfony\Component\HttpClient\RetryableHttpClient; use Symfony\Component\HttpClient\ScopingHttpClient; +use Symfony\Component\HttpClient\ThrottlingHttpClient; use Symfony\Component\HttpFoundation\IpUtils; use Symfony\Component\HttpKernel\DependencyInjection\LoggerPass; use Symfony\Component\HttpKernel\Fragment\FragmentUriGeneratorInterface; @@ -1936,9 +1937,6 @@ public function testHttpClientOverrideDefaultOptions() public function testHttpClientRetry() { - if (!class_exists(RetryableHttpClient::class)) { - $this->expectException(LogicException::class); - } $container = $this->createContainerFromFile('http_client_retry'); $this->assertSame([429, 500 => ['GET', 'HEAD']], $container->getDefinition('http_client.retry_strategy')->getArgument(0)); @@ -1996,6 +1994,35 @@ public function testHttpClientFullDefaultOptions() $this->assertSame(['foo' => ['bar' => 'baz']], $defaultOptions['extra']); } + public function testHttpClientRateLimiter() + { + if (!class_exists(ThrottlingHttpClient::class)) { + $this->expectException(LogicException::class); + } + + $container = $this->createContainerFromFile('http_client_rate_limiter'); + + $this->assertTrue($container->hasDefinition('http_client.throttling')); + $definition = $container->getDefinition('http_client.throttling'); + $this->assertSame(ThrottlingHttpClient::class, $definition->getClass()); + $this->assertSame('http_client', $definition->getDecoratedService()[0]); + $this->assertCount(2, $arguments = $definition->getArguments()); + $this->assertInstanceOf(Reference::class, $arguments[0]); + $this->assertSame('http_client.throttling.inner', (string) $arguments[0]); + $this->assertInstanceOf(Reference::class, $arguments[1]); + $this->assertSame('http_client.throttling.limiter', (string) $arguments[1]); + + $this->assertTrue($container->hasDefinition('foo.throttling')); + $definition = $container->getDefinition('foo.throttling'); + $this->assertSame(ThrottlingHttpClient::class, $definition->getClass()); + $this->assertSame('foo', $definition->getDecoratedService()[0]); + $this->assertCount(2, $arguments = $definition->getArguments()); + $this->assertInstanceOf(Reference::class, $arguments[0]); + $this->assertSame('foo.throttling.inner', (string) $arguments[0]); + $this->assertInstanceOf(Reference::class, $arguments[1]); + $this->assertSame('foo.throttling.limiter', (string) $arguments[1]); + } + public static function provideMailer(): array { return [ @@ -2330,7 +2357,7 @@ protected function createContainer(array $data = []) ], $data))); } - protected function createContainerFromFile(string $file, array $data = [], bool $resetCompilerPasses = true, bool $compile = true, FrameworkExtension $extension = null): ContainerBuilder + protected function createContainerFromFile(string $file, array $data = [], bool $resetCompilerPasses = true, bool $compile = true, ?FrameworkExtension $extension = null): ContainerBuilder { $cacheKey = md5(static::class.$file.serialize($data)); if ($compile && isset(self::$containerCache[$cacheKey])) { diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/Controller/AnnotatedController.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/Controller/AnnotatedController.php index 1fdb31dccebe8..d4e3b0f62f934 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/Controller/AnnotatedController.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/Controller/AnnotatedController.php @@ -18,7 +18,7 @@ class AnnotatedController { #[Route('/null_request', name: 'null_request')] - public function requestDefaultNullAction(Request $request = null): Response + public function requestDefaultNullAction(?Request $request = null): Response { return new Response($request ? $request::class : null); } 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 20386304e2ab2..cf32c88808914 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 @@ -19,7 +19,7 @@ class Configuration implements ConfigurationInterface { private ?CustomConfig $customConfig; - public function __construct(CustomConfig $customConfig = null) + public function __construct(?CustomConfig $customConfig = null) { $this->customConfig = $customConfig; } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/CachePoolClearCommandTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/CachePoolClearCommandTest.php index ab740d804af32..dbd78645d881c 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/CachePoolClearCommandTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/CachePoolClearCommandTest.php @@ -143,7 +143,7 @@ public function testExcludedPool() $this->assertStringContainsString('[OK] Cache was successfully cleared.', $tester->getDisplay()); } - private function createCommandTester(array $poolNames = null) + private function createCommandTester(?array $poolNames = null) { $application = new Application(static::$kernel); $application->add(new CachePoolClearCommand(static::getContainer()->get('cache.global_clearer'), $poolNames)); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/PropertyInfoTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/PropertyInfoTest.php index c61955d37bc20..18cd61b08519c 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/PropertyInfoTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/PropertyInfoTest.php @@ -11,7 +11,8 @@ namespace Symfony\Bundle\FrameworkBundle\Tests\Functional; -use Symfony\Component\PropertyInfo\Type; +use Symfony\Component\PropertyInfo\Type as LegacyType; +use Symfony\Component\TypeInfo\Type; class PropertyInfoTest extends AbstractWebTestCase { @@ -19,7 +20,29 @@ public function testPhpDocPriority() { static::bootKernel(['test_case' => 'Serializer']); - $this->assertEquals([new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_INT))], static::getContainer()->get('property_info')->getTypes('Symfony\Bundle\FrameworkBundle\Tests\Functional\Dummy', 'codes')); + $propertyInfo = static::getContainer()->get('property_info'); + + if (!method_exists($propertyInfo, 'getType')) { + $this->markTestSkipped(); + } + + $this->assertEquals(Type::list(Type::int()), $propertyInfo->getType(Dummy::class, 'codes')); + } + + /** + * @group legacy + */ + public function testPhpDocPriorityLegacy() + { + static::bootKernel(['test_case' => 'Serializer']); + + $propertyInfo = static::getContainer()->get('property_info'); + + if (!method_exists($propertyInfo, 'getTypes')) { + $this->markTestSkipped(); + } + + $this->assertEquals([new LegacyType('array', false, null, true, new LegacyType('int'), new LegacyType('int'))], $propertyInfo->getTypes(Dummy::class, 'codes')); } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/RouterTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/RouterTest.php index 3e185b54c5553..11c0dc7e6e259 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/RouterTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/RouterTest.php @@ -23,6 +23,8 @@ use Symfony\Component\DependencyInjection\Container; use Symfony\Component\DependencyInjection\Exception\ParameterNotFoundException; use Symfony\Component\DependencyInjection\Exception\RuntimeException; +use Symfony\Component\DependencyInjection\ParameterBag\ContainerBag; +use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; use Symfony\Component\Routing\Loader\YamlFileLoader; use Symfony\Component\Routing\Route; use Symfony\Component\Routing\RouteCollection; @@ -406,7 +408,8 @@ public function testExceptionOnNonStringParameter() $routes->add('foo', new Route('/%object%')); $sc = $this->getPsr11ServiceContainer($routes); - $parameters = $this->getParameterBag(['object' => new \stdClass()]); + $parameters = new Container(); + $parameters->set('object', new \stdClass()); $router = new Router($sc, 'foo', [], null, $parameters); @@ -424,19 +427,15 @@ public function testExceptionOnNonStringParameterWithSfContainer() $sc = $this->getServiceContainer($routes); - $pc = $this->createMock(ContainerInterface::class); - $pc - ->expects($this->once()) - ->method('get') - ->willReturn(new \stdClass()) - ; + $pc = new Container(); + $pc->set('object', new \stdClass()); $router = new Router($sc, 'foo', [], null, $pc); $this->expectException(RuntimeException::class); $this->expectExceptionMessage('The container parameter "object", used in the route configuration value "/%object%", must be a string or numeric, but it is of type "stdClass".'); - $router->getRouteCollection()->get('foo'); + $router->getRouteCollection(); } /** @@ -483,7 +482,9 @@ public function testGetRouteCollectionAddsContainerParametersResource() $router = new Router($sc, 'foo', [], null, $parameters); - $router->getRouteCollection(); + $routeCollection = $router->getRouteCollection(); + + $this->assertEquals([new ContainerParametersResource(['locale' => 'en'])], $routeCollection->getResources()); } public function testGetRouteCollectionAddsContainerParametersResourceWithSfContainer() @@ -617,13 +618,8 @@ private function getServiceContainer(RouteCollection $routes): Container ->willReturn($routes) ; - $sc = $this->getMockBuilder(Container::class)->onlyMethods(['get'])->getMock(); - - $sc - ->expects($this->once()) - ->method('get') - ->willReturn($loader) - ; + $sc = new Container(); + $sc->set('routing.loader', $loader); return $sc; } @@ -638,26 +634,14 @@ private function getPsr11ServiceContainer(RouteCollection $routes): ContainerInt ->willReturn($routes) ; - $sc = $this->createMock(ContainerInterface::class); - - $sc - ->expects($this->once()) - ->method('get') - ->willReturn($loader) - ; + $container = new Container(); + $container->set('routing.loader', $loader); - return $sc; + return $container; } private function getParameterBag(array $params = []): ContainerInterface { - $bag = $this->createMock(ContainerInterface::class); - $bag - ->expects($this->any()) - ->method('get') - ->willReturnCallback(fn ($key) => $params[$key] ?? null) - ; - - return $bag; + return new ContainerBag(new Container(new ParameterBag($params))); } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Translation/TranslatorTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Translation/TranslatorTest.php index 1c8efd9b1a984..ff15a6d1d24f3 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Translation/TranslatorTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Translation/TranslatorTest.php @@ -15,7 +15,7 @@ use Symfony\Bundle\FrameworkBundle\Translation\Translator; use Symfony\Component\Config\Resource\DirectoryResource; use Symfony\Component\Config\Resource\FileExistenceResource; -use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\DependencyInjection\Container; use Symfony\Component\Filesystem\Filesystem; use Symfony\Component\Translation\Exception\InvalidArgumentException; use Symfony\Component\Translation\Formatter\MessageFormatter; @@ -117,8 +117,7 @@ public function testLoadResourcesWithoutCaching() public function testGetDefaultLocale() { - $container = $this->createMock(\Psr\Container\ContainerInterface::class); - $translator = new Translator($container, new MessageFormatter(), 'en'); + $translator = new Translator(new Container(), new MessageFormatter(), 'en'); $this->assertSame('en', $translator->getLocale()); } @@ -127,9 +126,8 @@ public function testInvalidOptions() { $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage('The Translator does not support the following options: \'foo\''); - $container = $this->createMock(ContainerInterface::class); - new Translator($container, new MessageFormatter(), 'en', [], ['foo' => 'bar']); + new Translator(new Container(), new MessageFormatter(), 'en', [], ['foo' => 'bar']); } /** @dataProvider getDebugModeAndCacheDirCombinations */ @@ -294,12 +292,9 @@ protected function getLoader() protected function getContainer($loader) { - $container = $this->createMock(ContainerInterface::class); - $container - ->expects($this->any()) - ->method('get') - ->willReturn($loader) - ; + $container = new Container(); + $container->set('loader', $loader); + $container->set('yml', $loader); return $container; } diff --git a/src/Symfony/Bundle/FrameworkBundle/Translation/Translator.php b/src/Symfony/Bundle/FrameworkBundle/Translation/Translator.php index 91a070923b09a..1482f02440b20 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Translation/Translator.php +++ b/src/Symfony/Bundle/FrameworkBundle/Translation/Translator.php @@ -95,7 +95,7 @@ public function __construct(ContainerInterface $container, MessageFormatterInter parent::__construct($defaultLocale, $formatter, $this->options['cache_dir'], $this->options['debug'], $this->options['cache_vary']); } - public function warmUp(string $cacheDir, string $buildDir = null): array + public function warmUp(string $cacheDir, ?string $buildDir = null): array { // skip warmUp when translator doesn't use cache if (null === $this->options['cache_dir']) { @@ -116,7 +116,7 @@ public function warmUp(string $cacheDir, string $buildDir = null): array return []; } - public function addResource(string $format, mixed $resource, string $locale, string $domain = null): void + public function addResource(string $format, mixed $resource, string $locale, ?string $domain = null): void { if ($this->resourceFiles) { $this->addResourceFiles(); diff --git a/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md b/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md index 86de9b80f136d..f704e00d92de1 100644 --- a/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md @@ -10,7 +10,9 @@ CHANGELOG --- * Enabling SecurityBundle and not configuring it is not allowed - * Remove configuration options `enable_authenticator_manager`, `csrf_token_generator` and `require_previous_session` + * Remove the `enable_authenticator_manager` config option + * Remove the `security.firewalls.logout.csrf_token_generator` config option, use `security.firewalls.logout.csrf_token_manager` instead + * Remove the `require_previous_session` config option from authenticators 6.4 --- diff --git a/src/Symfony/Bundle/SecurityBundle/CacheWarmer/ExpressionCacheWarmer.php b/src/Symfony/Bundle/SecurityBundle/CacheWarmer/ExpressionCacheWarmer.php index 756b98423a1f9..5b146871cbe07 100644 --- a/src/Symfony/Bundle/SecurityBundle/CacheWarmer/ExpressionCacheWarmer.php +++ b/src/Symfony/Bundle/SecurityBundle/CacheWarmer/ExpressionCacheWarmer.php @@ -37,7 +37,7 @@ public function isOptional(): bool return true; } - public function warmUp(string $cacheDir, string $buildDir = null): array + public function warmUp(string $cacheDir, ?string $buildDir = null): array { foreach ($this->expressions as $expression) { $this->expressionLanguage->parse($expression, ['token', 'user', 'object', 'subject', 'role_names', 'request', 'trust_resolver']); diff --git a/src/Symfony/Bundle/SecurityBundle/DataCollector/SecurityDataCollector.php b/src/Symfony/Bundle/SecurityBundle/DataCollector/SecurityDataCollector.php index 6ec5b7d04d29d..667bc4f93da04 100644 --- a/src/Symfony/Bundle/SecurityBundle/DataCollector/SecurityDataCollector.php +++ b/src/Symfony/Bundle/SecurityBundle/DataCollector/SecurityDataCollector.php @@ -44,7 +44,7 @@ class SecurityDataCollector extends DataCollector implements LateDataCollectorIn private ?TraceableFirewallListener $firewall; private bool $hasVarDumper; - public function __construct(TokenStorageInterface $tokenStorage = null, RoleHierarchyInterface $roleHierarchy = null, LogoutUrlGenerator $logoutUrlGenerator = null, AccessDecisionManagerInterface $accessDecisionManager = null, FirewallMapInterface $firewallMap = null, TraceableFirewallListener $firewall = null) + public function __construct(?TokenStorageInterface $tokenStorage = null, ?RoleHierarchyInterface $roleHierarchy = null, ?LogoutUrlGenerator $logoutUrlGenerator = null, ?AccessDecisionManagerInterface $accessDecisionManager = null, ?FirewallMapInterface $firewallMap = null, ?TraceableFirewallListener $firewall = null) { $this->tokenStorage = $tokenStorage; $this->roleHierarchy = $roleHierarchy; @@ -55,7 +55,7 @@ public function __construct(TokenStorageInterface $tokenStorage = null, RoleHier $this->hasVarDumper = class_exists(ClassStub::class); } - public function collect(Request $request, Response $response, \Throwable $exception = null): void + public function collect(Request $request, Response $response, ?\Throwable $exception = null): void { if (null === $this->tokenStorage) { $this->data = [ diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/MainConfiguration.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/MainConfiguration.php index d079b2a1ac360..bfd96d7ca089d 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/MainConfiguration.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/MainConfiguration.php @@ -265,6 +265,7 @@ private function addFirewallsSection(ArrayNodeDefinition $rootNode, array $facto ->scalarNode('domain')->defaultNull()->end() ->scalarNode('secure')->defaultFalse()->end() ->scalarNode('samesite')->defaultNull()->end() + ->scalarNode('partitioned')->defaultFalse()->end() ->end() ->end() ->end() diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/AccessToken/CasTokenHandlerFactory.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/AccessToken/CasTokenHandlerFactory.php new file mode 100644 index 0000000000000..a0c2ca047bc40 --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/AccessToken/CasTokenHandlerFactory.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\SecurityBundle\DependencyInjection\Security\AccessToken; + +use Symfony\Component\Config\Definition\Builder\NodeBuilder; +use Symfony\Component\DependencyInjection\ChildDefinition; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\Security\Http\AccessToken\Cas\Cas2Handler; + +class CasTokenHandlerFactory implements TokenHandlerFactoryInterface +{ + public function create(ContainerBuilder $container, string $id, array|string $config): void + { + $container->setDefinition($id, new ChildDefinition('security.access_token_handler.cas')); + + $container + ->register('security.access_token_handler.cas', Cas2Handler::class) + ->setArguments([ + new Reference('request_stack'), + $config['validation_url'], + $config['prefix'], + $config['http_client'] ? new Reference($config['http_client']) : null, + ]); + } + + public function getKey(): string + { + return 'cas'; + } + + public function addConfiguration(NodeBuilder $node): void + { + $node + ->arrayNode($this->getKey()) + ->fixXmlConfig($this->getKey()) + ->children() + ->scalarNode('validation_url') + ->info('CAS server validation URL') + ->isRequired() + ->end() + ->scalarNode('prefix') + ->info('CAS prefix') + ->defaultValue('cas') + ->end() + ->scalarNode('http_client') + ->info('HTTP Client service') + ->defaultNull() + ->end() + ->end() + ->end(); + } +} diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/FormLoginLdapFactory.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/FormLoginLdapFactory.php index 705d079c5d73e..53a778c70afa5 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/FormLoginLdapFactory.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/FormLoginLdapFactory.php @@ -32,7 +32,7 @@ public function addConfiguration(NodeDefinition $node): void $node ->children() ->scalarNode('service')->defaultValue('ldap')->end() - ->scalarNode('dn_string')->defaultValue('{username}')->end() + ->scalarNode('dn_string')->defaultValue('{user_identifier}')->end() ->scalarNode('query_string')->end() ->scalarNode('search_dn')->defaultValue('')->end() ->scalarNode('search_password')->defaultValue('')->end() diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/HttpBasicLdapFactory.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/HttpBasicLdapFactory.php index 3d7946115c433..2889b6f7ebc73 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/HttpBasicLdapFactory.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/HttpBasicLdapFactory.php @@ -77,7 +77,7 @@ public function addConfiguration(NodeDefinition $node): void $node ->children() ->scalarNode('service')->defaultValue('ldap')->end() - ->scalarNode('dn_string')->defaultValue('{username}')->end() + ->scalarNode('dn_string')->defaultValue('{user_identifier}')->end() ->scalarNode('query_string')->end() ->scalarNode('search_dn')->defaultValue('')->end() ->scalarNode('search_password')->defaultValue('')->end() diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/JsonLoginLdapFactory.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/JsonLoginLdapFactory.php index 61266854c8f5e..7e0ceb6bf3ce6 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/JsonLoginLdapFactory.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/JsonLoginLdapFactory.php @@ -29,7 +29,7 @@ public function addConfiguration(NodeDefinition $node): void $node ->children() ->scalarNode('service')->defaultValue('ldap')->end() - ->scalarNode('dn_string')->defaultValue('{username}')->end() + ->scalarNode('dn_string')->defaultValue('{user_identifier}')->end() ->scalarNode('query_string')->end() ->scalarNode('search_dn')->defaultValue('')->end() ->scalarNode('search_password')->defaultValue('')->end() diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/UserProvider/LdapFactory.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/UserProvider/LdapFactory.php index a719b3f0d955e..b8d442fd99251 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/UserProvider/LdapFactory.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/UserProvider/LdapFactory.php @@ -64,7 +64,7 @@ public function addConfiguration(NodeDefinition $node): void ->prototype('scalar')->end() ->end() ->scalarNode('uid_key')->defaultValue('sAMAccountName')->end() - ->scalarNode('filter')->defaultValue('({uid_key}={username})')->end() + ->scalarNode('filter')->defaultValue('({uid_key}={user_identifier})')->end() ->scalarNode('password_attribute')->defaultNull()->end() ->end() ; diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php index d784183dfc875..4f1850ba5ae28 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php @@ -35,6 +35,7 @@ use Symfony\Component\EventDispatcher\EventDispatcher; use Symfony\Component\ExpressionLanguage\Expression; use Symfony\Component\ExpressionLanguage\ExpressionLanguage; +use Symfony\Flex\Command\InstallRecipesCommand; use Symfony\Component\Form\Extension\PasswordHasher\PasswordHasherExtension; use Symfony\Component\HttpFoundation\ChainRequestMatcher; use Symfony\Component\HttpFoundation\RequestMatcher\AttributesRequestMatcher; @@ -91,7 +92,9 @@ public function prepend(ContainerBuilder $container): void public function load(array $configs, ContainerBuilder $container): void { if (!array_filter($configs)) { - throw new InvalidConfigurationException(sprintf('Enabling bundle "%s" and not configuring it is not allowed.', SecurityBundle::class)); + $hint = class_exists(InstallRecipesCommand::class) ? 'Try running "composer symfony:recipes:install symfony/security-bundle".' : 'Please define your settings for the "security" config section.'; + + throw new InvalidConfigurationException('The SecurityBundle is enabled but is not configured. '.$hint); } $mainConfig = $this->getConfiguration($configs, $container); @@ -752,7 +755,7 @@ private function createHasher(array $config): Reference|array $config['algorithm'] = 'native'; $config['native_algorithm'] = \PASSWORD_ARGON2I; } else { - throw new InvalidConfigurationException(sprintf('Algorithm "argon2i" is not available. Either use "%s" or upgrade to PHP 7.2+ instead.', \defined('SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13') ? 'argon2id", "auto' : 'auto')); + throw new InvalidConfigurationException(sprintf('Algorithm "argon2i" is not available; use "%s" instead.', \defined('SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13') ? 'argon2id" or "auto' : 'auto')); } return $this->createHasher($config); @@ -765,7 +768,7 @@ private function createHasher(array $config): Reference|array $config['algorithm'] = 'native'; $config['native_algorithm'] = \PASSWORD_ARGON2ID; } else { - throw new InvalidConfigurationException(sprintf('Algorithm "argon2id" is not available. Either use "%s", upgrade to PHP 7.3+ or use libsodium 1.0.15+ instead.', \defined('PASSWORD_ARGON2I') || $hasSodium ? 'argon2i", "auto' : 'auto')); + throw new InvalidConfigurationException(sprintf('Algorithm "argon2id" is not available; use "%s" or libsodium 1.0.15+ instead.', \defined('PASSWORD_ARGON2I') || $hasSodium ? 'argon2i", "auto' : 'auto')); } return $this->createHasher($config); @@ -917,7 +920,7 @@ private function createExpression(ContainerBuilder $container, string $expressio return $this->expressions[$id] = new Reference($id); } - private function createRequestMatcher(ContainerBuilder $container, string $path = null, string $host = null, int $port = null, array $methods = [], array $ips = null, array $attributes = []): Reference + private function createRequestMatcher(ContainerBuilder $container, ?string $path = null, ?string $host = null, ?int $port = null, array $methods = [], ?array $ips = null, array $attributes = []): Reference { if ($methods) { $methods = array_map('strtoupper', $methods); diff --git a/src/Symfony/Bundle/SecurityBundle/LoginLink/FirewallAwareLoginLinkHandler.php b/src/Symfony/Bundle/SecurityBundle/LoginLink/FirewallAwareLoginLinkHandler.php index ff2ea2ddc9305..2bcbcade0c5f0 100644 --- a/src/Symfony/Bundle/SecurityBundle/LoginLink/FirewallAwareLoginLinkHandler.php +++ b/src/Symfony/Bundle/SecurityBundle/LoginLink/FirewallAwareLoginLinkHandler.php @@ -38,7 +38,7 @@ public function __construct(FirewallMap $firewallMap, ContainerInterface $loginL $this->requestStack = $requestStack; } - public function createLoginLink(UserInterface $user, Request $request = null, int $lifetime = null): LoginLinkDetails + public function createLoginLink(UserInterface $user, ?Request $request = null, ?int $lifetime = null): LoginLinkDetails { return $this->getForFirewall()->createLoginLink($user, $request, $lifetime); } diff --git a/src/Symfony/Bundle/SecurityBundle/Resources/config/schema/security-1.0.xsd b/src/Symfony/Bundle/SecurityBundle/Resources/config/schema/security-1.0.xsd index 5b0a0594111c8..f5b69c7e5615c 100644 --- a/src/Symfony/Bundle/SecurityBundle/Resources/config/schema/security-1.0.xsd +++ b/src/Symfony/Bundle/SecurityBundle/Resources/config/schema/security-1.0.xsd @@ -377,6 +377,7 @@ + diff --git a/src/Symfony/Bundle/SecurityBundle/Security.php b/src/Symfony/Bundle/SecurityBundle/Security.php index 0117101be49e4..c4b505d8981c7 100644 --- a/src/Symfony/Bundle/SecurityBundle/Security.php +++ b/src/Symfony/Bundle/SecurityBundle/Security.php @@ -81,7 +81,7 @@ public function getFirewallConfig(Request $request): ?FirewallConfig * * @return Response|null The authenticator success response if any */ - public function login(UserInterface $user, string $authenticatorName = null, string $firewallName = null, array $badges = []): ?Response + public function login(UserInterface $user, ?string $authenticatorName = null, ?string $firewallName = null, array $badges = []): ?Response { $request = $this->container->get('request_stack')->getCurrentRequest(); if (null === $request) { diff --git a/src/Symfony/Bundle/SecurityBundle/Security/FirewallContext.php b/src/Symfony/Bundle/SecurityBundle/Security/FirewallContext.php index fe0e3704aacf5..c100d3531b2c2 100644 --- a/src/Symfony/Bundle/SecurityBundle/Security/FirewallContext.php +++ b/src/Symfony/Bundle/SecurityBundle/Security/FirewallContext.php @@ -30,7 +30,7 @@ class FirewallContext /** * @param iterable $listeners */ - public function __construct(iterable $listeners, ExceptionListener $exceptionListener = null, LogoutListener $logoutListener = null, FirewallConfig $config = null) + public function __construct(iterable $listeners, ?ExceptionListener $exceptionListener = null, ?LogoutListener $logoutListener = null, ?FirewallConfig $config = null) { $this->listeners = $listeners; $this->exceptionListener = $exceptionListener; diff --git a/src/Symfony/Bundle/SecurityBundle/SecurityBundle.php b/src/Symfony/Bundle/SecurityBundle/SecurityBundle.php index b2e81a7f4b92b..0527adb9f66be 100644 --- a/src/Symfony/Bundle/SecurityBundle/SecurityBundle.php +++ b/src/Symfony/Bundle/SecurityBundle/SecurityBundle.php @@ -23,6 +23,7 @@ use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\RegisterTokenUsageTrackingPass; use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\ReplaceDecoratedRememberMeHandlerPass; use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\SortFirewallListenersPass; +use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\AccessToken\CasTokenHandlerFactory; use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\AccessToken\OidcTokenHandlerFactory; use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\AccessToken\OidcUserInfoTokenHandlerFactory; use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\AccessToken\ServiceTokenHandlerFactory; @@ -78,6 +79,7 @@ public function build(ContainerBuilder $container): void new ServiceTokenHandlerFactory(), new OidcUserInfoTokenHandlerFactory(), new OidcTokenHandlerFactory(), + new CasTokenHandlerFactory(), ])); $extension->addUserProviderFactory(new InMemoryFactory()); diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DataCollector/SecurityDataCollectorTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/DataCollector/SecurityDataCollectorTest.php index df4ba4e9ed9f1..1839080eeffbf 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DataCollector/SecurityDataCollectorTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DataCollector/SecurityDataCollectorTest.php @@ -227,7 +227,7 @@ public function testCollectCollectsDecisionLogWhenStrategyIsAffirmative() $voter2 = new DummyVoter(); $decoratedVoter1 = new TraceableVoter($voter1, new class() implements EventDispatcherInterface { - public function dispatch(object $event, string $eventName = null): object + public function dispatch(object $event, ?string $eventName = null): object { return new \stdClass(); } @@ -302,7 +302,7 @@ public function testCollectCollectsDecisionLogWhenStrategyIsUnanimous() $voter2 = new DummyVoter(); $decoratedVoter1 = new TraceableVoter($voter1, new class() implements EventDispatcherInterface { - public function dispatch(object $event, string $eventName = null): object + public function dispatch(object $event, ?string $eventName = null): object { return new \stdClass(); } diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Compiler/RegisterEntryPointsPassTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Compiler/RegisterEntryPointsPassTest.php index b10b8a810bc7a..d2fb348676bc7 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Compiler/RegisterEntryPointsPassTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Compiler/RegisterEntryPointsPassTest.php @@ -93,7 +93,7 @@ public function onAuthenticationFailure(Request $request, AuthenticationExceptio ], JsonResponse::HTTP_FORBIDDEN); } - public function start(Request $request, AuthenticationException $authException = null): Response + public function start(Request $request, ?AuthenticationException $authException = null): Response { } } diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/MainConfigurationTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/MainConfigurationTest.php index 52a392fe870f7..8d3fed44695d2 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/MainConfigurationTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/MainConfigurationTest.php @@ -141,6 +141,39 @@ public function testLogoutCsrf() } } + public function testLogoutDeleteCookies() + { + $config = [ + 'firewalls' => [ + 'stub' => [ + 'logout' => [ + 'delete_cookies' => [ + 'my_cookie' => [ + 'path' => '/', + 'domain' => 'example.org', + 'secure' => true, + 'samesite' => 'none', + 'partitioned' => true, + ], + ], + ], + ], + ], + ]; + $config = array_merge(static::$minimalConfig, $config); + + $processor = new Processor(); + $configuration = new MainConfiguration([], []); + $processedConfig = $processor->processConfiguration($configuration, [$config]); + $this->assertArrayHasKey('delete_cookies', $processedConfig['firewalls']['stub']['logout']); + $deleteCookies = $processedConfig['firewalls']['stub']['logout']['delete_cookies']; + $this->assertSame('/', $deleteCookies['my_cookie']['path']); + $this->assertSame('example.org', $deleteCookies['my_cookie']['domain']); + $this->assertTrue($deleteCookies['my_cookie']['secure']); + $this->assertSame('none', $deleteCookies['my_cookie']['samesite']); + $this->assertTrue($deleteCookies['my_cookie']['partitioned']); + } + public function testDefaultUserCheckers() { $processor = new Processor(); diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Security/Factory/AccessTokenFactoryTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Security/Factory/AccessTokenFactoryTest.php index e1f55817eee68..f3e12e8190ced 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Security/Factory/AccessTokenFactoryTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Security/Factory/AccessTokenFactoryTest.php @@ -12,6 +12,7 @@ namespace Symfony\Bundle\SecurityBundle\Tests\DependencyInjection\Security\Factory; use PHPUnit\Framework\TestCase; +use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\AccessToken\CasTokenHandlerFactory; use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\AccessToken\OidcTokenHandlerFactory; use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\AccessToken\OidcUserInfoTokenHandlerFactory; use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\AccessToken\ServiceTokenHandlerFactory; @@ -76,6 +77,27 @@ public function testIdTokenHandlerConfiguration() $this->assertTrue($container->hasDefinition('security.access_token_handler.firewall1')); } + public function testCasTokenHandlerConfiguration() + { + $container = new ContainerBuilder(); + $config = [ + 'token_handler' => ['cas' => ['validation_url' => 'https://www.example.com/cas/validate']], + ]; + + $factory = new AccessTokenFactory($this->createTokenHandlerFactories()); + $finalizedConfig = $this->processConfig($config, $factory); + + $factory->createAuthenticator($container, 'firewall1', $finalizedConfig, 'userprovider'); + + $this->assertTrue($container->hasDefinition('security.access_token_handler.cas')); + + $arguments = $container->getDefinition('security.access_token_handler.cas')->getArguments(); + $this->assertSame((string) $arguments[0], 'request_stack'); + $this->assertSame($arguments[1], 'https://www.example.com/cas/validate'); + $this->assertSame($arguments[2], 'cas'); + $this->assertNull($arguments[3]); + } + public function testOidcUserInfoTokenHandlerConfigurationWithExistingClient() { $container = new ContainerBuilder(); @@ -218,6 +240,7 @@ private function createTokenHandlerFactories(): array new ServiceTokenHandlerFactory(), new OidcUserInfoTokenHandlerFactory(), new OidcTokenHandlerFactory(), + new CasTokenHandlerFactory(), ]; } } diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/SecurityExtensionTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/SecurityExtensionTest.php index c62f9407bd1ee..23aa17b9adb57 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/SecurityExtensionTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/SecurityExtensionTest.php @@ -869,11 +869,9 @@ public function testNothingDoneWithEmptyConfiguration() $container->loadFromExtension('security'); $this->expectException(InvalidConfigurationException::class); - $this->expectExceptionMessage('Enabling bundle "Symfony\Bundle\SecurityBundle\SecurityBundle" and not configuring it is not allowed.'); + $this->expectExceptionMessage('The SecurityBundle is enabled but is not configured. Please define your settings for the "security" config section.'); $container->compile(); - - $this->assertFalse($container->has('security.authorization_checker')); } public function testCustomHasherWithMigrateFrom() diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/AccessTokenTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/AccessTokenTest.php index 6cc2b1f0fb150..00c11bf40a211 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/AccessTokenTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/AccessTokenTest.php @@ -17,6 +17,8 @@ use Jose\Component\Signature\JWSBuilder; use Jose\Component\Signature\Serializer\CompactSerializer; use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; +use Symfony\Component\HttpClient\MockHttpClient; +use Symfony\Component\HttpClient\Response\MockResponse; use Symfony\Component\HttpFoundation\Response; class AccessTokenTest extends AbstractWebTestCase @@ -383,4 +385,27 @@ public function testOidcSuccess() $this->assertSame(200, $response->getStatusCode()); $this->assertSame(['message' => 'Welcome @dunglas!'], json_decode($response->getContent(), true)); } + + public function testCasSuccess() + { + $casResponse = new MockResponse(<< + + dunglas + PGTIOU-84678-8a9d + + + BODY + ); + + $client = $this->createClient(['test_case' => 'AccessToken', 'root_config' => 'config_cas.yml']); + $client->getContainer()->set('Symfony\Contracts\HttpClient\HttpClientInterface', new MockHttpClient($casResponse)); + + $client->request('GET', '/foo?ticket=PGTIOU-84678-8a9d', [], [], []); + $response = $client->getResponse(); + + $this->assertInstanceOf(Response::class, $response); + $this->assertSame(200, $response->getStatusCode()); + $this->assertSame(['message' => 'Welcome @dunglas!'], json_decode($response->getContent(), true)); + } } diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/CsrfFormLoginBundle/Resources/views/Login/after_login.html.twig b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/CsrfFormLoginBundle/Resources/views/Login/after_login.html.twig index 9d5035516fa9f..9a9bfbc731397 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/CsrfFormLoginBundle/Resources/views/Login/after_login.html.twig +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/CsrfFormLoginBundle/Resources/views/Login/after_login.html.twig @@ -1,8 +1,8 @@ {% extends "base.html.twig" %} {% block body %} - Hello {{ app.user.userIdentifier }}!

- You're browsing to path "{{ app.request.pathInfo }}".

+ Hello {{ app.user.userIdentifier }}!

+ You're browsing to path "{{ app.request.pathInfo }}".

Log out. Log out. {% endblock %} diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/CsrfFormLoginBundle/Resources/views/Login/login.html.twig b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/CsrfFormLoginBundle/Resources/views/Login/login.html.twig index 47badfedb7967..a21ea7259b1b5 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/CsrfFormLoginBundle/Resources/views/Login/login.html.twig +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/CsrfFormLoginBundle/Resources/views/Login/login.html.twig @@ -6,7 +6,7 @@ {{ form_widget(form) }} {# Note: ensure the submit name does not conflict with the form's name or it may clobber field data #} - + {% endblock %} diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/FirewallEntryPointBundle/Security/EntryPointStub.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/FirewallEntryPointBundle/Security/EntryPointStub.php index 56552b99c7983..16a757260cf27 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/FirewallEntryPointBundle/Security/EntryPointStub.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/FirewallEntryPointBundle/Security/EntryPointStub.php @@ -20,7 +20,7 @@ class EntryPointStub implements AuthenticationEntryPointInterface { public const RESPONSE_TEXT = '2be8e651259189d841a19eecdf37e771e2431741'; - public function start(Request $request, AuthenticationException $authException = null): Response + public function start(Request $request, ?AuthenticationException $authException = null): Response { return new Response(self::RESPONSE_TEXT); } diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/FormLoginBundle/Controller/LoginController.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/FormLoginBundle/Controller/LoginController.php index dd8c1a2d055ef..16e823a03c36b 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/FormLoginBundle/Controller/LoginController.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/FormLoginBundle/Controller/LoginController.php @@ -29,7 +29,7 @@ public function __construct(ContainerInterface $container) $this->container = $container; } - public function loginAction(Request $request, UserInterface $user = null) + public function loginAction(Request $request, ?UserInterface $user = null) { // get the login error if there is one if ($request->attributes->has(SecurityRequestAttributes::AUTHENTICATION_ERROR)) { diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/FormLoginBundle/Resources/views/Localized/login.html.twig b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/FormLoginBundle/Resources/views/Localized/login.html.twig index d147bd1addc64..de0da3bb589c0 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/FormLoginBundle/Resources/views/Localized/login.html.twig +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/FormLoginBundle/Resources/views/Localized/login.html.twig @@ -8,14 +8,14 @@
- + - + - + - +
{% endblock %} diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/FormLoginBundle/Resources/views/Login/after_login.html.twig b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/FormLoginBundle/Resources/views/Login/after_login.html.twig index d48269aeca674..fd51df2a4383f 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/FormLoginBundle/Resources/views/Login/after_login.html.twig +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/FormLoginBundle/Resources/views/Login/after_login.html.twig @@ -1,7 +1,7 @@ {% extends "base.html.twig" %} {% block body %} - Hello {{ user.userIdentifier }}!

+ Hello {{ user.userIdentifier }}!

You're browsing to path "{{ app.request.pathInfo }}". Log out. diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/FormLoginBundle/Resources/views/Login/login.html.twig b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/FormLoginBundle/Resources/views/Login/login.html.twig index 9e41e0223337d..34ea19f2bde62 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/FormLoginBundle/Resources/views/Login/login.html.twig +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/FormLoginBundle/Resources/views/Login/login.html.twig @@ -9,14 +9,14 @@
- + - + - + - +
{% endblock %} diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/AccessToken/config_cas.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/AccessToken/config_cas.yml new file mode 100644 index 0000000000000..2cd2abc566c05 --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/AccessToken/config_cas.yml @@ -0,0 +1,41 @@ +imports: + - { resource: ./../config/framework.yml } + +framework: + http_method_override: false + serializer: ~ + +security: + password_hashers: + Symfony\Component\Security\Core\User\InMemoryUser: plaintext + + providers: + in_memory: + memory: + users: + dunglas: { password: foo, roles: [ROLE_USER] } + + firewalls: + main: + pattern: ^/ + access_token: + token_handler: + cas: + validation_url: 'https://www.example.com/cas/serviceValidate' + http_client: 'Symfony\Contracts\HttpClient\HttpClientInterface' + token_extractors: + - security.access_token_extractor.cas + + access_control: + - { path: ^/foo, roles: ROLE_USER } + +services: + _defaults: + public: true + + security.access_token_extractor.cas: + class: Symfony\Component\Security\Http\AccessToken\QueryAccessTokenExtractor + arguments: + - 'ticket' + + Symfony\Contracts\HttpClient\HttpClientInterface: ~ diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/templates/base.html.twig b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/templates/base.html.twig index 32645815dc359..caf6f6efb6db1 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/templates/base.html.twig +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/templates/base.html.twig @@ -1,7 +1,7 @@ - + {% block title %}Welcome!{% endblock %} {% block stylesheets %}{% endblock %} diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/LoginLink/FirewallAwareLoginLinkHandlerTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/LoginLink/FirewallAwareLoginLinkHandlerTest.php index 92703f41ec3c7..eff35a8304749 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/LoginLink/FirewallAwareLoginLinkHandlerTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/LoginLink/FirewallAwareLoginLinkHandlerTest.php @@ -12,10 +12,10 @@ namespace Symfony\Bundle\SecurityBundle\Tests\LoginLink; use PHPUnit\Framework\TestCase; -use Psr\Container\ContainerInterface; use Symfony\Bundle\SecurityBundle\LoginLink\FirewallAwareLoginLinkHandler; use Symfony\Bundle\SecurityBundle\Security\FirewallConfig; use Symfony\Bundle\SecurityBundle\Security\FirewallMap; +use Symfony\Component\DependencyInjection\Container; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\Security\Core\User\UserInterface; @@ -66,13 +66,11 @@ private function createFirewallMap(string $firewallName) private function createLocator(array $linkers) { - $locator = $this->createMock(ContainerInterface::class); - $locator->expects($this->any()) - ->method('has') - ->willReturnCallback(fn ($firewallName) => isset($linkers[$firewallName])); - $locator->expects($this->any()) - ->method('get') - ->willReturnCallback(fn ($firewallName) => $linkers[$firewallName]); + $locator = new Container(); + + foreach ($linkers as $class => $service) { + $locator->set($class, $service); + } return $locator; } diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/SecurityTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/SecurityTest.php index 844e4b387aac8..4be14111682bb 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/SecurityTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/SecurityTest.php @@ -16,6 +16,7 @@ use Symfony\Bundle\SecurityBundle\Security; use Symfony\Bundle\SecurityBundle\Security\FirewallConfig; use Symfony\Bundle\SecurityBundle\Security\FirewallMap; +use Symfony\Component\DependencyInjection\Container; use Symfony\Component\DependencyInjection\ServiceLocator; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\RequestStack; @@ -135,17 +136,11 @@ public function testLogin() $user = $this->createMock(UserInterface::class); $userChecker = $this->createMock(UserCheckerInterface::class); - $container = $this->createMock(ContainerInterface::class); - $container - ->expects($this->atLeastOnce()) - ->method('get') - ->willReturnMap([ - ['request_stack', $requestStack], - ['security.firewall.map', $firewallMap], - ['security.authenticator.managers_locator', $this->createContainer('main', $userAuthenticator)], - ['security.user_checker', $userChecker], - ]) - ; + $container = new Container(); + $container->set('request_stack', $requestStack); + $container->set('security.firewall.map', $firewallMap); + $container->set('security.authenticator.managers_locator', $this->createContainer('main', $userAuthenticator)); + $container->set('security.user_checker', $userChecker); $firewallMap->expects($this->once())->method('getFirewallConfig')->willReturn($firewall); $userAuthenticator->expects($this->once())->method('authenticateUser')->with($user, $authenticator, $request); @@ -181,17 +176,11 @@ public function testLoginReturnsAuthenticatorResponse() $userChecker = $this->createMock(UserCheckerInterface::class); $userAuthenticator = $this->createMock(UserAuthenticatorInterface::class); - $container = $this->createMock(ContainerInterface::class); - $container - ->expects($this->atLeastOnce()) - ->method('get') - ->willReturnMap([ - ['request_stack', $requestStack], - ['security.firewall.map', $firewallMap], - ['security.authenticator.managers_locator', $this->createContainer('main', $userAuthenticator)], - ['security.user_checker', $userChecker], - ]) - ; + $container = new Container(); + $container->set('request_stack', $requestStack); + $container->set('security.firewall.map', $firewallMap); + $container->set('security.authenticator.managers_locator', $this->createContainer('main', $userAuthenticator)); + $container->set('security.user_checker', $userChecker); $firewallMap->expects($this->once())->method('getFirewallConfig')->willReturn($firewall); $userChecker->expects($this->once())->method('checkPreAuth')->with($user); @@ -230,16 +219,10 @@ public function testLoginWithoutAuthenticatorThrows() $user = $this->createMock(UserInterface::class); $userChecker = $this->createMock(UserCheckerInterface::class); - $container = $this->createMock(ContainerInterface::class); - $container - ->expects($this->atLeastOnce()) - ->method('get') - ->willReturnMap([ - ['request_stack', $requestStack], - ['security.firewall.map', $firewallMap], - ['security.user_checker', $userChecker], - ]) - ; + $container = new Container(); + $container->set('request_stack', $requestStack); + $container->set('security.firewall.map', $firewallMap); + $container->set('security.user_checker', $userChecker); $firewallMap->expects($this->once())->method('getFirewallConfig')->willReturn($firewall); @@ -256,14 +239,8 @@ public function testLoginWithoutRequestContext() $requestStack = new RequestStack(); $user = $this->createMock(UserInterface::class); - $container = $this->createMock(ContainerInterface::class); - $container - ->expects($this->atLeastOnce()) - ->method('get') - ->willReturnMap([ - ['request_stack', $requestStack], - ]) - ; + $container = new Container(); + $container->set('request_stack', $requestStack); $security = new Security($container, ['main' => null]); @@ -300,26 +277,14 @@ public function testLogout() ->willReturn($firewallConfig) ; - $eventDispatcherLocator = $this->createMock(ContainerInterface::class); - $eventDispatcherLocator - ->expects($this->atLeastOnce()) - ->method('get') - ->willReturnMap([ - ['my_firewall', $eventDispatcher], - ]) - ; + $eventDispatcherLocator = new Container(); + $eventDispatcherLocator->set('my_firewall', $eventDispatcher); - $container = $this->createMock(ContainerInterface::class); - $container - ->expects($this->atLeastOnce()) - ->method('get') - ->willReturnMap([ - ['request_stack', $requestStack], - ['security.token_storage', $tokenStorage], - ['security.firewall.map', $firewallMap], - ['security.firewall.event_dispatcher_locator', $eventDispatcherLocator], - ]) - ; + $container = new Container(); + $container->set('request_stack', $requestStack); + $container->set('security.token_storage', $tokenStorage); + $container->set('security.firewall.map', $firewallMap); + $container->set('security.firewall.event_dispatcher_locator', $eventDispatcherLocator); $security = new Security($container); $security->logout(false); } @@ -346,16 +311,10 @@ public function testLogoutWithoutFirewall() ->willReturn(null) ; - $container = $this->createMock(ContainerInterface::class); - $container - ->expects($this->atLeastOnce()) - ->method('get') - ->willReturnMap([ - ['request_stack', $requestStack], - ['security.token_storage', $tokenStorage], - ['security.firewall.map', $firewallMap], - ]) - ; + $container = new Container(); + $container->set('request_stack', $requestStack); + $container->set('security.token_storage', $tokenStorage); + $container->set('security.firewall.map', $firewallMap); $this->expectException(LogicException::class); $security = new Security($container); @@ -393,24 +352,14 @@ public function testLogoutWithResponse() $firewallConfig = new FirewallConfig('my_firewall', 'user_checker'); $firewallMap->expects($this->once())->method('getFirewallConfig')->willReturn($firewallConfig); - $eventDispatcherLocator = $this->createMock(ContainerInterface::class); - $eventDispatcherLocator - ->expects($this->atLeastOnce()) - ->method('get') - ->willReturnMap([['my_firewall', $eventDispatcher]]) - ; + $eventDispatcherLocator = new Container(); + $eventDispatcherLocator->set('my_firewall', $eventDispatcher); - $container = $this->createMock(ContainerInterface::class); - $container - ->expects($this->atLeastOnce()) - ->method('get') - ->willReturnMap([ - ['request_stack', $requestStack], - ['security.token_storage', $tokenStorage], - ['security.firewall.map', $firewallMap], - ['security.firewall.event_dispatcher_locator', $eventDispatcherLocator], - ]) - ; + $container = new Container(); + $container->set('request_stack', $requestStack); + $container->set('security.token_storage', $tokenStorage); + $container->set('security.firewall.map', $firewallMap); + $container->set('security.firewall.event_dispatcher_locator', $eventDispatcherLocator); $security = new Security($container); $response = $security->logout(false); @@ -449,29 +398,18 @@ public function testLogoutWithValidCsrf() $firewallConfig = new FirewallConfig(name: 'my_firewall', userChecker: 'user_checker', logout: ['csrf_parameter' => '_csrf_token', 'csrf_token_id' => 'logout']); $firewallMap->expects($this->once())->method('getFirewallConfig')->willReturn($firewallConfig); - $eventDispatcherLocator = $this->createMock(ContainerInterface::class); - $eventDispatcherLocator - ->expects($this->atLeastOnce()) - ->method('get') - ->willReturnMap([['my_firewall', $eventDispatcher]]) - ; + $eventDispatcherLocator = new Container(); + $eventDispatcherLocator->set('my_firewall', $eventDispatcher); $csrfTokenManager = $this->createMock(CsrfTokenManagerInterface::class); $csrfTokenManager->expects($this->once())->method('isTokenValid')->with($this->equalTo(new CsrfToken('logout', 'dummytoken')))->willReturn(true); - $container = $this->createMock(ContainerInterface::class); - $container->expects($this->once())->method('has')->with('security.csrf.token_manager')->willReturn(true); - $container - ->expects($this->atLeastOnce()) - ->method('get') - ->willReturnMap([ - ['request_stack', $requestStack], - ['security.token_storage', $tokenStorage], - ['security.firewall.map', $firewallMap], - ['security.firewall.event_dispatcher_locator', $eventDispatcherLocator], - ['security.csrf.token_manager', $csrfTokenManager], - ]) - ; + $container = new Container(); + $container->set('request_stack', $requestStack); + $container->set('security.token_storage', $tokenStorage); + $container->set('security.firewall.map', $firewallMap); + $container->set('security.firewall.event_dispatcher_locator', $eventDispatcherLocator); + $container->set('security.csrf.token_manager', $csrfTokenManager); $security = new Security($container); $response = $security->logout(); @@ -483,14 +421,8 @@ public function testLogoutWithoutRequestContext() { $requestStack = new RequestStack(); - $container = $this->createMock(ContainerInterface::class); - $container - ->expects($this->atLeastOnce()) - ->method('get') - ->willReturnMap([ - ['request_stack', $requestStack], - ]) - ; + $container = new Container(); + $container->set('request_stack', $requestStack); $security = new Security($container, ['main' => null]); diff --git a/src/Symfony/Bundle/TwigBundle/CacheWarmer/TemplateCacheWarmer.php b/src/Symfony/Bundle/TwigBundle/CacheWarmer/TemplateCacheWarmer.php index c40425f460b2f..69b0b2cecbd83 100644 --- a/src/Symfony/Bundle/TwigBundle/CacheWarmer/TemplateCacheWarmer.php +++ b/src/Symfony/Bundle/TwigBundle/CacheWarmer/TemplateCacheWarmer.php @@ -37,7 +37,7 @@ public function __construct(ContainerInterface $container, iterable $iterator) $this->iterator = $iterator; } - public function warmUp(string $cacheDir, string $buildDir = null): array + public function warmUp(string $cacheDir, ?string $buildDir = null): array { $this->twig ??= $this->container->get('twig'); diff --git a/src/Symfony/Bundle/TwigBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/TwigBundle/DependencyInjection/Configuration.php index ab6ceb2932f46..9d08c976a8982 100644 --- a/src/Symfony/Bundle/TwigBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/TwigBundle/DependencyInjection/Configuration.php @@ -153,7 +153,7 @@ private function addTwigOptions(ArrayNodeDefinition $rootNode): void ->normalizeKeys(false) ->useAttributeAsKey('paths') ->beforeNormalization() - ->always() + ->ifArray() ->then(function ($paths) { $normalized = []; foreach ($paths as $path => $namespace) { diff --git a/src/Symfony/Bundle/TwigBundle/TemplateIterator.php b/src/Symfony/Bundle/TwigBundle/TemplateIterator.php index 7242cfb0c5d44..bd42f1ac07e8d 100644 --- a/src/Symfony/Bundle/TwigBundle/TemplateIterator.php +++ b/src/Symfony/Bundle/TwigBundle/TemplateIterator.php @@ -36,7 +36,7 @@ class TemplateIterator implements \IteratorAggregate * @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, array $namePatterns = []) + public function __construct(KernelInterface $kernel, array $paths = [], ?string $defaultPath = null, array $namePatterns = []) { $this->kernel = $kernel; $this->paths = $paths; @@ -78,7 +78,7 @@ public function getIterator(): \Traversable * * @return string[] */ - private function findTemplatesInDirectory(string $dir, string $namespace = null, array $excludeDirs = []): array + private function findTemplatesInDirectory(string $dir, ?string $namespace = null, array $excludeDirs = []): array { if (!is_dir($dir)) { return []; diff --git a/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/ConfigurationTest.php b/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/ConfigurationTest.php index 41627c48041e3..6ed43087579ce 100644 --- a/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/ConfigurationTest.php +++ b/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/ConfigurationTest.php @@ -52,4 +52,16 @@ public function testArrayKeysInGlobalsAreNotNormalized() $this->assertSame(['global' => ['value' => ['some-key' => 'some-value']]], $config['globals']); } + + public function testNullPathsAreConvertedToIterable() + { + $input = [ + 'paths' => null, + ]; + + $processor = new Processor(); + $config = $processor->processConfiguration(new Configuration(), [$input]); + + $this->assertSame([], $config['paths']); + } } diff --git a/src/Symfony/Bundle/WebProfilerBundle/Controller/ExceptionPanelController.php b/src/Symfony/Bundle/WebProfilerBundle/Controller/ExceptionPanelController.php index 1e3168bafc44b..a0704bb532cf8 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Controller/ExceptionPanelController.php +++ b/src/Symfony/Bundle/WebProfilerBundle/Controller/ExceptionPanelController.php @@ -28,7 +28,7 @@ class ExceptionPanelController private HtmlErrorRenderer $errorRenderer; private ?Profiler $profiler; - public function __construct(HtmlErrorRenderer $errorRenderer, Profiler $profiler = null) + public function __construct(HtmlErrorRenderer $errorRenderer, ?Profiler $profiler = null) { $this->errorRenderer = $errorRenderer; $this->profiler = $profiler; diff --git a/src/Symfony/Bundle/WebProfilerBundle/Controller/ProfilerController.php b/src/Symfony/Bundle/WebProfilerBundle/Controller/ProfilerController.php index df36246cb7604..23895f70bb6ec 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Controller/ProfilerController.php +++ b/src/Symfony/Bundle/WebProfilerBundle/Controller/ProfilerController.php @@ -41,7 +41,7 @@ class ProfilerController private ?ContentSecurityPolicyHandler $cspHandler; private ?string $baseDir; - public function __construct(UrlGeneratorInterface $generator, ?Profiler $profiler, Environment $twig, array $templates, ContentSecurityPolicyHandler $cspHandler = null, string $baseDir = null) + public function __construct(UrlGeneratorInterface $generator, ?Profiler $profiler, Environment $twig, array $templates, ?ContentSecurityPolicyHandler $cspHandler = null, ?string $baseDir = null) { $this->generator = $generator; $this->profiler = $profiler; @@ -127,7 +127,7 @@ public function panelAction(Request $request, string $token): Response * * @throws NotFoundHttpException */ - public function toolbarAction(Request $request, string $token = null): Response + public function toolbarAction(Request $request, ?string $token = null): Response { if (null === $this->profiler) { throw new NotFoundHttpException('The profiler must be enabled.'); diff --git a/src/Symfony/Bundle/WebProfilerBundle/Controller/RouterController.php b/src/Symfony/Bundle/WebProfilerBundle/Controller/RouterController.php index 04841e3cf3703..f9f7686dcb249 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Controller/RouterController.php +++ b/src/Symfony/Bundle/WebProfilerBundle/Controller/RouterController.php @@ -40,7 +40,7 @@ class RouterController */ private iterable $expressionLanguageProviders; - public function __construct(?Profiler $profiler, Environment $twig, UrlMatcherInterface $matcher = null, RouteCollection $routes = null, iterable $expressionLanguageProviders = []) + public function __construct(?Profiler $profiler, Environment $twig, ?UrlMatcherInterface $matcher = null, ?RouteCollection $routes = null, iterable $expressionLanguageProviders = []) { $this->profiler = $profiler; $this->twig = $twig; diff --git a/src/Symfony/Bundle/WebProfilerBundle/EventListener/WebDebugToolbarListener.php b/src/Symfony/Bundle/WebProfilerBundle/EventListener/WebDebugToolbarListener.php index 891ede6c94d0b..c2b350ff05d68 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/EventListener/WebDebugToolbarListener.php +++ b/src/Symfony/Bundle/WebProfilerBundle/EventListener/WebDebugToolbarListener.php @@ -48,7 +48,7 @@ class WebDebugToolbarListener implements EventSubscriberInterface private ?ContentSecurityPolicyHandler $cspHandler; private ?DumpDataCollector $dumpDataCollector; - public function __construct(Environment $twig, bool $interceptRedirects = false, int $mode = self::ENABLED, UrlGeneratorInterface $urlGenerator = null, string $excludedAjaxPaths = '^/bundles|^/_wdt', ContentSecurityPolicyHandler $cspHandler = null, DumpDataCollector $dumpDataCollector = null) + public function __construct(Environment $twig, bool $interceptRedirects = false, int $mode = self::ENABLED, ?UrlGeneratorInterface $urlGenerator = null, string $excludedAjaxPaths = '^/bundles|^/_wdt', ?ContentSecurityPolicyHandler $cspHandler = null, ?DumpDataCollector $dumpDataCollector = null) { $this->twig = $twig; $this->urlGenerator = $urlGenerator; diff --git a/src/Symfony/Bundle/WebProfilerBundle/Profiler/CodeExtension.php b/src/Symfony/Bundle/WebProfilerBundle/Profiler/CodeExtension.php index 40657a0f645ce..7fb51772d6d3d 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Profiler/CodeExtension.php +++ b/src/Symfony/Bundle/WebProfilerBundle/Profiler/CodeExtension.php @@ -157,7 +157,7 @@ public function fileExcerpt(string $file, int $line, int $srcContext = 3): ?stri /** * Formats a file path. */ - public function formatFile(string $file, int $line, string $text = null): string + public function formatFile(string $file, int $line, ?string $text = null): string { $file = trim($file); 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 3884c8e71e784..7d108394f37da 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/notifier.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/notifier.html.twig @@ -134,11 +134,11 @@

Notification

-                                                            {{- 'Subject: ' ~ notification.getSubject() }}
- {{- 'Content: ' ~ notification.getContent() }}
- {{- 'Importance: ' ~ notification.getImportance() }}
- {{- 'Emoji: ' ~ (notification.getEmoji() is empty ? '(empty)' : notification.getEmoji()) }}
- {{- 'Exception: ' ~ notification.getException() ?? '(empty)' }}
+ {{- 'Subject: ' ~ notification.getSubject() }}
+ {{- 'Content: ' ~ notification.getContent() }}
+ {{- 'Importance: ' ~ notification.getImportance() }}
+ {{- 'Emoji: ' ~ (notification.getEmoji() is empty ? '(empty)' : notification.getEmoji()) }}
+ {{- 'Exception: ' ~ notification.getException() ?? '(empty)' }}
{{- 'ExceptionAsString: ' ~ (notification.getExceptionAsString() is empty ? '(empty)' : notification.getExceptionAsString()) }}
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 9c11fe9199b81..1eaa87b976d4c 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/base.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/base.html.twig @@ -1,9 +1,9 @@ - - - + + + {% block title %}Symfony Profiler{% endblock %} {% set request_collector = profile is defined ? profile.collectors.request|default(null) : null %} diff --git a/src/Symfony/Bundle/WebProfilerBundle/Twig/WebProfilerExtension.php b/src/Symfony/Bundle/WebProfilerBundle/Twig/WebProfilerExtension.php index 6df2a24727e10..59361f4018bb3 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Twig/WebProfilerExtension.php +++ b/src/Symfony/Bundle/WebProfilerBundle/Twig/WebProfilerExtension.php @@ -37,7 +37,7 @@ class WebProfilerExtension extends ProfilerExtension private int $stackLevel = 0; - public function __construct(HtmlDumper $dumper = null) + public function __construct(?HtmlDumper $dumper = null) { $this->dumper = $dumper ?? new HtmlDumper(); $this->dumper->setOutput($this->output = fopen('php://memory', 'r+')); @@ -77,7 +77,7 @@ public function dumpData(Environment $env, Data $data, int $maxDepth = 0): strin return str_replace("\n$1"', $message); diff --git a/src/Symfony/Component/Asset/Exception/AssetNotFoundException.php b/src/Symfony/Component/Asset/Exception/AssetNotFoundException.php index ac3d2fa8f37bd..82e88947cb461 100644 --- a/src/Symfony/Component/Asset/Exception/AssetNotFoundException.php +++ b/src/Symfony/Component/Asset/Exception/AssetNotFoundException.php @@ -24,7 +24,7 @@ class AssetNotFoundException extends RuntimeException * @param int $code Exception code * @param \Throwable $previous Previous exception used for the exception chaining */ - public function __construct(string $message, array $alternatives = [], int $code = 0, \Throwable $previous = null) + public function __construct(string $message, array $alternatives = [], int $code = 0, ?\Throwable $previous = null) { parent::__construct($message, $code, $previous); diff --git a/src/Symfony/Component/Asset/Package.php b/src/Symfony/Component/Asset/Package.php index a9c9dca3eae20..c696c328f6075 100644 --- a/src/Symfony/Component/Asset/Package.php +++ b/src/Symfony/Component/Asset/Package.php @@ -27,7 +27,7 @@ class Package implements PackageInterface public function __construct( private VersionStrategyInterface $versionStrategy, - ContextInterface $context = null, + ?ContextInterface $context = null, ) { $this->context = $context ?? new NullContext(); } diff --git a/src/Symfony/Component/Asset/Packages.php b/src/Symfony/Component/Asset/Packages.php index 8c31e21de280c..01b4e814cca58 100644 --- a/src/Symfony/Component/Asset/Packages.php +++ b/src/Symfony/Component/Asset/Packages.php @@ -54,7 +54,7 @@ public function addPackage(string $name, PackageInterface $package): void * @throws InvalidArgumentException If there is no package by that name * @throws LogicException If no default package is defined */ - public function getPackage(string $name = null): PackageInterface + public function getPackage(?string $name = null): PackageInterface { if (null === $name) { if (null === $this->defaultPackage) { @@ -77,7 +77,7 @@ public function getPackage(string $name = null): PackageInterface * @param string $path A public path * @param string|null $packageName A package name */ - public function getVersion(string $path, string $packageName = null): string + public function getVersion(string $path, ?string $packageName = null): string { return $this->getPackage($packageName)->getVersion($path); } @@ -92,7 +92,7 @@ public function getVersion(string $path, string $packageName = null): string * * @return string A public path which takes into account the base path and URL path */ - public function getUrl(string $path, string $packageName = null): string + public function getUrl(string $path, ?string $packageName = null): string { return $this->getPackage($packageName)->getUrl($path); } diff --git a/src/Symfony/Component/Asset/PathPackage.php b/src/Symfony/Component/Asset/PathPackage.php index d8e08a3c34807..d03a8c8d1b7e4 100644 --- a/src/Symfony/Component/Asset/PathPackage.php +++ b/src/Symfony/Component/Asset/PathPackage.php @@ -31,7 +31,7 @@ class PathPackage extends Package /** * @param string $basePath The base path to be prepended to relative paths */ - public function __construct(string $basePath, VersionStrategyInterface $versionStrategy, ContextInterface $context = null) + public function __construct(string $basePath, VersionStrategyInterface $versionStrategy, ?ContextInterface $context = null) { parent::__construct($versionStrategy, $context); diff --git a/src/Symfony/Component/Asset/UrlPackage.php b/src/Symfony/Component/Asset/UrlPackage.php index 34c0e4ff909b9..0b884f42e9803 100644 --- a/src/Symfony/Component/Asset/UrlPackage.php +++ b/src/Symfony/Component/Asset/UrlPackage.php @@ -41,7 +41,7 @@ class UrlPackage extends Package /** * @param string|string[] $baseUrls Base asset URLs */ - public function __construct(string|array $baseUrls, VersionStrategyInterface $versionStrategy, ContextInterface $context = null) + public function __construct(string|array $baseUrls, VersionStrategyInterface $versionStrategy, ?ContextInterface $context = null) { parent::__construct($versionStrategy, $context); diff --git a/src/Symfony/Component/Asset/VersionStrategy/StaticVersionStrategy.php b/src/Symfony/Component/Asset/VersionStrategy/StaticVersionStrategy.php index 9fa2ee1d459c5..7df7c86cb6d2d 100644 --- a/src/Symfony/Component/Asset/VersionStrategy/StaticVersionStrategy.php +++ b/src/Symfony/Component/Asset/VersionStrategy/StaticVersionStrategy.php @@ -26,7 +26,7 @@ class StaticVersionStrategy implements VersionStrategyInterface */ public function __construct( private string $version, - string $format = null, + ?string $format = null, ) { $this->format = $format ?: '%s?%s'; } diff --git a/src/Symfony/Component/AssetMapper/AssetMapperDevServerSubscriber.php b/src/Symfony/Component/AssetMapper/AssetMapperDevServerSubscriber.php index 4d6cb0682d7c6..abdedfa0099c8 100644 --- a/src/Symfony/Component/AssetMapper/AssetMapperDevServerSubscriber.php +++ b/src/Symfony/Component/AssetMapper/AssetMapperDevServerSubscriber.php @@ -119,7 +119,7 @@ public function onKernelRequest(RequestEvent $event): void return; } - $pathInfo = $event->getRequest()->getPathInfo(); + $pathInfo = rawurldecode($event->getRequest()->getPathInfo()); if (!str_starts_with($pathInfo, $this->publicPrefix)) { return; } diff --git a/src/Symfony/Component/AssetMapper/AssetMapperRepository.php b/src/Symfony/Component/AssetMapper/AssetMapperRepository.php index b001c49bead9e..f79d17318feec 100644 --- a/src/Symfony/Component/AssetMapper/AssetMapperRepository.php +++ b/src/Symfony/Component/AssetMapper/AssetMapperRepository.php @@ -34,6 +34,7 @@ public function __construct( private readonly string $projectRootDir, private readonly array $excludedPathPatterns = [], private readonly bool $excludeDotFiles = true, + private readonly bool $debug = true, ) { } @@ -147,7 +148,7 @@ private function getDirectories(): array $this->absolutePaths = []; foreach ($this->paths as $path => $namespace) { if ($filesystem->isAbsolutePath($path)) { - if (!file_exists($path)) { + if (!file_exists($path) && $this->debug) { throw new \InvalidArgumentException(sprintf('The asset mapper directory "%s" does not exist.', $path)); } $this->absolutePaths[realpath($path)] = $namespace; @@ -161,7 +162,9 @@ private function getDirectories(): array continue; } - throw new \InvalidArgumentException(sprintf('The asset mapper directory "%s" does not exist.', $path)); + if ($this->debug) { + throw new \InvalidArgumentException(sprintf('The asset mapper directory "%s" does not exist.', $path)); + } } return $this->absolutePaths; diff --git a/src/Symfony/Component/AssetMapper/Compiler/CssAssetUrlCompiler.php b/src/Symfony/Component/AssetMapper/Compiler/CssAssetUrlCompiler.php index c159f97ef0aa6..09a8beb8b1a2c 100644 --- a/src/Symfony/Component/AssetMapper/Compiler/CssAssetUrlCompiler.php +++ b/src/Symfony/Component/AssetMapper/Compiler/CssAssetUrlCompiler.php @@ -70,7 +70,7 @@ public function supports(MappedAsset $asset): bool return 'css' === $asset->publicExtension; } - private function handleMissingImport(string $message, \Throwable $e = null): void + private function handleMissingImport(string $message, ?\Throwable $e = null): void { match ($this->missingImportMode) { AssetCompilerInterface::MISSING_IMPORT_IGNORE => null, diff --git a/src/Symfony/Component/AssetMapper/Compiler/JavaScriptImportPathCompiler.php b/src/Symfony/Component/AssetMapper/Compiler/JavaScriptImportPathCompiler.php index 93e04846fce96..be81a58027edf 100644 --- a/src/Symfony/Component/AssetMapper/Compiler/JavaScriptImportPathCompiler.php +++ b/src/Symfony/Component/AssetMapper/Compiler/JavaScriptImportPathCompiler.php @@ -27,8 +27,8 @@ */ final class JavaScriptImportPathCompiler implements AssetCompilerInterface { - // https://regex101.com/r/fquriB/1 - private const IMPORT_PATTERN = '/(?:import\s*(?:(?:\*\s*as\s+\w+|[\w\s{},*]+)\s*from\s*)?|\bimport\()\s*[\'"`](\.\/[^\'"`]+|(\.\.\/)*[^\'"`]+)[\'"`]\s*[;\)]?/m'; + // https://regex101.com/r/qFoeoR/1 + private const IMPORT_PATTERN = '/(?:\'(?:[^\'\\\\]|\\\\.)*\'|"(?:[^"\\\\]|\\\\.)*")|(?:import\s*(?:(?:\*\s*as\s+\w+|[\w\s{},*]+)\s*from\s*)?|\bimport\()\s*[\'"`](\.\/[^\'"`]+|(\.\.\/)*[^\'"`]+)[\'"`]\s*[;\)]?/m'; public function __construct( private readonly ImportMapConfigReader $importMapConfigReader, @@ -42,6 +42,11 @@ public function compile(string $content, MappedAsset $asset, AssetMapperInterfac return preg_replace_callback(self::IMPORT_PATTERN, function ($matches) use ($asset, $assetMapper, $content) { $fullImportString = $matches[0][0]; + // Ignore enquoted strings (e.g. console.log("import 'foo';") + if (!isset($matches[1][0])) { + return $fullImportString; + } + if ($this->isCommentedOut($matches[0][1], $content)) { return $fullImportString; } @@ -105,7 +110,7 @@ private function makeRelativeForJavaScript(string $path): string return './'.$path; } - private function handleMissingImport(string $message, \Throwable $e = null): void + private function handleMissingImport(string $message, ?\Throwable $e = null): void { match ($this->missingImportMode) { AssetCompilerInterface::MISSING_IMPORT_IGNORE => null, diff --git a/src/Symfony/Component/AssetMapper/Exception/CircularAssetsException.php b/src/Symfony/Component/AssetMapper/Exception/CircularAssetsException.php index a7e39ace40cbc..fc61149370dfd 100644 --- a/src/Symfony/Component/AssetMapper/Exception/CircularAssetsException.php +++ b/src/Symfony/Component/AssetMapper/Exception/CircularAssetsException.php @@ -18,7 +18,7 @@ */ class CircularAssetsException extends RuntimeException { - public function __construct(private MappedAsset $mappedAsset, string $message = '', int $code = 0, \Throwable $previous = null) + public function __construct(private MappedAsset $mappedAsset, string $message = '', int $code = 0, ?\Throwable $previous = null) { parent::__construct($message, $code, $previous); } diff --git a/src/Symfony/Component/AssetMapper/ImportMap/ImportMapAuditor.php b/src/Symfony/Component/AssetMapper/ImportMap/ImportMapAuditor.php index 112e68906dfd7..f53e8df2df704 100644 --- a/src/Symfony/Component/AssetMapper/ImportMap/ImportMapAuditor.php +++ b/src/Symfony/Component/AssetMapper/ImportMap/ImportMapAuditor.php @@ -23,7 +23,7 @@ class ImportMapAuditor public function __construct( private readonly ImportMapConfigReader $configReader, - HttpClientInterface $httpClient = null, + ?HttpClientInterface $httpClient = null, ) { $this->httpClient = $httpClient ?? HttpClient::create(); } diff --git a/src/Symfony/Component/AssetMapper/ImportMap/ImportMapVersionChecker.php b/src/Symfony/Component/AssetMapper/ImportMap/ImportMapVersionChecker.php index d07d8ce1ac2f1..b0af5736eb821 100644 --- a/src/Symfony/Component/AssetMapper/ImportMap/ImportMapVersionChecker.php +++ b/src/Symfony/Component/AssetMapper/ImportMap/ImportMapVersionChecker.php @@ -26,7 +26,7 @@ class ImportMapVersionChecker public function __construct( private ImportMapConfigReader $importMapConfigReader, private RemotePackageDownloader $packageDownloader, - HttpClientInterface $httpClient = null, + ?HttpClientInterface $httpClient = null, ) { $this->httpClient = $httpClient ?? HttpClient::create(); } diff --git a/src/Symfony/Component/AssetMapper/ImportMap/PackageRequireOptions.php b/src/Symfony/Component/AssetMapper/ImportMap/PackageRequireOptions.php index 6875bca9d1e59..c1bb34a8f66cd 100644 --- a/src/Symfony/Component/AssetMapper/ImportMap/PackageRequireOptions.php +++ b/src/Symfony/Component/AssetMapper/ImportMap/PackageRequireOptions.php @@ -26,7 +26,7 @@ public function __construct( */ public readonly string $packageModuleSpecifier, public readonly ?string $versionConstraint = null, - string $importName = null, + ?string $importName = null, public readonly ?string $path = null, public readonly bool $entrypoint = false, ) { diff --git a/src/Symfony/Component/AssetMapper/ImportMap/RemotePackageDownloader.php b/src/Symfony/Component/AssetMapper/ImportMap/RemotePackageDownloader.php index a5f2849817eec..0d18ef2c5d533 100644 --- a/src/Symfony/Component/AssetMapper/ImportMap/RemotePackageDownloader.php +++ b/src/Symfony/Component/AssetMapper/ImportMap/RemotePackageDownloader.php @@ -32,7 +32,7 @@ public function __construct( * * @return string[] The downloaded packages */ - public function downloadPackages(callable $progressCallback = null): array + public function downloadPackages(?callable $progressCallback = null): array { try { $installed = $this->loadInstalled(); diff --git a/src/Symfony/Component/AssetMapper/ImportMap/Resolver/JsDelivrEsmResolver.php b/src/Symfony/Component/AssetMapper/ImportMap/Resolver/JsDelivrEsmResolver.php index 082e790be399f..0788bbb77385c 100644 --- a/src/Symfony/Component/AssetMapper/ImportMap/Resolver/JsDelivrEsmResolver.php +++ b/src/Symfony/Component/AssetMapper/ImportMap/Resolver/JsDelivrEsmResolver.php @@ -35,7 +35,7 @@ final class JsDelivrEsmResolver implements PackageResolverInterface private HttpClientInterface $httpClient; public function __construct( - HttpClientInterface $httpClient = null, + ?HttpClientInterface $httpClient = null, ) { $this->httpClient = $httpClient ?? HttpClient::create(); } @@ -163,7 +163,7 @@ public function resolvePackages(array $packagesToRequire): array * * @return array}> */ - public function downloadPackages(array $importMapEntries, callable $progressCallback = null): array + public function downloadPackages(array $importMapEntries, ?callable $progressCallback = null): array { $responses = []; foreach ($importMapEntries as $package => $entry) { @@ -336,7 +336,7 @@ private function makeImportsBare(string $content, array &$dependencies, array &$ /** * Determine the URL pattern to be used by the HTTP Client. */ - private function resolveUrlPattern(string $packageName, string $path, ImportMapType $type = null): string + private function resolveUrlPattern(string $packageName, string $path, ?ImportMapType $type = null): string { // The URL for the es-module-shims polyfill package uses the CSS pattern to // prevent a syntax error in the browser console, so check the package name diff --git a/src/Symfony/Component/AssetMapper/ImportMap/Resolver/PackageResolverInterface.php b/src/Symfony/Component/AssetMapper/ImportMap/Resolver/PackageResolverInterface.php index defd04716baa3..354fa9d151be7 100644 --- a/src/Symfony/Component/AssetMapper/ImportMap/Resolver/PackageResolverInterface.php +++ b/src/Symfony/Component/AssetMapper/ImportMap/Resolver/PackageResolverInterface.php @@ -39,5 +39,5 @@ public function resolvePackages(array $packagesToRequire): array; * * @return array}> */ - public function downloadPackages(array $importMapEntries, callable $progressCallback = null): array; + public function downloadPackages(array $importMapEntries, ?callable $progressCallback = null): array; } diff --git a/src/Symfony/Component/AssetMapper/MappedAsset.php b/src/Symfony/Component/AssetMapper/MappedAsset.php index 0962ec1c3fb73..763e3ccc03ccc 100644 --- a/src/Symfony/Component/AssetMapper/MappedAsset.php +++ b/src/Symfony/Component/AssetMapper/MappedAsset.php @@ -59,12 +59,12 @@ final class MappedAsset */ public function __construct( public readonly string $logicalPath, - string $sourcePath = null, - string $publicPathWithoutDigest = null, - string $publicPath = null, - string $content = null, - string $digest = null, - bool $isPredigested = null, + ?string $sourcePath = null, + ?string $publicPathWithoutDigest = null, + ?string $publicPath = null, + ?string $content = null, + ?string $digest = null, + ?bool $isPredigested = null, bool $isVendor = false, array $dependencies = [], array $fileDependencies = [], diff --git a/src/Symfony/Component/AssetMapper/Tests/AssetMapperDevServerSubscriberFunctionalTest.php b/src/Symfony/Component/AssetMapper/Tests/AssetMapperDevServerSubscriberFunctionalTest.php index f83ff87f9426c..c6b2e7ecae9a6 100644 --- a/src/Symfony/Component/AssetMapper/Tests/AssetMapperDevServerSubscriberFunctionalTest.php +++ b/src/Symfony/Component/AssetMapper/Tests/AssetMapperDevServerSubscriberFunctionalTest.php @@ -35,6 +35,20 @@ public function testGettingAssetWorks() $this->assertTrue($response->headers->has('X-Assets-Dev')); } + public function testGettingAssetWithNonAsciiFilenameWorks() + { + $client = static::createClient(); + + $client->request('GET', '/assets/voilà-6344422da690fcc471f23f7a8966cd1c.css'); + $response = $client->getResponse(); + $this->assertSame(200, $response->getStatusCode()); + $this->assertSame(<<getInternalResponse()->getContent()); + } + public function test404OnUnknownAsset() { $client = static::createClient(); diff --git a/src/Symfony/Component/AssetMapper/Tests/Command/AssetMapperCompileCommandTest.php b/src/Symfony/Component/AssetMapper/Tests/Command/AssetMapperCompileCommandTest.php index 05283f33df5d7..ec7e3835b8a86 100644 --- a/src/Symfony/Component/AssetMapper/Tests/Command/AssetMapperCompileCommandTest.php +++ b/src/Symfony/Component/AssetMapper/Tests/Command/AssetMapperCompileCommandTest.php @@ -69,7 +69,7 @@ public function testAssetsAreCompiled() $finder = new Finder(); $finder->in($targetBuildDir)->files(); - $this->assertCount(12, $finder); // 9 files + manifest.json & importmap.json + entrypoint.file6.json + $this->assertCount(13, $finder); // 10 files + manifest.json & importmap.json + entrypoint.file6.json $this->assertFileExists($targetBuildDir.'/manifest.json'); $this->assertSame([ @@ -82,6 +82,7 @@ public function testAssetsAreCompiled() 'subdir/file6.js', 'vendor/@hotwired/stimulus/stimulus.index.js', 'vendor/lodash/lodash.index.js', + 'voilà.css', ], array_keys(json_decode(file_get_contents($targetBuildDir.'/manifest.json'), true))); $this->assertFileExists($targetBuildDir.'/importmap.json'); diff --git a/src/Symfony/Component/AssetMapper/Tests/Compiler/JavaScriptImportPathCompilerTest.php b/src/Symfony/Component/AssetMapper/Tests/Compiler/JavaScriptImportPathCompilerTest.php index c0894825b62aa..e616211b5b9dc 100644 --- a/src/Symfony/Component/AssetMapper/Tests/Compiler/JavaScriptImportPathCompilerTest.php +++ b/src/Symfony/Component/AssetMapper/Tests/Compiler/JavaScriptImportPathCompilerTest.php @@ -67,6 +67,7 @@ public function testCompileFindsCorrectImports(string $input, array $expectedJav ->method('getAssetFromSourcePath') ->willReturnCallback(function ($path) { return match ($path) { + '/project/assets/foo.js' => new MappedAsset('foo.js', '/can/be/anything.js', publicPathWithoutDigest: '/assets/foo.js'), '/project/assets/other.js' => new MappedAsset('other.js', '/can/be/anything.js', publicPathWithoutDigest: '/assets/other.js'), '/project/assets/subdir/foo.js' => new MappedAsset('subdir/foo.js', '/can/be/anything.js', publicPathWithoutDigest: '/assets/subdir/foo.js'), '/project/assets/styles.css' => new MappedAsset('styles.css', '/can/be/anything.js', publicPathWithoutDigest: '/assets/styles.css'), @@ -269,6 +270,63 @@ public static function provideCompileTests(): iterable 'expectedJavaScriptImports' => ['/assets/other.js' => ['lazy' => true, 'asset' => 'other.js', 'add' => true]], ]; + yield 'import_in_double_quoted_string_is_ignored' => [ + 'input' => << [], + ]; + + yield 'import_in_double_quoted_string_with_escaped_quote_is_ignored' => [ + 'input' => << [], + ]; + + yield 'import_in_single_quoted_string_is_ignored' => [ + 'input' => << [], + ]; + + yield 'import_after_a_string_is_parsed' => [ + 'input' => << ['/assets/foo.js' => ['lazy' => true, 'asset' => 'foo.js', 'add' => true]], + ]; + + yield 'import_before_a_string_is_parsed' => [ + 'input' => << ['/assets/other.js' => ['lazy' => true, 'asset' => 'other.js', 'add' => true]], + ]; + + yield 'import_before_and_after_a_string_is_parsed' => [ + 'input' => << [ + '/assets/other.js' => ['lazy' => true, 'asset' => 'other.js', 'add' => true], + '/assets/subdir/foo.js' => ['lazy' => true, 'asset' => 'subdir/foo.js', 'add' => true], + ], + ]; + yield 'bare_import_not_in_importmap' => [ 'input' => 'import "some_module";', 'expectedJavaScriptImports' => [], diff --git a/src/Symfony/Component/AssetMapper/Tests/Factory/MappedAssetFactoryTest.php b/src/Symfony/Component/AssetMapper/Tests/Factory/MappedAssetFactoryTest.php index 8127bd3d3be3a..d4e129a50ccfc 100644 --- a/src/Symfony/Component/AssetMapper/Tests/Factory/MappedAssetFactoryTest.php +++ b/src/Symfony/Component/AssetMapper/Tests/Factory/MappedAssetFactoryTest.php @@ -137,7 +137,7 @@ public function testCreateMappedAssetInVendor() $this->assertTrue($asset->isVendor); } - private function createFactory(AssetCompilerInterface $extraCompiler = null): MappedAssetFactory + private function createFactory(?AssetCompilerInterface $extraCompiler = null): MappedAssetFactory { $compilers = [ new JavaScriptImportPathCompiler($this->createMock(ImportMapConfigReader::class)), diff --git a/src/Symfony/Component/AssetMapper/Tests/Fixtures/AssetMapperTestAppKernel.php b/src/Symfony/Component/AssetMapper/Tests/Fixtures/AssetMapperTestAppKernel.php index 52092b6bf6eae..d8c44a257bdc3 100644 --- a/src/Symfony/Component/AssetMapper/Tests/Fixtures/AssetMapperTestAppKernel.php +++ b/src/Symfony/Component/AssetMapper/Tests/Fixtures/AssetMapperTestAppKernel.php @@ -43,7 +43,7 @@ public function registerContainerConfiguration(LoaderInterface $loader): void 'http_client' => true, 'assets' => null, 'asset_mapper' => [ - 'paths' => ['dir1', 'dir2', 'assets'], + 'paths' => ['dir1', 'dir2', 'non_ascii', 'assets'], ], 'test' => true, ]); diff --git "a/src/Symfony/Component/AssetMapper/Tests/Fixtures/non_ascii/voil\303\240.css" "b/src/Symfony/Component/AssetMapper/Tests/Fixtures/non_ascii/voil\303\240.css" new file mode 100644 index 0000000000000..f9a66cb6613c8 --- /dev/null +++ "b/src/Symfony/Component/AssetMapper/Tests/Fixtures/non_ascii/voil\303\240.css" @@ -0,0 +1,2 @@ +/* voilà.css */ +body {} diff --git a/src/Symfony/Component/AssetMapper/Tests/ImportMap/ImportMapGeneratorTest.php b/src/Symfony/Component/AssetMapper/Tests/ImportMap/ImportMapGeneratorTest.php index 273e02747a24c..deafa38e2cef8 100644 --- a/src/Symfony/Component/AssetMapper/Tests/ImportMap/ImportMapGeneratorTest.php +++ b/src/Symfony/Component/AssetMapper/Tests/ImportMap/ImportMapGeneratorTest.php @@ -745,7 +745,7 @@ private static function createLocalEntry(string $importName, string $path, Impor return ImportMapEntry::createLocal($importName, $type, path: $path, isEntrypoint: $isEntrypoint); } - private static function createRemoteEntry(string $importName, string $version, string $path = null, ImportMapType $type = ImportMapType::JS, string $packageSpecifier = null): ImportMapEntry + private static function createRemoteEntry(string $importName, string $version, ?string $path = null, ImportMapType $type = ImportMapType::JS, ?string $packageSpecifier = null): ImportMapEntry { $packageSpecifier = $packageSpecifier ?? $importName; $path = $path ?? '/vendor/any-path.js'; diff --git a/src/Symfony/Component/AssetMapper/Tests/ImportMap/ImportMapManagerTest.php b/src/Symfony/Component/AssetMapper/Tests/ImportMap/ImportMapManagerTest.php index 6ab4363b7fddc..3198b11ee76a6 100644 --- a/src/Symfony/Component/AssetMapper/Tests/ImportMap/ImportMapManagerTest.php +++ b/src/Symfony/Component/AssetMapper/Tests/ImportMap/ImportMapManagerTest.php @@ -425,7 +425,7 @@ private static function createLocalEntry(string $importName, string $path, Impor return ImportMapEntry::createLocal($importName, $type, path: $path, isEntrypoint: $isEntrypoint); } - private static function createRemoteEntry(string $importName, string $version, string $path = null, ImportMapType $type = ImportMapType::JS, string $packageSpecifier = null): ImportMapEntry + private static function createRemoteEntry(string $importName, string $version, ?string $path = null, ImportMapType $type = ImportMapType::JS, ?string $packageSpecifier = null): ImportMapEntry { $packageSpecifier = $packageSpecifier ?? $importName; $path = $path ?? '/vendor/any-path.js'; diff --git a/src/Symfony/Component/AssetMapper/Tests/ImportMap/ImportMapUpdateCheckerTest.php b/src/Symfony/Component/AssetMapper/Tests/ImportMap/ImportMapUpdateCheckerTest.php index 7356fb758877c..e01a2362a85f0 100644 --- a/src/Symfony/Component/AssetMapper/Tests/ImportMap/ImportMapUpdateCheckerTest.php +++ b/src/Symfony/Component/AssetMapper/Tests/ImportMap/ImportMapUpdateCheckerTest.php @@ -205,7 +205,7 @@ private function responseFactory($method, $url): MockResponse return $map[$url] ?? new MockResponse('Not found', ['http_code' => 404]); } - private static function createRemoteEntry(string $importName, string $version, ImportMapType $type = ImportMapType::JS, string $packageSpecifier = null): ImportMapEntry + private static function createRemoteEntry(string $importName, string $version, ImportMapType $type = ImportMapType::JS, ?string $packageSpecifier = null): ImportMapEntry { $packageSpecifier = $packageSpecifier ?? $importName; diff --git a/src/Symfony/Component/AssetMapper/Tests/ImportMap/ImportMapVersionCheckerTest.php b/src/Symfony/Component/AssetMapper/Tests/ImportMap/ImportMapVersionCheckerTest.php index 5eab19b2f6b6c..43346d3de33aa 100644 --- a/src/Symfony/Component/AssetMapper/Tests/ImportMap/ImportMapVersionCheckerTest.php +++ b/src/Symfony/Component/AssetMapper/Tests/ImportMap/ImportMapVersionCheckerTest.php @@ -405,7 +405,7 @@ public static function getNpmSpecificVersionConstraints() ]; } - private static function createRemoteEntry(string $importName, string $version, string $packageModuleSpecifier = null): ImportMapEntry + private static function createRemoteEntry(string $importName, string $version, ?string $packageModuleSpecifier = null): ImportMapEntry { $packageModuleSpecifier = $packageModuleSpecifier ?? $importName; diff --git a/src/Symfony/Component/AssetMapper/Tests/ImportMap/Resolver/JsDelivrEsmResolverTest.php b/src/Symfony/Component/AssetMapper/Tests/ImportMap/Resolver/JsDelivrEsmResolverTest.php index 50eb55228b338..f5fb90d2c90c9 100644 --- a/src/Symfony/Component/AssetMapper/Tests/ImportMap/Resolver/JsDelivrEsmResolverTest.php +++ b/src/Symfony/Component/AssetMapper/Tests/ImportMap/Resolver/JsDelivrEsmResolverTest.php @@ -688,7 +688,7 @@ public static function provideImportRegex(): iterable ]; } - private static function createRemoteEntry(string $importName, string $version, ImportMapType $type = ImportMapType::JS, string $packageSpecifier = null): ImportMapEntry + private static function createRemoteEntry(string $importName, string $version, ImportMapType $type = ImportMapType::JS, ?string $packageSpecifier = null): ImportMapEntry { $packageSpecifier = $packageSpecifier ?? $importName; diff --git a/src/Symfony/Component/BrowserKit/AbstractBrowser.php b/src/Symfony/Component/BrowserKit/AbstractBrowser.php index 90f55999c4c9e..d4b5a43aa68fa 100644 --- a/src/Symfony/Component/BrowserKit/AbstractBrowser.php +++ b/src/Symfony/Component/BrowserKit/AbstractBrowser.php @@ -54,7 +54,7 @@ abstract class AbstractBrowser /** * @param array $server The server parameters (equivalent of $_SERVER) */ - public function __construct(array $server = [], History $history = null, CookieJar $cookieJar = null) + public function __construct(array $server = [], ?History $history = null, ?CookieJar $cookieJar = null) { $this->setServerParameters($server); $this->history = $history ?? new History(); @@ -142,7 +142,7 @@ public function getServerParameter(string $key, mixed $default = ''): mixed return $this->server[$key] ?? $default; } - public function xmlHttpRequest(string $method, string $uri, array $parameters = [], array $files = [], array $server = [], string $content = null, bool $changeHistory = true): Crawler + public function xmlHttpRequest(string $method, string $uri, array $parameters = [], array $files = [], array $server = [], ?string $content = null, bool $changeHistory = true): Crawler { $this->setServerParameter('HTTP_X_REQUESTED_WITH', 'XMLHttpRequest'); @@ -323,7 +323,7 @@ public function submitForm(string $button, array $fieldValues = [], string $meth * @param string $content The raw body data * @param bool $changeHistory Whether to update the history or not (only used internally for back(), forward(), and reload()) */ - public function request(string $method, string $uri, array $parameters = [], array $files = [], array $server = [], string $content = null, bool $changeHistory = true): Crawler + public function request(string $method, string $uri, array $parameters = [], array $files = [], array $server = [], ?string $content = null, bool $changeHistory = true): Crawler { if ($this->isMainRequest) { $this->redirectCount = 0; diff --git a/src/Symfony/Component/BrowserKit/Cookie.php b/src/Symfony/Component/BrowserKit/Cookie.php index 992eeed7c7bbb..c3d1af3812436 100644 --- a/src/Symfony/Component/BrowserKit/Cookie.php +++ b/src/Symfony/Component/BrowserKit/Cookie.php @@ -56,8 +56,8 @@ class Cookie public function __construct( private string $name, ?string $value, - string $expires = null, - string $path = null, + ?string $expires = null, + ?string $path = null, private string $domain = '', private bool $secure = false, private bool $httponly = true, @@ -123,7 +123,7 @@ public function __toString(): string * * @throws InvalidArgumentException */ - public static function fromString(string $cookie, string $url = null): static + public static function fromString(string $cookie, ?string $url = null): static { $parts = explode(';', $cookie); diff --git a/src/Symfony/Component/BrowserKit/CookieJar.php b/src/Symfony/Component/BrowserKit/CookieJar.php index 59445d5fbea2f..bdaf65ef56052 100644 --- a/src/Symfony/Component/BrowserKit/CookieJar.php +++ b/src/Symfony/Component/BrowserKit/CookieJar.php @@ -35,7 +35,7 @@ public function set(Cookie $cookie): void * (this behavior ensures a BC behavior with previous versions of * Symfony). */ - public function get(string $name, string $path = '/', string $domain = null): ?Cookie + public function get(string $name, string $path = '/', ?string $domain = null): ?Cookie { $this->flushExpiredCookies(); @@ -67,7 +67,7 @@ public function get(string $name, string $path = '/', string $domain = null): ?C * all cookies for the given name/path expire (this behavior * ensures a BC behavior with previous versions of Symfony). */ - public function expire(string $name, ?string $path = '/', string $domain = null): void + public function expire(string $name, ?string $path = '/', ?string $domain = null): void { $path ??= '/'; @@ -105,7 +105,7 @@ public function clear(): void * * @param string[] $setCookies Set-Cookie headers from an HTTP response */ - public function updateFromSetCookie(array $setCookies, string $uri = null): void + public function updateFromSetCookie(array $setCookies, ?string $uri = null): void { $cookies = []; @@ -131,7 +131,7 @@ public function updateFromSetCookie(array $setCookies, string $uri = null): void /** * Updates the cookie jar from a Response object. */ - public function updateFromResponse(Response $response, string $uri = null): void + public function updateFromResponse(Response $response, ?string $uri = null): void { $this->updateFromSetCookie($response->getHeader('Set-Cookie', false), $uri); } diff --git a/src/Symfony/Component/BrowserKit/HttpBrowser.php b/src/Symfony/Component/BrowserKit/HttpBrowser.php index 4b61c86ec79e6..9d84bda751ba5 100644 --- a/src/Symfony/Component/BrowserKit/HttpBrowser.php +++ b/src/Symfony/Component/BrowserKit/HttpBrowser.php @@ -29,7 +29,7 @@ class HttpBrowser extends AbstractBrowser { private HttpClientInterface $client; - public function __construct(HttpClientInterface $client = null, History $history = null, CookieJar $cookieJar = null) + public function __construct(?HttpClientInterface $client = null, ?History $history = null, ?CookieJar $cookieJar = null) { if (!$client && !class_exists(HttpClient::class)) { throw new LogicException(sprintf('You cannot use "%s" as the HttpClient component is not installed. Try running "composer require symfony/http-client".', __CLASS__)); diff --git a/src/Symfony/Component/BrowserKit/Test/Constraint/BrowserCookieValueSame.php b/src/Symfony/Component/BrowserKit/Test/Constraint/BrowserCookieValueSame.php index ef9b4a05920b8..b3aa746ee3dfa 100644 --- a/src/Symfony/Component/BrowserKit/Test/Constraint/BrowserCookieValueSame.php +++ b/src/Symfony/Component/BrowserKit/Test/Constraint/BrowserCookieValueSame.php @@ -22,7 +22,7 @@ final class BrowserCookieValueSame extends Constraint private string $path; private ?string $domain; - public function __construct(string $name, string $value, bool $raw = false, string $path = '/', string $domain = null) + public function __construct(string $name, string $value, bool $raw = false, string $path = '/', ?string $domain = null) { $this->name = $name; $this->path = $path; diff --git a/src/Symfony/Component/BrowserKit/Test/Constraint/BrowserHasCookie.php b/src/Symfony/Component/BrowserKit/Test/Constraint/BrowserHasCookie.php index e6d7ab4f48475..ae39d61d646f1 100644 --- a/src/Symfony/Component/BrowserKit/Test/Constraint/BrowserHasCookie.php +++ b/src/Symfony/Component/BrowserKit/Test/Constraint/BrowserHasCookie.php @@ -20,7 +20,7 @@ final class BrowserHasCookie extends Constraint private string $path; private ?string $domain; - public function __construct(string $name, string $path = '/', string $domain = null) + public function __construct(string $name, string $path = '/', ?string $domain = null) { $this->name = $name; $this->path = $path; diff --git a/src/Symfony/Component/BrowserKit/Tests/AbstractBrowserTest.php b/src/Symfony/Component/BrowserKit/Tests/AbstractBrowserTest.php index e136a9049d25d..2267fca448799 100644 --- a/src/Symfony/Component/BrowserKit/Tests/AbstractBrowserTest.php +++ b/src/Symfony/Component/BrowserKit/Tests/AbstractBrowserTest.php @@ -21,7 +21,7 @@ class AbstractBrowserTest extends TestCase { - public function getBrowser(array $server = [], History $history = null, CookieJar $cookieJar = null) + public function getBrowser(array $server = [], ?History $history = null, ?CookieJar $cookieJar = null) { return new TestClient($server, $history, $cookieJar); } diff --git a/src/Symfony/Component/BrowserKit/Tests/HttpBrowserTest.php b/src/Symfony/Component/BrowserKit/Tests/HttpBrowserTest.php index 44f61289d8d6a..e1f19b16ce814 100644 --- a/src/Symfony/Component/BrowserKit/Tests/HttpBrowserTest.php +++ b/src/Symfony/Component/BrowserKit/Tests/HttpBrowserTest.php @@ -19,7 +19,7 @@ class HttpBrowserTest extends AbstractBrowserTest { - public function getBrowser(array $server = [], History $history = null, CookieJar $cookieJar = null) + public function getBrowser(array $server = [], ?History $history = null, ?CookieJar $cookieJar = null) { return new TestHttpClient($server, $history, $cookieJar); } diff --git a/src/Symfony/Component/BrowserKit/Tests/TestHttpClient.php b/src/Symfony/Component/BrowserKit/Tests/TestHttpClient.php index c11e6831847b4..3d0a354f5b340 100644 --- a/src/Symfony/Component/BrowserKit/Tests/TestHttpClient.php +++ b/src/Symfony/Component/BrowserKit/Tests/TestHttpClient.php @@ -23,7 +23,7 @@ class TestHttpClient extends HttpBrowser protected ?Response $nextResponse = null; protected string $nextScript; - public function __construct(array $server = [], History $history = null, CookieJar $cookieJar = null) + public function __construct(array $server = [], ?History $history = null, ?CookieJar $cookieJar = null) { $client = new MockHttpClient(function (string $method, string $url, array $options) { if (null === $this->nextResponse) { diff --git a/src/Symfony/Component/Cache/Adapter/AbstractAdapter.php b/src/Symfony/Component/Cache/Adapter/AbstractAdapter.php index 68db8c16959eb..1d88fbf87ddeb 100644 --- a/src/Symfony/Component/Cache/Adapter/AbstractAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/AbstractAdapter.php @@ -86,7 +86,7 @@ static function ($deferred, $namespace, &$expiredIds, $getId, $defaultLifetime) * * Using ApcuAdapter makes system caches compatible with read-only filesystems. */ - public static function createSystemCache(string $namespace, int $defaultLifetime, string $version, string $directory, LoggerInterface $logger = null): AdapterInterface + public static function createSystemCache(string $namespace, int $defaultLifetime, string $version, string $directory, ?LoggerInterface $logger = null): AdapterInterface { $opcache = new PhpFilesAdapter($namespace, $defaultLifetime, $directory, true); if (null !== $logger) { diff --git a/src/Symfony/Component/Cache/Adapter/ApcuAdapter.php b/src/Symfony/Component/Cache/Adapter/ApcuAdapter.php index 6bd36cdb5c966..03b512f05e3f9 100644 --- a/src/Symfony/Component/Cache/Adapter/ApcuAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/ApcuAdapter.php @@ -26,7 +26,7 @@ class ApcuAdapter extends AbstractAdapter public function __construct( string $namespace = '', int $defaultLifetime = 0, - string $version = null, + ?string $version = null, private ?MarshallerInterface $marshaller = null, ) { if (!static::isSupported()) { diff --git a/src/Symfony/Component/Cache/Adapter/ArrayAdapter.php b/src/Symfony/Component/Cache/Adapter/ArrayAdapter.php index f54cff4c56548..0f1c49db902aa 100644 --- a/src/Symfony/Component/Cache/Adapter/ArrayAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/ArrayAdapter.php @@ -70,7 +70,7 @@ static function ($key, $value, $isHit, $tags) { ); } - public function get(string $key, callable $callback, float $beta = null, array &$metadata = null): mixed + public function get(string $key, callable $callback, ?float $beta = null, ?array &$metadata = null): mixed { $item = $this->getItem($key); $metadata = $item->getMetadata(); diff --git a/src/Symfony/Component/Cache/Adapter/ChainAdapter.php b/src/Symfony/Component/Cache/Adapter/ChainAdapter.php index 3d0edb60d4e45..1418cff04a635 100644 --- a/src/Symfony/Component/Cache/Adapter/ChainAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/ChainAdapter.php @@ -88,7 +88,7 @@ static function ($sourceItem, $item, $defaultLifetime, $sourceMetadata = null) { ); } - public function get(string $key, callable $callback, float $beta = null, array &$metadata = null): mixed + public function get(string $key, callable $callback, ?float $beta = null, ?array &$metadata = null): mixed { $doSave = true; $callback = static function (CacheItem $item, bool &$save) use ($callback, &$doSave) { @@ -98,7 +98,7 @@ public function get(string $key, callable $callback, float $beta = null, array & return $value; }; - $wrap = function (CacheItem $item = null, bool &$save = true) use ($key, $callback, $beta, &$wrap, &$doSave, &$metadata) { + $wrap = function (?CacheItem $item = null, bool &$save = true) use ($key, $callback, $beta, &$wrap, &$doSave, &$metadata) { static $lastItem; static $i = 0; $adapter = $this->adapters[$i]; diff --git a/src/Symfony/Component/Cache/Adapter/CouchbaseBucketAdapter.php b/src/Symfony/Component/Cache/Adapter/CouchbaseBucketAdapter.php index 6c04859d148a1..1e5190f3d44e5 100644 --- a/src/Symfony/Component/Cache/Adapter/CouchbaseBucketAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/CouchbaseBucketAdapter.php @@ -46,7 +46,7 @@ public function __construct( private \CouchbaseBucket $bucket, string $namespace = '', int $defaultLifetime = 0, - MarshallerInterface $marshaller = null, + ?MarshallerInterface $marshaller = null, ) { if (!static::isSupported()) { throw new CacheException('Couchbase >= 2.6.0 < 3.0.0 is required.'); diff --git a/src/Symfony/Component/Cache/Adapter/CouchbaseCollectionAdapter.php b/src/Symfony/Component/Cache/Adapter/CouchbaseCollectionAdapter.php index 5cc4af1ad8a86..9646bc340fdef 100644 --- a/src/Symfony/Component/Cache/Adapter/CouchbaseCollectionAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/CouchbaseCollectionAdapter.php @@ -35,7 +35,7 @@ public function __construct( private Collection $connection, string $namespace = '', int $defaultLifetime = 0, - MarshallerInterface $marshaller = null, + ?MarshallerInterface $marshaller = null, ) { if (!static::isSupported()) { throw new CacheException('Couchbase >= 3.0.5 < 4.0.0 is required.'); diff --git a/src/Symfony/Component/Cache/Adapter/DoctrineDbalAdapter.php b/src/Symfony/Component/Cache/Adapter/DoctrineDbalAdapter.php index 5d78caec4709e..ae2bea7ed232a 100644 --- a/src/Symfony/Component/Cache/Adapter/DoctrineDbalAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/DoctrineDbalAdapter.php @@ -60,7 +60,7 @@ public function __construct( private string $namespace = '', int $defaultLifetime = 0, array $options = [], - MarshallerInterface $marshaller = null, + ?MarshallerInterface $marshaller = null, ) { if (isset($namespace[0]) && preg_match('#[^-+.A-Za-z0-9]#', $namespace, $match)) { throw new InvalidArgumentException(sprintf('Namespace contains "%s" but only characters in [-+.A-Za-z0-9] are allowed.', $match[0])); diff --git a/src/Symfony/Component/Cache/Adapter/FilesystemAdapter.php b/src/Symfony/Component/Cache/Adapter/FilesystemAdapter.php index 7185dd4877e42..13daa568c7cdd 100644 --- a/src/Symfony/Component/Cache/Adapter/FilesystemAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/FilesystemAdapter.php @@ -20,7 +20,7 @@ class FilesystemAdapter extends AbstractAdapter implements PruneableInterface { use FilesystemTrait; - public function __construct(string $namespace = '', int $defaultLifetime = 0, string $directory = null, MarshallerInterface $marshaller = null) + public function __construct(string $namespace = '', int $defaultLifetime = 0, ?string $directory = null, ?MarshallerInterface $marshaller = null) { $this->marshaller = $marshaller ?? new DefaultMarshaller(); parent::__construct('', $defaultLifetime); diff --git a/src/Symfony/Component/Cache/Adapter/FilesystemTagAwareAdapter.php b/src/Symfony/Component/Cache/Adapter/FilesystemTagAwareAdapter.php index e78536794ede2..80edee433dba0 100644 --- a/src/Symfony/Component/Cache/Adapter/FilesystemTagAwareAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/FilesystemTagAwareAdapter.php @@ -35,7 +35,7 @@ class FilesystemTagAwareAdapter extends AbstractTagAwareAdapter implements Prune */ private const TAG_FOLDER = 'tags'; - public function __construct(string $namespace = '', int $defaultLifetime = 0, string $directory = null, MarshallerInterface $marshaller = null) + public function __construct(string $namespace = '', int $defaultLifetime = 0, ?string $directory = null, ?MarshallerInterface $marshaller = null) { $this->marshaller = new TagAwareMarshaller($marshaller); parent::__construct('', $defaultLifetime); diff --git a/src/Symfony/Component/Cache/Adapter/MemcachedAdapter.php b/src/Symfony/Component/Cache/Adapter/MemcachedAdapter.php index 54c87e55b7425..033d9871e6eaa 100644 --- a/src/Symfony/Component/Cache/Adapter/MemcachedAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/MemcachedAdapter.php @@ -45,7 +45,7 @@ class MemcachedAdapter extends AbstractAdapter * * Using a MemcachedAdapter as a pure items store is fine. */ - public function __construct(\Memcached $client, string $namespace = '', int $defaultLifetime = 0, MarshallerInterface $marshaller = null) + public function __construct(\Memcached $client, string $namespace = '', int $defaultLifetime = 0, ?MarshallerInterface $marshaller = null) { if (!static::isSupported()) { throw new CacheException('Memcached > 3.1.5 is required.'); diff --git a/src/Symfony/Component/Cache/Adapter/NullAdapter.php b/src/Symfony/Component/Cache/Adapter/NullAdapter.php index 07c7af8162402..d5d2ef6b40d03 100644 --- a/src/Symfony/Component/Cache/Adapter/NullAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/NullAdapter.php @@ -37,7 +37,7 @@ static function ($key) { ); } - public function get(string $key, callable $callback, float $beta = null, array &$metadata = null): mixed + public function get(string $key, callable $callback, ?float $beta = null, ?array &$metadata = null): mixed { $save = true; diff --git a/src/Symfony/Component/Cache/Adapter/PdoAdapter.php b/src/Symfony/Component/Cache/Adapter/PdoAdapter.php index 0dd1b168bbb1f..c6ec11a0a8407 100644 --- a/src/Symfony/Component/Cache/Adapter/PdoAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/PdoAdapter.php @@ -54,7 +54,7 @@ class PdoAdapter extends AbstractAdapter implements PruneableInterface * @throws InvalidArgumentException When PDO error mode is not PDO::ERRMODE_EXCEPTION * @throws InvalidArgumentException When namespace contains invalid characters */ - public function __construct(#[\SensitiveParameter] \PDO|string $connOrDsn, string $namespace = '', int $defaultLifetime = 0, array $options = [], MarshallerInterface $marshaller = null) + public function __construct(#[\SensitiveParameter] \PDO|string $connOrDsn, string $namespace = '', int $defaultLifetime = 0, array $options = [], ?MarshallerInterface $marshaller = null) { if (\is_string($connOrDsn) && str_contains($connOrDsn, '://')) { throw new InvalidArgumentException(sprintf('Usage of Doctrine DBAL URL with "%s" is not supported. Use a PDO DSN or "%s" instead.', __CLASS__, DoctrineDbalAdapter::class)); diff --git a/src/Symfony/Component/Cache/Adapter/PhpArrayAdapter.php b/src/Symfony/Component/Cache/Adapter/PhpArrayAdapter.php index f7794a197ebd0..f92a2380f1e59 100644 --- a/src/Symfony/Component/Cache/Adapter/PhpArrayAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/PhpArrayAdapter.php @@ -78,7 +78,7 @@ public static function create(string $file, CacheItemPoolInterface $fallbackPool return new static($file, $fallbackPool); } - public function get(string $key, callable $callback, float $beta = null, array &$metadata = null): mixed + public function get(string $key, callable $callback, ?float $beta = null, ?array &$metadata = null): mixed { if (!isset($this->values)) { $this->initialize(); diff --git a/src/Symfony/Component/Cache/Adapter/PhpFilesAdapter.php b/src/Symfony/Component/Cache/Adapter/PhpFilesAdapter.php index 558199950ec61..917ff161f4742 100644 --- a/src/Symfony/Component/Cache/Adapter/PhpFilesAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/PhpFilesAdapter.php @@ -45,7 +45,7 @@ class PhpFilesAdapter extends AbstractAdapter implements PruneableInterface public function __construct( string $namespace = '', int $defaultLifetime = 0, - string $directory = null, + ?string $directory = null, private bool $appendOnly = false, ) { self::$startTime ??= $_SERVER['REQUEST_TIME'] ?? time(); diff --git a/src/Symfony/Component/Cache/Adapter/ProxyAdapter.php b/src/Symfony/Component/Cache/Adapter/ProxyAdapter.php index 88fccde4a6819..c022dd5fa9fc0 100644 --- a/src/Symfony/Component/Cache/Adapter/ProxyAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/ProxyAdapter.php @@ -80,7 +80,7 @@ static function (CacheItemInterface $innerItem, CacheItem $item, $expiry = null) ); } - public function get(string $key, callable $callback, float $beta = null, array &$metadata = null): mixed + public function get(string $key, callable $callback, ?float $beta = null, ?array &$metadata = null): mixed { if (!$this->pool instanceof CacheInterface) { return $this->doGet($this, $key, $callback, $beta, $metadata); diff --git a/src/Symfony/Component/Cache/Adapter/RedisAdapter.php b/src/Symfony/Component/Cache/Adapter/RedisAdapter.php index d8e37b1d7b2f3..e33f2f65fc927 100644 --- a/src/Symfony/Component/Cache/Adapter/RedisAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/RedisAdapter.php @@ -18,7 +18,7 @@ class RedisAdapter extends AbstractAdapter { use RedisTrait; - public function __construct(\Redis|\RedisArray|\RedisCluster|\Predis\ClientInterface|\Relay\Relay $redis, string $namespace = '', int $defaultLifetime = 0, MarshallerInterface $marshaller = null) + public function __construct(\Redis|\RedisArray|\RedisCluster|\Predis\ClientInterface|\Relay\Relay $redis, string $namespace = '', int $defaultLifetime = 0, ?MarshallerInterface $marshaller = null) { $this->init($redis, $namespace, $defaultLifetime, $marshaller); } diff --git a/src/Symfony/Component/Cache/Adapter/RedisTagAwareAdapter.php b/src/Symfony/Component/Cache/Adapter/RedisTagAwareAdapter.php index 7305bb2165c77..f71622b6bc208 100644 --- a/src/Symfony/Component/Cache/Adapter/RedisTagAwareAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/RedisTagAwareAdapter.php @@ -63,7 +63,7 @@ public function __construct( \Redis|Relay|\RedisArray|\RedisCluster|\Predis\ClientInterface $redis, private string $namespace = '', int $defaultLifetime = 0, - MarshallerInterface $marshaller = null, + ?MarshallerInterface $marshaller = null, ) { if ($redis instanceof \Predis\ClientInterface && $redis->getConnection() instanceof ClusterInterface && !$redis->getConnection() instanceof PredisCluster) { throw new InvalidArgumentException(sprintf('Unsupported Predis cluster connection: only "%s" is, "%s" given.', PredisCluster::class, get_debug_type($redis->getConnection()))); diff --git a/src/Symfony/Component/Cache/Adapter/TagAwareAdapter.php b/src/Symfony/Component/Cache/Adapter/TagAwareAdapter.php index ce94e474074aa..1fe1cde6a5b83 100644 --- a/src/Symfony/Component/Cache/Adapter/TagAwareAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/TagAwareAdapter.php @@ -52,7 +52,7 @@ class TagAwareAdapter implements TagAwareAdapterInterface, TagAwareCacheInterfac public function __construct( AdapterInterface $itemsPool, - AdapterInterface $tagsPool = null, + ?AdapterInterface $tagsPool = null, private float $knownTagVersionsTtl = 0.15, ) { $this->pool = $itemsPool; diff --git a/src/Symfony/Component/Cache/Adapter/TraceableAdapter.php b/src/Symfony/Component/Cache/Adapter/TraceableAdapter.php index 784c156b48b7b..b5bce143f7218 100644 --- a/src/Symfony/Component/Cache/Adapter/TraceableAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/TraceableAdapter.php @@ -35,7 +35,7 @@ public function __construct(AdapterInterface $pool) $this->pool = $pool; } - public function get(string $key, callable $callback, float $beta = null, array &$metadata = null): mixed + public function get(string $key, callable $callback, ?float $beta = null, ?array &$metadata = null): mixed { if (!$this->pool instanceof CacheInterface) { throw new \BadMethodCallException(sprintf('Cannot call "%s::get()": this class doesn\'t implement "%s".', get_debug_type($this->pool), CacheInterface::class)); diff --git a/src/Symfony/Component/Cache/DataCollector/CacheDataCollector.php b/src/Symfony/Component/Cache/DataCollector/CacheDataCollector.php index 08ab8816c1687..b9bcdaf132572 100644 --- a/src/Symfony/Component/Cache/DataCollector/CacheDataCollector.php +++ b/src/Symfony/Component/Cache/DataCollector/CacheDataCollector.php @@ -36,7 +36,7 @@ public function addInstance(string $name, TraceableAdapter $instance): void $this->instances[$name] = $instance; } - public function collect(Request $request, Response $response, \Throwable $exception = null): void + public function collect(Request $request, Response $response, ?\Throwable $exception = null): void { $empty = ['calls' => [], 'adapters' => [], 'config' => [], 'options' => [], 'statistics' => []]; $this->data = ['instances' => $empty, 'total' => $empty]; diff --git a/src/Symfony/Component/Cache/DependencyInjection/CachePoolPass.php b/src/Symfony/Component/Cache/DependencyInjection/CachePoolPass.php index 49d0ba32b7900..081d82cd72cb2 100644 --- a/src/Symfony/Component/Cache/DependencyInjection/CachePoolPass.php +++ b/src/Symfony/Component/Cache/DependencyInjection/CachePoolPass.php @@ -178,11 +178,11 @@ public function process(ContainerBuilder $container): void $container->removeDefinition('cache.early_expiration_handler'); } - $notAliasedCacheClearerId = $aliasedCacheClearerId = 'cache.global_clearer'; - while ($container->hasAlias('cache.global_clearer')) { - $aliasedCacheClearerId = (string) $container->getAlias('cache.global_clearer'); + $notAliasedCacheClearerId = 'cache.global_clearer'; + while ($container->hasAlias($notAliasedCacheClearerId)) { + $notAliasedCacheClearerId = (string) $container->getAlias($notAliasedCacheClearerId); } - if ($container->hasDefinition($aliasedCacheClearerId)) { + if ($container->hasDefinition($notAliasedCacheClearerId)) { $clearers[$notAliasedCacheClearerId] = $allPools; } diff --git a/src/Symfony/Component/Cache/LockRegistry.php b/src/Symfony/Component/Cache/LockRegistry.php index 4b750cb44eeac..c5c5fde898978 100644 --- a/src/Symfony/Component/Cache/LockRegistry.php +++ b/src/Symfony/Component/Cache/LockRegistry.php @@ -83,7 +83,7 @@ public static function setFiles(array $files): array return $previousFiles; } - public static function compute(callable $callback, ItemInterface $item, bool &$save, CacheInterface $pool, \Closure $setMetadata = null, LoggerInterface $logger = null): mixed + public static function compute(callable $callback, ItemInterface $item, bool &$save, CacheInterface $pool, ?\Closure $setMetadata = null, ?LoggerInterface $logger = null): mixed { if ('\\' === \DIRECTORY_SEPARATOR && null === self::$lockedFiles) { // disable locking on Windows by default diff --git a/src/Symfony/Component/Cache/Marshaller/DefaultMarshaller.php b/src/Symfony/Component/Cache/Marshaller/DefaultMarshaller.php index 973b137ae3eee..34bbeb8930078 100644 --- a/src/Symfony/Component/Cache/Marshaller/DefaultMarshaller.php +++ b/src/Symfony/Component/Cache/Marshaller/DefaultMarshaller.php @@ -23,7 +23,7 @@ class DefaultMarshaller implements MarshallerInterface private bool $useIgbinarySerialize = true; private bool $throwOnSerializationFailure = false; - public function __construct(bool $useIgbinarySerialize = null, bool $throwOnSerializationFailure = false) + public function __construct(?bool $useIgbinarySerialize = null, bool $throwOnSerializationFailure = false) { if (null === $useIgbinarySerialize) { $useIgbinarySerialize = \extension_loaded('igbinary') && version_compare('3.1.6', phpversion('igbinary'), '<='); diff --git a/src/Symfony/Component/Cache/Marshaller/SodiumMarshaller.php b/src/Symfony/Component/Cache/Marshaller/SodiumMarshaller.php index ae6d9e1c4b57d..77d16e8b878e3 100644 --- a/src/Symfony/Component/Cache/Marshaller/SodiumMarshaller.php +++ b/src/Symfony/Component/Cache/Marshaller/SodiumMarshaller.php @@ -30,7 +30,7 @@ class SodiumMarshaller implements MarshallerInterface */ public function __construct( private array $decryptionKeys, - MarshallerInterface $marshaller = null, + ?MarshallerInterface $marshaller = null, ) { if (!self::isSupported()) { throw new CacheException('The "sodium" PHP extension is not loaded.'); diff --git a/src/Symfony/Component/Cache/Marshaller/TagAwareMarshaller.php b/src/Symfony/Component/Cache/Marshaller/TagAwareMarshaller.php index f5c2867af2cf8..825f32cc0e0dc 100644 --- a/src/Symfony/Component/Cache/Marshaller/TagAwareMarshaller.php +++ b/src/Symfony/Component/Cache/Marshaller/TagAwareMarshaller.php @@ -20,7 +20,7 @@ class TagAwareMarshaller implements MarshallerInterface { private MarshallerInterface $marshaller; - public function __construct(MarshallerInterface $marshaller = null) + public function __construct(?MarshallerInterface $marshaller = null) { $this->marshaller = $marshaller ?? new DefaultMarshaller(); } diff --git a/src/Symfony/Component/Cache/Messenger/EarlyExpirationDispatcher.php b/src/Symfony/Component/Cache/Messenger/EarlyExpirationDispatcher.php index dee265887b900..d9726347925fc 100644 --- a/src/Symfony/Component/Cache/Messenger/EarlyExpirationDispatcher.php +++ b/src/Symfony/Component/Cache/Messenger/EarlyExpirationDispatcher.php @@ -28,12 +28,12 @@ class EarlyExpirationDispatcher public function __construct( private MessageBusInterface $bus, private ReverseContainer $reverseContainer, - callable $callbackWrapper = null, + ?callable $callbackWrapper = null, ) { $this->callbackWrapper = null === $callbackWrapper ? null : $callbackWrapper(...); } - public function __invoke(callable $callback, CacheItem $item, bool &$save, AdapterInterface $pool, \Closure $setMetadata, LoggerInterface $logger = null): mixed + public function __invoke(callable $callback, CacheItem $item, bool &$save, AdapterInterface $pool, \Closure $setMetadata, ?LoggerInterface $logger = null): mixed { 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 diff --git a/src/Symfony/Component/Cache/Tests/Adapter/AbstractRedisAdapterTestCase.php b/src/Symfony/Component/Cache/Tests/Adapter/AbstractRedisAdapterTestCase.php index 52f9500da0ed7..c83365cc73f35 100644 --- a/src/Symfony/Component/Cache/Tests/Adapter/AbstractRedisAdapterTestCase.php +++ b/src/Symfony/Component/Cache/Tests/Adapter/AbstractRedisAdapterTestCase.php @@ -25,7 +25,7 @@ abstract class AbstractRedisAdapterTestCase extends AdapterTestCase protected static \Redis|Relay|\RedisArray|\RedisCluster|\Predis\ClientInterface $redis; - public function createCachePool(int $defaultLifetime = 0, string $testMethod = null): CacheItemPoolInterface + public function createCachePool(int $defaultLifetime = 0, ?string $testMethod = null): CacheItemPoolInterface { return new RedisAdapter(self::$redis, str_replace('\\', '.', __CLASS__), $defaultLifetime); } diff --git a/src/Symfony/Component/Cache/Tests/Adapter/AdapterTestCase.php b/src/Symfony/Component/Cache/Tests/Adapter/AdapterTestCase.php index e48c8b4e8bcec..8c4b404853f31 100644 --- a/src/Symfony/Component/Cache/Tests/Adapter/AdapterTestCase.php +++ b/src/Symfony/Component/Cache/Tests/Adapter/AdapterTestCase.php @@ -234,7 +234,7 @@ public function testPrune() /** @var PruneableInterface|CacheItemPoolInterface $cache */ $cache = $this->createCachePool(); - $doSet = function ($name, $value, \DateInterval $expiresAfter = null) use ($cache) { + $doSet = function ($name, $value, ?\DateInterval $expiresAfter = null) use ($cache) { $item = $cache->getItem($name); $item->set($value); diff --git a/src/Symfony/Component/Cache/Tests/Adapter/ChainAdapterTest.php b/src/Symfony/Component/Cache/Tests/Adapter/ChainAdapterTest.php index a72b783babd41..6f849a6bd08a6 100644 --- a/src/Symfony/Component/Cache/Tests/Adapter/ChainAdapterTest.php +++ b/src/Symfony/Component/Cache/Tests/Adapter/ChainAdapterTest.php @@ -30,7 +30,7 @@ */ class ChainAdapterTest extends AdapterTestCase { - public function createCachePool(int $defaultLifetime = 0, string $testMethod = null): CacheItemPoolInterface + public function createCachePool(int $defaultLifetime = 0, ?string $testMethod = null): CacheItemPoolInterface { if ('testGetMetadata' === $testMethod) { return new ChainAdapter([new FilesystemAdapter('a', $defaultLifetime), new FilesystemAdapter('b', $defaultLifetime)], $defaultLifetime); diff --git a/src/Symfony/Component/Cache/Tests/Adapter/DoctrineDbalAdapterTest.php b/src/Symfony/Component/Cache/Tests/Adapter/DoctrineDbalAdapterTest.php index 63d0045213b47..9b7c448767781 100644 --- a/src/Symfony/Component/Cache/Tests/Adapter/DoctrineDbalAdapterTest.php +++ b/src/Symfony/Component/Cache/Tests/Adapter/DoctrineDbalAdapterTest.php @@ -107,7 +107,7 @@ public function testConfigureSchemaTableExists() /** * @dataProvider provideDsnWithSQLite */ - public function testDsnWithSQLite(string $dsn, string $file = null) + public function testDsnWithSQLite(string $dsn, ?string $file = null) { try { $pool = new DoctrineDbalAdapter($dsn); diff --git a/src/Symfony/Component/Cache/Tests/Adapter/MemcachedAdapterTest.php b/src/Symfony/Component/Cache/Tests/Adapter/MemcachedAdapterTest.php index 2534e90e94579..88950042fcde9 100644 --- a/src/Symfony/Component/Cache/Tests/Adapter/MemcachedAdapterTest.php +++ b/src/Symfony/Component/Cache/Tests/Adapter/MemcachedAdapterTest.php @@ -43,7 +43,7 @@ public static function setUpBeforeClass(): void } } - public function createCachePool(int $defaultLifetime = 0, string $testMethod = null, string $namespace = null): CacheItemPoolInterface + public function createCachePool(int $defaultLifetime = 0, ?string $testMethod = null, ?string $namespace = null): CacheItemPoolInterface { $client = $defaultLifetime ? AbstractAdapter::createConnection('memcached://'.getenv('MEMCACHED_HOST')) : self::$client; diff --git a/src/Symfony/Component/Cache/Tests/Adapter/NamespacedProxyAdapterTest.php b/src/Symfony/Component/Cache/Tests/Adapter/NamespacedProxyAdapterTest.php index a4edc7a608db5..4e6ebede0a596 100644 --- a/src/Symfony/Component/Cache/Tests/Adapter/NamespacedProxyAdapterTest.php +++ b/src/Symfony/Component/Cache/Tests/Adapter/NamespacedProxyAdapterTest.php @@ -21,7 +21,7 @@ */ class NamespacedProxyAdapterTest extends ProxyAdapterTest { - public function createCachePool(int $defaultLifetime = 0, string $testMethod = null): CacheItemPoolInterface + public function createCachePool(int $defaultLifetime = 0, ?string $testMethod = null): CacheItemPoolInterface { if ('testGetMetadata' === $testMethod) { return new ProxyAdapter(new FilesystemAdapter(), 'foo', $defaultLifetime); diff --git a/src/Symfony/Component/Cache/Tests/Adapter/PdoAdapterTest.php b/src/Symfony/Component/Cache/Tests/Adapter/PdoAdapterTest.php index 48ec520debd16..f55a4ce20796a 100644 --- a/src/Symfony/Component/Cache/Tests/Adapter/PdoAdapterTest.php +++ b/src/Symfony/Component/Cache/Tests/Adapter/PdoAdapterTest.php @@ -79,7 +79,7 @@ public function testCleanupExpiredItems() /** * @dataProvider provideDsnSQLite */ - public function testDsnWithSQLite(string $dsn, string $file = null) + public function testDsnWithSQLite(string $dsn, ?string $file = null) { try { $pool = new PdoAdapter($dsn); diff --git a/src/Symfony/Component/Cache/Tests/Adapter/PhpArrayAdapterTest.php b/src/Symfony/Component/Cache/Tests/Adapter/PhpArrayAdapterTest.php index 440352c9b63f6..5bbe4d1d7be13 100644 --- a/src/Symfony/Component/Cache/Tests/Adapter/PhpArrayAdapterTest.php +++ b/src/Symfony/Component/Cache/Tests/Adapter/PhpArrayAdapterTest.php @@ -75,7 +75,7 @@ protected function tearDown(): void } } - public function createCachePool(int $defaultLifetime = 0, string $testMethod = null): CacheItemPoolInterface + public function createCachePool(int $defaultLifetime = 0, ?string $testMethod = null): CacheItemPoolInterface { if ('testGetMetadata' === $testMethod || 'testClearPrefix' === $testMethod) { return new PhpArrayAdapter(self::$file, new FilesystemAdapter()); diff --git a/src/Symfony/Component/Cache/Tests/Adapter/PredisTagAwareAdapterTest.php b/src/Symfony/Component/Cache/Tests/Adapter/PredisTagAwareAdapterTest.php index 0971f80c553e5..0468e89449729 100644 --- a/src/Symfony/Component/Cache/Tests/Adapter/PredisTagAwareAdapterTest.php +++ b/src/Symfony/Component/Cache/Tests/Adapter/PredisTagAwareAdapterTest.php @@ -27,7 +27,7 @@ protected function setUp(): void $this->skippedTests['testTagItemExpiry'] = 'Testing expiration slows down the test suite'; } - public function createCachePool(int $defaultLifetime = 0, string $testMethod = null): CacheItemPoolInterface + public function createCachePool(int $defaultLifetime = 0, ?string $testMethod = null): CacheItemPoolInterface { $this->assertInstanceOf(\Predis\Client::class, self::$redis); $adapter = new RedisTagAwareAdapter(self::$redis, str_replace('\\', '.', __CLASS__), $defaultLifetime); diff --git a/src/Symfony/Component/Cache/Tests/Adapter/PredisTagAwareClusterAdapterTest.php b/src/Symfony/Component/Cache/Tests/Adapter/PredisTagAwareClusterAdapterTest.php index af25b2df52c45..3a118dc17147e 100644 --- a/src/Symfony/Component/Cache/Tests/Adapter/PredisTagAwareClusterAdapterTest.php +++ b/src/Symfony/Component/Cache/Tests/Adapter/PredisTagAwareClusterAdapterTest.php @@ -27,7 +27,7 @@ protected function setUp(): void $this->skippedTests['testTagItemExpiry'] = 'Testing expiration slows down the test suite'; } - public function createCachePool(int $defaultLifetime = 0, string $testMethod = null): CacheItemPoolInterface + public function createCachePool(int $defaultLifetime = 0, ?string $testMethod = null): CacheItemPoolInterface { $this->assertInstanceOf(\Predis\Client::class, self::$redis); $adapter = new RedisTagAwareAdapter(self::$redis, str_replace('\\', '.', __CLASS__), $defaultLifetime); diff --git a/src/Symfony/Component/Cache/Tests/Adapter/ProxyAdapterAndRedisAdapterTest.php b/src/Symfony/Component/Cache/Tests/Adapter/ProxyAdapterAndRedisAdapterTest.php index ed811fb26a9c1..4bff8c33909d7 100644 --- a/src/Symfony/Component/Cache/Tests/Adapter/ProxyAdapterAndRedisAdapterTest.php +++ b/src/Symfony/Component/Cache/Tests/Adapter/ProxyAdapterAndRedisAdapterTest.php @@ -32,7 +32,7 @@ public static function setUpBeforeClass(): void self::$redis = AbstractAdapter::createConnection('redis://'.getenv('REDIS_HOST')); } - public function createCachePool($defaultLifetime = 0, string $testMethod = null): CacheItemPoolInterface + public function createCachePool($defaultLifetime = 0, ?string $testMethod = null): CacheItemPoolInterface { return new ProxyAdapter(new RedisAdapter(self::$redis, str_replace('\\', '.', __CLASS__), 100), 'ProxyNS', $defaultLifetime); } diff --git a/src/Symfony/Component/Cache/Tests/Adapter/ProxyAdapterTest.php b/src/Symfony/Component/Cache/Tests/Adapter/ProxyAdapterTest.php index 71122a98b6740..765dd7565dc76 100644 --- a/src/Symfony/Component/Cache/Tests/Adapter/ProxyAdapterTest.php +++ b/src/Symfony/Component/Cache/Tests/Adapter/ProxyAdapterTest.php @@ -29,7 +29,7 @@ class ProxyAdapterTest extends AdapterTestCase 'testPrune' => 'ProxyAdapter just proxies', ]; - public function createCachePool(int $defaultLifetime = 0, string $testMethod = null): CacheItemPoolInterface + public function createCachePool(int $defaultLifetime = 0, ?string $testMethod = null): CacheItemPoolInterface { if ('testGetMetadata' === $testMethod) { return new ProxyAdapter(new FilesystemAdapter(), '', $defaultLifetime); diff --git a/src/Symfony/Component/Cache/Tests/Adapter/RedisAdapterTest.php b/src/Symfony/Component/Cache/Tests/Adapter/RedisAdapterTest.php index 3b44d63371e3d..7b8e11ea5faf2 100644 --- a/src/Symfony/Component/Cache/Tests/Adapter/RedisAdapterTest.php +++ b/src/Symfony/Component/Cache/Tests/Adapter/RedisAdapterTest.php @@ -28,7 +28,7 @@ public static function setUpBeforeClass(): void self::$redis = AbstractAdapter::createConnection('redis://'.getenv('REDIS_HOST'), ['lazy' => true]); } - public function createCachePool(int $defaultLifetime = 0, string $testMethod = null): CacheItemPoolInterface + public function createCachePool(int $defaultLifetime = 0, ?string $testMethod = null): CacheItemPoolInterface { if ('testClearWithPrefix' === $testMethod && \defined('Redis::SCAN_PREFIX')) { self::$redis->setOption(\Redis::OPT_SCAN, \Redis::SCAN_PREFIX); diff --git a/src/Symfony/Component/Cache/Tests/Adapter/RedisClusterAdapterTest.php b/src/Symfony/Component/Cache/Tests/Adapter/RedisClusterAdapterTest.php index ebee3200d6bce..3b7450e139254 100644 --- a/src/Symfony/Component/Cache/Tests/Adapter/RedisClusterAdapterTest.php +++ b/src/Symfony/Component/Cache/Tests/Adapter/RedisClusterAdapterTest.php @@ -35,7 +35,7 @@ public static function setUpBeforeClass(): void self::$redis->setOption(\Redis::OPT_PREFIX, 'prefix_'); } - public function createCachePool(int $defaultLifetime = 0, string $testMethod = null): CacheItemPoolInterface + public function createCachePool(int $defaultLifetime = 0, ?string $testMethod = null): CacheItemPoolInterface { if ('testClearWithPrefix' === $testMethod && \defined('Redis::SCAN_PREFIX')) { self::$redis->setOption(\Redis::OPT_SCAN, \Redis::SCAN_PREFIX); diff --git a/src/Symfony/Component/Cache/Tests/Adapter/RedisTagAwareAdapterTest.php b/src/Symfony/Component/Cache/Tests/Adapter/RedisTagAwareAdapterTest.php index 12e3b6ff55365..f00eb9de8aaeb 100644 --- a/src/Symfony/Component/Cache/Tests/Adapter/RedisTagAwareAdapterTest.php +++ b/src/Symfony/Component/Cache/Tests/Adapter/RedisTagAwareAdapterTest.php @@ -28,7 +28,7 @@ protected function setUp(): void $this->skippedTests['testTagItemExpiry'] = 'Testing expiration slows down the test suite'; } - public function createCachePool(int $defaultLifetime = 0, string $testMethod = null): CacheItemPoolInterface + public function createCachePool(int $defaultLifetime = 0, ?string $testMethod = null): CacheItemPoolInterface { if ('testClearWithPrefix' === $testMethod && \defined('Redis::SCAN_PREFIX')) { self::$redis->setOption(\Redis::OPT_SCAN, \Redis::SCAN_PREFIX); diff --git a/src/Symfony/Component/Cache/Tests/Adapter/RedisTagAwareArrayAdapterTest.php b/src/Symfony/Component/Cache/Tests/Adapter/RedisTagAwareArrayAdapterTest.php index b5823711dc858..860709bf7f2cb 100644 --- a/src/Symfony/Component/Cache/Tests/Adapter/RedisTagAwareArrayAdapterTest.php +++ b/src/Symfony/Component/Cache/Tests/Adapter/RedisTagAwareArrayAdapterTest.php @@ -27,7 +27,7 @@ protected function setUp(): void $this->skippedTests['testTagItemExpiry'] = 'Testing expiration slows down the test suite'; } - public function createCachePool(int $defaultLifetime = 0, string $testMethod = null): CacheItemPoolInterface + public function createCachePool(int $defaultLifetime = 0, ?string $testMethod = null): CacheItemPoolInterface { if ('testClearWithPrefix' === $testMethod && \defined('Redis::SCAN_PREFIX')) { self::$redis->setOption(\Redis::OPT_SCAN, \Redis::SCAN_PREFIX); diff --git a/src/Symfony/Component/Cache/Tests/Adapter/RedisTagAwareClusterAdapterTest.php b/src/Symfony/Component/Cache/Tests/Adapter/RedisTagAwareClusterAdapterTest.php index d4a1bc97779ca..c7d143d3a35db 100644 --- a/src/Symfony/Component/Cache/Tests/Adapter/RedisTagAwareClusterAdapterTest.php +++ b/src/Symfony/Component/Cache/Tests/Adapter/RedisTagAwareClusterAdapterTest.php @@ -28,7 +28,7 @@ protected function setUp(): void $this->skippedTests['testTagItemExpiry'] = 'Testing expiration slows down the test suite'; } - public function createCachePool(int $defaultLifetime = 0, string $testMethod = null): CacheItemPoolInterface + public function createCachePool(int $defaultLifetime = 0, ?string $testMethod = null): CacheItemPoolInterface { if ('testClearWithPrefix' === $testMethod && \defined('Redis::SCAN_PREFIX')) { self::$redis->setOption(\Redis::OPT_SCAN, \Redis::SCAN_PREFIX); diff --git a/src/Symfony/Component/Cache/Tests/DependencyInjection/CachePoolPassTest.php b/src/Symfony/Component/Cache/Tests/DependencyInjection/CachePoolPassTest.php index 98a093ed0222f..ef64d1932da8f 100644 --- a/src/Symfony/Component/Cache/Tests/DependencyInjection/CachePoolPassTest.php +++ b/src/Symfony/Component/Cache/Tests/DependencyInjection/CachePoolPassTest.php @@ -20,8 +20,10 @@ use Symfony\Component\Cache\DependencyInjection\CachePoolPass; use Symfony\Component\DependencyInjection\ChildDefinition; use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\HttpKernel\CacheClearer\Psr6CacheClearer; class CachePoolPassTest extends TestCase { @@ -233,4 +235,33 @@ public function testChainAdapterPool() $this->assertInstanceOf(ChildDefinition::class, $doctrineCachePool); $this->assertSame('cache.app', $doctrineCachePool->getParent()); } + + public function testGlobalClearerAlias() + { + $container = new ContainerBuilder(); + $container->setParameter('kernel.container_class', 'app'); + $container->setParameter('kernel.project_dir', 'foo'); + + $container->register('cache.default_clearer', Psr6CacheClearer::class); + + $container->setDefinition('cache.system_clearer', new ChildDefinition('cache.default_clearer')); + + $container->setDefinition('cache.foo_bar_clearer', new ChildDefinition('cache.default_clearer')); + $container->setAlias('cache.global_clearer', 'cache.foo_bar_clearer'); + + $container->register('cache.adapter.array', ArrayAdapter::class) + ->setAbstract(true) + ->addTag('cache.pool'); + + $cachePool = new ChildDefinition('cache.adapter.array'); + $cachePool->addTag('cache.pool', ['clearer' => 'cache.system_clearer']); + $container->setDefinition('app.cache_pool', $cachePool); + + $this->cachePoolPass->process($container); + + $definition = $container->getDefinition('cache.foo_bar_clearer'); + + $this->assertTrue($definition->hasTag('cache.pool.clearer')); + $this->assertEquals(['app.cache_pool' => new Reference('app.cache_pool', ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE)], $definition->getArgument(0)); + } } diff --git a/src/Symfony/Component/Cache/Tests/Traits/RedisProxiesTest.php b/src/Symfony/Component/Cache/Tests/Traits/RedisProxiesTest.php index a59b558859c3b..01d96fb9e0480 100644 --- a/src/Symfony/Component/Cache/Tests/Traits/RedisProxiesTest.php +++ b/src/Symfony/Component/Cache/Tests/Traits/RedisProxiesTest.php @@ -101,7 +101,17 @@ public function testRedis6Proxy($class, $stub) continue; } $return = $method->getReturnType() instanceof \ReflectionNamedType && 'void' === (string) $method->getReturnType() ? '' : 'return '; - $methods[] = "\n ".str_replace('timeout = 0.0', 'timeout = 0', ProxyHelper::exportSignature($method, false, $args))."\n".<<name) { + $signature = str_replace(': \Redis|array|false', ': \Redis|array', $signature); + } + + if ('RedisCluster' === $class && 'publish' === $method->name) { + $signature = str_replace(': \RedisCluster|bool|int', ': \RedisCluster|bool', $signature); + } + + $methods[] = "\n ".str_replace('timeout = 0.0', 'timeout = 0', $signature)."\n".<<lazyObjectState->realInstance ??= (\$this->lazyObjectState->initializer)())->{$method->name}({$args}); } diff --git a/src/Symfony/Component/Cache/Traits/ContractsTrait.php b/src/Symfony/Component/Cache/Traits/ContractsTrait.php index 083ce1f9dbd3f..8d830f0abf941 100644 --- a/src/Symfony/Component/Cache/Traits/ContractsTrait.php +++ b/src/Symfony/Component/Cache/Traits/ContractsTrait.php @@ -59,7 +59,7 @@ public function setCallbackWrapper(?callable $callbackWrapper): callable return $previousWrapper; } - private function doGet(AdapterInterface $pool, string $key, callable $callback, ?float $beta, array &$metadata = null): mixed + private function doGet(AdapterInterface $pool, string $key, callable $callback, ?float $beta, ?array &$metadata = null): mixed { 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)); diff --git a/src/Symfony/Component/Cache/Traits/FilesystemCommonTrait.php b/src/Symfony/Component/Cache/Traits/FilesystemCommonTrait.php index cd14886ffab2e..7fa0f2e874d63 100644 --- a/src/Symfony/Component/Cache/Traits/FilesystemCommonTrait.php +++ b/src/Symfony/Component/Cache/Traits/FilesystemCommonTrait.php @@ -82,7 +82,7 @@ protected function doUnlink(string $file): bool return @unlink($file); } - private function write(string $file, string $data, int $expiresAt = null): bool + private function write(string $file, string $data, ?int $expiresAt = null): bool { $unlink = false; set_error_handler(static fn ($type, $message, $file, $line) => throw new \ErrorException($message, 0, $type, $file, $line)); @@ -119,7 +119,7 @@ private function write(string $file, string $data, int $expiresAt = null): bool } } - private function getFile(string $id, bool $mkdir = false, string $directory = null): string + private function getFile(string $id, bool $mkdir = false, ?string $directory = null): string { // Use xxh128 to favor speed over security, which is not an issue here $hash = str_replace('/', '-', base64_encode(hash('xxh128', static::class.$id, true))); diff --git a/src/Symfony/Component/Cache/Traits/RedisTrait.php b/src/Symfony/Component/Cache/Traits/RedisTrait.php index 9852484288dc8..8574c9c17e168 100644 --- a/src/Symfony/Component/Cache/Traits/RedisTrait.php +++ b/src/Symfony/Component/Cache/Traits/RedisTrait.php @@ -569,7 +569,7 @@ protected function doSave(array $values, int $lifetime): array|bool return $failed; } - private function pipeline(\Closure $generator, object $redis = null): \Generator + private function pipeline(\Closure $generator, ?object $redis = null): \Generator { $ids = []; $redis ??= $this->redis; diff --git a/src/Symfony/Component/Clock/DatePoint.php b/src/Symfony/Component/Clock/DatePoint.php index 95d23191eac05..5d8ace6374bc8 100644 --- a/src/Symfony/Component/Clock/DatePoint.php +++ b/src/Symfony/Component/Clock/DatePoint.php @@ -21,7 +21,7 @@ final class DatePoint extends \DateTimeImmutable /** * @throws \DateMalformedStringException When $datetime is invalid */ - public function __construct(string $datetime = 'now', \DateTimeZone $timezone = null, parent $reference = null) + public function __construct(string $datetime = 'now', ?\DateTimeZone $timezone = null, ?parent $reference = null) { $now = $reference ?? Clock::get()->now(); @@ -51,7 +51,7 @@ public function __construct(string $datetime = 'now', \DateTimeZone $timezone = /** * @throws \DateMalformedStringException When $format or $datetime are invalid */ - public static function createFromFormat(string $format, string $datetime, \DateTimeZone $timezone = null): static + public static function createFromFormat(string $format, string $datetime, ?\DateTimeZone $timezone = null): static { return parent::createFromFormat($format, $datetime, $timezone) ?: throw new \DateMalformedStringException(static::getLastErrors()['errors'][0] ?? 'Invalid date string or format.'); } diff --git a/src/Symfony/Component/Clock/MockClock.php b/src/Symfony/Component/Clock/MockClock.php index b742c4331e052..ab64f1cbaf86f 100644 --- a/src/Symfony/Component/Clock/MockClock.php +++ b/src/Symfony/Component/Clock/MockClock.php @@ -26,7 +26,7 @@ final class MockClock implements ClockInterface * @throws \DateMalformedStringException When $now is invalid * @throws \DateInvalidTimeZoneException When $timezone is invalid */ - public function __construct(\DateTimeImmutable|string $now = 'now', \DateTimeZone|string $timezone = null) + public function __construct(\DateTimeImmutable|string $now = 'now', \DateTimeZone|string|null $timezone = null) { if (\PHP_VERSION_ID >= 80300 && \is_string($timezone)) { $timezone = new \DateTimeZone($timezone); diff --git a/src/Symfony/Component/Clock/MonotonicClock.php b/src/Symfony/Component/Clock/MonotonicClock.php index a834dde1dbc56..d27bf9c3134e0 100644 --- a/src/Symfony/Component/Clock/MonotonicClock.php +++ b/src/Symfony/Component/Clock/MonotonicClock.php @@ -25,7 +25,7 @@ final class MonotonicClock implements ClockInterface /** * @throws \DateInvalidTimeZoneException When $timezone is invalid */ - public function __construct(\DateTimeZone|string $timezone = null) + public function __construct(\DateTimeZone|string|null $timezone = null) { if (false === $offset = hrtime()) { throw new \RuntimeException('hrtime() returned false: the runtime environment does not provide access to a monotonic timer.'); diff --git a/src/Symfony/Component/Clock/NativeClock.php b/src/Symfony/Component/Clock/NativeClock.php index 9480dae5f6957..b580a886cf566 100644 --- a/src/Symfony/Component/Clock/NativeClock.php +++ b/src/Symfony/Component/Clock/NativeClock.php @@ -23,7 +23,7 @@ final class NativeClock implements ClockInterface /** * @throws \DateInvalidTimeZoneException When $timezone is invalid */ - public function __construct(\DateTimeZone|string $timezone = null) + public function __construct(\DateTimeZone|string|null $timezone = null) { $this->timezone = \is_string($timezone ??= date_default_timezone_get()) ? $this->withTimeZone($timezone)->timezone : $timezone; } diff --git a/src/Symfony/Component/Config/Builder/ClassBuilder.php b/src/Symfony/Component/Config/Builder/ClassBuilder.php index 3ab8b01a8cfa2..f34ab8519b82f 100644 --- a/src/Symfony/Component/Config/Builder/ClassBuilder.php +++ b/src/Symfony/Component/Config/Builder/ClassBuilder.php @@ -119,7 +119,7 @@ public function addMethod(string $name, string $body, array $params = []): void $this->methods[] = new Method(strtr($body, ['NAME' => $this->camelCase($name)] + $params)); } - public function addProperty(string $name, string $classType = null, string $defaultValue = null): Property + public function addProperty(string $name, ?string $classType = null, ?string $defaultValue = null): Property { $property = new Property($name, '_' !== $name[0] ? $this->camelCase($name) : $name); if (null !== $classType) { diff --git a/src/Symfony/Component/Config/CHANGELOG.md b/src/Symfony/Component/Config/CHANGELOG.md index 51e2d1fee567b..79709aaa78f85 100644 --- a/src/Symfony/Component/Config/CHANGELOG.md +++ b/src/Symfony/Component/Config/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +7.1 +--- + + * Allow custom meta location in `ResourceCheckerConfigCache` + 7.0 --- diff --git a/src/Symfony/Component/Config/ConfigCacheInterface.php b/src/Symfony/Component/Config/ConfigCacheInterface.php index f3ea53bde64da..7b9d388973ba5 100644 --- a/src/Symfony/Component/Config/ConfigCacheInterface.php +++ b/src/Symfony/Component/Config/ConfigCacheInterface.php @@ -41,5 +41,5 @@ public function isFresh(): bool; * * @throws \RuntimeException When the cache file cannot be written */ - public function write(string $content, array $metadata = null): void; + public function write(string $content, ?array $metadata = null): void; } diff --git a/src/Symfony/Component/Config/Definition/Builder/ArrayNodeDefinition.php b/src/Symfony/Component/Config/Definition/Builder/ArrayNodeDefinition.php index dd6c58da746c1..4596151fba054 100644 --- a/src/Symfony/Component/Config/Definition/Builder/ArrayNodeDefinition.php +++ b/src/Symfony/Component/Config/Definition/Builder/ArrayNodeDefinition.php @@ -33,11 +33,11 @@ class ArrayNodeDefinition extends NodeDefinition implements ParentNodeDefinition protected ?string $key = null; protected bool $removeKeyItem = false; protected bool $addDefaults = false; - protected int|string|array|null|false $addDefaultChildren = false; + protected int|string|array|false|null $addDefaultChildren = false; protected NodeBuilder $nodeBuilder; protected bool $normalizeKeys = true; - public function __construct(?string $name, NodeParentInterface $parent = null) + public function __construct(?string $name, ?NodeParentInterface $parent = null) { parent::__construct($name, $parent); @@ -123,7 +123,7 @@ public function addDefaultsIfNotSet(): static * * @return $this */ - public function addDefaultChildrenIfNoneSet(int|string|array $children = null): static + public function addDefaultChildrenIfNoneSet(int|string|array|null $children = null): static { $this->addDefaultChildren = $children; @@ -166,7 +166,7 @@ public function disallowNewKeysInSubsequentConfigs(): static * * @return $this */ - public function fixXmlConfig(string $singular, string $plural = null): static + public function fixXmlConfig(string $singular, ?string $plural = null): static { $this->normalization()->remap($singular, $plural); diff --git a/src/Symfony/Component/Config/Definition/Builder/BooleanNodeDefinition.php b/src/Symfony/Component/Config/Definition/Builder/BooleanNodeDefinition.php index 3d8fad4d55d31..15e63961ab727 100644 --- a/src/Symfony/Component/Config/Definition/Builder/BooleanNodeDefinition.php +++ b/src/Symfony/Component/Config/Definition/Builder/BooleanNodeDefinition.php @@ -21,7 +21,7 @@ */ class BooleanNodeDefinition extends ScalarNodeDefinition { - public function __construct(?string $name, NodeParentInterface $parent = null) + public function __construct(?string $name, ?NodeParentInterface $parent = null) { parent::__construct($name, $parent); diff --git a/src/Symfony/Component/Config/Definition/Builder/ExprBuilder.php b/src/Symfony/Component/Config/Definition/Builder/ExprBuilder.php index dd82e8308cc5e..f5547a6e0a405 100644 --- a/src/Symfony/Component/Config/Definition/Builder/ExprBuilder.php +++ b/src/Symfony/Component/Config/Definition/Builder/ExprBuilder.php @@ -40,7 +40,7 @@ public function __construct( * * @return $this */ - public function always(\Closure $then = null): static + public function always(?\Closure $then = null): static { $this->ifPart = static fn () => true; $this->allowedTypes = self::TYPE_ANY; @@ -59,7 +59,7 @@ public function always(\Closure $then = null): static * * @return $this */ - public function ifTrue(\Closure $closure = null): static + public function ifTrue(?\Closure $closure = null): static { $this->ifPart = $closure ?? static fn ($v) => true === $v; $this->allowedTypes = self::TYPE_ANY; diff --git a/src/Symfony/Component/Config/Definition/Builder/NodeDefinition.php b/src/Symfony/Component/Config/Definition/Builder/NodeDefinition.php index cdfd1993dc86a..54e976e246ec6 100644 --- a/src/Symfony/Component/Config/Definition/Builder/NodeDefinition.php +++ b/src/Symfony/Component/Config/Definition/Builder/NodeDefinition.php @@ -38,7 +38,7 @@ abstract class NodeDefinition implements NodeParentInterface protected NodeParentInterface|NodeInterface|null $parent; protected array $attributes = []; - public function __construct(?string $name, NodeParentInterface $parent = null) + public function __construct(?string $name, ?NodeParentInterface $parent = null) { $this->parent = $parent; $this->name = $name; diff --git a/src/Symfony/Component/Config/Definition/Builder/NormalizationBuilder.php b/src/Symfony/Component/Config/Definition/Builder/NormalizationBuilder.php index a64fc76b7bf24..8a8141c19fd6b 100644 --- a/src/Symfony/Component/Config/Definition/Builder/NormalizationBuilder.php +++ b/src/Symfony/Component/Config/Definition/Builder/NormalizationBuilder.php @@ -35,7 +35,7 @@ public function __construct( * * @return $this */ - public function remap(string $key, string $plural = null): static + public function remap(string $key, ?string $plural = null): static { $this->remappings[] = [$key, null === $plural ? $key.'s' : $plural]; @@ -47,7 +47,7 @@ public function remap(string $key, string $plural = null): static * * @return ExprBuilder|$this */ - public function before(\Closure $closure = null): ExprBuilder|static + public function before(?\Closure $closure = null): ExprBuilder|static { if (null !== $closure) { $this->before[] = $closure; diff --git a/src/Symfony/Component/Config/Definition/Builder/TreeBuilder.php b/src/Symfony/Component/Config/Definition/Builder/TreeBuilder.php index 97544b94d87df..5170e19d1e266 100644 --- a/src/Symfony/Component/Config/Definition/Builder/TreeBuilder.php +++ b/src/Symfony/Component/Config/Definition/Builder/TreeBuilder.php @@ -23,7 +23,7 @@ class TreeBuilder implements NodeParentInterface protected ?NodeInterface $tree = null; protected ?NodeDefinition $root = null; - public function __construct(string $name, string $type = 'array', NodeBuilder $builder = null) + public function __construct(string $name, string $type = 'array', ?NodeBuilder $builder = null) { $builder ??= new NodeBuilder(); $this->root = $builder->node($name, $type)->setParent($this); diff --git a/src/Symfony/Component/Config/Definition/Builder/ValidationBuilder.php b/src/Symfony/Component/Config/Definition/Builder/ValidationBuilder.php index d99cbb1bcb96e..ad22393495d6a 100644 --- a/src/Symfony/Component/Config/Definition/Builder/ValidationBuilder.php +++ b/src/Symfony/Component/Config/Definition/Builder/ValidationBuilder.php @@ -30,7 +30,7 @@ public function __construct( * * @return ExprBuilder|$this */ - public function rule(\Closure $closure = null): ExprBuilder|static + public function rule(?\Closure $closure = null): ExprBuilder|static { if (null !== $closure) { $this->rules[] = $closure; diff --git a/src/Symfony/Component/Config/Definition/Configurator/DefinitionConfigurator.php b/src/Symfony/Component/Config/Definition/Configurator/DefinitionConfigurator.php index 006a444bedcb0..13fe45ca45557 100644 --- a/src/Symfony/Component/Config/Definition/Configurator/DefinitionConfigurator.php +++ b/src/Symfony/Component/Config/Definition/Configurator/DefinitionConfigurator.php @@ -29,7 +29,7 @@ public function __construct( ) { } - public function import(string $resource, string $type = null, bool $ignoreErrors = false): void + public function import(string $resource, ?string $type = null, bool $ignoreErrors = false): void { $this->loader->setCurrentDir(\dirname($this->path)); $this->loader->import($resource, $type, $ignoreErrors, $this->file); diff --git a/src/Symfony/Component/Config/Definition/Dumper/XmlReferenceDumper.php b/src/Symfony/Component/Config/Definition/Dumper/XmlReferenceDumper.php index 277bf0f71f08a..34584c43b8ae1 100644 --- a/src/Symfony/Component/Config/Definition/Dumper/XmlReferenceDumper.php +++ b/src/Symfony/Component/Config/Definition/Dumper/XmlReferenceDumper.php @@ -31,12 +31,12 @@ class XmlReferenceDumper { private ?string $reference = null; - public function dump(ConfigurationInterface $configuration, string $namespace = null): string + public function dump(ConfigurationInterface $configuration, ?string $namespace = null): string { return $this->dumpNode($configuration->getConfigTreeBuilder()->buildTree(), $namespace); } - public function dumpNode(NodeInterface $node, string $namespace = null): string + public function dumpNode(NodeInterface $node, ?string $namespace = null): string { $this->reference = ''; $this->writeNode($node, 0, true, $namespace); @@ -46,7 +46,7 @@ public function dumpNode(NodeInterface $node, string $namespace = null): string return $ref; } - private function writeNode(NodeInterface $node, int $depth = 0, bool $root = false, string $namespace = null): void + private function writeNode(NodeInterface $node, int $depth = 0, bool $root = false, ?string $namespace = null): void { $rootName = ($root ? 'config' : $node->getName()); $rootNamespace = ($namespace ?: ($root ? 'http://example.org/schema/dic/'.$node->getName() : null)); diff --git a/src/Symfony/Component/Config/Definition/Dumper/YamlReferenceDumper.php b/src/Symfony/Component/Config/Definition/Dumper/YamlReferenceDumper.php index 46d6ec61eef4f..4f720aa97f9f4 100644 --- a/src/Symfony/Component/Config/Definition/Dumper/YamlReferenceDumper.php +++ b/src/Symfony/Component/Config/Definition/Dumper/YamlReferenceDumper.php @@ -71,7 +71,7 @@ public function dumpNode(NodeInterface $node): string return $ref; } - private function writeNode(NodeInterface $node, NodeInterface $parentNode = null, int $depth = 0, bool $prototypedArray = false): void + private function writeNode(NodeInterface $node, ?NodeInterface $parentNode = null, int $depth = 0, bool $prototypedArray = false): void { $comments = []; $default = ''; diff --git a/src/Symfony/Component/Config/Definition/EnumNode.php b/src/Symfony/Component/Config/Definition/EnumNode.php index 6bbe6fd39e55b..29fe0bdfba8c6 100644 --- a/src/Symfony/Component/Config/Definition/EnumNode.php +++ b/src/Symfony/Component/Config/Definition/EnumNode.php @@ -22,7 +22,7 @@ class EnumNode extends ScalarNode { private array $values; - public function __construct(?string $name, NodeInterface $parent = null, array $values = [], string $pathSeparator = BaseNode::DEFAULT_PATH_SEPARATOR) + public function __construct(?string $name, ?NodeInterface $parent = null, array $values = [], string $pathSeparator = BaseNode::DEFAULT_PATH_SEPARATOR) { if (!$values) { throw new \InvalidArgumentException('$values must contain at least one element.'); diff --git a/src/Symfony/Component/Config/Definition/Loader/DefinitionFileLoader.php b/src/Symfony/Component/Config/Definition/Loader/DefinitionFileLoader.php index 506f787cab4cb..940b894f77323 100644 --- a/src/Symfony/Component/Config/Definition/Loader/DefinitionFileLoader.php +++ b/src/Symfony/Component/Config/Definition/Loader/DefinitionFileLoader.php @@ -34,7 +34,7 @@ public function __construct( parent::__construct($locator); } - public function load(mixed $resource, string $type = null): mixed + public function load(mixed $resource, ?string $type = null): mixed { // the loader variable is exposed to the included file below $loader = $this; @@ -57,7 +57,7 @@ public function load(mixed $resource, string $type = null): mixed return null; } - public function supports(mixed $resource, string $type = null): bool + public function supports(mixed $resource, ?string $type = null): bool { if (!\is_string($resource)) { return false; diff --git a/src/Symfony/Component/Config/Definition/NumericNode.php b/src/Symfony/Component/Config/Definition/NumericNode.php index 1ea84ba85f7f2..b55ee922bdb6f 100644 --- a/src/Symfony/Component/Config/Definition/NumericNode.php +++ b/src/Symfony/Component/Config/Definition/NumericNode.php @@ -23,7 +23,7 @@ class NumericNode extends ScalarNode protected int|float|null $min; protected int|float|null $max; - public function __construct(?string $name, NodeInterface $parent = null, int|float $min = null, int|float $max = null, string $pathSeparator = BaseNode::DEFAULT_PATH_SEPARATOR) + public function __construct(?string $name, ?NodeInterface $parent = null, int|float|null $min = null, int|float|null $max = null, string $pathSeparator = BaseNode::DEFAULT_PATH_SEPARATOR) { parent::__construct($name, $parent, $pathSeparator); $this->min = $min; diff --git a/src/Symfony/Component/Config/Definition/Processor.php b/src/Symfony/Component/Config/Definition/Processor.php index dc3d4c69bbe44..272ddcc447360 100644 --- a/src/Symfony/Component/Config/Definition/Processor.php +++ b/src/Symfony/Component/Config/Definition/Processor.php @@ -67,7 +67,7 @@ public function processConfiguration(ConfigurationInterface $configuration, arra * @param string $key The key to normalize * @param string|null $plural The plural form of the key if it is irregular */ - public static function normalizeConfig(array $config, string $key, string $plural = null): array + public static function normalizeConfig(array $config, string $key, ?string $plural = null): array { $plural ??= $key.'s'; diff --git a/src/Symfony/Component/Config/Exception/FileLoaderImportCircularReferenceException.php b/src/Symfony/Component/Config/Exception/FileLoaderImportCircularReferenceException.php index da0b55ba8ca61..2d2a4de004945 100644 --- a/src/Symfony/Component/Config/Exception/FileLoaderImportCircularReferenceException.php +++ b/src/Symfony/Component/Config/Exception/FileLoaderImportCircularReferenceException.php @@ -18,7 +18,7 @@ */ class FileLoaderImportCircularReferenceException extends LoaderLoadException { - public function __construct(array $resources, int $code = 0, \Throwable $previous = null) + public function __construct(array $resources, int $code = 0, ?\Throwable $previous = null) { $message = sprintf('Circular reference detected in "%s" ("%s" > "%s").', $this->varToString($resources[0]), implode('" > "', $resources), $resources[0]); diff --git a/src/Symfony/Component/Config/Exception/FileLocatorFileNotFoundException.php b/src/Symfony/Component/Config/Exception/FileLocatorFileNotFoundException.php index 591182e53bac5..5641a31457289 100644 --- a/src/Symfony/Component/Config/Exception/FileLocatorFileNotFoundException.php +++ b/src/Symfony/Component/Config/Exception/FileLocatorFileNotFoundException.php @@ -20,7 +20,7 @@ class FileLocatorFileNotFoundException extends \InvalidArgumentException { private array $paths; - public function __construct(string $message = '', int $code = 0, \Throwable $previous = null, array $paths = []) + public function __construct(string $message = '', int $code = 0, ?\Throwable $previous = null, array $paths = []) { parent::__construct($message, $code, $previous); diff --git a/src/Symfony/Component/Config/Exception/LoaderLoadException.php b/src/Symfony/Component/Config/Exception/LoaderLoadException.php index 495e9da272ab5..a2d5e33c7392f 100644 --- a/src/Symfony/Component/Config/Exception/LoaderLoadException.php +++ b/src/Symfony/Component/Config/Exception/LoaderLoadException.php @@ -25,7 +25,7 @@ class LoaderLoadException extends \Exception * @param \Throwable|null $previous A previous exception * @param string|null $type The type of resource */ - public function __construct(mixed $resource, string $sourceResource = null, int $code = 0, \Throwable $previous = null, string $type = null) + public function __construct(mixed $resource, ?string $sourceResource = null, int $code = 0, ?\Throwable $previous = null, ?string $type = null) { if (!\is_string($resource)) { try { diff --git a/src/Symfony/Component/Config/FileLocator.php b/src/Symfony/Component/Config/FileLocator.php index a2a35b1c7e628..bf800694b519e 100644 --- a/src/Symfony/Component/Config/FileLocator.php +++ b/src/Symfony/Component/Config/FileLocator.php @@ -30,7 +30,12 @@ public function __construct(string|array $paths = []) $this->paths = (array) $paths; } - public function locate(string $name, string $currentPath = null, bool $first = true): string|array + /** + * @return string|string[] + * + * @psalm-return ($first is true ? string : string[]) + */ + public function locate(string $name, ?string $currentPath = null, bool $first = true): string|array { if ('' === $name) { throw new \InvalidArgumentException('An empty file name is not valid to be located.'); diff --git a/src/Symfony/Component/Config/FileLocatorInterface.php b/src/Symfony/Component/Config/FileLocatorInterface.php index 526d35048412e..87cecf47729bb 100644 --- a/src/Symfony/Component/Config/FileLocatorInterface.php +++ b/src/Symfony/Component/Config/FileLocatorInterface.php @@ -25,10 +25,12 @@ interface FileLocatorInterface * @param string|null $currentPath The current path * @param bool $first Whether to return the first occurrence or an array of filenames * - * @return string|array The full path to the file or an array of file paths + * @return string|string[] The full path to the file or an array of file paths * * @throws \InvalidArgumentException If $name is empty * @throws FileLocatorFileNotFoundException If a file is not found + * + * @psalm-return ($first is true ? string : string[]) */ - public function locate(string $name, string $currentPath = null, bool $first = true): string|array; + public function locate(string $name, ?string $currentPath = null, bool $first = true): string|array; } diff --git a/src/Symfony/Component/Config/Loader/DelegatingLoader.php b/src/Symfony/Component/Config/Loader/DelegatingLoader.php index fac3724e9eac3..045a559e2bad0 100644 --- a/src/Symfony/Component/Config/Loader/DelegatingLoader.php +++ b/src/Symfony/Component/Config/Loader/DelegatingLoader.php @@ -28,7 +28,7 @@ public function __construct(LoaderResolverInterface $resolver) $this->resolver = $resolver; } - public function load(mixed $resource, string $type = null): mixed + public function load(mixed $resource, ?string $type = null): mixed { if (false === $loader = $this->resolver->resolve($resource, $type)) { throw new LoaderLoadException($resource, null, 0, null, $type); @@ -37,7 +37,7 @@ public function load(mixed $resource, string $type = null): mixed return $loader->load($resource, $type); } - public function supports(mixed $resource, string $type = null): bool + public function supports(mixed $resource, ?string $type = null): bool { return false !== $this->resolver->resolve($resource, $type); } diff --git a/src/Symfony/Component/Config/Loader/FileLoader.php b/src/Symfony/Component/Config/Loader/FileLoader.php index af1e5e7443764..c217cd85bfedd 100644 --- a/src/Symfony/Component/Config/Loader/FileLoader.php +++ b/src/Symfony/Component/Config/Loader/FileLoader.php @@ -31,7 +31,7 @@ abstract class FileLoader extends Loader private ?string $currentDir = null; - public function __construct(FileLocatorInterface $locator, string $env = null) + public function __construct(FileLocatorInterface $locator, ?string $env = null) { $this->locator = $locator; parent::__construct($env); @@ -66,7 +66,7 @@ public function getLocator(): FileLocatorInterface * @throws FileLoaderImportCircularReferenceException * @throws FileLocatorFileNotFoundException */ - public function import(mixed $resource, string $type = null, bool $ignoreErrors = false, string $sourceResource = null, string|array $exclude = null): mixed + public function import(mixed $resource, ?string $type = null, bool $ignoreErrors = false, ?string $sourceResource = null, string|array|null $exclude = null): mixed { if (\is_string($resource) && \strlen($resource) !== ($i = strcspn($resource, '*?{[')) && !str_contains($resource, "\n")) { $excluded = []; @@ -97,7 +97,7 @@ public function import(mixed $resource, string $type = null, bool $ignoreErrors /** * @internal */ - protected function glob(string $pattern, bool $recursive, array|GlobResource &$resource = null, bool $ignoreErrors = false, bool $forExclusion = false, array $excluded = []): iterable + protected function glob(string $pattern, bool $recursive, array|GlobResource|null &$resource = null, bool $ignoreErrors = false, bool $forExclusion = false, array $excluded = []): iterable { if (\strlen($pattern) === $i = strcspn($pattern, '*?{[')) { $prefix = $pattern; @@ -129,7 +129,7 @@ protected function glob(string $pattern, bool $recursive, array|GlobResource &$r yield from $resource; } - private function doImport(mixed $resource, string $type = null, bool $ignoreErrors = false, string $sourceResource = null): mixed + private function doImport(mixed $resource, ?string $type = null, bool $ignoreErrors = false, ?string $sourceResource = null): mixed { try { $loader = $this->resolve($resource, $type); diff --git a/src/Symfony/Component/Config/Loader/GlobFileLoader.php b/src/Symfony/Component/Config/Loader/GlobFileLoader.php index f921ec555a654..31eebf69d8b15 100644 --- a/src/Symfony/Component/Config/Loader/GlobFileLoader.php +++ b/src/Symfony/Component/Config/Loader/GlobFileLoader.php @@ -18,12 +18,12 @@ */ class GlobFileLoader extends FileLoader { - public function load(mixed $resource, string $type = null): mixed + public function load(mixed $resource, ?string $type = null): mixed { return $this->import($resource); } - public function supports(mixed $resource, string $type = null): bool + public function supports(mixed $resource, ?string $type = null): bool { return 'glob' === $type; } diff --git a/src/Symfony/Component/Config/Loader/Loader.php b/src/Symfony/Component/Config/Loader/Loader.php index df94e9777777b..dc65ecb528be4 100644 --- a/src/Symfony/Component/Config/Loader/Loader.php +++ b/src/Symfony/Component/Config/Loader/Loader.php @@ -40,7 +40,7 @@ public function setResolver(LoaderResolverInterface $resolver): void /** * Imports a resource. */ - public function import(mixed $resource, string $type = null): mixed + public function import(mixed $resource, ?string $type = null): mixed { return $this->resolve($resource, $type)->load($resource, $type); } @@ -50,7 +50,7 @@ public function import(mixed $resource, string $type = null): mixed * * @throws LoaderLoadException If no loader is found */ - public function resolve(mixed $resource, string $type = null): LoaderInterface + public function resolve(mixed $resource, ?string $type = null): LoaderInterface { if ($this->supports($resource, $type)) { return $this; diff --git a/src/Symfony/Component/Config/Loader/LoaderInterface.php b/src/Symfony/Component/Config/Loader/LoaderInterface.php index 0de7c8bb8fbcf..6ed1893a53ae4 100644 --- a/src/Symfony/Component/Config/Loader/LoaderInterface.php +++ b/src/Symfony/Component/Config/Loader/LoaderInterface.php @@ -23,14 +23,14 @@ interface LoaderInterface * * @throws \Exception If something went wrong */ - public function load(mixed $resource, string $type = null): mixed; + public function load(mixed $resource, ?string $type = null): mixed; /** * Returns whether this class supports the given resource. * * @param mixed $resource A resource */ - public function supports(mixed $resource, string $type = null): bool; + public function supports(mixed $resource, ?string $type = null): bool; /** * Gets the loader resolver. diff --git a/src/Symfony/Component/Config/Loader/LoaderResolver.php b/src/Symfony/Component/Config/Loader/LoaderResolver.php index 50bcc7fe90684..8308d7e895c0b 100644 --- a/src/Symfony/Component/Config/Loader/LoaderResolver.php +++ b/src/Symfony/Component/Config/Loader/LoaderResolver.php @@ -36,7 +36,7 @@ public function __construct(array $loaders = []) } } - public function resolve(mixed $resource, string $type = null): LoaderInterface|false + public function resolve(mixed $resource, ?string $type = null): LoaderInterface|false { foreach ($this->loaders as $loader) { if ($loader->supports($resource, $type)) { diff --git a/src/Symfony/Component/Config/Loader/LoaderResolverInterface.php b/src/Symfony/Component/Config/Loader/LoaderResolverInterface.php index 076c5207c9c16..a8bb3a43766f4 100644 --- a/src/Symfony/Component/Config/Loader/LoaderResolverInterface.php +++ b/src/Symfony/Component/Config/Loader/LoaderResolverInterface.php @@ -23,5 +23,5 @@ interface LoaderResolverInterface * * @param string|null $type The resource type or null if unknown */ - public function resolve(mixed $resource, string $type = null): LoaderInterface|false; + public function resolve(mixed $resource, ?string $type = null): LoaderInterface|false; } diff --git a/src/Symfony/Component/Config/Resource/ClassExistenceResource.php b/src/Symfony/Component/Config/Resource/ClassExistenceResource.php index 72932c969a12f..e2175b9ba7b48 100644 --- a/src/Symfony/Component/Config/Resource/ClassExistenceResource.php +++ b/src/Symfony/Component/Config/Resource/ClassExistenceResource.php @@ -35,7 +35,7 @@ class ClassExistenceResource implements SelfCheckingResourceInterface */ public function __construct( private string $resource, - bool $exists = null, + ?bool $exists = null, ) { if (null !== $exists) { $this->exists = [$exists, null]; @@ -139,7 +139,7 @@ public function __wakeup(): void * * @internal */ - public static function throwOnRequiredClass(string $class, \Exception $previous = null): void + public static function throwOnRequiredClass(string $class, ?\Exception $previous = null): void { // If the passed class is the resource being checked, we shouldn't throw. if (null === $previous && self::$autoloadedClass === $class) { diff --git a/src/Symfony/Component/Config/ResourceCheckerConfigCache.php b/src/Symfony/Component/Config/ResourceCheckerConfigCache.php index 5634f1ecfa9d0..8704bc91e311a 100644 --- a/src/Symfony/Component/Config/ResourceCheckerConfigCache.php +++ b/src/Symfony/Component/Config/ResourceCheckerConfigCache.php @@ -23,14 +23,19 @@ */ class ResourceCheckerConfigCache implements ConfigCacheInterface { + private string $metaFile; + /** * @param string $file The absolute cache path * @param iterable $resourceCheckers The ResourceCheckers to use for the freshness check + * @param string|null $metaFile The absolute path to the meta file, defaults to $file.meta if null */ public function __construct( private string $file, private iterable $resourceCheckers = [], + ?string $metaFile = null, ) { + $this->metaFile = $metaFile ?? $file.'.meta'; } public function getPath(): string @@ -61,7 +66,7 @@ public function isFresh(): bool return true; // shortcut - if we don't have any checkers we don't need to bother with the meta file at all } - $metadata = $this->getMetaFile(); + $metadata = $this->metaFile; if (!is_file($metadata)) { return false; @@ -100,7 +105,7 @@ public function isFresh(): bool * * @throws \RuntimeException When cache file can't be written */ - public function write(string $content, array $metadata = null): void + public function write(string $content, ?array $metadata = null): void { $mode = 0666; $umask = umask(); @@ -113,9 +118,9 @@ public function write(string $content, array $metadata = null): void } if (null !== $metadata) { - $filesystem->dumpFile($this->getMetaFile(), serialize($metadata)); + $filesystem->dumpFile($this->metaFile, serialize($metadata)); try { - $filesystem->chmod($this->getMetaFile(), $mode, $umask); + $filesystem->chmod($this->metaFile, $mode, $umask); } catch (IOException) { // discard chmod failure (some filesystem may not support it) } @@ -126,14 +131,6 @@ public function write(string $content, array $metadata = null): void } } - /** - * Gets the meta file path. - */ - private function getMetaFile(): string - { - return $this->file.'.meta'; - } - private function safelyUnserialize(string $file): mixed { $meta = false; diff --git a/src/Symfony/Component/Config/Tests/Builder/GeneratedConfigTest.php b/src/Symfony/Component/Config/Tests/Builder/GeneratedConfigTest.php index db2ade6ffa204..722df54cbcf26 100644 --- a/src/Symfony/Component/Config/Tests/Builder/GeneratedConfigTest.php +++ b/src/Symfony/Component/Config/Tests/Builder/GeneratedConfigTest.php @@ -162,7 +162,7 @@ public function testSetExtraKeyMethodIsNotGeneratedWhenAllowExtraKeysIsFalse() /** * Generate the ConfigBuilder or return an already generated instance. */ - private function generateConfigBuilder(string $configurationClass, string $outputDir = null) + private function generateConfigBuilder(string $configurationClass, ?string $outputDir = null) { $outputDir ??= sys_get_temp_dir().\DIRECTORY_SEPARATOR.uniqid('sf_config_builder', true); if (!str_contains($outputDir, __DIR__)) { diff --git a/src/Symfony/Component/Config/Tests/Definition/Builder/ExprBuilderTest.php b/src/Symfony/Component/Config/Tests/Definition/Builder/ExprBuilderTest.php index 873ffb4051e96..656919e65f617 100644 --- a/src/Symfony/Component/Config/Tests/Definition/Builder/ExprBuilderTest.php +++ b/src/Symfony/Component/Config/Tests/Definition/Builder/ExprBuilderTest.php @@ -223,7 +223,7 @@ protected function getTestBuilder(): ExprBuilder * @param array|null $config The config you want to use for the finalization, if nothing provided * a simple ['key'=>'value'] will be used */ - protected function finalizeTestBuilder(NodeDefinition $nodeDefinition, array $config = null): array + protected function finalizeTestBuilder(NodeDefinition $nodeDefinition, ?array $config = null): array { return $nodeDefinition ->end() diff --git a/src/Symfony/Component/Config/Tests/Loader/FileLoaderTest.php b/src/Symfony/Component/Config/Tests/Loader/FileLoaderTest.php index 4b7464a3cd977..185213964753b 100644 --- a/src/Symfony/Component/Config/Tests/Loader/FileLoaderTest.php +++ b/src/Symfony/Component/Config/Tests/Loader/FileLoaderTest.php @@ -155,12 +155,12 @@ class TestFileLoader extends FileLoader { private bool $supports = true; - public function load(mixed $resource, string $type = null): mixed + public function load(mixed $resource, ?string $type = null): mixed { return $resource; } - public function supports(mixed $resource, string $type = null): bool + public function supports(mixed $resource, ?string $type = null): bool { return $this->supports; } diff --git a/src/Symfony/Component/Config/Tests/Loader/LoaderTest.php b/src/Symfony/Component/Config/Tests/Loader/LoaderTest.php index 385103cebe2ec..70bfb8fc15005 100644 --- a/src/Symfony/Component/Config/Tests/Loader/LoaderTest.php +++ b/src/Symfony/Component/Config/Tests/Loader/LoaderTest.php @@ -105,11 +105,11 @@ public function testImportWithType() class ProjectLoader1 extends Loader { - public function load(mixed $resource, string $type = null): mixed + public function load(mixed $resource, ?string $type = null): mixed { } - public function supports(mixed $resource, string $type = null): bool + public function supports(mixed $resource, ?string $type = null): bool { return \is_string($resource) && 'foo' === pathinfo($resource, \PATHINFO_EXTENSION); } diff --git a/src/Symfony/Component/Config/Tests/Resource/ReflectionClassResourceTest.php b/src/Symfony/Component/Config/Tests/Resource/ReflectionClassResourceTest.php index 10e4e169d3f7f..07741556b1950 100644 --- a/src/Symfony/Component/Config/Tests/Resource/ReflectionClassResourceTest.php +++ b/src/Symfony/Component/Config/Tests/Resource/ReflectionClassResourceTest.php @@ -63,7 +63,7 @@ public function testIsFreshForDeletedResources() /** * @dataProvider provideHashedSignature */ - public function testHashedSignature(bool $changeExpected, int $changedLine, ?string $changedCode, \Closure $setContext = null) + public function testHashedSignature(bool $changeExpected, int $changedLine, ?string $changedCode, ?\Closure $setContext = null) { if ($setContext) { $setContext(); diff --git a/src/Symfony/Component/Config/Tests/ResourceCheckerConfigCacheTest.php b/src/Symfony/Component/Config/Tests/ResourceCheckerConfigCacheTest.php index 37f30a49d4ec0..53b66b88e447f 100644 --- a/src/Symfony/Component/Config/Tests/ResourceCheckerConfigCacheTest.php +++ b/src/Symfony/Component/Config/Tests/ResourceCheckerConfigCacheTest.php @@ -21,14 +21,17 @@ class ResourceCheckerConfigCacheTest extends TestCase { private string $cacheFile; + private string $metaFile; + protected function setUp(): void { $this->cacheFile = tempnam(sys_get_temp_dir(), 'config_'); + $this->metaFile = tempnam(sys_get_temp_dir(), 'config_'); } protected function tearDown(): void { - $files = [$this->cacheFile, "{$this->cacheFile}.meta"]; + $files = [$this->cacheFile, "{$this->cacheFile}.meta", $this->metaFile]; foreach ($files as $file) { if (file_exists($file)) { @@ -148,4 +151,15 @@ public function testCacheIsNotFreshIfNotExistsMetaFile() $this->assertFalse($cache->isFresh()); } + + public function testCacheWithCustomMetaFile() + { + $this->assertStringEqualsFile($this->metaFile, ''); + + $checker = $this->createMock(ResourceCheckerInterface::class); + $cache = new ResourceCheckerConfigCache($this->cacheFile, [$checker], $this->metaFile); + $cache->write('foo', [new FileResource(__FILE__)]); + + $this->assertStringNotEqualsFile($this->metaFile, ''); + } } diff --git a/src/Symfony/Component/Config/Util/XmlUtils.php b/src/Symfony/Component/Config/Util/XmlUtils.php index a21d261a248c7..d86a5823ad994 100644 --- a/src/Symfony/Component/Config/Util/XmlUtils.php +++ b/src/Symfony/Component/Config/Util/XmlUtils.php @@ -42,7 +42,7 @@ private function __construct() * @throws InvalidXmlException When parsing of XML with schema or callable produces any errors unrelated to the XML parsing itself * @throws \RuntimeException When DOM extension is missing */ - public static function parse(string $content, string|callable $schemaOrCallable = null): \DOMDocument + public static function parse(string $content, string|callable|null $schemaOrCallable = null): \DOMDocument { if (!\extension_loaded('dom')) { throw new \LogicException('Extension DOM is required.'); @@ -112,7 +112,7 @@ public static function parse(string $content, string|callable $schemaOrCallable * @throws XmlParsingException When XML parsing returns any errors * @throws \RuntimeException When DOM extension is missing */ - public static function loadFile(string $file, string|callable $schemaOrCallable = null): \DOMDocument + public static function loadFile(string $file, string|callable|null $schemaOrCallable = null): \DOMDocument { if (!is_file($file)) { throw new \InvalidArgumentException(sprintf('Resource "%s" is not a file.', $file)); diff --git a/src/Symfony/Component/Console/Application.php b/src/Symfony/Component/Console/Application.php index 00acef1dd1df0..9c301471c6581 100644 --- a/src/Symfony/Component/Console/Application.php +++ b/src/Symfony/Component/Console/Application.php @@ -135,7 +135,7 @@ public function setSignalsToDispatchEvent(int ...$signalsToDispatchEvent): void * * @throws \Exception When running fails. Bypass this when {@link setCatchExceptions()}. */ - public function run(InputInterface $input = null, OutputInterface $output = null): int + public function run(?InputInterface $input = null, ?OutputInterface $output = null): int { if (\function_exists('putenv')) { @putenv('LINES='.$this->terminal->getHeight()); @@ -760,7 +760,7 @@ public function find(string $name): Command * * @return Command[] */ - public function all(string $namespace = null): array + public function all(?string $namespace = null): array { $this->init(); @@ -1128,7 +1128,7 @@ private function getAbbreviationSuggestions(array $abbrevs): string * * This method is not part of public API and should not be used directly. */ - public function extractNamespace(string $name, int $limit = null): string + public function extractNamespace(string $name, ?int $limit = null): string { $parts = explode(':', $name, -1); diff --git a/src/Symfony/Component/Console/CI/GithubActionReporter.php b/src/Symfony/Component/Console/CI/GithubActionReporter.php index 7e5565469a954..2cae6fd8ba34c 100644 --- a/src/Symfony/Component/Console/CI/GithubActionReporter.php +++ b/src/Symfony/Component/Console/CI/GithubActionReporter.php @@ -57,7 +57,7 @@ public static function isGithubActionEnvironment(): bool * * @see https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-commands-for-github-actions#setting-an-error-message */ - public function error(string $message, string $file = null, int $line = null, int $col = null): void + public function error(string $message, ?string $file = null, ?int $line = null, ?int $col = null): void { $this->log('error', $message, $file, $line, $col); } @@ -67,7 +67,7 @@ public function error(string $message, string $file = null, int $line = null, in * * @see https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-commands-for-github-actions#setting-a-warning-message */ - public function warning(string $message, string $file = null, int $line = null, int $col = null): void + public function warning(string $message, ?string $file = null, ?int $line = null, ?int $col = null): void { $this->log('warning', $message, $file, $line, $col); } @@ -77,12 +77,12 @@ public function warning(string $message, string $file = null, int $line = null, * * @see https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-commands-for-github-actions#setting-a-debug-message */ - public function debug(string $message, string $file = null, int $line = null, int $col = null): void + public function debug(string $message, ?string $file = null, ?int $line = null, ?int $col = null): void { $this->log('debug', $message, $file, $line, $col); } - private function log(string $type, string $message, string $file = null, int $line = null, int $col = null): void + private function log(string $type, string $message, ?string $file = null, ?int $line = null, ?int $col = null): void { // Some values must be encoded. $message = strtr($message, self::ESCAPED_DATA); diff --git a/src/Symfony/Component/Console/Command/Command.php b/src/Symfony/Component/Console/Command/Command.php index ef1e7c31e9087..03da6db43f335 100644 --- a/src/Symfony/Component/Console/Command/Command.php +++ b/src/Symfony/Component/Console/Command/Command.php @@ -77,7 +77,7 @@ public static function getDefaultDescription(): ?string * * @throws LogicException When the command name is empty */ - public function __construct(string $name = null) + public function __construct(?string $name = null) { $this->definition = new InputDefinition(); @@ -283,7 +283,7 @@ public function run(InputInterface $input, OutputInterface $output): int } /** - * Adds suggestions to $suggestions for the current completion input (e.g. option or argument). + * Supplies suggestions when resolving possible completion options for input (e.g. option or argument). */ public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void { @@ -401,15 +401,15 @@ public function getNativeDefinition(): InputDefinition /** * Adds an argument. * - * @param $mode The argument mode: InputArgument::REQUIRED or InputArgument::OPTIONAL - * @param $default The default value (for InputArgument::OPTIONAL mode only) + * @param $mode The argument mode: InputArgument::REQUIRED or InputArgument::OPTIONAL + * @param $default The default value (for InputArgument::OPTIONAL mode only) * @param array|\Closure(CompletionInput,CompletionSuggestions):list $suggestedValues The values used for input completion * * @return $this * * @throws InvalidArgumentException When argument mode is not valid */ - public function addArgument(string $name, int $mode = null, string $description = '', mixed $default = null, array|\Closure $suggestedValues = []): static + public function addArgument(string $name, ?int $mode = null, string $description = '', mixed $default = null, array|\Closure $suggestedValues = []): static { $this->definition->addArgument(new InputArgument($name, $mode, $description, $default, $suggestedValues)); $this->fullDefinition?->addArgument(new InputArgument($name, $mode, $description, $default, $suggestedValues)); @@ -420,16 +420,16 @@ public function addArgument(string $name, int $mode = null, string $description /** * Adds an option. * - * @param $shortcut The shortcuts, can be null, a string of shortcuts delimited by | or an array of shortcuts - * @param $mode The option mode: One of the InputOption::VALUE_* constants - * @param $default The default value (must be null for InputOption::VALUE_NONE) + * @param $shortcut The shortcuts, can be null, a string of shortcuts delimited by | or an array of shortcuts + * @param $mode The option mode: One of the InputOption::VALUE_* constants + * @param $default The default value (must be null for InputOption::VALUE_NONE) * @param array|\Closure(CompletionInput,CompletionSuggestions):list $suggestedValues The values used for input completion * * @return $this * * @throws InvalidArgumentException If option mode is invalid or incompatible */ - public function addOption(string $name, string|array $shortcut = null, int $mode = null, string $description = '', mixed $default = null, array|\Closure $suggestedValues = []): static + public function addOption(string $name, string|array|null $shortcut = null, ?int $mode = null, string $description = '', mixed $default = null, array|\Closure $suggestedValues = []): static { $this->definition->addOption(new InputOption($name, $shortcut, $mode, $description, $default, $suggestedValues)); $this->fullDefinition?->addOption(new InputOption($name, $shortcut, $mode, $description, $default, $suggestedValues)); diff --git a/src/Symfony/Component/Console/Command/LazyCommand.php b/src/Symfony/Component/Console/Command/LazyCommand.php index df44b3d01d8ec..fd2c300d7fb90 100644 --- a/src/Symfony/Component/Console/Command/LazyCommand.php +++ b/src/Symfony/Component/Console/Command/LazyCommand.php @@ -117,7 +117,7 @@ public function getNativeDefinition(): InputDefinition /** * @param array|\Closure(CompletionInput,CompletionSuggestions):list $suggestedValues The values used for input completion */ - public function addArgument(string $name, int $mode = null, string $description = '', mixed $default = null, array|\Closure $suggestedValues = []): static + public function addArgument(string $name, ?int $mode = null, string $description = '', mixed $default = null, array|\Closure $suggestedValues = []): static { $this->getCommand()->addArgument($name, $mode, $description, $default, $suggestedValues); @@ -127,7 +127,7 @@ public function addArgument(string $name, int $mode = null, string $description /** * @param array|\Closure(CompletionInput,CompletionSuggestions):list $suggestedValues The values used for input completion */ - public function addOption(string $name, string|array $shortcut = null, int $mode = null, string $description = '', mixed $default = null, array|\Closure $suggestedValues = []): static + public function addOption(string $name, string|array|null $shortcut = null, ?int $mode = null, string $description = '', mixed $default = null, array|\Closure $suggestedValues = []): static { $this->getCommand()->addOption($name, $shortcut, $mode, $description, $default, $suggestedValues); diff --git a/src/Symfony/Component/Console/Command/LockableTrait.php b/src/Symfony/Component/Console/Command/LockableTrait.php index c1006a65c0aff..cd7548f02f9e9 100644 --- a/src/Symfony/Component/Console/Command/LockableTrait.php +++ b/src/Symfony/Component/Console/Command/LockableTrait.php @@ -29,7 +29,7 @@ trait LockableTrait /** * Locks a command. */ - private function lock(string $name = null, bool $blocking = false): bool + private function lock(?string $name = null, bool $blocking = false): bool { if (!class_exists(SemaphoreStore::class)) { throw new LogicException('To enable the locking feature you must install the symfony/lock component. Try running "composer require symfony/lock".'); diff --git a/src/Symfony/Component/Console/Command/TraceableCommand.php b/src/Symfony/Component/Console/Command/TraceableCommand.php index d8c46b7faa1ed..9ffb68da39766 100644 --- a/src/Symfony/Component/Console/Command/TraceableCommand.php +++ b/src/Symfony/Component/Console/Command/TraceableCommand.php @@ -134,7 +134,7 @@ public function ignoreValidationErrors(): void parent::ignoreValidationErrors(); } - public function setApplication(Application $application = null): void + public function setApplication(?Application $application = null): void { $this->command->setApplication($application); } @@ -209,14 +209,14 @@ public function getNativeDefinition(): InputDefinition return $this->command->getNativeDefinition(); } - public function addArgument(string $name, int $mode = null, string $description = '', mixed $default = null, array|\Closure $suggestedValues = []): static + public function addArgument(string $name, ?int $mode = null, string $description = '', mixed $default = null, array|\Closure $suggestedValues = []): static { $this->command->addArgument($name, $mode, $description, $default, $suggestedValues); return $this; } - public function addOption(string $name, string|array $shortcut = null, int $mode = null, string $description = '', mixed $default = null, array|\Closure $suggestedValues = []): static + public function addOption(string $name, string|array|null $shortcut = null, ?int $mode = null, string $description = '', mixed $default = null, array|\Closure $suggestedValues = []): static { $this->command->addOption($name, $shortcut, $mode, $description, $default, $suggestedValues); diff --git a/src/Symfony/Component/Console/DataCollector/CommandDataCollector.php b/src/Symfony/Component/Console/DataCollector/CommandDataCollector.php index 16a0eadf4802f..45138c7dc0f86 100644 --- a/src/Symfony/Component/Console/DataCollector/CommandDataCollector.php +++ b/src/Symfony/Component/Console/DataCollector/CommandDataCollector.php @@ -27,7 +27,7 @@ */ final class CommandDataCollector extends DataCollector { - public function collect(Request $request, Response $response, \Throwable $exception = null): void + public function collect(Request $request, Response $response, ?\Throwable $exception = null): void { if (!$request instanceof CliRequest) { return; diff --git a/src/Symfony/Component/Console/Descriptor/XmlDescriptor.php b/src/Symfony/Component/Console/Descriptor/XmlDescriptor.php index 72580fd9852b4..866c718566fec 100644 --- a/src/Symfony/Component/Console/Descriptor/XmlDescriptor.php +++ b/src/Symfony/Component/Console/Descriptor/XmlDescriptor.php @@ -79,7 +79,7 @@ public function getCommandDocument(Command $command, bool $short = false): \DOMD return $dom; } - public function getApplicationDocument(Application $application, string $namespace = null, bool $short = false): \DOMDocument + public function getApplicationDocument(Application $application, ?string $namespace = null, bool $short = false): \DOMDocument { $dom = new \DOMDocument('1.0', 'UTF-8'); $dom->appendChild($rootXml = $dom->createElement('symfony')); diff --git a/src/Symfony/Component/Console/Event/ConsoleErrorEvent.php b/src/Symfony/Component/Console/Event/ConsoleErrorEvent.php index f469fbcee0653..1c0d62652d913 100644 --- a/src/Symfony/Component/Console/Event/ConsoleErrorEvent.php +++ b/src/Symfony/Component/Console/Event/ConsoleErrorEvent.php @@ -28,7 +28,7 @@ public function __construct( InputInterface $input, OutputInterface $output, private \Throwable $error, - Command $command = null, + ?Command $command = null, ) { parent::__construct($command, $input, $output); } diff --git a/src/Symfony/Component/Console/Exception/CommandNotFoundException.php b/src/Symfony/Component/Console/Exception/CommandNotFoundException.php index 47750d5f94131..246f04fa20e31 100644 --- a/src/Symfony/Component/Console/Exception/CommandNotFoundException.php +++ b/src/Symfony/Component/Console/Exception/CommandNotFoundException.php @@ -28,7 +28,7 @@ public function __construct( string $message, private array $alternatives = [], int $code = 0, - \Throwable $previous = null, + ?\Throwable $previous = null, ) { parent::__construct($message, $code, $previous); } diff --git a/src/Symfony/Component/Console/Formatter/OutputFormatterStyle.php b/src/Symfony/Component/Console/Formatter/OutputFormatterStyle.php index 4582ccd051116..20a65b517c568 100644 --- a/src/Symfony/Component/Console/Formatter/OutputFormatterStyle.php +++ b/src/Symfony/Component/Console/Formatter/OutputFormatterStyle.php @@ -33,7 +33,7 @@ class OutputFormatterStyle implements OutputFormatterStyleInterface * @param string|null $foreground The style foreground color name * @param string|null $background The style background color name */ - public function __construct(string $foreground = null, string $background = null, array $options = []) + public function __construct(?string $foreground = null, ?string $background = null, array $options = []) { $this->color = new Color($this->foreground = $foreground ?: '', $this->background = $background ?: '', $this->options = $options); } diff --git a/src/Symfony/Component/Console/Formatter/OutputFormatterStyleStack.php b/src/Symfony/Component/Console/Formatter/OutputFormatterStyleStack.php index c3726a35d840a..4985213abc075 100644 --- a/src/Symfony/Component/Console/Formatter/OutputFormatterStyleStack.php +++ b/src/Symfony/Component/Console/Formatter/OutputFormatterStyleStack.php @@ -26,7 +26,7 @@ class OutputFormatterStyleStack implements ResetInterface private OutputFormatterStyleInterface $emptyStyle; - public function __construct(OutputFormatterStyleInterface $emptyStyle = null) + public function __construct(?OutputFormatterStyleInterface $emptyStyle = null) { $this->emptyStyle = $emptyStyle ?? new OutputFormatterStyle(); $this->reset(); @@ -53,7 +53,7 @@ public function push(OutputFormatterStyleInterface $style): void * * @throws InvalidArgumentException When style tags incorrectly nested */ - public function pop(OutputFormatterStyleInterface $style = null): OutputFormatterStyleInterface + public function pop(?OutputFormatterStyleInterface $style = null): OutputFormatterStyleInterface { if (!$this->styles) { return $this->emptyStyle; diff --git a/src/Symfony/Component/Console/Helper/Helper.php b/src/Symfony/Component/Console/Helper/Helper.php index afb20ad50377a..de090063a1380 100644 --- a/src/Symfony/Component/Console/Helper/Helper.php +++ b/src/Symfony/Component/Console/Helper/Helper.php @@ -74,7 +74,7 @@ public static function length(?string $string): int /** * Returns the subset of a string, using mb_substr if it is available. */ - public static function substr(?string $string, int $from, int $length = null): string + public static function substr(?string $string, int $from, ?int $length = null): string { $string ??= ''; diff --git a/src/Symfony/Component/Console/Helper/HelperSet.php b/src/Symfony/Component/Console/Helper/HelperSet.php index 42153b68d01c0..30df9f9582cc1 100644 --- a/src/Symfony/Component/Console/Helper/HelperSet.php +++ b/src/Symfony/Component/Console/Helper/HelperSet.php @@ -35,7 +35,7 @@ public function __construct(array $helpers = []) } } - public function set(HelperInterface $helper, string $alias = null): void + public function set(HelperInterface $helper, ?string $alias = null): void { $this->helpers[$helper->getName()] = $helper; if (null !== $alias) { diff --git a/src/Symfony/Component/Console/Helper/ProcessHelper.php b/src/Symfony/Component/Console/Helper/ProcessHelper.php index 26d35a1a89d12..3ef6f71f753aa 100644 --- a/src/Symfony/Component/Console/Helper/ProcessHelper.php +++ b/src/Symfony/Component/Console/Helper/ProcessHelper.php @@ -32,7 +32,7 @@ class ProcessHelper extends Helper * @param callable|null $callback A PHP callback to run whenever there is some * output available on STDOUT or STDERR */ - public function run(OutputInterface $output, array|Process $cmd, string $error = null, callable $callback = null, int $verbosity = OutputInterface::VERBOSITY_VERY_VERBOSE): Process + public function run(OutputInterface $output, array|Process $cmd, ?string $error = null, ?callable $callback = null, int $verbosity = OutputInterface::VERBOSITY_VERY_VERBOSE): Process { if (!class_exists(Process::class)) { throw new \LogicException('The ProcessHelper cannot be run as the Process component is not installed. Try running "compose require symfony/process".'); @@ -94,7 +94,7 @@ public function run(OutputInterface $output, array|Process $cmd, string $error = * * @see run() */ - public function mustRun(OutputInterface $output, array|Process $cmd, string $error = null, callable $callback = null): Process + public function mustRun(OutputInterface $output, array|Process $cmd, ?string $error = null, ?callable $callback = null): Process { $process = $this->run($output, $cmd, $error, $callback); @@ -108,7 +108,7 @@ public function mustRun(OutputInterface $output, array|Process $cmd, string $err /** * Wraps a Process callback to add debugging output. */ - public function wrapCallback(OutputInterface $output, Process $process, callable $callback = null): callable + public function wrapCallback(OutputInterface $output, Process $process, ?callable $callback = null): callable { if ($output instanceof ConsoleOutputInterface) { $output = $output->getErrorOutput(); diff --git a/src/Symfony/Component/Console/Helper/ProgressBar.php b/src/Symfony/Component/Console/Helper/ProgressBar.php index a1f806d5f3113..70fe50ee4970f 100644 --- a/src/Symfony/Component/Console/Helper/ProgressBar.php +++ b/src/Symfony/Component/Console/Helper/ProgressBar.php @@ -313,7 +313,7 @@ public function maxSecondsBetweenRedraws(float $seconds): void * * @return iterable */ - public function iterate(iterable $iterable, int $max = null): iterable + public function iterate(iterable $iterable, ?int $max = null): iterable { if (0 === $max) { $max = null; @@ -346,7 +346,7 @@ public function iterate(iterable $iterable, int $max = null): iterable * @param int|null $max Number of steps to complete the bar (0 if indeterminate), null to leave unchanged * @param int $startAt The starting point of the bar (useful e.g. when resuming a previously started bar) */ - public function start(int $max = null, int $startAt = 0): void + public function start(?int $max = null, int $startAt = 0): void { $this->startTime = time(); $this->step = $startAt; diff --git a/src/Symfony/Component/Console/Helper/ProgressIndicator.php b/src/Symfony/Component/Console/Helper/ProgressIndicator.php index 8ebf991458f26..969d83539d8e2 100644 --- a/src/Symfony/Component/Console/Helper/ProgressIndicator.php +++ b/src/Symfony/Component/Console/Helper/ProgressIndicator.php @@ -50,9 +50,9 @@ class ProgressIndicator */ public function __construct( private OutputInterface $output, - string $format = null, + ?string $format = null, private int $indicatorChangeInterval = 100, - array $indicatorValues = null, + ?array $indicatorValues = null, ) { $format ??= $this->determineBestFormat(); diff --git a/src/Symfony/Component/Console/Helper/QuestionHelper.php b/src/Symfony/Component/Console/Helper/QuestionHelper.php index cb75ac914cb06..0967156cae04d 100644 --- a/src/Symfony/Component/Console/Helper/QuestionHelper.php +++ b/src/Symfony/Component/Console/Helper/QuestionHelper.php @@ -34,11 +34,6 @@ */ class QuestionHelper extends Helper { - /** - * @var resource|null - */ - private $inputStream; - private static bool $stty = true; private static bool $stdinIsInteractive; @@ -59,16 +54,15 @@ public function ask(InputInterface $input, OutputInterface $output, Question $qu return $this->getDefaultAnswer($question); } - if ($input instanceof StreamableInputInterface && $stream = $input->getStream()) { - $this->inputStream = $stream; - } + $inputStream = $input instanceof StreamableInputInterface ? $input->getStream() : null; + $inputStream ??= STDIN; try { if (!$question->getValidator()) { - return $this->doAsk($output, $question); + return $this->doAsk($inputStream, $output, $question); } - $interviewer = fn () => $this->doAsk($output, $question); + $interviewer = fn () => $this->doAsk($inputStream, $output, $question); return $this->validateAttempts($interviewer, $output, $question); } catch (MissingInputException $exception) { @@ -98,13 +92,14 @@ public static function disableStty(): void /** * Asks the question to the user. * + * @param resource $inputStream + * * @throws RuntimeException In case the fallback is deactivated and the response cannot be hidden */ - private function doAsk(OutputInterface $output, Question $question): mixed + private function doAsk($inputStream, OutputInterface $output, Question $question): mixed { $this->writePrompt($output, $question); - $inputStream = $this->inputStream ?: \STDIN; $autocomplete = $question->getAutocompleterCallback(); if (null === $autocomplete || !self::$stty || !Terminal::hasSttyAvailable()) { diff --git a/src/Symfony/Component/Console/Helper/Table.php b/src/Symfony/Component/Console/Helper/Table.php index 87cea7f77b621..1b32ae39fdbeb 100644 --- a/src/Symfony/Component/Console/Helper/Table.php +++ b/src/Symfony/Component/Console/Helper/Table.php @@ -460,7 +460,7 @@ public function render(): void * * +-----+-----------+-------+ */ - private function renderRowSeparator(int $type = self::SEPARATOR_MID, string $title = null, string $titleFormat = null): void + private function renderRowSeparator(int $type = self::SEPARATOR_MID, ?string $title = null, ?string $titleFormat = null): void { if (!$count = $this->numberOfColumns) { return; @@ -525,7 +525,7 @@ private function renderColumnSeparator(int $type = self::BORDER_OUTSIDE): string * * | 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens | */ - private function renderRow(array $row, string $cellFormat, string $firstCellFormat = null): void + private function renderRow(array $row, string $cellFormat, ?string $firstCellFormat = null): void { $rowContent = $this->renderColumnSeparator(self::BORDER_OUTSIDE); $columns = $this->getRowColumns($row); diff --git a/src/Symfony/Component/Console/Helper/TableStyle.php b/src/Symfony/Component/Console/Helper/TableStyle.php index bbad98e73ccd7..be956c109edf5 100644 --- a/src/Symfony/Component/Console/Helper/TableStyle.php +++ b/src/Symfony/Component/Console/Helper/TableStyle.php @@ -88,7 +88,7 @@ public function getPaddingChar(): string * * @return $this */ - public function setHorizontalBorderChars(string $outside, string $inside = null): static + public function setHorizontalBorderChars(string $outside, ?string $inside = null): static { $this->horizontalOutsideBorderChar = $outside; $this->horizontalInsideBorderChar = $inside ?? $outside; @@ -113,7 +113,7 @@ public function setHorizontalBorderChars(string $outside, string $inside = null) * * @return $this */ - public function setVerticalBorderChars(string $outside, string $inside = null): static + public function setVerticalBorderChars(string $outside, ?string $inside = null): static { $this->verticalOutsideBorderChar = $outside; $this->verticalInsideBorderChar = $inside ?? $outside; @@ -167,7 +167,7 @@ public function getBorderChars(): array * * @return $this */ - public function setCrossingChars(string $cross, string $topLeft, string $topMid, string $topRight, string $midRight, string $bottomRight, string $bottomMid, string $bottomLeft, string $midLeft, string $topLeftBottom = null, string $topMidBottom = null, string $topRightBottom = null): static + public function setCrossingChars(string $cross, string $topLeft, string $topMid, string $topRight, string $midRight, string $bottomRight, string $bottomMid, string $bottomLeft, string $midLeft, ?string $topLeftBottom = null, ?string $topMidBottom = null, ?string $topRightBottom = null): static { $this->crossingChar = $cross; $this->crossingTopLeftChar = $topLeft; diff --git a/src/Symfony/Component/Console/Input/ArgvInput.php b/src/Symfony/Component/Console/Input/ArgvInput.php index 9ae2f54f572b0..8621d62cf7121 100644 --- a/src/Symfony/Component/Console/Input/ArgvInput.php +++ b/src/Symfony/Component/Console/Input/ArgvInput.php @@ -43,7 +43,7 @@ class ArgvInput extends Input private array $tokens; private array $parsed; - public function __construct(array $argv = null, InputDefinition $definition = null) + public function __construct(?array $argv = null, ?InputDefinition $definition = null) { $argv ??= $_SERVER['argv'] ?? []; diff --git a/src/Symfony/Component/Console/Input/ArrayInput.php b/src/Symfony/Component/Console/Input/ArrayInput.php index 2697e3721a681..d27ff411ee620 100644 --- a/src/Symfony/Component/Console/Input/ArrayInput.php +++ b/src/Symfony/Component/Console/Input/ArrayInput.php @@ -27,7 +27,7 @@ class ArrayInput extends Input { public function __construct( private array $parameters, - InputDefinition $definition = null, + ?InputDefinition $definition = null, ) { parent::__construct($definition); } diff --git a/src/Symfony/Component/Console/Input/Input.php b/src/Symfony/Component/Console/Input/Input.php index 6a9248b7aa8df..5a8b9a27a2b58 100644 --- a/src/Symfony/Component/Console/Input/Input.php +++ b/src/Symfony/Component/Console/Input/Input.php @@ -34,7 +34,7 @@ abstract class Input implements InputInterface, StreamableInputInterface protected array $arguments = []; protected bool $interactive = true; - public function __construct(InputDefinition $definition = null) + public function __construct(?InputDefinition $definition = null) { if (null === $definition) { $this->definition = new InputDefinition(); diff --git a/src/Symfony/Component/Console/Input/InputArgument.php b/src/Symfony/Component/Console/Input/InputArgument.php index 9d6c17fa3c2c4..a5d949277748e 100644 --- a/src/Symfony/Component/Console/Input/InputArgument.php +++ b/src/Symfony/Component/Console/Input/InputArgument.php @@ -25,16 +25,27 @@ */ class InputArgument { + /** + * Providing an argument is required (e.g. just 'app:foo' is not allowed). + */ public const REQUIRED = 1; + + /** + * Providing an argument is optional (e.g. 'app:foo' and 'app:foo bar' are both allowed). This is the default behavior of arguments. + */ public const OPTIONAL = 2; + + /** + * The argument accepts multiple values and turn them into an array (e.g. 'app:foo bar baz' will result in value ['bar', 'baz']). + */ public const IS_ARRAY = 4; private int $mode; - private string|int|bool|array|null|float $default; + private string|int|bool|array|float|null $default; /** * @param string $name The argument name - * @param int|null $mode The argument mode: a bit mask of self::REQUIRED, self::OPTIONAL and self::IS_ARRAY + * @param int-mask-of|null $mode The argument mode: a bit mask of self::REQUIRED, self::OPTIONAL and self::IS_ARRAY * @param string $description A description text * @param string|bool|int|float|array|null $default The default value (for self::OPTIONAL mode only) * @param array|\Closure(CompletionInput,CompletionSuggestions):list $suggestedValues The values used for input completion @@ -43,14 +54,14 @@ class InputArgument */ public function __construct( private string $name, - int $mode = null, + ?int $mode = null, private string $description = '', - string|bool|int|float|array $default = null, + string|bool|int|float|array|null $default = null, private \Closure|array $suggestedValues = [], ) { if (null === $mode) { $mode = self::OPTIONAL; - } elseif ($mode > 7 || $mode < 1) { + } elseif ($mode >= (self::IS_ARRAY << 1) || $mode < 1) { throw new InvalidArgumentException(sprintf('Argument mode "%s" is not valid.', $mode)); } @@ -89,8 +100,6 @@ public function isArray(): bool /** * Sets the default value. - * - * @throws LogicException When incorrect default value is given */ public function setDefault(string|bool|int|float|array|null $default): void { @@ -117,13 +126,16 @@ public function getDefault(): string|bool|int|float|array|null return $this->default; } + /** + * Returns true if the argument has values for input completion. + */ public function hasCompletion(): bool { return [] !== $this->suggestedValues; } /** - * Adds suggestions to $suggestions for the current completion input. + * Supplies suggestions when command resolves possible completion options for input. * * @see Command::complete() */ diff --git a/src/Symfony/Component/Console/Input/InputOption.php b/src/Symfony/Component/Console/Input/InputOption.php index 5d305518a4c4c..50851c44c6531 100644 --- a/src/Symfony/Component/Console/Input/InputOption.php +++ b/src/Symfony/Component/Console/Input/InputOption.php @@ -46,18 +46,18 @@ class InputOption public const VALUE_IS_ARRAY = 8; /** - * The option may have either positive or negative value (e.g. --ansi or --no-ansi). + * The option allows passing a negated variant (e.g. --ansi or --no-ansi). */ public const VALUE_NEGATABLE = 16; private string $name; - private string|array|null $shortcut; + private ?string $shortcut; private int $mode; - private string|int|bool|array|null|float $default; + private string|int|bool|array|float|null $default; /** * @param string|array|null $shortcut The shortcuts, can be null, a string of shortcuts delimited by | or an array of shortcuts - * @param int|null $mode The option mode: One of the VALUE_* constants + * @param int-mask-of|null $mode The option mode: One of the VALUE_* constants * @param string|bool|int|float|array|null $default The default value (must be null for self::VALUE_NONE) * @param array|\Closure(CompletionInput,CompletionSuggestions):list $suggestedValues The values used for input completion * @@ -65,10 +65,10 @@ class InputOption */ public function __construct( string $name, - string|array $shortcut = null, - int $mode = null, + string|array|null $shortcut = null, + ?int $mode = null, private string $description = '', - string|bool|int|float|array $default = null, + string|bool|int|float|array|null $default = null, private array|\Closure $suggestedValues = [], ) { if (str_starts_with($name, '--')) { @@ -79,7 +79,7 @@ public function __construct( throw new InvalidArgumentException('An option name cannot be empty.'); } - if (empty($shortcut)) { + if ('' === $shortcut || [] === $shortcut || false === $shortcut) { $shortcut = null; } @@ -88,10 +88,10 @@ public function __construct( $shortcut = implode('|', $shortcut); } $shortcuts = preg_split('{(\|)-?}', ltrim($shortcut, '-')); - $shortcuts = array_filter($shortcuts); + $shortcuts = array_filter($shortcuts, 'strlen'); $shortcut = implode('|', $shortcuts); - if (empty($shortcut)) { + if ('' === $shortcut) { throw new InvalidArgumentException('An option shortcut cannot be empty.'); } } @@ -175,11 +175,19 @@ public function isArray(): bool return self::VALUE_IS_ARRAY === (self::VALUE_IS_ARRAY & $this->mode); } + /** + * Returns true if the option allows passing a negated variant. + * + * @return bool true if mode is self::VALUE_NEGATABLE, false otherwise + */ public function isNegatable(): bool { return self::VALUE_NEGATABLE === (self::VALUE_NEGATABLE & $this->mode); } + /** + * Sets the default value. + */ public function setDefault(string|bool|int|float|array|null $default): void { if (self::VALUE_NONE === (self::VALUE_NONE & $this->mode) && null !== $default) { @@ -213,13 +221,16 @@ public function getDescription(): string return $this->description; } + /** + * Returns true if the option has values for input completion. + */ public function hasCompletion(): bool { return [] !== $this->suggestedValues; } /** - * Adds suggestions to $suggestions for the current completion input. + * Supplies suggestions when command resolves possible completion options for input. * * @see Command::complete() */ diff --git a/src/Symfony/Component/Console/Output/ConsoleOutput.php b/src/Symfony/Component/Console/Output/ConsoleOutput.php index f9e6c77109078..2ad3dbcf384a3 100644 --- a/src/Symfony/Component/Console/Output/ConsoleOutput.php +++ b/src/Symfony/Component/Console/Output/ConsoleOutput.php @@ -37,7 +37,7 @@ class ConsoleOutput extends StreamOutput implements ConsoleOutputInterface * @param bool|null $decorated Whether to decorate messages (null for auto-guessing) * @param OutputFormatterInterface|null $formatter Output formatter instance (null to use default OutputFormatter) */ - public function __construct(int $verbosity = self::VERBOSITY_NORMAL, bool $decorated = null, OutputFormatterInterface $formatter = null) + public function __construct(int $verbosity = self::VERBOSITY_NORMAL, ?bool $decorated = null, ?OutputFormatterInterface $formatter = null) { parent::__construct($this->openOutputStream(), $verbosity, $decorated, $formatter); diff --git a/src/Symfony/Component/Console/Output/ConsoleSectionOutput.php b/src/Symfony/Component/Console/Output/ConsoleSectionOutput.php index d5b5aff766849..ded97c70e01d6 100644 --- a/src/Symfony/Component/Console/Output/ConsoleSectionOutput.php +++ b/src/Symfony/Component/Console/Output/ConsoleSectionOutput.php @@ -61,7 +61,7 @@ public function setMaxHeight(int $maxHeight): void * * @param int $lines Number of lines to clear. If null, then the entire output of this section is cleared */ - public function clear(int $lines = null): void + public function clear(?int $lines = null): void { if (empty($this->content) || !$this->isDecorated()) { return; diff --git a/src/Symfony/Component/Console/Output/Output.php b/src/Symfony/Component/Console/Output/Output.php index fe8564bb9b4ed..2bb105748b8f8 100644 --- a/src/Symfony/Component/Console/Output/Output.php +++ b/src/Symfony/Component/Console/Output/Output.php @@ -37,7 +37,7 @@ abstract class Output implements OutputInterface * @param bool $decorated Whether to decorate messages * @param OutputFormatterInterface|null $formatter Output formatter instance (null to use default OutputFormatter) */ - public function __construct(?int $verbosity = self::VERBOSITY_NORMAL, bool $decorated = false, OutputFormatterInterface $formatter = null) + public function __construct(?int $verbosity = self::VERBOSITY_NORMAL, bool $decorated = false, ?OutputFormatterInterface $formatter = null) { $this->verbosity = $verbosity ?? self::VERBOSITY_NORMAL; $this->formatter = $formatter ?? new OutputFormatter(); diff --git a/src/Symfony/Component/Console/Output/StreamOutput.php b/src/Symfony/Component/Console/Output/StreamOutput.php index 55e403d3351a7..aa3fe8b5d2ced 100644 --- a/src/Symfony/Component/Console/Output/StreamOutput.php +++ b/src/Symfony/Component/Console/Output/StreamOutput.php @@ -40,7 +40,7 @@ class StreamOutput extends Output * * @throws InvalidArgumentException When first argument is not a real stream */ - public function __construct($stream, int $verbosity = self::VERBOSITY_NORMAL, bool $decorated = null, OutputFormatterInterface $formatter = null) + public function __construct($stream, int $verbosity = self::VERBOSITY_NORMAL, ?bool $decorated = null, ?OutputFormatterInterface $formatter = null) { if (!\is_resource($stream) || 'stream' !== get_resource_type($stream)) { throw new InvalidArgumentException('The StreamOutput class needs a stream as its first argument.'); @@ -94,6 +94,10 @@ protected function hasColorSupport(): bool return false; } + if (!$this->isTty()) { + return false; + } + if (\DIRECTORY_SEPARATOR === '\\' && \function_exists('sapi_windows_vt100_support') && @sapi_windows_vt100_support($this->stream) @@ -101,10 +105,51 @@ protected function hasColorSupport(): bool return true; } - return 'Hyper' === getenv('TERM_PROGRAM') + if ('Hyper' === getenv('TERM_PROGRAM') + || false !== getenv('COLORTERM') || false !== getenv('ANSICON') || 'ON' === getenv('ConEmuANSI') - || str_starts_with((string) getenv('TERM'), 'xterm') - || stream_isatty($this->stream); + ) { + return true; + } + + $term = (string) getenv('TERM'); + + if ('dumb' === $term) { + return false; + } + + // See https://github.com/chalk/supports-color/blob/d4f413efaf8da045c5ab440ed418ef02dbb28bf1/index.js#L157 + return 1 === @preg_match('/^((screen|xterm|vt100|vt220|putty|rxvt|ansi|cygwin|linux).*)|(.*-256(color)?(-bce)?)$/', $term); + } + + /** + * Checks if the stream is a TTY, i.e; whether the output stream is connected to a terminal. + * + * Reference: Composer\Util\Platform::isTty + * https://github.com/composer/composer + */ + private function isTty(): bool + { + // Detect msysgit/mingw and assume this is a tty because detection + // does not work correctly, see https://github.com/composer/composer/issues/9690 + if (\in_array(strtoupper((string) getenv('MSYSTEM')), ['MINGW32', 'MINGW64'], true)) { + return true; + } + + // Modern cross-platform function, includes the fstat fallback so if it is present we trust it + if (\function_exists('stream_isatty')) { + return stream_isatty($this->stream); + } + + // Only trusting this if it is positive, otherwise prefer fstat fallback. + if (\function_exists('posix_isatty') && posix_isatty($this->stream)) { + return true; + } + + $stat = @fstat($this->stream); + + // Check if formatted mode is S_IFCHR + return $stat ? 0020000 === ($stat['mode'] & 0170000) : false; } } diff --git a/src/Symfony/Component/Console/Output/TrimmedBufferOutput.php b/src/Symfony/Component/Console/Output/TrimmedBufferOutput.php index 5655e7bc814ae..c1862a2bd7ffa 100644 --- a/src/Symfony/Component/Console/Output/TrimmedBufferOutput.php +++ b/src/Symfony/Component/Console/Output/TrimmedBufferOutput.php @@ -24,7 +24,7 @@ class TrimmedBufferOutput extends Output private int $maxLength; private string $buffer = ''; - public function __construct(int $maxLength, ?int $verbosity = self::VERBOSITY_NORMAL, bool $decorated = false, OutputFormatterInterface $formatter = null) + public function __construct(int $maxLength, ?int $verbosity = self::VERBOSITY_NORMAL, bool $decorated = false, ?OutputFormatterInterface $formatter = null) { if ($maxLength <= 0) { throw new InvalidArgumentException(sprintf('"%s()" expects a strictly positive maxLength. Got %d.', __METHOD__, $maxLength)); diff --git a/src/Symfony/Component/Console/Question/Question.php b/src/Symfony/Component/Console/Question/Question.php index a82125c91e87f..46a60c798b0ba 100644 --- a/src/Symfony/Component/Console/Question/Question.php +++ b/src/Symfony/Component/Console/Question/Question.php @@ -36,7 +36,7 @@ class Question */ public function __construct( private string $question, - private null|string|bool|int|float $default = null, + private string|bool|int|float|null $default = null, ) { } diff --git a/src/Symfony/Component/Console/SingleCommandApplication.php b/src/Symfony/Component/Console/SingleCommandApplication.php index 4f0b5ba3cc6e6..ff1c17247fc4f 100644 --- a/src/Symfony/Component/Console/SingleCommandApplication.php +++ b/src/Symfony/Component/Console/SingleCommandApplication.php @@ -46,7 +46,7 @@ public function setAutoExit(bool $autoExit): static return $this; } - public function run(InputInterface $input = null, OutputInterface $output = null): int + public function run(?InputInterface $input = null, ?OutputInterface $output = null): int { if ($this->running) { return parent::run($input, $output); diff --git a/src/Symfony/Component/Console/Style/StyleInterface.php b/src/Symfony/Component/Console/Style/StyleInterface.php index 869b160902790..fcc5bc775f8a9 100644 --- a/src/Symfony/Component/Console/Style/StyleInterface.php +++ b/src/Symfony/Component/Console/Style/StyleInterface.php @@ -71,12 +71,12 @@ public function table(array $headers, array $rows): void; /** * Asks a question. */ - public function ask(string $question, string $default = null, callable $validator = null): mixed; + public function ask(string $question, ?string $default = null, ?callable $validator = null): mixed; /** * Asks a question with the user input hidden. */ - public function askHidden(string $question, callable $validator = null): mixed; + public function askHidden(string $question, ?callable $validator = null): mixed; /** * Asks for confirmation. diff --git a/src/Symfony/Component/Console/Style/SymfonyStyle.php b/src/Symfony/Component/Console/Style/SymfonyStyle.php index 3fd531d981fe0..19ad892efba3c 100644 --- a/src/Symfony/Component/Console/Style/SymfonyStyle.php +++ b/src/Symfony/Component/Console/Style/SymfonyStyle.php @@ -60,7 +60,7 @@ public function __construct( /** * 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): void + public function block(string|array $messages, ?string $type = null, ?string $style = null, string $prefix = ' ', bool $padding = false, bool $escape = true): void { $messages = \is_array($messages) ? array_values($messages) : [$messages]; @@ -208,7 +208,7 @@ public function definitionList(string|array|TableSeparator ...$list): void $this->horizontalTable($headers, [$row]); } - public function ask(string $question, string $default = null, callable $validator = null): mixed + public function ask(string $question, ?string $default = null, ?callable $validator = null): mixed { $question = new Question($question, $default); $question->setValidator($validator); @@ -216,7 +216,7 @@ public function ask(string $question, string $default = null, callable $validato return $this->askQuestion($question); } - public function askHidden(string $question, callable $validator = null): mixed + public function askHidden(string $question, ?callable $validator = null): mixed { $question = new Question($question); @@ -286,7 +286,7 @@ public function createProgressBar(int $max = 0): ProgressBar * * @return iterable */ - public function progressIterate(iterable $iterable, int $max = null): iterable + public function progressIterate(iterable $iterable, ?int $max = null): iterable { yield from $this->createProgressBar()->iterate($iterable, $max); @@ -397,7 +397,7 @@ private function writeBuffer(string $message, bool $newLine, int $type): void $this->bufferedOutput->write($message, $newLine, $type); } - private function createBlock(iterable $messages, string $type = null, string $style = null, string $prefix = ' ', bool $padding = false, bool $escape = false): array + private function createBlock(iterable $messages, ?string $type = null, ?string $style = null, string $prefix = ' ', bool $padding = false, bool $escape = false): array { $indentLength = 0; $prefixLength = Helper::width(Helper::removeDecoration($this->getFormatter(), $prefix)); diff --git a/src/Symfony/Component/Console/Tests/Formatter/OutputFormatterTest.php b/src/Symfony/Component/Console/Tests/Formatter/OutputFormatterTest.php index 610522a7e8088..f62fa088907d3 100644 --- a/src/Symfony/Component/Console/Tests/Formatter/OutputFormatterTest.php +++ b/src/Symfony/Component/Console/Tests/Formatter/OutputFormatterTest.php @@ -162,7 +162,7 @@ public function testInlineStyle() /** * @dataProvider provideInlineStyleOptionsCases */ - public function testInlineStyleOptions(string $tag, string $expected = null, string $input = null, bool $truecolor = false) + public function testInlineStyleOptions(string $tag, ?string $expected = null, ?string $input = null, bool $truecolor = false) { if ($truecolor && 'truecolor' !== getenv('COLORTERM')) { $this->markTestSkipped('The terminal does not support true colors.'); diff --git a/src/Symfony/Component/Console/Tests/Helper/HelperSetTest.php b/src/Symfony/Component/Console/Tests/Helper/HelperSetTest.php index 9fbb9afca9e48..389ee0ed31425 100644 --- a/src/Symfony/Component/Console/Tests/Helper/HelperSetTest.php +++ b/src/Symfony/Component/Console/Tests/Helper/HelperSetTest.php @@ -87,7 +87,7 @@ public function testIteration() } } - private function getGenericMockHelper($name, HelperSet $helperset = null) + private function getGenericMockHelper($name, ?HelperSet $helperset = null) { $mock_helper = $this->createMock(HelperInterface::class); $mock_helper->expects($this->any()) diff --git a/src/Symfony/Component/Console/Tests/Input/InputOptionTest.php b/src/Symfony/Component/Console/Tests/Input/InputOptionTest.php index 74bf69586fa89..7e3fb16da1fe9 100644 --- a/src/Symfony/Component/Console/Tests/Input/InputOptionTest.php +++ b/src/Symfony/Component/Console/Tests/Input/InputOptionTest.php @@ -59,6 +59,22 @@ public function testShortcut() $this->assertEquals('f|ff|fff', $option->getShortcut(), '__construct() removes the leading - of the shortcuts'); $option = new InputOption('foo'); $this->assertNull($option->getShortcut(), '__construct() makes the shortcut null by default'); + $option = new InputOption('foo', ''); + $this->assertNull($option->getShortcut(), '__construct() makes the shortcut null when given an empty string'); + $option = new InputOption('foo', []); + $this->assertNull($option->getShortcut(), '__construct() makes the shortcut null when given an empty array'); + $option = new InputOption('foo', ['f', '', 'fff']); + $this->assertEquals('f|fff', $option->getShortcut(), '__construct() removes empty shortcuts'); + $option = new InputOption('foo', 'f||fff'); + $this->assertEquals('f|fff', $option->getShortcut(), '__construct() removes empty shortcuts'); + $option = new InputOption('foo', '0'); + $this->assertEquals('0', $option->getShortcut(), '-0 is an acceptable shortcut value'); + $option = new InputOption('foo', ['0', 'z']); + $this->assertEquals('0|z', $option->getShortcut(), '-0 is an acceptable shortcut value when embedded in an array'); + $option = new InputOption('foo', '0|z'); + $this->assertEquals('0|z', $option->getShortcut(), '-0 is an acceptable shortcut value when embedded in a string-list'); + $option = new InputOption('foo', false); + $this->assertNull($option->getShortcut(), '__construct() makes the shortcut null when given a false as value'); } public function testModes() diff --git a/src/Symfony/Component/CssSelector/CHANGELOG.md b/src/Symfony/Component/CssSelector/CHANGELOG.md index c035d6b3db49e..d2b7fb1d62acf 100644 --- a/src/Symfony/Component/CssSelector/CHANGELOG.md +++ b/src/Symfony/Component/CssSelector/CHANGELOG.md @@ -1,8 +1,14 @@ CHANGELOG ========= +7.1 +--- + + * Add support for `:is()` + * Add support for `:where()` + 6.3 ------ +--- * Add support for `:scope` diff --git a/src/Symfony/Component/CssSelector/Node/MatchingNode.php b/src/Symfony/Component/CssSelector/Node/MatchingNode.php new file mode 100644 index 0000000000000..381ac4585d03a --- /dev/null +++ b/src/Symfony/Component/CssSelector/Node/MatchingNode.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Node; + +/** + * Represents a ":is()" node. + * + * This component is a port of the Python cssselect library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Hubert Lenoir + * + * @internal + */ +class MatchingNode extends AbstractNode +{ + /** + * @param array $arguments + */ + public function __construct( + public readonly NodeInterface $selector, + public readonly array $arguments = [], + ) { + } + + public function getSpecificity(): Specificity + { + $argumentsSpecificity = array_reduce( + $this->arguments, + fn ($c, $n) => 1 === $n->getSpecificity()->compareTo($c) ? $n->getSpecificity() : $c, + new Specificity(0, 0, 0), + ); + + return $this->selector->getSpecificity()->plus($argumentsSpecificity); + } + + public function __toString(): string + { + $selectorArguments = array_map( + fn ($n): string => ltrim((string) $n, '*'), + $this->arguments, + ); + + return sprintf('%s[%s:is(%s)]', $this->getNodeName(), $this->selector, implode(', ', $selectorArguments)); + } +} diff --git a/src/Symfony/Component/CssSelector/Node/SelectorNode.php b/src/Symfony/Component/CssSelector/Node/SelectorNode.php index 72c90235f009b..aebe50251e460 100644 --- a/src/Symfony/Component/CssSelector/Node/SelectorNode.php +++ b/src/Symfony/Component/CssSelector/Node/SelectorNode.php @@ -27,7 +27,7 @@ class SelectorNode extends AbstractNode public function __construct( private NodeInterface $tree, - string $pseudoElement = null, + ?string $pseudoElement = null, ) { $this->pseudoElement = $pseudoElement ? strtolower($pseudoElement) : null; } diff --git a/src/Symfony/Component/CssSelector/Node/SpecificityAdjustmentNode.php b/src/Symfony/Component/CssSelector/Node/SpecificityAdjustmentNode.php new file mode 100644 index 0000000000000..d49ed4c5f90e6 --- /dev/null +++ b/src/Symfony/Component/CssSelector/Node/SpecificityAdjustmentNode.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Node; + +/** + * Represents a ":where()" node. + * + * This component is a port of the Python cssselect library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Hubert Lenoir + * + * @internal + */ +class SpecificityAdjustmentNode extends AbstractNode +{ + /** + * @param array $arguments + */ + public function __construct( + public readonly NodeInterface $selector, + public readonly array $arguments = [], + ) { + } + + public function getSpecificity(): Specificity + { + return $this->selector->getSpecificity(); + } + + public function __toString(): string + { + $selectorArguments = array_map( + fn ($n) => ltrim((string) $n, '*'), + $this->arguments, + ); + + return sprintf('%s[%s:where(%s)]', $this->getNodeName(), $this->selector, implode(', ', $selectorArguments)); + } +} diff --git a/src/Symfony/Component/CssSelector/Parser/Parser.php b/src/Symfony/Component/CssSelector/Parser/Parser.php index 5313d3435ba9c..f7eea2f828fc3 100644 --- a/src/Symfony/Component/CssSelector/Parser/Parser.php +++ b/src/Symfony/Component/CssSelector/Parser/Parser.php @@ -29,7 +29,7 @@ class Parser implements ParserInterface { private Tokenizer $tokenizer; - public function __construct(Tokenizer $tokenizer = null) + public function __construct(?Tokenizer $tokenizer = null) { $this->tokenizer = $tokenizer ?? new Tokenizer(); } @@ -87,13 +87,17 @@ public static function parseSeries(array $tokens): array ]; } - private function parseSelectorList(TokenStream $stream): array + private function parseSelectorList(TokenStream $stream, bool $isArgument = false): array { $stream->skipWhitespace(); $selectors = []; while (true) { - $selectors[] = $this->parserSelectorNode($stream); + if ($isArgument && $stream->getPeek()->isDelimiter([')'])) { + break; + } + + $selectors[] = $this->parserSelectorNode($stream, $isArgument); if ($stream->getPeek()->isDelimiter([','])) { $stream->getNext(); @@ -106,15 +110,19 @@ private function parseSelectorList(TokenStream $stream): array return $selectors; } - private function parserSelectorNode(TokenStream $stream): Node\SelectorNode + private function parserSelectorNode(TokenStream $stream, bool $isArgument = false): Node\SelectorNode { - [$result, $pseudoElement] = $this->parseSimpleSelector($stream); + [$result, $pseudoElement] = $this->parseSimpleSelector($stream, false, $isArgument); while (true) { $stream->skipWhitespace(); $peek = $stream->getPeek(); - if ($peek->isFileEnd() || $peek->isDelimiter([','])) { + if ( + $peek->isFileEnd() + || $peek->isDelimiter([',']) + || ($isArgument && $peek->isDelimiter([')'])) + ) { break; } @@ -129,7 +137,7 @@ private function parserSelectorNode(TokenStream $stream): Node\SelectorNode $combinator = ' '; } - [$nextSelector, $pseudoElement] = $this->parseSimpleSelector($stream); + [$nextSelector, $pseudoElement] = $this->parseSimpleSelector($stream, false, $isArgument); $result = new Node\CombinedSelectorNode($result, $combinator, $nextSelector); } @@ -141,7 +149,7 @@ private function parserSelectorNode(TokenStream $stream): Node\SelectorNode * * @throws SyntaxErrorException */ - private function parseSimpleSelector(TokenStream $stream, bool $insideNegation = false): array + private function parseSimpleSelector(TokenStream $stream, bool $insideNegation = false, bool $isArgument = false): array { $stream->skipWhitespace(); @@ -154,7 +162,7 @@ private function parseSimpleSelector(TokenStream $stream, bool $insideNegation = if ($peek->isWhitespace() || $peek->isFileEnd() || $peek->isDelimiter([',', '+', '>', '~']) - || ($insideNegation && $peek->isDelimiter([')'])) + || ($isArgument && $peek->isDelimiter([')'])) ) { break; } @@ -215,7 +223,7 @@ private function parseSimpleSelector(TokenStream $stream, bool $insideNegation = throw SyntaxErrorException::nestedNot(); } - [$argument, $argumentPseudoElement] = $this->parseSimpleSelector($stream, true); + [$argument, $argumentPseudoElement] = $this->parseSimpleSelector($stream, true, true); $next = $stream->getNext(); if (null !== $argumentPseudoElement) { @@ -227,6 +235,24 @@ private function parseSimpleSelector(TokenStream $stream, bool $insideNegation = } $result = new Node\NegationNode($result, $argument); + } elseif ('is' === strtolower($identifier)) { + $selectors = $this->parseSelectorList($stream, true); + + $next = $stream->getNext(); + if (!$next->isDelimiter([')'])) { + throw SyntaxErrorException::unexpectedToken('")"', $next); + } + + $result = new Node\MatchingNode($result, $selectors); + } elseif ('where' === strtolower($identifier)) { + $selectors = $this->parseSelectorList($stream, true); + + $next = $stream->getNext(); + if (!$next->isDelimiter([')'])) { + throw SyntaxErrorException::unexpectedToken('")"', $next); + } + + $result = new Node\SpecificityAdjustmentNode($result, $selectors); } else { $arguments = []; $next = null; diff --git a/src/Symfony/Component/CssSelector/Tests/Node/MatchingNodeTest.php b/src/Symfony/Component/CssSelector/Tests/Node/MatchingNodeTest.php new file mode 100644 index 0000000000000..0bc718c4c6d49 --- /dev/null +++ b/src/Symfony/Component/CssSelector/Tests/Node/MatchingNodeTest.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\Component\CssSelector\Tests\Node; + +use Symfony\Component\CssSelector\Node\ClassNode; +use Symfony\Component\CssSelector\Node\ElementNode; +use Symfony\Component\CssSelector\Node\HashNode; +use Symfony\Component\CssSelector\Node\MatchingNode; + +class MatchingNodeTest extends AbstractNodeTestCase +{ + public static function getToStringConversionTestData() + { + return [ + [new MatchingNode(new ElementNode(), [ + new ClassNode(new ElementNode(), 'class'), + new HashNode(new ElementNode(), 'id'), + ]), 'Matching[Element[*]:is(Class[Element[*].class], Hash[Element[*]#id])]'], + ]; + } + + public static function getSpecificityValueTestData() + { + return [ + [new MatchingNode(new ElementNode(), [ + new ClassNode(new ElementNode(), 'class'), + new HashNode(new ElementNode(), 'id'), + ]), 100], + [new MatchingNode(new ClassNode(new ElementNode(), 'class'), [ + new ClassNode(new ElementNode(), 'class'), + new HashNode(new ElementNode(), 'id'), + ]), 110], + [new MatchingNode(new HashNode(new ElementNode(), 'id'), [ + new ClassNode(new ElementNode(), 'class'), + new HashNode(new ElementNode(), 'id'), + ]), 200], + ]; + } +} diff --git a/src/Symfony/Component/CssSelector/Tests/Node/SpecificityAdjustmentNodeTest.php b/src/Symfony/Component/CssSelector/Tests/Node/SpecificityAdjustmentNodeTest.php new file mode 100644 index 0000000000000..5c830571e5437 --- /dev/null +++ b/src/Symfony/Component/CssSelector/Tests/Node/SpecificityAdjustmentNodeTest.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Tests\Node; + +use Symfony\Component\CssSelector\Node\ClassNode; +use Symfony\Component\CssSelector\Node\ElementNode; +use Symfony\Component\CssSelector\Node\HashNode; +use Symfony\Component\CssSelector\Node\SpecificityAdjustmentNode; + +class SpecificityAdjustmentNodeTest extends AbstractNodeTestCase +{ + public static function getToStringConversionTestData() + { + return [ + [new SpecificityAdjustmentNode(new ElementNode(), [ + new ClassNode(new ElementNode(), 'class'), + new HashNode(new ElementNode(), 'id'), + ]), 'SpecificityAdjustment[Element[*]:where(Class[Element[*].class], Hash[Element[*]#id])]'], + ]; + } + + public static function getSpecificityValueTestData() + { + return [ + [new SpecificityAdjustmentNode(new ElementNode(), [ + new ClassNode(new ElementNode(), 'class'), + new HashNode(new ElementNode(), 'id'), + ]), 0], + [new SpecificityAdjustmentNode(new ClassNode(new ElementNode(), 'class'), [ + new ClassNode(new ElementNode(), 'class'), + new HashNode(new ElementNode(), 'id'), + ]), 10], + ]; + } +} diff --git a/src/Symfony/Component/CssSelector/Tests/Parser/ParserTest.php b/src/Symfony/Component/CssSelector/Tests/Parser/ParserTest.php index a8708ce47282e..509f6e35930e9 100644 --- a/src/Symfony/Component/CssSelector/Tests/Parser/ParserTest.php +++ b/src/Symfony/Component/CssSelector/Tests/Parser/ParserTest.php @@ -152,6 +152,10 @@ public static function getParserTestData() [':scope', ['Pseudo[Element[*]:scope]']], ['foo bar, :scope > div', ['CombinedSelector[Element[foo] Element[bar]]', 'CombinedSelector[Pseudo[Element[*]:scope] > Element[div]]']], ['foo bar,:scope > div', ['CombinedSelector[Element[foo] Element[bar]]', 'CombinedSelector[Pseudo[Element[*]:scope] > Element[div]]']], + ['div:is(.foo, #bar)', ['Matching[Element[div]:is(Selector[Class[Element[*].foo]], Selector[Hash[Element[*]#bar]])]']], + [':is(:hover, :visited)', ['Matching[Element[*]:is(Selector[Pseudo[Element[*]:hover]], Selector[Pseudo[Element[*]:visited]])]']], + ['div:where(.foo, #bar)', ['SpecificityAdjustment[Element[div]:where(Selector[Class[Element[*].foo]], Selector[Hash[Element[*]#bar]])]']], + [':where(:hover, :visited)', ['SpecificityAdjustment[Element[*]:where(Selector[Pseudo[Element[*]:hover]], Selector[Pseudo[Element[*]:visited]])]']], ]; } @@ -183,6 +187,7 @@ public static function getParserExceptionTestData() [':contains("foo', SyntaxErrorException::unclosedString(10)->getMessage()], ['foo!', SyntaxErrorException::unexpectedToken('selector', new Token(Token::TYPE_DELIMITER, '!', 3))->getMessage()], [':scope > div :scope header', SyntaxErrorException::notAtTheStartOfASelector('scope')->getMessage()], + [':not(:not(a))', SyntaxErrorException::nestedNot()->getMessage()], ]; } @@ -233,6 +238,18 @@ public static function getSpecificityTestData() ['foo::before', 2], ['foo:empty::before', 12], ['#lorem + foo#ipsum:first-child > bar:first-line', 213], + [':is(*)', 0], + [':is(foo)', 1], + [':is(.foo)', 10], + [':is(#foo)', 100], + [':is(#foo, :empty, foo)', 100], + ['#foo:is(#bar:empty)', 210], + [':where(*)', 0], + [':where(foo)', 0], + [':where(.foo)', 0], + [':where(#foo)', 0], + [':where(#foo, :empty, foo)', 0], + ['#foo:where(#bar:empty)', 100], ]; } diff --git a/src/Symfony/Component/CssSelector/Tests/XPath/TranslatorTest.php b/src/Symfony/Component/CssSelector/Tests/XPath/TranslatorTest.php index de15384382652..f521a94708423 100644 --- a/src/Symfony/Component/CssSelector/Tests/XPath/TranslatorTest.php +++ b/src/Symfony/Component/CssSelector/Tests/XPath/TranslatorTest.php @@ -233,6 +233,8 @@ public static function getCssToXPathTestData() ['div#container p', "div[@id = 'container']/descendant-or-self::*/p"], [':scope > div[dataimg=""]', "*[1]/div[@dataimg = '']"], [':scope', '*[1]'], + ['e:is(section, article) h1', "e[(name() = 'section') or (name() = 'article')]/descendant-or-self::*/h1"], + ['e:where(section, article) h1', "e[(name() = 'section') or (name() = 'article')]/descendant-or-self::*/h1"], ]; } @@ -367,6 +369,17 @@ public static function getHtmlIdsTestData() [':not(*)', []], ['a:not([href])', ['name-anchor']], ['ol :Not(li[class])', ['first-li', 'second-li', 'li-div', 'fifth-li', 'sixth-li', 'seventh-li']], + [':is(#first-li, #second-li)', ['first-li', 'second-li']], + ['a:is(#name-anchor, #tag-anchor)', ['name-anchor', 'tag-anchor']], + [':is(.c)', ['first-ol', 'third-li', 'fourth-li']], + ['a:is(:not(#name-anchor))', ['tag-anchor', 'nofollow-anchor']], + ['a:not(:is(#name-anchor))', ['tag-anchor', 'nofollow-anchor']], + [':where(#first-li, #second-li)', ['first-li', 'second-li']], + ['a:where(#name-anchor, #tag-anchor)', ['name-anchor', 'tag-anchor']], + [':where(.c)', ['first-ol', 'third-li', 'fourth-li']], + ['a:where(:not(#name-anchor))', ['tag-anchor', 'nofollow-anchor']], + ['a:not(:where(#name-anchor))', ['tag-anchor', 'nofollow-anchor']], + ['a:where(:is(#name-anchor), :where(#tag-anchor))', ['name-anchor', 'tag-anchor']], // HTML-specific [':link', ['link-href', 'tag-anchor', 'nofollow-anchor', 'area-href']], [':visited', []], @@ -428,6 +441,7 @@ public static function getHtmlShakespearTestData() [':scope > div', 1], [':scope > div > div[class=dialog]', 1], [':scope > div div', 242], + ['div:is(div#test .dialog) .direction', 4], ]; } } diff --git a/src/Symfony/Component/CssSelector/XPath/Extension/NodeExtension.php b/src/Symfony/Component/CssSelector/XPath/Extension/NodeExtension.php index f2cd617ac4cc8..87b70dfef9224 100644 --- a/src/Symfony/Component/CssSelector/XPath/Extension/NodeExtension.php +++ b/src/Symfony/Component/CssSelector/XPath/Extension/NodeExtension.php @@ -63,6 +63,8 @@ public function getNodeTranslators(): array 'Selector' => $this->translateSelector(...), 'CombinedSelector' => $this->translateCombinedSelector(...), 'Negation' => $this->translateNegation(...), + 'Matching' => $this->translateMatching(...), + 'SpecificityAdjustment' => $this->translateSpecificityAdjustment(...), 'Function' => $this->translateFunction(...), 'Pseudo' => $this->translatePseudo(...), 'Attribute' => $this->translateAttribute(...), @@ -95,6 +97,36 @@ public function translateNegation(Node\NegationNode $node, Translator $translato return $xpath->addCondition('0'); } + public function translateMatching(Node\MatchingNode $node, Translator $translator): XPathExpr + { + $xpath = $translator->nodeToXPath($node->selector); + + foreach ($node->arguments as $argument) { + $expr = $translator->nodeToXPath($argument); + $expr->addNameTest(); + if ($condition = $expr->getCondition()) { + $xpath->addCondition($condition, 'or'); + } + } + + return $xpath; + } + + public function translateSpecificityAdjustment(Node\SpecificityAdjustmentNode $node, Translator $translator): XPathExpr + { + $xpath = $translator->nodeToXPath($node->selector); + + foreach ($node->arguments as $argument) { + $expr = $translator->nodeToXPath($argument); + $expr->addNameTest(); + if ($condition = $expr->getCondition()) { + $xpath->addCondition($condition, 'or'); + } + } + + return $xpath; + } + public function translateFunction(Node\FunctionNode $node, Translator $translator): XPathExpr { $xpath = $translator->nodeToXPath($node->getSelector()); diff --git a/src/Symfony/Component/CssSelector/XPath/Translator.php b/src/Symfony/Component/CssSelector/XPath/Translator.php index 83e855b5c4158..9e66ce7ddbd08 100644 --- a/src/Symfony/Component/CssSelector/XPath/Translator.php +++ b/src/Symfony/Component/CssSelector/XPath/Translator.php @@ -48,7 +48,7 @@ class Translator implements TranslatorInterface private array $pseudoClassTranslators = []; private array $attributeMatchingTranslators = []; - public function __construct(ParserInterface $parser = null) + public function __construct(?ParserInterface $parser = null) { $this->mainParser = $parser ?? new Parser(); diff --git a/src/Symfony/Component/CssSelector/XPath/XPathExpr.php b/src/Symfony/Component/CssSelector/XPath/XPathExpr.php index 14f63ef978930..ceccab619cfe2 100644 --- a/src/Symfony/Component/CssSelector/XPath/XPathExpr.php +++ b/src/Symfony/Component/CssSelector/XPath/XPathExpr.php @@ -42,9 +42,9 @@ public function getElement(): string /** * @return $this */ - public function addCondition(string $condition): static + public function addCondition(string $condition, string $operator = 'and'): static { - $this->condition = $this->condition ? sprintf('(%s) and (%s)', $this->condition, $condition) : $condition; + $this->condition = $this->condition ? sprintf('(%s) %s (%s)', $this->condition, $operator, $condition) : $condition; return $this; } diff --git a/src/Symfony/Component/DependencyInjection/Argument/BoundArgument.php b/src/Symfony/Component/DependencyInjection/Argument/BoundArgument.php index be24e20af8345..22d94140a49ad 100644 --- a/src/Symfony/Component/DependencyInjection/Argument/BoundArgument.php +++ b/src/Symfony/Component/DependencyInjection/Argument/BoundArgument.php @@ -28,7 +28,7 @@ final class BoundArgument implements ArgumentInterface private int $type; private ?string $file; - public function __construct(mixed $value, bool $trackUsage = true, int $type = 0, string $file = null) + public function __construct(mixed $value, bool $trackUsage = true, int $type = 0, ?string $file = null) { $this->value = $value; if ($trackUsage) { diff --git a/src/Symfony/Component/DependencyInjection/Argument/ServiceLocator.php b/src/Symfony/Component/DependencyInjection/Argument/ServiceLocator.php index e58293489d419..8276f6a39485b 100644 --- a/src/Symfony/Component/DependencyInjection/Argument/ServiceLocator.php +++ b/src/Symfony/Component/DependencyInjection/Argument/ServiceLocator.php @@ -24,7 +24,7 @@ class ServiceLocator extends BaseServiceLocator private array $serviceMap; private ?array $serviceTypes; - public function __construct(\Closure $factory, array $serviceMap, array $serviceTypes = null) + public function __construct(\Closure $factory, array $serviceMap, ?array $serviceTypes = null) { $this->factory = $factory; $this->serviceMap = $serviceMap; diff --git a/src/Symfony/Component/DependencyInjection/Argument/TaggedIteratorArgument.php b/src/Symfony/Component/DependencyInjection/Argument/TaggedIteratorArgument.php index 86ab0b9020dcd..2e0a1fea8df1e 100644 --- a/src/Symfony/Component/DependencyInjection/Argument/TaggedIteratorArgument.php +++ b/src/Symfony/Component/DependencyInjection/Argument/TaggedIteratorArgument.php @@ -35,7 +35,7 @@ class TaggedIteratorArgument extends IteratorArgument * @param array $exclude Services to exclude from the iterator * @param bool $excludeSelf Whether to automatically exclude the referencing service from the iterator */ - public function __construct(string $tag, string $indexAttribute = null, string $defaultIndexMethod = null, bool $needsIndexes = false, string $defaultPriorityMethod = null, array $exclude = [], bool $excludeSelf = true) + public function __construct(string $tag, ?string $indexAttribute = null, ?string $defaultIndexMethod = null, bool $needsIndexes = false, ?string $defaultPriorityMethod = null, array $exclude = [], bool $excludeSelf = true) { parent::__construct([]); diff --git a/src/Symfony/Component/DependencyInjection/Attribute/AutoconfigureTag.php b/src/Symfony/Component/DependencyInjection/Attribute/AutoconfigureTag.php index ea738342c5bd3..dab5595618e28 100644 --- a/src/Symfony/Component/DependencyInjection/Attribute/AutoconfigureTag.php +++ b/src/Symfony/Component/DependencyInjection/Attribute/AutoconfigureTag.php @@ -23,7 +23,7 @@ class AutoconfigureTag extends Autoconfigure * @param string|null $name The tag name to add * @param array $attributes The tag attributes to attach to the tag */ - public function __construct(string $name = null, array $attributes = []) + public function __construct(?string $name = null, array $attributes = []) { parent::__construct( tags: [ diff --git a/src/Symfony/Component/DependencyInjection/Attribute/Autowire.php b/src/Symfony/Component/DependencyInjection/Attribute/Autowire.php index c17eb13702492..874092657883d 100644 --- a/src/Symfony/Component/DependencyInjection/Attribute/Autowire.php +++ b/src/Symfony/Component/DependencyInjection/Attribute/Autowire.php @@ -38,11 +38,11 @@ class Autowire * @param bool|class-string|class-string[] $lazy Whether to use lazy-loading for this argument */ public function __construct( - string|array|ArgumentInterface $value = null, - string $service = null, - string $expression = null, - string $env = null, - string $param = null, + string|array|ArgumentInterface|null $value = null, + ?string $service = null, + ?string $expression = null, + ?string $env = null, + ?string $param = null, bool|string|array $lazy = false, ) { if ($this->lazy = \is_string($lazy) ? [$lazy] : $lazy) { diff --git a/src/Symfony/Component/DependencyInjection/Attribute/AutowireCallable.php b/src/Symfony/Component/DependencyInjection/Attribute/AutowireCallable.php index 1fdc160e724c5..869e96e1ab93e 100644 --- a/src/Symfony/Component/DependencyInjection/Attribute/AutowireCallable.php +++ b/src/Symfony/Component/DependencyInjection/Attribute/AutowireCallable.php @@ -28,9 +28,9 @@ class AutowireCallable extends Autowire * @param bool|class-string $lazy Whether to use lazy-loading for this argument */ public function __construct( - string|array $callable = null, - string $service = null, - string $method = null, + string|array|null $callable = null, + ?string $service = null, + ?string $method = null, bool|string $lazy = false, ) { if (!(null !== $callable xor null !== $service)) { diff --git a/src/Symfony/Component/DependencyInjection/Attribute/AutowireIterator.php b/src/Symfony/Component/DependencyInjection/Attribute/AutowireIterator.php index 92e5f02d7ecb4..2f845c86aaaeb 100644 --- a/src/Symfony/Component/DependencyInjection/Attribute/AutowireIterator.php +++ b/src/Symfony/Component/DependencyInjection/Attribute/AutowireIterator.php @@ -31,9 +31,9 @@ class AutowireIterator extends Autowire */ public function __construct( string $tag, - string $indexAttribute = null, - string $defaultIndexMethod = null, - string $defaultPriorityMethod = null, + ?string $indexAttribute = null, + ?string $defaultIndexMethod = null, + ?string $defaultPriorityMethod = null, string|array $exclude = [], bool $excludeSelf = true, ) { diff --git a/src/Symfony/Component/DependencyInjection/Attribute/AutowireLocator.php b/src/Symfony/Component/DependencyInjection/Attribute/AutowireLocator.php index bd5c912697f17..3bf4d57575c0f 100644 --- a/src/Symfony/Component/DependencyInjection/Attribute/AutowireLocator.php +++ b/src/Symfony/Component/DependencyInjection/Attribute/AutowireLocator.php @@ -37,9 +37,9 @@ class AutowireLocator extends Autowire */ public function __construct( string|array $services, - string $indexAttribute = null, - string $defaultIndexMethod = null, - string $defaultPriorityMethod = null, + ?string $indexAttribute = null, + ?string $defaultIndexMethod = null, + ?string $defaultPriorityMethod = null, string|array $exclude = [], bool $excludeSelf = true, ) { diff --git a/src/Symfony/Component/DependencyInjection/Attribute/Lazy.php b/src/Symfony/Component/DependencyInjection/Attribute/Lazy.php new file mode 100644 index 0000000000000..54de2fed138a5 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Attribute/Lazy.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Attribute; + +#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_PARAMETER)] +class Lazy +{ + public function __construct( + public bool|string|null $lazy = true, + ) { + } +} diff --git a/src/Symfony/Component/DependencyInjection/Attribute/Target.php b/src/Symfony/Component/DependencyInjection/Attribute/Target.php index 57c0a7dbd5667..8255315a99613 100644 --- a/src/Symfony/Component/DependencyInjection/Attribute/Target.php +++ b/src/Symfony/Component/DependencyInjection/Attribute/Target.php @@ -38,7 +38,7 @@ public function getParsedName(): string return lcfirst(str_replace(' ', '', ucwords(preg_replace('/[^a-zA-Z0-9\x7f-\xff]++/', ' ', $this->name)))); } - public static function parseName(\ReflectionParameter $parameter, self &$attribute = null, string &$parsedName = null): string + public static function parseName(\ReflectionParameter $parameter, ?self &$attribute = null, ?string &$parsedName = null): string { $attribute = null; if (!$target = $parameter->getAttributes(self::class)[0] ?? null) { diff --git a/src/Symfony/Component/DependencyInjection/CHANGELOG.md b/src/Symfony/Component/DependencyInjection/CHANGELOG.md index ace4f5056cf8b..96ca110562db9 100644 --- a/src/Symfony/Component/DependencyInjection/CHANGELOG.md +++ b/src/Symfony/Component/DependencyInjection/CHANGELOG.md @@ -6,6 +6,7 @@ CHANGELOG * Add argument `$prepend` to `ContainerConfigurator::extension()` to prepend the configuration instead of appending it * Have `ServiceLocator` implement `ServiceCollectionInterface` + * Add `#[Lazy]` attribute as shortcut for `#[Autowire(lazy: [bool|string])]` and `#[Autoconfigure(lazy: [bool|string])]` 7.0 --- diff --git a/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php b/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php index d6564d409fc7c..41fa5052393f1 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php @@ -15,6 +15,7 @@ use Symfony\Component\DependencyInjection\Attribute\Autowire; use Symfony\Component\DependencyInjection\Attribute\AutowireCallable; use Symfony\Component\DependencyInjection\Attribute\AutowireDecorated; +use Symfony\Component\DependencyInjection\Attribute\Lazy; use Symfony\Component\DependencyInjection\Attribute\Target; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -299,7 +300,13 @@ private function autowireMethod(\ReflectionFunctionAbstract $reflectionMethod, a }; if ($checkAttributes) { - foreach ($parameter->getAttributes(Autowire::class, \ReflectionAttribute::IS_INSTANCEOF) as $attribute) { + $attributes = array_merge($parameter->getAttributes(Autowire::class, \ReflectionAttribute::IS_INSTANCEOF), $parameter->getAttributes(Lazy::class, \ReflectionAttribute::IS_INSTANCEOF)); + + if (1 < \count($attributes)) { + throw new AutowiringFailedException($this->currentId, 'Using both attributes #[Lazy] and #[Autowire] on an argument is not allowed; use the "lazy" parameter of #[Autowire] instead.'); + } + + foreach ($attributes as $attribute) { $attribute = $attribute->newInstance(); $invalidBehavior = $parameter->allowsNull() ? ContainerInterface::NULL_ON_INVALID_REFERENCE : ContainerBuilder::EXCEPTION_ON_INVALID_REFERENCE; @@ -686,7 +693,7 @@ private function getAliasesSuggestionForType(ContainerBuilder $container, string return null; } - private function populateAutowiringAlias(string $id, string $target = null): void + private function populateAutowiringAlias(string $id, ?string $target = null): void { if (!preg_match('/(?(DEFINE)(?[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+))^((?&V)(?:\\\\(?&V))*+)(?: \$((?&V)))?$/', $id, $m)) { return; @@ -706,7 +713,7 @@ private function populateAutowiringAlias(string $id, string $target = null): voi } } - private function getCombinedAlias(string $type, string $name = null): ?string + private function getCombinedAlias(string $type, ?string $name = null): ?string { if (str_contains($type, '&')) { $types = explode('&', $type); diff --git a/src/Symfony/Component/DependencyInjection/Compiler/CheckTypeDeclarationsPass.php b/src/Symfony/Component/DependencyInjection/Compiler/CheckTypeDeclarationsPass.php index 4830bad1a5385..074d899900915 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/CheckTypeDeclarationsPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/CheckTypeDeclarationsPass.php @@ -163,7 +163,7 @@ private function checkTypeDeclarations(Definition $checkedDefinition, \Reflectio /** * @throws InvalidParameterTypeException When a parameter is not compatible with the declared type */ - private function checkType(Definition $checkedDefinition, mixed $value, \ReflectionParameter $parameter, ?string $envPlaceholderUniquePrefix, \ReflectionType $reflectionType = null): void + private function checkType(Definition $checkedDefinition, mixed $value, \ReflectionParameter $parameter, ?string $envPlaceholderUniquePrefix, ?\ReflectionType $reflectionType = null): void { $reflectionType ??= $parameter->getType(); diff --git a/src/Symfony/Component/DependencyInjection/Compiler/InlineServiceDefinitionsPass.php b/src/Symfony/Component/DependencyInjection/Compiler/InlineServiceDefinitionsPass.php index 737049d489a77..b87ad69b0d536 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/InlineServiceDefinitionsPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/InlineServiceDefinitionsPass.php @@ -34,7 +34,7 @@ class InlineServiceDefinitionsPass extends AbstractRecursivePass private array $notInlinableIds = []; private ?ServiceReferenceGraph $graph = null; - public function __construct(AnalyzeServiceReferencesPass $analyzingPass = null) + public function __construct(?AnalyzeServiceReferencesPass $analyzingPass = null) { $this->analyzingPass = $analyzingPass; } diff --git a/src/Symfony/Component/DependencyInjection/Compiler/MergeExtensionConfigurationPass.php b/src/Symfony/Component/DependencyInjection/Compiler/MergeExtensionConfigurationPass.php index b49f4ed4ee910..6b19df1f71343 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/MergeExtensionConfigurationPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/MergeExtensionConfigurationPass.php @@ -153,7 +153,7 @@ class MergeExtensionConfigurationContainerBuilder extends ContainerBuilder { private string $extensionClass; - public function __construct(ExtensionInterface $extension, ParameterBagInterface $parameterBag = null) + public function __construct(ExtensionInterface $extension, ?ParameterBagInterface $parameterBag = null) { parent::__construct($parameterBag); @@ -175,7 +175,7 @@ public function compile(bool $resolveEnvPlaceholders = false): void throw new LogicException(sprintf('Cannot compile the container in extension "%s".', $this->extensionClass)); } - public function resolveEnvPlaceholders(mixed $value, string|bool $format = null, array &$usedEnvs = null): mixed + public function resolveEnvPlaceholders(mixed $value, string|bool|null $format = null, ?array &$usedEnvs = null): mixed { if (true !== $format || !\is_string($value)) { return parent::resolveEnvPlaceholders($value, $format, $usedEnvs); diff --git a/src/Symfony/Component/DependencyInjection/Compiler/RegisterAutoconfigureAttributesPass.php b/src/Symfony/Component/DependencyInjection/Compiler/RegisterAutoconfigureAttributesPass.php index d479743ecd301..ec40eee2d3872 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/RegisterAutoconfigureAttributesPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/RegisterAutoconfigureAttributesPass.php @@ -12,8 +12,10 @@ namespace Symfony\Component\DependencyInjection\Compiler; use Symfony\Component\DependencyInjection\Attribute\Autoconfigure; +use Symfony\Component\DependencyInjection\Attribute\Lazy; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Exception\AutoconfigureFailedException; use Symfony\Component\DependencyInjection\Loader\YamlFileLoader; /** @@ -42,7 +44,16 @@ public function accept(Definition $definition): bool public function processClass(ContainerBuilder $container, \ReflectionClass $class): void { - foreach ($class->getAttributes(Autoconfigure::class, \ReflectionAttribute::IS_INSTANCEOF) as $attribute) { + $autoconfigure = $class->getAttributes(Autoconfigure::class, \ReflectionAttribute::IS_INSTANCEOF); + $lazy = $class->getAttributes(Lazy::class, \ReflectionAttribute::IS_INSTANCEOF); + + if ($autoconfigure && $lazy) { + throw new AutoconfigureFailedException($class->name, 'Using both attributes #[Lazy] and #[Autoconfigure] on an argument is not allowed; use the "lazy" parameter of #[Autoconfigure] instead.'); + } + + $attributes = array_merge($autoconfigure, $lazy); + + foreach ($attributes as $attribute) { self::registerForAutoconfiguration($container, $class, $attribute); } } diff --git a/src/Symfony/Component/DependencyInjection/Compiler/ResolveBindingsPass.php b/src/Symfony/Component/DependencyInjection/Compiler/ResolveBindingsPass.php index 50e34335e9966..e00eb44b94b8d 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/ResolveBindingsPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/ResolveBindingsPass.php @@ -14,6 +14,7 @@ use Symfony\Component\DependencyInjection\Argument\BoundArgument; use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument; use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument; +use Symfony\Component\DependencyInjection\Attribute\Autowire; use Symfony\Component\DependencyInjection\Attribute\Target; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Definition; @@ -184,6 +185,13 @@ protected function processValue(mixed $value, bool $isRoot = false): mixed if (\array_key_exists($parameter->name, $arguments) && '' !== $arguments[$parameter->name]) { continue; } + if ( + $value->isAutowired() + && !$value->hasTag('container.ignore_attributes') + && $parameter->getAttributes(Autowire::class, \ReflectionAttribute::IS_INSTANCEOF) + ) { + continue; + } $typeHint = ltrim(ProxyHelper::exportType($parameter) ?? '', '?'); diff --git a/src/Symfony/Component/DependencyInjection/Compiler/ServiceLocatorTagPass.php b/src/Symfony/Component/DependencyInjection/Compiler/ServiceLocatorTagPass.php index cf35855f9230e..728feb0bd732f 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/ServiceLocatorTagPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/ServiceLocatorTagPass.php @@ -81,7 +81,7 @@ protected function processValue(mixed $value, bool $isRoot = false): mixed return new Reference($id); } - public static function register(ContainerBuilder $container, array $map, string $callerId = null): Reference + public static function register(ContainerBuilder $container, array $map, ?string $callerId = null): Reference { $locator = (new Definition(ServiceLocator::class)) ->addArgument(self::map($map)) diff --git a/src/Symfony/Component/DependencyInjection/Compiler/ServiceReferenceGraph.php b/src/Symfony/Component/DependencyInjection/Compiler/ServiceReferenceGraph.php index c90fc7ac5618d..8310fb2412b54 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/ServiceReferenceGraph.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/ServiceReferenceGraph.php @@ -74,7 +74,7 @@ public function clear(): void /** * Connects 2 nodes together in the Graph. */ - public function connect(?string $sourceId, mixed $sourceValue, ?string $destId, mixed $destValue = null, Reference $reference = null, bool $lazy = false, bool $weak = false, bool $byConstructor = false): void + public function connect(?string $sourceId, mixed $sourceValue, ?string $destId, mixed $destValue = null, ?Reference $reference = null, bool $lazy = false, bool $weak = false, bool $byConstructor = false): void { if (null === $sourceId || null === $destId) { return; diff --git a/src/Symfony/Component/DependencyInjection/Container.php b/src/Symfony/Component/DependencyInjection/Container.php index 8a570df9ecfb8..4e37fe9e43573 100644 --- a/src/Symfony/Component/DependencyInjection/Container.php +++ b/src/Symfony/Component/DependencyInjection/Container.php @@ -67,7 +67,7 @@ class Container implements ContainerInterface, ResetInterface private static \Closure $make; - public function __construct(ParameterBagInterface $parameterBag = null) + public function __construct(?ParameterBagInterface $parameterBag = null) { $this->parameterBag = $parameterBag ?? new EnvPlaceholderParameterBag(); } diff --git a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php index a5942a0b8effc..3f8aa599185fd 100644 --- a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php +++ b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php @@ -155,7 +155,7 @@ class ContainerBuilder extends Container implements TaggedContainerInterface 'mixed' => true, ]; - public function __construct(ParameterBagInterface $parameterBag = null) + public function __construct(?ParameterBagInterface $parameterBag = null) { parent::__construct($parameterBag); @@ -424,7 +424,7 @@ public function fileExists(string $path, bool|string $trackContents = true): boo * @throws BadMethodCallException When this ContainerBuilder is compiled * @throws \LogicException if the extension is not registered */ - public function loadFromExtension(string $extension, array $values = null): static + public function loadFromExtension(string $extension, ?array $values = null): static { if ($this->isCompiled()) { throw new BadMethodCallException('Cannot load from an extension on a compiled container.'); @@ -520,7 +520,7 @@ public function get(string $id, int $invalidBehavior = ContainerInterface::EXCEP return $this->doGet($id, $invalidBehavior); } - private function doGet(string $id, int $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE, array &$inlineServices = null, bool $isConstructorArgument = false): mixed + private function doGet(string $id, int $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE, ?array &$inlineServices = null, bool $isConstructorArgument = false): mixed { if (isset($inlineServices[$id])) { return $inlineServices[$id]; @@ -876,7 +876,7 @@ public function getAlias(string $id): Alias * This methods allows for simple registration of service definition * with a fluid interface. */ - public function register(string $id, string $class = null): Definition + public function register(string $id, ?string $class = null): Definition { return $this->setDefinition($id, new Definition($class)); } @@ -887,7 +887,7 @@ public function register(string $id, string $class = null): Definition * This method implements a shortcut for using setDefinition() with * an autowired definition. */ - public function autowire(string $id, string $class = null): Definition + public function autowire(string $id, ?string $class = null): Definition { return $this->setDefinition($id, (new Definition($class))->setAutowired(true)); } @@ -1001,7 +1001,7 @@ public function findDefinition(string $id): Definition * @throws RuntimeException When the service is a synthetic service * @throws InvalidArgumentException When configure callable is not callable */ - private function createService(Definition $definition, array &$inlineServices, bool $isConstructorArgument = false, string $id = null, bool|object $tryProxy = true): mixed + private function createService(Definition $definition, array &$inlineServices, bool $isConstructorArgument = false, ?string $id = null, bool|object $tryProxy = true): mixed { if (null === $id && isset($inlineServices[$h = spl_object_hash($definition)])) { return $inlineServices[$h]; @@ -1349,7 +1349,7 @@ public function registerAttributeForAutoconfiguration(string $attributeClass, ca * "$fooBar"-named arguments with $type as type-hint. Such arguments will * receive the service $id when autowiring is used. */ - public function registerAliasForArgument(string $id, string $type, string $name = null): Alias + public function registerAliasForArgument(string $id, string $type, ?string $name = null): Alias { $parsedName = (new Target($name ??= $id))->getParsedName(); @@ -1396,7 +1396,7 @@ public function getAutoconfiguredAttributes(): array * * @return mixed The value with env parameters resolved if a string or an array is passed */ - public function resolveEnvPlaceholders(mixed $value, string|bool $format = null, array &$usedEnvs = null): mixed + public function resolveEnvPlaceholders(mixed $value, string|bool|null $format = null, ?array &$usedEnvs = null): mixed { $bag = $this->getParameterBag(); if (true === $format ??= '%%env(%s)%%') { diff --git a/src/Symfony/Component/DependencyInjection/Definition.php b/src/Symfony/Component/DependencyInjection/Definition.php index 6c79ce59efa88..97b6728929d3a 100644 --- a/src/Symfony/Component/DependencyInjection/Definition.php +++ b/src/Symfony/Component/DependencyInjection/Definition.php @@ -61,7 +61,7 @@ class Definition */ public ?int $decorationOnInvalid = null; - public function __construct(string $class = null, array $arguments = []) + public function __construct(?string $class = null, array $arguments = []) { if (null !== $class) { $this->setClass($class); @@ -133,7 +133,7 @@ public function getFactory(): string|array|null * * @throws InvalidArgumentException in case the decorated service id and the new decorated service id are equals */ - public function setDecoratedService(?string $id, string $renamedId = null, int $priority = 0, int $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE): static + public function setDecoratedService(?string $id, ?string $renamedId = null, int $priority = 0, int $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE): static { if ($renamedId && $id === $renamedId) { throw new InvalidArgumentException(sprintf('The decorated service inner name for "%s" must be different than the service name itself.', $id)); @@ -257,10 +257,6 @@ public function replaceArgument(int|string $index, mixed $argument): static throw new OutOfBoundsException(sprintf('Cannot replace arguments for class "%s" if none have been configured yet.', $this->class)); } - if (\is_int($index) && ($index < 0 || $index > \count($this->arguments) - 1)) { - throw new OutOfBoundsException(sprintf('The index "%d" is not in the range [0, %d] of the arguments of class "%s".', $index, \count($this->arguments) - 1, $this->class)); - } - if (!\array_key_exists($index, $this->arguments)) { throw new OutOfBoundsException(sprintf('The argument "%s" doesn\'t exist in class "%s".', $index, $this->class)); } diff --git a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php index 6b92b77dff794..55db19552568e 100644 --- a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php +++ b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php @@ -1033,7 +1033,7 @@ private function addInlineReference(string $id, Definition $definition, string $ return $code; } - private function addInlineService(string $id, Definition $definition, Definition $inlineDef = null, bool $forConstructor = true): string + private function addInlineService(string $id, Definition $definition, ?Definition $inlineDef = null, bool $forConstructor = true): string { $code = ''; @@ -1093,7 +1093,7 @@ private function addInlineService(string $id, Definition $definition, Definition return $code."\n return \$instance;\n"; } - private function addServices(array &$services = null): string + private function addServices(?array &$services = null): string { $publicServices = $privateServices = ''; $definitions = $this->container->getDefinitions(); @@ -1135,7 +1135,7 @@ private function generateServiceFiles(array $services): iterable } } - private function addNewInstance(Definition $definition, string $return = '', string $id = null, bool $asGhostObject = false): string + private function addNewInstance(Definition $definition, string $return = '', ?string $id = null, bool $asGhostObject = false): string { $tail = $return ? str_repeat(')', substr_count($return, '(') - substr_count($return, ')')).";\n" : ''; @@ -1764,7 +1764,7 @@ private function getServiceConditionals(mixed $value): string return implode(' && ', $conditions); } - private function getDefinitionsFromArguments(array $arguments, \SplObjectStorage $definitions = null, array &$calls = [], bool $byConstructor = null): \SplObjectStorage + private function getDefinitionsFromArguments(array $arguments, ?\SplObjectStorage $definitions = null, array &$calls = [], ?bool $byConstructor = null): \SplObjectStorage { $definitions ??= new \SplObjectStorage(); @@ -1995,7 +1995,7 @@ private function dumpParameter(string $name): string return sprintf('$container->parameters[%s]', $this->doExport($name)); } - private function getServiceCall(string $id, Reference $reference = null): string + private function getServiceCall(string $id, ?Reference $reference = null): string { while ($this->container->hasAlias($id)) { $id = (string) $this->container->getAlias($id); diff --git a/src/Symfony/Component/DependencyInjection/Dumper/YamlDumper.php b/src/Symfony/Component/DependencyInjection/Dumper/YamlDumper.php index 5c96e3b328ecd..fac33bc6792d1 100644 --- a/src/Symfony/Component/DependencyInjection/Dumper/YamlDumper.php +++ b/src/Symfony/Component/DependencyInjection/Dumper/YamlDumper.php @@ -312,7 +312,7 @@ private function dumpValue(mixed $value): mixed } elseif ($value instanceof Definition) { return new TaggedValue('service', (new Parser())->parse("_:\n".$this->addService('_', $value), Yaml::PARSE_CUSTOM_TAGS)['_']['_']); } elseif ($value instanceof \UnitEnum) { - return new TaggedValue('php/const', sprintf('%s::%s', $value::class, $value->name)); + return new TaggedValue('php/enum', sprintf('%s::%s', $value::class, $value->name)); } elseif ($value instanceof AbstractArgument) { return new TaggedValue('abstract', $value->getText()); } elseif (\is_object($value) || \is_resource($value)) { @@ -322,7 +322,7 @@ private function dumpValue(mixed $value): mixed return $value; } - private function getServiceCall(string $id, Reference $reference = null): string + private function getServiceCall(string $id, ?Reference $reference = null): string { if (null !== $reference) { switch ($reference->getInvalidBehavior()) { diff --git a/src/Symfony/Component/DependencyInjection/EnvVarLoaderInterface.php b/src/Symfony/Component/DependencyInjection/EnvVarLoaderInterface.php index 0c547f8a5fae2..803156be2364b 100644 --- a/src/Symfony/Component/DependencyInjection/EnvVarLoaderInterface.php +++ b/src/Symfony/Component/DependencyInjection/EnvVarLoaderInterface.php @@ -19,7 +19,7 @@ interface EnvVarLoaderInterface { /** - * @return string[] Key/value pairs that can be accessed using the regular "%env()%" syntax + * @return array Key/value pairs that can be accessed using the regular "%env()%" syntax */ public function loadEnvVars(): array; } diff --git a/src/Symfony/Component/DependencyInjection/EnvVarProcessor.php b/src/Symfony/Component/DependencyInjection/EnvVarProcessor.php index 0e730978d3b5a..57392807f75ab 100644 --- a/src/Symfony/Component/DependencyInjection/EnvVarProcessor.php +++ b/src/Symfony/Component/DependencyInjection/EnvVarProcessor.php @@ -28,7 +28,7 @@ class EnvVarProcessor implements EnvVarProcessorInterface /** * @param \Traversable|null $loaders */ - public function __construct(ContainerInterface $container, \Traversable $loaders = null) + public function __construct(ContainerInterface $container, ?\Traversable $loaders = null) { $this->container = $container; $this->loaders = $loaders ?? new \ArrayIterator(); @@ -165,10 +165,16 @@ public function getEnv(string $prefix, string $name, \Closure $getEnv): mixed if (false !== $i || 'string' !== $prefix) { $env = $getEnv($name); } elseif ('' === ($env = $_ENV[$name] ?? (str_starts_with($name, 'HTTP_') ? null : ($_SERVER[$name] ?? null))) - || (false !== $env && false === ($env = $env ?? getenv($name) ?? false)) // null is a possible value because of thread safety issues + || (false !== $env && false === $env ??= getenv($name) ?? false) // null is a possible value because of thread safety issues ) { - foreach ($this->loadedVars as $vars) { - if (false !== ($env = ($vars[$name] ?? $env)) && '' !== $env) { + foreach ($this->loadedVars as $i => $vars) { + if (false === $env = $vars[$name] ?? $env) { + continue; + } + if ($env instanceof \Stringable) { + $this->loadedVars[$i][$name] = $env = (string) $env; + } + if ('' !== ($env ?? '')) { break; } } @@ -186,7 +192,13 @@ public function getEnv(string $prefix, string $name, \Closure $getEnv): mixed continue; } $this->loadedVars[] = $vars = $loader->loadEnvVars(); - if (false !== ($env = ($vars[$name] ?? $env)) && '' !== $env) { + if (false === $env = $vars[$name] ?? $env) { + continue; + } + if ($env instanceof \Stringable) { + $this->loadedVars[array_key_last($this->loadedVars)][$name] = $env = (string) $env; + } + if ('' !== ($env ?? '')) { $ended = false; break; } diff --git a/src/Symfony/Component/DependencyInjection/Exception/AutoconfigureFailedException.php b/src/Symfony/Component/DependencyInjection/Exception/AutoconfigureFailedException.php new file mode 100644 index 0000000000000..f7ce978599cf2 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Exception/AutoconfigureFailedException.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Exception; + +class AutoconfigureFailedException extends AutowiringFailedException +{ +} diff --git a/src/Symfony/Component/DependencyInjection/Exception/AutowiringFailedException.php b/src/Symfony/Component/DependencyInjection/Exception/AutowiringFailedException.php index 9304f1fdca542..53e05ceae9e4a 100644 --- a/src/Symfony/Component/DependencyInjection/Exception/AutowiringFailedException.php +++ b/src/Symfony/Component/DependencyInjection/Exception/AutowiringFailedException.php @@ -19,13 +19,11 @@ class AutowiringFailedException extends RuntimeException private string $serviceId; private ?\Closure $messageCallback = null; - public function __construct(string $serviceId, string|\Closure $message = '', int $code = 0, \Throwable $previous = null) + public function __construct(string $serviceId, string|\Closure $message = '', int $code = 0, ?\Throwable $previous = null) { $this->serviceId = $serviceId; - if ($message instanceof \Closure - && (\function_exists('xdebug_is_enabled') ? xdebug_is_enabled() : \function_exists('xdebug_info')) - ) { + if ($message instanceof \Closure && \function_exists('xdebug_is_enabled') && xdebug_is_enabled()) { $message = $message(); } diff --git a/src/Symfony/Component/DependencyInjection/Exception/EnvParameterException.php b/src/Symfony/Component/DependencyInjection/Exception/EnvParameterException.php index 48b5e486ae71d..6cd53c9f738ba 100644 --- a/src/Symfony/Component/DependencyInjection/Exception/EnvParameterException.php +++ b/src/Symfony/Component/DependencyInjection/Exception/EnvParameterException.php @@ -18,7 +18,7 @@ */ class EnvParameterException extends InvalidArgumentException { - public function __construct(array $envs, \Throwable $previous = null, string $message = 'Incompatible use of dynamic environment variables "%s" found in parameters.') + public function __construct(array $envs, ?\Throwable $previous = null, string $message = 'Incompatible use of dynamic environment variables "%s" found in parameters.') { parent::__construct(sprintf($message, implode('", "', $envs)), 0, $previous); } diff --git a/src/Symfony/Component/DependencyInjection/Exception/ParameterCircularReferenceException.php b/src/Symfony/Component/DependencyInjection/Exception/ParameterCircularReferenceException.php index c8029d08ac0c6..408801f43f594 100644 --- a/src/Symfony/Component/DependencyInjection/Exception/ParameterCircularReferenceException.php +++ b/src/Symfony/Component/DependencyInjection/Exception/ParameterCircularReferenceException.php @@ -20,7 +20,7 @@ class ParameterCircularReferenceException extends RuntimeException { private array $parameters; - public function __construct(array $parameters, \Throwable $previous = null) + public function __construct(array $parameters, ?\Throwable $previous = null) { parent::__construct(sprintf('Circular reference detected for parameter "%s" ("%s" > "%s").', $parameters[0], implode('" > "', $parameters), $parameters[0]), 0, $previous); diff --git a/src/Symfony/Component/DependencyInjection/Exception/ParameterNotFoundException.php b/src/Symfony/Component/DependencyInjection/Exception/ParameterNotFoundException.php index 55df87ee10752..37876e4cabc1e 100644 --- a/src/Symfony/Component/DependencyInjection/Exception/ParameterNotFoundException.php +++ b/src/Symfony/Component/DependencyInjection/Exception/ParameterNotFoundException.php @@ -34,7 +34,7 @@ class ParameterNotFoundException extends InvalidArgumentException implements Not * @param string[] $alternatives Some parameter name alternatives * @param string|null $nonNestedAlternative The alternative parameter name when the user expected dot notation for nested parameters */ - public function __construct(string $key, string $sourceId = null, string $sourceKey = null, \Throwable $previous = null, array $alternatives = [], string $nonNestedAlternative = null) + public function __construct(string $key, ?string $sourceId = null, ?string $sourceKey = null, ?\Throwable $previous = null, array $alternatives = [], ?string $nonNestedAlternative = null) { $this->key = $key; $this->sourceId = $sourceId; diff --git a/src/Symfony/Component/DependencyInjection/Exception/ServiceCircularReferenceException.php b/src/Symfony/Component/DependencyInjection/Exception/ServiceCircularReferenceException.php index 0d8609bd57ecb..f7a85bd251b45 100644 --- a/src/Symfony/Component/DependencyInjection/Exception/ServiceCircularReferenceException.php +++ b/src/Symfony/Component/DependencyInjection/Exception/ServiceCircularReferenceException.php @@ -21,7 +21,7 @@ class ServiceCircularReferenceException extends RuntimeException private string $serviceId; private array $path; - public function __construct(string $serviceId, array $path, \Throwable $previous = null) + public function __construct(string $serviceId, array $path, ?\Throwable $previous = null) { parent::__construct(sprintf('Circular reference detected for service "%s", path: "%s".', $serviceId, implode(' -> ', $path)), 0, $previous); diff --git a/src/Symfony/Component/DependencyInjection/Exception/ServiceNotFoundException.php b/src/Symfony/Component/DependencyInjection/Exception/ServiceNotFoundException.php index 2b0943c783cfa..a7f82ffd1b405 100644 --- a/src/Symfony/Component/DependencyInjection/Exception/ServiceNotFoundException.php +++ b/src/Symfony/Component/DependencyInjection/Exception/ServiceNotFoundException.php @@ -24,7 +24,7 @@ class ServiceNotFoundException extends InvalidArgumentException implements NotFo private ?string $sourceId; private array $alternatives; - public function __construct(string $id, string $sourceId = null, \Throwable $previous = null, array $alternatives = [], string $msg = null) + public function __construct(string $id, ?string $sourceId = null, ?\Throwable $previous = null, array $alternatives = [], ?string $msg = null) { if (null !== $msg) { // no-op diff --git a/src/Symfony/Component/DependencyInjection/ExpressionLanguage.php b/src/Symfony/Component/DependencyInjection/ExpressionLanguage.php index 1a7f5fd38efb0..84d45dbdd70c1 100644 --- a/src/Symfony/Component/DependencyInjection/ExpressionLanguage.php +++ b/src/Symfony/Component/DependencyInjection/ExpressionLanguage.php @@ -27,7 +27,7 @@ */ class ExpressionLanguage extends BaseExpressionLanguage { - public function __construct(CacheItemPoolInterface $cache = null, array $providers = [], callable $serviceCompiler = null, \Closure $getEnv = null) + public function __construct(?CacheItemPoolInterface $cache = null, array $providers = [], ?callable $serviceCompiler = null, ?\Closure $getEnv = null) { // prepend the default provider to let users override it easily array_unshift($providers, new ExpressionLanguageProvider($serviceCompiler, $getEnv)); diff --git a/src/Symfony/Component/DependencyInjection/ExpressionLanguageProvider.php b/src/Symfony/Component/DependencyInjection/ExpressionLanguageProvider.php index d0cc1f70b5939..6ae797d864ecc 100644 --- a/src/Symfony/Component/DependencyInjection/ExpressionLanguageProvider.php +++ b/src/Symfony/Component/DependencyInjection/ExpressionLanguageProvider.php @@ -30,7 +30,7 @@ class ExpressionLanguageProvider implements ExpressionFunctionProviderInterface private ?\Closure $getEnv; - public function __construct(callable $serviceCompiler = null, \Closure $getEnv = null) + public function __construct(?callable $serviceCompiler = null, ?\Closure $getEnv = null) { $this->serviceCompiler = null === $serviceCompiler ? null : $serviceCompiler(...); $this->getEnv = $getEnv; diff --git a/src/Symfony/Component/DependencyInjection/LazyProxy/PhpDumper/DumperInterface.php b/src/Symfony/Component/DependencyInjection/LazyProxy/PhpDumper/DumperInterface.php index 6f6cc3fcc4508..05f2fbfb50dc2 100644 --- a/src/Symfony/Component/DependencyInjection/LazyProxy/PhpDumper/DumperInterface.php +++ b/src/Symfony/Component/DependencyInjection/LazyProxy/PhpDumper/DumperInterface.php @@ -25,7 +25,7 @@ interface DumperInterface * * @param bool|null &$asGhostObject Set to true after the call if the proxy is a ghost object */ - public function isProxyCandidate(Definition $definition, bool &$asGhostObject = null, string $id = null): bool; + public function isProxyCandidate(Definition $definition, ?bool &$asGhostObject = null, ?string $id = null): bool; /** * Generates the code to be used to instantiate a proxy in the dumped factory code. @@ -35,5 +35,5 @@ public function getProxyFactoryCode(Definition $definition, string $id, string $ /** * Generates the code for the lazy proxy. */ - public function getProxyCode(Definition $definition, string $id = null): string; + public function getProxyCode(Definition $definition, ?string $id = null): string; } diff --git a/src/Symfony/Component/DependencyInjection/LazyProxy/PhpDumper/LazyServiceDumper.php b/src/Symfony/Component/DependencyInjection/LazyProxy/PhpDumper/LazyServiceDumper.php index 282353916a970..7e8d7db84c041 100644 --- a/src/Symfony/Component/DependencyInjection/LazyProxy/PhpDumper/LazyServiceDumper.php +++ b/src/Symfony/Component/DependencyInjection/LazyProxy/PhpDumper/LazyServiceDumper.php @@ -26,7 +26,7 @@ public function __construct( ) { } - public function isProxyCandidate(Definition $definition, bool &$asGhostObject = null, string $id = null): bool + public function isProxyCandidate(Definition $definition, ?bool &$asGhostObject = null, ?string $id = null): bool { $asGhostObject = false; @@ -96,7 +96,7 @@ public function getProxyFactoryCode(Definition $definition, string $id, string $ EOF; } - public function getProxyCode(Definition $definition, string $id = null): string + public function getProxyCode(Definition $definition, ?string $id = null): string { if (!$this->isProxyCandidate($definition, $asGhostObject, $id)) { throw new InvalidArgumentException(sprintf('Cannot instantiate lazy proxy for service "%s".', $id ?? $definition->getClass())); @@ -139,7 +139,7 @@ public function getProxyCode(Definition $definition, string $id = null): string } } - public function getProxyClass(Definition $definition, bool $asGhostObject, \ReflectionClass &$class = null): string + public function getProxyClass(Definition $definition, bool $asGhostObject, ?\ReflectionClass &$class = null): string { $class = 'object' !== $definition->getClass() ? $definition->getClass() : 'stdClass'; $class = new \ReflectionClass($class); diff --git a/src/Symfony/Component/DependencyInjection/LazyProxy/PhpDumper/NullDumper.php b/src/Symfony/Component/DependencyInjection/LazyProxy/PhpDumper/NullDumper.php index daa6fed79fdb3..c987b19d4c632 100644 --- a/src/Symfony/Component/DependencyInjection/LazyProxy/PhpDumper/NullDumper.php +++ b/src/Symfony/Component/DependencyInjection/LazyProxy/PhpDumper/NullDumper.php @@ -22,7 +22,7 @@ */ class NullDumper implements DumperInterface { - public function isProxyCandidate(Definition $definition, bool &$asGhostObject = null, string $id = null): bool + public function isProxyCandidate(Definition $definition, ?bool &$asGhostObject = null, ?string $id = null): bool { return $asGhostObject = false; } @@ -32,7 +32,7 @@ public function getProxyFactoryCode(Definition $definition, string $id, string $ return ''; } - public function getProxyCode(Definition $definition, string $id = null): string + public function getProxyCode(Definition $definition, ?string $id = null): string { return ''; } diff --git a/src/Symfony/Component/DependencyInjection/Loader/ClosureLoader.php b/src/Symfony/Component/DependencyInjection/Loader/ClosureLoader.php index 94305ae9438b2..1e3061d4fd45e 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/ClosureLoader.php +++ b/src/Symfony/Component/DependencyInjection/Loader/ClosureLoader.php @@ -25,18 +25,18 @@ class ClosureLoader extends Loader { private ContainerBuilder $container; - public function __construct(ContainerBuilder $container, string $env = null) + public function __construct(ContainerBuilder $container, ?string $env = null) { $this->container = $container; parent::__construct($env); } - public function load(mixed $resource, string $type = null): mixed + public function load(mixed $resource, ?string $type = null): mixed { return $resource($this->container, $this->env); } - public function supports(mixed $resource, string $type = null): bool + public function supports(mixed $resource, ?string $type = null): bool { return $resource instanceof \Closure; } diff --git a/src/Symfony/Component/DependencyInjection/Loader/Configurator/AbstractServiceConfigurator.php b/src/Symfony/Component/DependencyInjection/Loader/Configurator/AbstractServiceConfigurator.php index fcb37fc287fd5..295a35109be72 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/Configurator/AbstractServiceConfigurator.php +++ b/src/Symfony/Component/DependencyInjection/Loader/Configurator/AbstractServiceConfigurator.php @@ -20,7 +20,7 @@ abstract class AbstractServiceConfigurator extends AbstractConfigurator protected ?string $id; private array $defaultTags = []; - public function __construct(ServicesConfigurator $parent, Definition $definition, string $id = null, array $defaultTags = []) + public function __construct(ServicesConfigurator $parent, Definition $definition, ?string $id = null, array $defaultTags = []) { $this->parent = $parent; $this->definition = $definition; @@ -42,7 +42,7 @@ public function __destruct() /** * Registers a service. */ - final public function set(?string $id, string $class = null): ServiceConfigurator + final public function set(?string $id, ?string $class = null): ServiceConfigurator { $this->__destruct(); @@ -106,7 +106,7 @@ final public function stack(string $id, array $services): AliasConfigurator /** * Registers a service. */ - final public function __invoke(string $id, string $class = null): ServiceConfigurator + final public function __invoke(string $id, ?string $class = null): ServiceConfigurator { $this->__destruct(); diff --git a/src/Symfony/Component/DependencyInjection/Loader/Configurator/ContainerConfigurator.php b/src/Symfony/Component/DependencyInjection/Loader/Configurator/ContainerConfigurator.php index ad1110e17442a..aed608053a5c2 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/Configurator/ContainerConfigurator.php +++ b/src/Symfony/Component/DependencyInjection/Loader/Configurator/ContainerConfigurator.php @@ -38,7 +38,7 @@ class ContainerConfigurator extends AbstractConfigurator private int $anonymousCount = 0; private ?string $env; - public function __construct(ContainerBuilder $container, PhpFileLoader $loader, array &$instanceof, string $path, string $file, string $env = null) + public function __construct(ContainerBuilder $container, PhpFileLoader $loader, array &$instanceof, string $path, string $file, ?string $env = null) { $this->container = $container; $this->loader = $loader; @@ -64,7 +64,7 @@ final public function extension(string $namespace, array $config, bool $prepend $this->container->loadFromExtension($namespace, static::processValue($config)); } - final public function import(string $resource, string $type = null, bool|string $ignoreErrors = false): void + final public function import(string $resource, ?string $type = null, bool|string $ignoreErrors = false): void { $this->loader->setCurrentDir(\dirname($this->path)); $this->loader->import($resource, $type, $ignoreErrors, $this->file); @@ -117,7 +117,7 @@ function service(string $serviceId): ReferenceConfigurator /** * Creates an inline service. */ -function inline_service(string $class = null): InlineServiceConfigurator +function inline_service(?string $class = null): InlineServiceConfigurator { return new InlineServiceConfigurator(new Definition($class)); } @@ -147,7 +147,7 @@ function iterator(array $values): IteratorArgument /** * Creates a lazy iterator by tag name. */ -function tagged_iterator(string $tag, string $indexAttribute = null, string $defaultIndexMethod = null, string $defaultPriorityMethod = null, string|array $exclude = [], bool $excludeSelf = true): TaggedIteratorArgument +function tagged_iterator(string $tag, ?string $indexAttribute = null, ?string $defaultIndexMethod = null, ?string $defaultPriorityMethod = null, string|array $exclude = [], bool $excludeSelf = true): TaggedIteratorArgument { return new TaggedIteratorArgument($tag, $indexAttribute, $defaultIndexMethod, false, $defaultPriorityMethod, (array) $exclude, $excludeSelf); } @@ -155,7 +155,7 @@ function tagged_iterator(string $tag, string $indexAttribute = null, string $def /** * Creates a service locator by tag name. */ -function tagged_locator(string $tag, string $indexAttribute = null, string $defaultIndexMethod = null, string $defaultPriorityMethod = null, string|array $exclude = [], bool $excludeSelf = true): ServiceLocatorArgument +function tagged_locator(string $tag, ?string $indexAttribute = null, ?string $defaultIndexMethod = null, ?string $defaultPriorityMethod = null, string|array $exclude = [], bool $excludeSelf = true): ServiceLocatorArgument { return new ServiceLocatorArgument(new TaggedIteratorArgument($tag, $indexAttribute, $defaultIndexMethod, true, $defaultPriorityMethod, (array) $exclude, $excludeSelf)); } diff --git a/src/Symfony/Component/DependencyInjection/Loader/Configurator/DefaultsConfigurator.php b/src/Symfony/Component/DependencyInjection/Loader/Configurator/DefaultsConfigurator.php index 2236cd77a8802..1f26c978858da 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/Configurator/DefaultsConfigurator.php +++ b/src/Symfony/Component/DependencyInjection/Loader/Configurator/DefaultsConfigurator.php @@ -28,7 +28,7 @@ class DefaultsConfigurator extends AbstractServiceConfigurator private ?string $path; - public function __construct(ServicesConfigurator $parent, Definition $definition, string $path = null) + public function __construct(ServicesConfigurator $parent, Definition $definition, ?string $path = null) { parent::__construct($parent, $definition, null, []); diff --git a/src/Symfony/Component/DependencyInjection/Loader/Configurator/InstanceofConfigurator.php b/src/Symfony/Component/DependencyInjection/Loader/Configurator/InstanceofConfigurator.php index 2db004051e5e2..9de0baa4cb13e 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/Configurator/InstanceofConfigurator.php +++ b/src/Symfony/Component/DependencyInjection/Loader/Configurator/InstanceofConfigurator.php @@ -33,7 +33,7 @@ class InstanceofConfigurator extends AbstractServiceConfigurator private ?string $path; - public function __construct(ServicesConfigurator $parent, Definition $definition, string $id, string $path = null) + public function __construct(ServicesConfigurator $parent, Definition $definition, string $id, ?string $path = null) { parent::__construct($parent, $definition, $id, []); diff --git a/src/Symfony/Component/DependencyInjection/Loader/Configurator/PrototypeConfigurator.php b/src/Symfony/Component/DependencyInjection/Loader/Configurator/PrototypeConfigurator.php index 4ab957a85ce30..5d844722d6f0c 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/Configurator/PrototypeConfigurator.php +++ b/src/Symfony/Component/DependencyInjection/Loader/Configurator/PrototypeConfigurator.php @@ -44,7 +44,7 @@ class PrototypeConfigurator extends AbstractServiceConfigurator private bool $allowParent; private ?string $path; - public function __construct(ServicesConfigurator $parent, PhpFileLoader $loader, Definition $defaults, string $namespace, string $resource, bool $allowParent, string $path = null) + public function __construct(ServicesConfigurator $parent, PhpFileLoader $loader, Definition $defaults, string $namespace, string $resource, bool $allowParent, ?string $path = null) { $definition = new Definition(); if (!$defaults->isPublic() || !$defaults->isPrivate()) { diff --git a/src/Symfony/Component/DependencyInjection/Loader/Configurator/ServiceConfigurator.php b/src/Symfony/Component/DependencyInjection/Loader/Configurator/ServiceConfigurator.php index 9042ed1d6b494..57f498acf6662 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/Configurator/ServiceConfigurator.php +++ b/src/Symfony/Component/DependencyInjection/Loader/Configurator/ServiceConfigurator.php @@ -49,7 +49,7 @@ class ServiceConfigurator extends AbstractServiceConfigurator private ?string $path; private bool $destructed = false; - public function __construct(ContainerBuilder $container, array $instanceof, bool $allowParent, ServicesConfigurator $parent, Definition $definition, ?string $id, array $defaultTags, string $path = null) + public function __construct(ContainerBuilder $container, array $instanceof, bool $allowParent, ServicesConfigurator $parent, Definition $definition, ?string $id, array $defaultTags, ?string $path = null) { $this->container = $container; $this->instanceof = $instanceof; diff --git a/src/Symfony/Component/DependencyInjection/Loader/Configurator/ServicesConfigurator.php b/src/Symfony/Component/DependencyInjection/Loader/Configurator/ServicesConfigurator.php index ee4d1ad16039d..0c2e5a461f953 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/Configurator/ServicesConfigurator.php +++ b/src/Symfony/Component/DependencyInjection/Loader/Configurator/ServicesConfigurator.php @@ -34,7 +34,7 @@ class ServicesConfigurator extends AbstractConfigurator private string $anonymousHash; private int $anonymousCount; - public function __construct(ContainerBuilder $container, PhpFileLoader $loader, array &$instanceof, string $path = null, int &$anonymousCount = 0) + public function __construct(ContainerBuilder $container, PhpFileLoader $loader, array &$instanceof, ?string $path = null, int &$anonymousCount = 0) { $this->defaults = new Definition(); $this->container = $container; @@ -70,7 +70,7 @@ final public function instanceof(string $fqcn): InstanceofConfigurator * @param string|null $id The service id, or null to create an anonymous service * @param string|null $class The class of the service, or null when $id is also the class name */ - final public function set(?string $id, string $class = null): ServiceConfigurator + final public function set(?string $id, ?string $class = null): ServiceConfigurator { $defaults = $this->defaults; $definition = new Definition(); @@ -180,7 +180,7 @@ final public function stack(string $id, array $services): AliasConfigurator /** * Registers a service. */ - final public function __invoke(string $id, string $class = null): ServiceConfigurator + final public function __invoke(string $id, ?string $class = null): ServiceConfigurator { return $this->set($id, $class); } diff --git a/src/Symfony/Component/DependencyInjection/Loader/Configurator/Traits/DecorateTrait.php b/src/Symfony/Component/DependencyInjection/Loader/Configurator/Traits/DecorateTrait.php index ae6d3c9487382..afb56ae3d1907 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/Configurator/Traits/DecorateTrait.php +++ b/src/Symfony/Component/DependencyInjection/Loader/Configurator/Traits/DecorateTrait.php @@ -25,7 +25,7 @@ trait DecorateTrait * * @throws InvalidArgumentException in case the decorated service id and the new decorated service id are equals */ - final public function decorate(?string $id, string $renamedId = null, int $priority = 0, int $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE): static + final public function decorate(?string $id, ?string $renamedId = null, int $priority = 0, int $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE): static { $this->definition->setDecoratedService($id, $renamedId, $priority, $invalidBehavior); diff --git a/src/Symfony/Component/DependencyInjection/Loader/DirectoryLoader.php b/src/Symfony/Component/DependencyInjection/Loader/DirectoryLoader.php index 1b5e81d1981c0..d435366f05ee6 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/DirectoryLoader.php +++ b/src/Symfony/Component/DependencyInjection/Loader/DirectoryLoader.php @@ -18,7 +18,7 @@ */ class DirectoryLoader extends FileLoader { - public function load(mixed $file, string $type = null): mixed + public function load(mixed $file, ?string $type = null): mixed { $file = rtrim($file, '/'); $path = $this->locator->locate($file); @@ -39,7 +39,7 @@ public function load(mixed $file, string $type = null): mixed return null; } - public function supports(mixed $resource, string $type = null): bool + public function supports(mixed $resource, ?string $type = null): bool { if ('directory' === $type) { return true; diff --git a/src/Symfony/Component/DependencyInjection/Loader/FileLoader.php b/src/Symfony/Component/DependencyInjection/Loader/FileLoader.php index 185fb33d9f77b..76432ad360861 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/FileLoader.php +++ b/src/Symfony/Component/DependencyInjection/Loader/FileLoader.php @@ -46,7 +46,7 @@ abstract class FileLoader extends BaseFileLoader protected array $aliases = []; protected bool $autoRegisterAliasesForSinglyImplementedInterfaces = true; - public function __construct(ContainerBuilder $container, FileLocatorInterface $locator, string $env = null) + public function __construct(ContainerBuilder $container, FileLocatorInterface $locator, ?string $env = null) { $this->container = $container; @@ -56,7 +56,7 @@ public function __construct(ContainerBuilder $container, FileLocatorInterface $l /** * @param bool|string $ignoreErrors Whether errors should be ignored; pass "not_found" to ignore only when the loaded resource is not found */ - public function import(mixed $resource, string $type = null, bool|string $ignoreErrors = false, string $sourceResource = null, $exclude = null): mixed + public function import(mixed $resource, ?string $type = null, bool|string $ignoreErrors = false, ?string $sourceResource = null, $exclude = null): mixed { $args = \func_get_args(); @@ -96,7 +96,7 @@ public function import(mixed $resource, string $type = null, bool|string $ignore * @param string|string[]|null $exclude A globbed path of files to exclude or an array of globbed paths of files to exclude * @param string|null $source The path to the file that defines the auto-discovery rule */ - public function registerClasses(Definition $prototype, string $namespace, string $resource, string|array $exclude = null, string $source = null): void + public function registerClasses(Definition $prototype, string $namespace, string $resource, string|array|null $exclude = null, ?string $source = null): void { if (!str_ends_with($namespace, '\\')) { throw new InvalidArgumentException(sprintf('Namespace prefix must end with a "\\": "%s".', $namespace)); diff --git a/src/Symfony/Component/DependencyInjection/Loader/GlobFileLoader.php b/src/Symfony/Component/DependencyInjection/Loader/GlobFileLoader.php index 50349b25793d0..4716f11a7f8c3 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/GlobFileLoader.php +++ b/src/Symfony/Component/DependencyInjection/Loader/GlobFileLoader.php @@ -18,7 +18,7 @@ */ class GlobFileLoader extends FileLoader { - public function load(mixed $resource, string $type = null): mixed + public function load(mixed $resource, ?string $type = null): mixed { foreach ($this->glob($resource, false, $globResource) as $path => $info) { $this->import($path); @@ -29,7 +29,7 @@ public function load(mixed $resource, string $type = null): mixed return null; } - public function supports(mixed $resource, string $type = null): bool + public function supports(mixed $resource, ?string $type = null): bool { return 'glob' === $type; } diff --git a/src/Symfony/Component/DependencyInjection/Loader/IniFileLoader.php b/src/Symfony/Component/DependencyInjection/Loader/IniFileLoader.php index c177790e37c91..424fbdd51a2b3 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/IniFileLoader.php +++ b/src/Symfony/Component/DependencyInjection/Loader/IniFileLoader.php @@ -21,7 +21,7 @@ */ class IniFileLoader extends FileLoader { - public function load(mixed $resource, string $type = null): mixed + public function load(mixed $resource, ?string $type = null): mixed { $path = $this->locator->locate($resource); @@ -55,7 +55,7 @@ public function load(mixed $resource, string $type = null): mixed return null; } - public function supports(mixed $resource, string $type = null): bool + public function supports(mixed $resource, ?string $type = null): bool { if (!\is_string($resource)) { return false; diff --git a/src/Symfony/Component/DependencyInjection/Loader/PhpFileLoader.php b/src/Symfony/Component/DependencyInjection/Loader/PhpFileLoader.php index 13021f2f00a2e..9acaa8cffbfbc 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/PhpFileLoader.php +++ b/src/Symfony/Component/DependencyInjection/Loader/PhpFileLoader.php @@ -36,13 +36,13 @@ class PhpFileLoader extends FileLoader protected bool $autoRegisterAliasesForSinglyImplementedInterfaces = false; private ?ConfigBuilderGeneratorInterface $generator; - public function __construct(ContainerBuilder $container, FileLocatorInterface $locator, string $env = null, ConfigBuilderGeneratorInterface $generator = null) + public function __construct(ContainerBuilder $container, FileLocatorInterface $locator, ?string $env = null, ?ConfigBuilderGeneratorInterface $generator = null) { parent::__construct($container, $locator, $env); $this->generator = $generator; } - public function load(mixed $resource, string $type = null): mixed + public function load(mixed $resource, ?string $type = null): mixed { // the container and loader variables are exposed to the included file below $container = $this->container; @@ -71,7 +71,7 @@ public function load(mixed $resource, string $type = null): mixed return null; } - public function supports(mixed $resource, string $type = null): bool + public function supports(mixed $resource, ?string $type = null): bool { if (!\is_string($resource)) { return false; diff --git a/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php b/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php index d26833828e147..6fedd9821acee 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php +++ b/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php @@ -41,7 +41,7 @@ class XmlFileLoader extends FileLoader protected bool $autoRegisterAliasesForSinglyImplementedInterfaces = false; - public function load(mixed $resource, string $type = null): mixed + public function load(mixed $resource, ?string $type = null): mixed { $path = $this->locator->locate($resource); @@ -68,7 +68,7 @@ public function load(mixed $resource, string $type = null): mixed return null; } - private function loadXml(\DOMDocument $xml, string $path, \DOMNode $root = null): void + private function loadXml(\DOMDocument $xml, string $path, ?\DOMNode $root = null): void { $defaults = $this->getServiceDefaults($xml, $path, $root); @@ -93,7 +93,7 @@ private function loadXml(\DOMDocument $xml, string $path, \DOMNode $root = null) } } - public function supports(mixed $resource, string $type = null): bool + public function supports(mixed $resource, ?string $type = null): bool { if (!\is_string($resource)) { return false; @@ -106,14 +106,14 @@ public function supports(mixed $resource, string $type = null): bool return 'xml' === $type; } - private function parseParameters(\DOMDocument $xml, string $file, \DOMNode $root = null): void + private function parseParameters(\DOMDocument $xml, string $file, ?\DOMNode $root = null): void { if ($parameters = $this->getChildren($root ?? $xml->documentElement, 'parameters')) { $this->container->getParameterBag()->add($this->getArgumentsAsPhp($parameters[0], 'parameter', $file)); } } - private function parseImports(\DOMDocument $xml, string $file, \DOMNode $root = null): void + private function parseImports(\DOMDocument $xml, string $file, ?\DOMNode $root = null): void { $xpath = new \DOMXPath($xml); $xpath->registerNamespace('container', self::NS); @@ -129,7 +129,7 @@ private function parseImports(\DOMDocument $xml, string $file, \DOMNode $root = } } - private function parseDefinitions(\DOMDocument $xml, string $file, Definition $defaults, \DOMNode $root = null): void + private function parseDefinitions(\DOMDocument $xml, string $file, Definition $defaults, ?\DOMNode $root = null): void { $xpath = new \DOMXPath($xml); $xpath->registerNamespace('container', self::NS); @@ -187,7 +187,7 @@ private function parseDefinitions(\DOMDocument $xml, string $file, Definition $d } } - private function getServiceDefaults(\DOMDocument $xml, string $file, \DOMNode $root = null): Definition + private function getServiceDefaults(\DOMDocument $xml, string $file, ?\DOMNode $root = null): Definition { $xpath = new \DOMXPath($xml); $xpath->registerNamespace('container', self::NS); @@ -469,7 +469,7 @@ private function parseFileToDOM(string $file): \DOMDocument /** * Processes anonymous services. */ - private function processAnonymousServices(\DOMDocument $xml, string $file, \DOMNode $root = null): void + private function processAnonymousServices(\DOMDocument $xml, string $file, ?\DOMNode $root = null): void { $definitions = []; $count = 0; diff --git a/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php b/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php index eb399b8652bdc..c74e606be3831 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php +++ b/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php @@ -116,7 +116,7 @@ class YamlFileLoader extends FileLoader private int $anonymousServicesCount; private string $anonymousServicesSuffix; - public function load(mixed $resource, string $type = null): mixed + public function load(mixed $resource, ?string $type = null): mixed { $path = $this->locator->locate($resource); @@ -180,7 +180,7 @@ private function loadContent(array $content, string $path): void } } - public function supports(mixed $resource, string $type = null): bool + public function supports(mixed $resource, ?string $type = null): bool { if (!\is_string($resource)) { return false; diff --git a/src/Symfony/Component/DependencyInjection/ServiceLocator.php b/src/Symfony/Component/DependencyInjection/ServiceLocator.php index 74b03da6a1dbc..fbf76690f2720 100644 --- a/src/Symfony/Component/DependencyInjection/ServiceLocator.php +++ b/src/Symfony/Component/DependencyInjection/ServiceLocator.php @@ -142,7 +142,7 @@ private function createCircularReferenceException(string $id, array $path): Cont return new ServiceCircularReferenceException($id, $path); } - private function formatAlternatives(array $alternatives = null, string $separator = 'and'): string + private function formatAlternatives(?array $alternatives = null, string $separator = 'and'): string { $format = '"%s"%s'; if (null === $alternatives) { diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php index 3045367aad1a4..62ed73a767cf9 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php @@ -1368,4 +1368,29 @@ public function testNestedAttributes() ]; $this->assertEquals($expected, $container->getDefinition(AutowireNestedAttributes::class)->getArgument(0)); } + + public function testLazyServiceAttribute() + { + $container = new ContainerBuilder(); + $container->register('a', A::class)->setAutowired(true); + $container->register('foo', LazyServiceAttributeAutowiring::class)->setAutowired(true); + + (new AutowirePass())->process($container); + + $expected = new Reference('.lazy.'.A::class); + $this->assertEquals($expected, $container->getDefinition('foo')->getArgument(0)); + } + + public function testLazyNotCompatibleWithAutowire() + { + $container = new ContainerBuilder(); + $container->register('a', A::class)->setAutowired(true); + $container->register('foo', LazyAutowireServiceAttributesAutowiring::class)->setAutowired(true); + + try { + (new AutowirePass())->process($container); + } catch (AutowiringFailedException $e) { + $this->assertSame('Using both attributes #[Lazy] and #[Autowire] on an argument is not allowed; use the "lazy" parameter of #[Autowire] instead.', $e->getMessage()); + } + } } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/IntegrationTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/IntegrationTest.php index a2896d1492a6a..cd0ac69738674 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/IntegrationTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/IntegrationTest.php @@ -15,6 +15,7 @@ use Psr\Container\ContainerInterface; use Symfony\Component\Config\FileLocator; use Symfony\Component\DependencyInjection\Alias; +use Symfony\Component\DependencyInjection\Argument\BoundArgument; use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument; use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument; use Symfony\Component\DependencyInjection\ChildDefinition; @@ -50,6 +51,7 @@ use Symfony\Component\DependencyInjection\Tests\Fixtures\TaggedLocatorConsumerWithDefaultIndexMethodAndWithDefaultPriorityMethod; use Symfony\Component\DependencyInjection\Tests\Fixtures\TaggedLocatorConsumerWithDefaultPriorityMethod; use Symfony\Component\DependencyInjection\Tests\Fixtures\TaggedLocatorConsumerWithoutIndex; +use Symfony\Component\DependencyInjection\Tests\Fixtures\TaggedLocatorConsumerWithServiceSubscriber; use Symfony\Component\DependencyInjection\Tests\Fixtures\TaggedService1; use Symfony\Component\DependencyInjection\Tests\Fixtures\TaggedService2; use Symfony\Component\DependencyInjection\Tests\Fixtures\TaggedService3; @@ -246,7 +248,7 @@ public function testAliasDecoratedService() /** * @dataProvider getYamlCompileTests */ - public function testYamlContainerCompiles($directory, $actualServiceId, $expectedServiceId, ContainerBuilder $mainContainer = null) + public function testYamlContainerCompiles($directory, $actualServiceId, $expectedServiceId, ?ContainerBuilder $mainContainer = null) { // allow a container to be passed in, which might have autoconfigure settings $container = $mainContainer ?? new ContainerBuilder(); @@ -1118,6 +1120,66 @@ public function testTaggedIteratorAndLocatorWithExclude() $this->assertTrue($locator->has(AutoconfiguredService2::class)); $this->assertFalse($locator->has(TaggedConsumerWithExclude::class)); } + + public function testAutowireAttributeHasPriorityOverBindings() + { + $container = new ContainerBuilder(); + $container->register(FooTagClass::class) + ->setPublic(true) + ->addTag('foo_bar', ['key' => 'tagged_service']) + ; + $container->register(TaggedLocatorConsumerWithServiceSubscriber::class) + ->setBindings([ + '$locator' => new BoundArgument(new Reference('service_container'), false), + ]) + ->setPublic(true) + ->setAutowired(true) + ->addTag('container.service_subscriber') + ; + $container->register('subscribed_service', \stdClass::class) + ->setPublic(true) + ; + + $container->compile(); + + /** @var TaggedLocatorConsumerWithServiceSubscriber $s */ + $s = $container->get(TaggedLocatorConsumerWithServiceSubscriber::class); + + self::assertInstanceOf(ContainerInterface::class, $subscriberLocator = $s->getContainer()); + self::assertTrue($subscriberLocator->has('subscribed_service')); + self::assertNotSame($subscriberLocator, $taggedLocator = $s->getLocator()); + self::assertInstanceOf(ContainerInterface::class, $taggedLocator); + self::assertTrue($taggedLocator->has('tagged_service')); + } + + public function testBindingsWithAutowireAttributeAndAutowireFalse() + { + $container = new ContainerBuilder(); + $container->register(FooTagClass::class) + ->setPublic(true) + ->addTag('foo_bar', ['key' => 'tagged_service']) + ; + $container->register(TaggedLocatorConsumerWithServiceSubscriber::class) + ->setBindings([ + '$locator' => new BoundArgument(new Reference('service_container'), false), + ]) + ->setPublic(true) + ->setAutowired(false) + ->addTag('container.service_subscriber') + ; + $container->register('subscribed_service', \stdClass::class) + ->setPublic(true) + ; + + $container->compile(); + + /** @var TaggedLocatorConsumerWithServiceSubscriber $s */ + $s = $container->get(TaggedLocatorConsumerWithServiceSubscriber::class); + + self::assertNull($s->getContainer()); + self::assertInstanceOf(ContainerInterface::class, $taggedLocator = $s->getLocator()); + self::assertSame($container, $taggedLocator); + } } class ServiceSubscriberStub implements ServiceSubscriberInterface diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/RegisterAutoconfigureAttributesPassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/RegisterAutoconfigureAttributesPassTest.php index 877c50f027fa2..f1f70ed30cd9a 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/RegisterAutoconfigureAttributesPassTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/RegisterAutoconfigureAttributesPassTest.php @@ -16,9 +16,13 @@ use Symfony\Component\DependencyInjection\ChildDefinition; use Symfony\Component\DependencyInjection\Compiler\RegisterAutoconfigureAttributesPass; use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Exception\AutoconfigureFailedException; use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\Tests\Fixtures\AutoconfigureAttributed; use Symfony\Component\DependencyInjection\Tests\Fixtures\AutoconfiguredInterface; +use Symfony\Component\DependencyInjection\Tests\Fixtures\LazyAutoconfigured; +use Symfony\Component\DependencyInjection\Tests\Fixtures\LazyLoaded; +use Symfony\Component\DependencyInjection\Tests\Fixtures\MultipleAutoconfigureAttributed; use Symfony\Component\DependencyInjection\Tests\Fixtures\ParentNotExists; use Symfony\Component\DependencyInjection\Tests\Fixtures\StaticConstructorAutoconfigure; @@ -104,4 +108,46 @@ public function testStaticConstructor() ; $this->assertEquals([StaticConstructorAutoconfigure::class => $expected], $container->getAutoconfiguredInstanceof()); } + + public function testLazyServiceAttribute() + { + $container = new ContainerBuilder(); + $container->register('foo', LazyLoaded::class) + ->setAutoconfigured(true); + + (new RegisterAutoconfigureAttributesPass())->process($container); + + $expected = (new ChildDefinition('')) + ->setLazy(true) + ; + $this->assertEquals([LazyLoaded::class => $expected], $container->getAutoconfiguredInstanceof()); + } + + public function testLazyNotCompatibleWithAutoconfigureAttribute() + { + $container = new ContainerBuilder(); + $container->register('foo', LazyAutoconfigured::class) + ->setAutoconfigured(true); + + try { + (new RegisterAutoconfigureAttributesPass())->process($container); + } catch (AutoconfigureFailedException $e) { + $this->assertSame('Using both attributes #[Lazy] and #[Autoconfigure] on an argument is not allowed; use the "lazy" parameter of #[Autoconfigure] instead.', $e->getMessage()); + } + } + + public function testMultipleAutoconfigureAllowed() + { + $container = new ContainerBuilder(); + $container->register('foo', MultipleAutoconfigureAttributed::class) + ->setAutoconfigured(true); + + (new RegisterAutoconfigureAttributesPass())->process($container); + + $expected = (new ChildDefinition('')) + ->addTag('foo') + ->addTag('bar') + ; + $this->assertEquals([MultipleAutoconfigureAttributed::class => $expected], $container->getAutoconfiguredInstanceof()); + } } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/ValidateEnvPlaceholdersPassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/ValidateEnvPlaceholdersPassTest.php index cdaa1e2cd8c5d..8c5c4cc32323e 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/ValidateEnvPlaceholdersPassTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/ValidateEnvPlaceholdersPassTest.php @@ -374,7 +374,7 @@ class EnvExtension extends Extension private ConfigurationInterface $configuration; private array $config; - public function __construct(ConfigurationInterface $configuration = null) + public function __construct(?ConfigurationInterface $configuration = null) { $this->configuration = $configuration ?? new EnvConfiguration(); } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Config/ContainerParametersResourceCheckerTest.php b/src/Symfony/Component/DependencyInjection/Tests/Config/ContainerParametersResourceCheckerTest.php index 9fefdd49e01e3..dfd6be81432a4 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Config/ContainerParametersResourceCheckerTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Config/ContainerParametersResourceCheckerTest.php @@ -11,22 +11,21 @@ namespace Symfony\Component\DependencyInjection\Tests\Config; -use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Symfony\Component\DependencyInjection\Config\ContainerParametersResource; use Symfony\Component\DependencyInjection\Config\ContainerParametersResourceChecker; -use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\DependencyInjection\Container; class ContainerParametersResourceCheckerTest extends TestCase { private ContainerParametersResource $resource; private ContainerParametersResourceChecker $resourceChecker; - private MockObject&ContainerInterface $container; + private Container $container; protected function setUp(): void { $this->resource = new ContainerParametersResource(['locales' => ['fr', 'en'], 'default_locale' => 'fr']); - $this->container = $this->createMock(ContainerInterface::class); + $this->container = new Container(); $this->resourceChecker = new ContainerParametersResourceChecker($this->container); } @@ -47,29 +46,16 @@ public function testIsFresh(callable $mockContainer, $expected) public static function isFreshProvider() { - yield 'not fresh on missing parameter' => [function (MockObject $container) { - $container->method('hasParameter')->with('locales')->willReturn(false); + yield 'not fresh on missing parameter' => [function (Container $container) { }, false]; - yield 'not fresh on different value' => [function (MockObject $container) { - $container->method('getParameter')->with('locales')->willReturn(['nl', 'es']); + yield 'not fresh on different value' => [function (Container $container) { + $container->setParameter('locales', ['nl', 'es']); }, false]; - yield 'fresh on every identical parameters' => [function (MockObject $container) { - $container->expects(self::exactly(2))->method('hasParameter')->willReturn(true); - $container->expects(self::exactly(2))->method('getParameter') - ->willReturnCallback(function (...$args) { - static $series = [ - [['locales'], ['fr', 'en']], - [['default_locale'], 'fr'], - ]; - - [$expectedArgs, $return] = array_shift($series); - self::assertSame($expectedArgs, $args); - - return $return; - }) - ; + yield 'fresh on every identical parameters' => [function (Container $container) { + $container->setParameter('locales', ['fr', 'en']); + $container->setParameter('default_locale', 'fr'); }, true]; } } diff --git a/src/Symfony/Component/DependencyInjection/Tests/DefinitionTest.php b/src/Symfony/Component/DependencyInjection/Tests/DefinitionTest.php index 8f33418671f63..3a7c3a98002ca 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/DefinitionTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/DefinitionTest.php @@ -292,7 +292,7 @@ public function testReplaceArgumentShouldCheckBounds() $def->addArgument('foo'); $this->expectException(\OutOfBoundsException::class); - $this->expectExceptionMessage('The index "1" is not in the range [0, 0] of the arguments of class "stdClass".'); + $this->expectExceptionMessage('The argument "1" doesn\'t exist in class "stdClass".'); $def->replaceArgument(1, 'bar'); } @@ -307,6 +307,17 @@ public function testReplaceArgumentWithoutExistingArgumentsShouldCheckBounds() $def->replaceArgument(0, 'bar'); } + public function testReplaceArgumentWithNonConsecutiveIntIndex() + { + $def = new Definition('stdClass'); + + $def->setArguments([1 => 'foo']); + $this->assertSame([1 => 'foo'], $def->getArguments()); + + $def->replaceArgument(1, 'bar'); + $this->assertSame([1 => 'bar'], $def->getArguments()); + } + public function testSetGetProperties() { $def = new Definition('stdClass'); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Dumper/YamlDumperTest.php b/src/Symfony/Component/DependencyInjection/Tests/Dumper/YamlDumperTest.php index 68931050b2182..f9ff3fff786a3 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Dumper/YamlDumperTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Dumper/YamlDumperTest.php @@ -163,7 +163,11 @@ public function testDumpHandlesEnumeration() $container->compile(); $dumper = new YamlDumper($container); - $this->assertEquals(file_get_contents(self::$fixturesPath.'/yaml/services_with_enumeration.yml'), $dumper->dump()); + if (str_starts_with(Yaml::dump(FooUnitEnum::BAR), '!php/enum')) { + $this->assertEquals(file_get_contents(self::$fixturesPath.'/yaml/services_with_enumeration_enum_tag.yml'), $dumper->dump()); + } else { + $this->assertEquals(file_get_contents(self::$fixturesPath.'/yaml/services_with_enumeration.yml'), $dumper->dump()); + } } /** diff --git a/src/Symfony/Component/DependencyInjection/Tests/EnvVarProcessorTest.php b/src/Symfony/Component/DependencyInjection/Tests/EnvVarProcessorTest.php index acda307a16702..54b036d80f053 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/EnvVarProcessorTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/EnvVarProcessorTest.php @@ -808,6 +808,12 @@ public function loadEnvVars(): array return [ 'FOO_ENV_LOADER' => '123', 'BAZ_ENV_LOADER' => '', + 'LAZY_ENV_LOADER' => new class() { + public function __toString() + { + return ''; + } + }, ]; } }; @@ -819,6 +825,12 @@ public function loadEnvVars(): array 'FOO_ENV_LOADER' => '234', 'BAR_ENV_LOADER' => '456', 'BAZ_ENV_LOADER' => '567', + 'LAZY_ENV_LOADER' => new class() { + public function __toString() + { + return '678'; + } + }, ]; } }; @@ -841,6 +853,9 @@ public function loadEnvVars(): array $result = $processor->getEnv('string', 'FOO_ENV_LOADER', function () {}); $this->assertSame('123', $result); // check twice + $result = $processor->getEnv('string', 'LAZY_ENV_LOADER', function () {}); + $this->assertSame('678', $result); + unset($_ENV['BAZ_ENV_LOADER']); unset($_ENV['BUZ_ENV_LOADER']); } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/LazyAutoconfigured.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/LazyAutoconfigured.php new file mode 100644 index 0000000000000..7145e18ee9f6a --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/LazyAutoconfigured.php @@ -0,0 +1,11 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Tests\Fixtures; + +use Psr\Container\ContainerInterface; +use Symfony\Component\DependencyInjection\Attribute\TaggedLocator; +use Symfony\Contracts\Service\Attribute\Required; +use Symfony\Contracts\Service\ServiceSubscriberInterface; + +final class TaggedLocatorConsumerWithServiceSubscriber implements ServiceSubscriberInterface +{ + private ?ContainerInterface $container = null; + + public function __construct( + #[TaggedLocator('foo_bar', indexAttribute: 'key')] + private ContainerInterface $locator, + ) { + } + + public function getLocator(): ContainerInterface + { + return $this->locator; + } + + public function getContainer(): ?ContainerInterface + { + return $this->container; + } + + #[Required] + public function setContainer(ContainerInterface $container): void + { + $this->container = $container; + } + + public static function getSubscribedServices(): array + { + return [ + 'subscribed_service', + ]; + } +} diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/autowiring_classes_80.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/autowiring_classes_80.php index 69ca09218812c..ed4cc2994d657 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/autowiring_classes_80.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/autowiring_classes_80.php @@ -5,6 +5,7 @@ use Symfony\Component\DependencyInjection\Attribute\AsDecorator; use Symfony\Component\DependencyInjection\Attribute\Autowire; use Symfony\Component\DependencyInjection\Attribute\AutowireDecorated; +use Symfony\Component\DependencyInjection\Attribute\Lazy; use Symfony\Component\DependencyInjection\Attribute\TaggedIterator; use Symfony\Component\DependencyInjection\Attribute\TaggedLocator; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -125,3 +126,17 @@ public function __construct( { } } + +class LazyServiceAttributeAutowiring +{ + public function __construct(#[Lazy] A $a) + { + } +} + +class LazyAutowireServiceAttributesAutowiring +{ + public function __construct(#[Lazy, Autowire(lazy: true)] A $a) + { + } +} diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services_with_enumeration.yml b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services_with_enumeration.yml index 0d335703383bc..992a83b14515c 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services_with_enumeration.yml +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services_with_enumeration.yml @@ -10,4 +10,4 @@ services: Symfony\Component\DependencyInjection\Tests\Fixtures\FooClassWithEnumAttribute: class: Symfony\Component\DependencyInjection\Tests\Fixtures\FooClassWithEnumAttribute public: true - arguments: [!php/const 'Symfony\Component\DependencyInjection\Tests\Fixtures\FooUnitEnum::BAR'] + arguments: [!php/enum 'Symfony\Component\DependencyInjection\Tests\Fixtures\FooUnitEnum::BAR'] diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services_with_enumeration_enum_tag.yml b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services_with_enumeration_enum_tag.yml new file mode 100644 index 0000000000000..f166e2809cacd --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services_with_enumeration_enum_tag.yml @@ -0,0 +1,13 @@ +parameters: + unit_enum: !php/enum Symfony\Component\DependencyInjection\Tests\Fixtures\FooUnitEnum::BAR + enum_array: [!php/enum Symfony\Component\DependencyInjection\Tests\Fixtures\FooUnitEnum::BAR, !php/enum Symfony\Component\DependencyInjection\Tests\Fixtures\FooUnitEnum::FOO] + +services: + service_container: + class: Symfony\Component\DependencyInjection\ContainerInterface + public: true + synthetic: true + Symfony\Component\DependencyInjection\Tests\Fixtures\FooClassWithEnumAttribute: + class: Symfony\Component\DependencyInjection\Tests\Fixtures\FooClassWithEnumAttribute + public: true + arguments: [!php/enum 'Symfony\Component\DependencyInjection\Tests\Fixtures\FooUnitEnum::BAR'] diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services_with_invalid_enumeration.yml b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services_with_invalid_enumeration.yml index b9f74e0f468ab..9676a70dd3c1b 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services_with_invalid_enumeration.yml +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services_with_invalid_enumeration.yml @@ -7,4 +7,4 @@ services: Symfony\Component\DependencyInjection\Tests\Fixtures\FooClassWithEnumAttribute: class: Symfony\Component\DependencyInjection\Tests\Fixtures\FooClassWithEnumAttribute public: true - arguments: [!php/const 'Symfony\Component\DependencyInjection\Tests\Fixtures\FooUnitEnum::BAZ'] + arguments: [!php/enum 'Symfony\Component\DependencyInjection\Tests\Fixtures\FooUnitEnum::BAZ'] diff --git a/src/Symfony/Component/DependencyInjection/Tests/LazyProxy/Instantiator/RealServiceInstantiatorTest.php b/src/Symfony/Component/DependencyInjection/Tests/LazyProxy/Instantiator/RealServiceInstantiatorTest.php index 544c046d0097b..dadc9737a9c45 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/LazyProxy/Instantiator/RealServiceInstantiatorTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/LazyProxy/Instantiator/RealServiceInstantiatorTest.php @@ -12,7 +12,7 @@ namespace Symfony\Component\DependencyInjection\Tests\LazyProxy\Instantiator; use PHPUnit\Framework\TestCase; -use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\DependencyInjection\Container; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\LazyProxy\Instantiator\RealServiceInstantiator; @@ -27,9 +27,8 @@ public function testInstantiateProxy() { $instantiator = new RealServiceInstantiator(); $instance = new \stdClass(); - $container = $this->createMock(ContainerInterface::class); $callback = fn () => $instance; - $this->assertSame($instance, $instantiator->instantiateProxy($container, new Definition(), 'foo', $callback)); + $this->assertSame($instance, $instantiator->instantiateProxy(new Container(), new Definition(), 'foo', $callback)); } } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Loader/FileLoaderTest.php b/src/Symfony/Component/DependencyInjection/Tests/Loader/FileLoaderTest.php index 2dd904428d086..406e51eba789a 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Loader/FileLoaderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Loader/FileLoaderTest.php @@ -377,12 +377,12 @@ public function noAutoRegisterAliasesForSinglyImplementedInterfaces() $this->autoRegisterAliasesForSinglyImplementedInterfaces = false; } - public function load(mixed $resource, string $type = null): mixed + public function load(mixed $resource, ?string $type = null): mixed { return $resource; } - public function supports(mixed $resource, string $type = null): bool + public function supports(mixed $resource, ?string $type = null): bool { return false; } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Loader/GlobFileLoaderTest.php b/src/Symfony/Component/DependencyInjection/Tests/Loader/GlobFileLoaderTest.php index f7f003b132ccb..0cf48dcb34e88 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Loader/GlobFileLoaderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Loader/GlobFileLoaderTest.php @@ -38,7 +38,7 @@ public function testLoadAddsTheGlobResourceToTheContainer() class GlobFileLoaderWithoutImport extends GlobFileLoader { - public function import(mixed $resource, string $type = null, bool|string $ignoreErrors = false, string $sourceResource = null, $exclude = null): mixed + public function import(mixed $resource, ?string $type = null, bool|string $ignoreErrors = false, ?string $sourceResource = null, $exclude = null): mixed { return null; } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Loader/YamlFileLoaderTest.php b/src/Symfony/Component/DependencyInjection/Tests/Loader/YamlFileLoaderTest.php index 6b8512684d66d..5be7ed74ad7f6 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Loader/YamlFileLoaderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Loader/YamlFileLoaderTest.php @@ -43,6 +43,7 @@ use Symfony\Component\DependencyInjection\Tests\Fixtures\NamedArgumentsDummy; use Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype; use Symfony\Component\ExpressionLanguage\Expression; +use Symfony\Component\Yaml\Yaml; class YamlFileLoaderTest extends TestCase { @@ -1020,7 +1021,13 @@ public function testInvalidEnumeration() $loader = new YamlFileLoader($container, new FileLocator(self::$fixturesPath.'/yaml')); $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('The constant "Symfony\Component\DependencyInjection\Tests\Fixtures\FooUnitEnum::BAZ" is not defined'); + + if (str_starts_with(Yaml::dump(FooUnitEnum::BAR), '!php/enum')) { + $this->expectExceptionMessage('The string "Symfony\Component\DependencyInjection\Tests\Fixtures\FooUnitEnum::BAZ" is not the name of a valid enum'); + } else { + $this->expectExceptionMessage('The enum "Symfony\Component\DependencyInjection\Tests\Fixtures\FooUnitEnum::BAZ" is not defined'); + } + $loader->load('services_with_invalid_enumeration.yml'); } diff --git a/src/Symfony/Component/DependencyInjection/TypedReference.php b/src/Symfony/Component/DependencyInjection/TypedReference.php index fd1008a64eaa4..acdd3b89fc70a 100644 --- a/src/Symfony/Component/DependencyInjection/TypedReference.php +++ b/src/Symfony/Component/DependencyInjection/TypedReference.php @@ -29,7 +29,7 @@ class TypedReference extends Reference * @param string|null $name The name of the argument targeting the service * @param array $attributes The attributes to be used */ - public function __construct(string $id, string $type, int $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE, string $name = null, array $attributes = []) + public function __construct(string $id, string $type, int $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE, ?string $name = null, array $attributes = []) { $this->name = $type === $id ? $name : null; parent::__construct($id, $invalidBehavior); diff --git a/src/Symfony/Component/DomCrawler/AbstractUriElement.php b/src/Symfony/Component/DomCrawler/AbstractUriElement.php index 9a3712bb2da5f..d75dcd219405d 100644 --- a/src/Symfony/Component/DomCrawler/AbstractUriElement.php +++ b/src/Symfony/Component/DomCrawler/AbstractUriElement.php @@ -29,7 +29,7 @@ abstract class AbstractUriElement * * @throws \InvalidArgumentException if the node is not a link */ - public function __construct(\DOMElement $node, string $currentUri = null, ?string $method = 'GET') + public function __construct(\DOMElement $node, ?string $currentUri = null, ?string $method = 'GET') { $this->setNode($node); $this->method = $method ? strtoupper($method) : null; diff --git a/src/Symfony/Component/DomCrawler/Crawler.php b/src/Symfony/Component/DomCrawler/Crawler.php index 315e225035251..85d9c1eb36c7e 100644 --- a/src/Symfony/Component/DomCrawler/Crawler.php +++ b/src/Symfony/Component/DomCrawler/Crawler.php @@ -60,7 +60,7 @@ class Crawler implements \Countable, \IteratorAggregate /** * @param \DOMNodeList|\DOMNode|\DOMNode[]|string|null $node A Node to use as the base for the crawling */ - public function __construct(\DOMNodeList|\DOMNode|array|string $node = null, string $uri = null, string $baseHref = null, bool $useHtml5Parser = true) + public function __construct(\DOMNodeList|\DOMNode|array|string|null $node = null, ?string $uri = null, ?string $baseHref = null, bool $useHtml5Parser = true) { $this->uri = $uri; $this->baseHref = $baseHref ?: $uri; @@ -128,7 +128,7 @@ public function add(\DOMNodeList|\DOMNode|array|string|null $node): void * or ISO-8859-1 as a fallback, which is the default charset defined by the * HTTP 1.1 specification. */ - public function addContent(string $content, string $type = null): void + public function addContent(string $content, ?string $type = null): void { if (empty($type)) { $type = str_starts_with($content, 'createSubCrawler(\array_slice($this->nodes, $offset, $length)); } @@ -479,7 +479,7 @@ public function ancestors(): static * @throws \InvalidArgumentException When current node is empty * @throws \RuntimeException If the CssSelector Component is not available and $selector is provided */ - public function children(string $selector = null): static + public function children(?string $selector = null): static { if (!$this->nodes) { throw new \InvalidArgumentException('The current node list is empty.'); @@ -504,7 +504,7 @@ public function children(string $selector = null): static * * @throws \InvalidArgumentException When current node is empty */ - public function attr(string $attribute, string $default = null): ?string + public function attr(string $attribute, ?string $default = null): ?string { if (!$this->nodes) { if (null !== $default) { @@ -543,7 +543,7 @@ public function nodeName(): string * * @throws \InvalidArgumentException When current node is empty */ - public function text(string $default = null, bool $normalizeWhitespace = true): string + public function text(?string $default = null, bool $normalizeWhitespace = true): string { if (!$this->nodes) { if (null !== $default) { @@ -591,7 +591,7 @@ public function innerText(bool $normalizeWhitespace = true): string * * @throws \InvalidArgumentException When current node is empty */ - public function html(string $default = null): string + public function html(?string $default = null): string { if (!$this->nodes) { if (null !== $default) { @@ -840,7 +840,7 @@ public function images(): array * * @throws \InvalidArgumentException If the current node list is empty or the selected node is not instance of DOMElement */ - public function form(array $values = null, string $method = null): Form + public function form(?array $values = null, ?string $method = null): Form { if (!$this->nodes) { throw new \InvalidArgumentException('The current node list is empty.'); diff --git a/src/Symfony/Component/DomCrawler/Form.php b/src/Symfony/Component/DomCrawler/Form.php index 1747bf9f02d43..fc2849e8b3087 100644 --- a/src/Symfony/Component/DomCrawler/Form.php +++ b/src/Symfony/Component/DomCrawler/Form.php @@ -33,7 +33,7 @@ class Form extends Link implements \ArrayAccess * * @throws \LogicException if the node is not a button inside a form tag */ - public function __construct(\DOMElement $node, string $currentUri = null, string $method = null, string $baseHref = null) + public function __construct(\DOMElement $node, ?string $currentUri = null, ?string $method = null, ?string $baseHref = null) { parent::__construct($node, $currentUri, $method); $this->baseHref = $baseHref; diff --git a/src/Symfony/Component/DomCrawler/Image.php b/src/Symfony/Component/DomCrawler/Image.php index 5573928446f18..dc7c0b42b0032 100644 --- a/src/Symfony/Component/DomCrawler/Image.php +++ b/src/Symfony/Component/DomCrawler/Image.php @@ -16,7 +16,7 @@ */ class Image extends AbstractUriElement { - public function __construct(\DOMElement $node, string $currentUri = null) + public function __construct(\DOMElement $node, ?string $currentUri = null) { parent::__construct($node, $currentUri, 'GET'); } diff --git a/src/Symfony/Component/DomCrawler/Tests/AbstractCrawlerTestCase.php b/src/Symfony/Component/DomCrawler/Tests/AbstractCrawlerTestCase.php index 2169a49a4379a..90c97e7452ce7 100644 --- a/src/Symfony/Component/DomCrawler/Tests/AbstractCrawlerTestCase.php +++ b/src/Symfony/Component/DomCrawler/Tests/AbstractCrawlerTestCase.php @@ -21,7 +21,7 @@ abstract class AbstractCrawlerTestCase extends TestCase { abstract public static function getDoctype(): string; - protected function createCrawler($node = null, string $uri = null, string $baseHref = null, bool $useHtml5Parser = true) + protected function createCrawler($node = null, ?string $uri = null, ?string $baseHref = null, bool $useHtml5Parser = true) { return new Crawler($node, $uri, $baseHref, $useHtml5Parser); } diff --git a/src/Symfony/Component/Dotenv/Dotenv.php b/src/Symfony/Component/Dotenv/Dotenv.php index 9908d63ca2ec4..e29051e06db24 100644 --- a/src/Symfony/Component/Dotenv/Dotenv.php +++ b/src/Symfony/Component/Dotenv/Dotenv.php @@ -96,7 +96,7 @@ public function load(string $path, string ...$extraPaths): void * @throws FormatException when a file has a syntax error * @throws PathException when a file does not exist or is not readable */ - public function loadEnv(string $path, string $envKey = null, string $defaultEnv = 'dev', array $testEnvs = ['test'], bool $overrideExistingVars = false): void + public function loadEnv(string $path, ?string $envKey = null, string $defaultEnv = 'dev', array $testEnvs = ['test'], bool $overrideExistingVars = false): void { $this->populatePath($path); diff --git a/src/Symfony/Component/Dotenv/Exception/FormatException.php b/src/Symfony/Component/Dotenv/Exception/FormatException.php index 7d8ec8f21270e..81c00bb3aa2a3 100644 --- a/src/Symfony/Component/Dotenv/Exception/FormatException.php +++ b/src/Symfony/Component/Dotenv/Exception/FormatException.php @@ -22,7 +22,7 @@ public function __construct( string $message, private FormatExceptionContext $context, int $code = 0, - \Throwable $previous = null, + ?\Throwable $previous = null, ) { parent::__construct(sprintf("%s in \"%s\" at line %d.\n%s", $message, $context->getPath(), $context->getLineno(), $context->getDetails()), $code, $previous); } diff --git a/src/Symfony/Component/Dotenv/Exception/PathException.php b/src/Symfony/Component/Dotenv/Exception/PathException.php index 4a4d71722223d..e432b2e33a8bf 100644 --- a/src/Symfony/Component/Dotenv/Exception/PathException.php +++ b/src/Symfony/Component/Dotenv/Exception/PathException.php @@ -18,7 +18,7 @@ */ final class PathException extends \RuntimeException implements ExceptionInterface { - public function __construct(string $path, int $code = 0, \Throwable $previous = null) + public function __construct(string $path, int $code = 0, ?\Throwable $previous = null) { parent::__construct(sprintf('Unable to read the "%s" environment file.', $path), $code, $previous); } diff --git a/src/Symfony/Component/Dotenv/Tests/Command/DebugCommandTest.php b/src/Symfony/Component/Dotenv/Tests/Command/DebugCommandTest.php index 5760bf1c80095..c2f698108cdfb 100644 --- a/src/Symfony/Component/Dotenv/Tests/Command/DebugCommandTest.php +++ b/src/Symfony/Component/Dotenv/Tests/Command/DebugCommandTest.php @@ -290,7 +290,7 @@ public function testCompletion() $this->assertSame(['FOO', 'TEST'], $tester->complete([''])); } - private function executeCommand(string $projectDirectory, string $env, array $input = [], string $dotenvPath = null): string + private function executeCommand(string $projectDirectory, string $env, array $input = [], ?string $dotenvPath = null): string { $_SERVER['TEST_ENV_KEY'] = $env; (new Dotenv('TEST_ENV_KEY'))->bootEnv($dotenvPath ?? $projectDirectory.'/.env'); diff --git a/src/Symfony/Component/Emoji/.gitattributes b/src/Symfony/Component/Emoji/.gitattributes new file mode 100644 index 0000000000000..9e72316d442f0 --- /dev/null +++ b/src/Symfony/Component/Emoji/.gitattributes @@ -0,0 +1,8 @@ +/Resources/bin/build.php export-ignore +/Resources/bin/composer.json export-ignore +/Resources/bin/Makefile export-ignore +/Resources/bin/README.md export-ignore +/Tests export-ignore +/phpunit.xml.dist export-ignore +/.gitattributes export-ignore +/.gitignore export-ignore diff --git a/src/Symfony/Component/Emoji/.gitignore b/src/Symfony/Component/Emoji/.gitignore new file mode 100644 index 0000000000000..c49a5d8df5c65 --- /dev/null +++ b/src/Symfony/Component/Emoji/.gitignore @@ -0,0 +1,3 @@ +vendor/ +composer.lock +phpunit.xml diff --git a/src/Symfony/Component/Emoji/CHANGELOG.md b/src/Symfony/Component/Emoji/CHANGELOG.md new file mode 100644 index 0000000000000..5f941ae21a99a --- /dev/null +++ b/src/Symfony/Component/Emoji/CHANGELOG.md @@ -0,0 +1,7 @@ +CHANGELOG +========= + +7.1 +--- + + * Add the component diff --git a/src/Symfony/Component/Emoji/EmojiTransliterator.php b/src/Symfony/Component/Emoji/EmojiTransliterator.php new file mode 100644 index 0000000000000..2a19f89cfc548 --- /dev/null +++ b/src/Symfony/Component/Emoji/EmojiTransliterator.php @@ -0,0 +1,141 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Emoji; + +use Symfony\Component\Emoji\Util\GzipStreamWrapper; + +if (!class_exists(\Transliterator::class)) { + throw new \LogicException(sprintf('You cannot use the "%s\EmojiTransliterator" class as the "intl" extension is not installed. See https://php.net/intl.', __NAMESPACE__)); +} + +final class EmojiTransliterator extends \Transliterator +{ + private const QUICK_CHECK = "\xA9\xAE\xE2\xE3\xF0"; + private const REVERSEABLE_IDS = [ + 'emoji-github' => 'github-emoji', + 'emoji-slack' => 'slack-emoji', + 'github-emoji' => 'emoji-github', + 'slack-emoji' => 'emoji-slack', + ]; + + public readonly string $id; + + private array $map; + + private string $quickCheck; + + private \Transliterator $transliterator; + + public static function create(string $id, int $direction = self::FORWARD): self + { + $id = strtolower($id); + + if (!isset(self::REVERSEABLE_IDS[$id]) && !str_starts_with($id, 'emoji-')) { + $id = 'emoji-'.$id; + } + + if (self::REVERSE === $direction) { + if (!isset(self::REVERSEABLE_IDS[$id])) { + // Create a failing reverse-transliterator to populate intl_get_error_*() + \Transliterator::createFromRules('A > B')->createInverse(); + + throw new \IntlException(intl_get_error_message(), intl_get_error_code()); + } + $id = self::REVERSEABLE_IDS[$id]; + } + + $file = __DIR__."/Resources/data/{$id}.php"; + if (!preg_match('/^[a-z0-9@_\\.\\-]*$/', $id) || !is_file($file) && !is_file($file .= '.gz')) { + \Transliterator::create($id); // Populate intl_get_error_*() + + throw new \IntlException(intl_get_error_message(), intl_get_error_code()); + } + + /** + * @var array $maps + */ + static $maps; + + // Create an instance of \Transliterator with a custom id; that's the only way + static $newInstance; + $instance = ($newInstance ??= (new \ReflectionClass(self::class))->newInstanceWithoutConstructor(...))(); + $instance->id = $id; + $instance->map = $maps[$id] ??= str_ends_with($file, '.gz') ? GzipStreamWrapper::require($file) : require $file; + + return $instance; + } + + public function createInverse(): self + { + return self::create($this->id, \Transliterator::REVERSE); + } + + public function getErrorCode(): int|false + { + return $this->transliterator?->getErrorCode() ?? 0; + } + + public function getErrorMessage(): string|false + { + return $this->transliterator?->getErrorMessage() ?? false; + } + + public static function listIDs(): array + { + static $ids = []; + + if ($ids) { + return $ids; + } + + foreach (scandir(__DIR__.'/Resources/data/') as $file) { + if (str_ends_with($file, '.php.gz')) { + $ids[] = substr($file, 0, -7); + } elseif (str_ends_with($file, '.php')) { + $ids[] = substr($file, 0, -4); + } + } + + return $ids; + } + + public function transliterate(string $string, int $start = 0, int $end = -1): string|false + { + $this->quickCheck ??= str_starts_with(array_key_first($this->map), ':') ? ':' : self::QUICK_CHECK; + + if (0 === $start && -1 === $end && preg_match('//u', $string)) { + return \strlen($string) === strcspn($string, $this->quickCheck) ? $string : strtr($string, $this->map); + } + + // Here we rely on intl to validate the $string, $start and $end arguments + // and to slice the string. Slicing is done by replacing the part if $string + // between $start and $end by a unique cookie that can be reliably used to + // identify which part of $string should be transliterated. + + static $cookie; + static $transliterator; + + $cookie ??= hash('xxh128', random_bytes(8)); + $this->transliterator ??= clone $transliterator ??= \Transliterator::createFromRules('[:any:]* > '.$cookie); + + if (false === $result = $this->transliterator->transliterate($string, $start, $end)) { + return false; + } + + $parts = explode($cookie, $result); + $start = \strlen($parts[0]); + $length = -\strlen($parts[1]) ?: null; + $string = substr($string, $start, $length); + + return $parts[0].(\strlen($string) === strcspn($string, $this->quickCheck) ? $string : strtr($string, $this->map)).$parts[1]; + } +} diff --git a/src/Symfony/Component/Emoji/LICENSE b/src/Symfony/Component/Emoji/LICENSE new file mode 100644 index 0000000000000..e374a5c8339d3 --- /dev/null +++ b/src/Symfony/Component/Emoji/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2024-present Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/src/Symfony/Component/Emoji/README.md b/src/Symfony/Component/Emoji/README.md new file mode 100644 index 0000000000000..b5027369514d6 --- /dev/null +++ b/src/Symfony/Component/Emoji/README.md @@ -0,0 +1,18 @@ +Emoji Component +=============== + +The Emoji component provides access to emoji characters and sequences from +the [Unicode CLDR](https://cldr.unicode.org/index). + +If you have the zlib extension enabled, you can compress the data by running: + + php vendor/symfony/emoji/Resources/bin/compress + +Resources +--------- + + * [Documentation](https://symfony.com/doc/current/components/emoji.html) + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) diff --git a/src/Symfony/Component/Intl/Resources/emoji/Makefile b/src/Symfony/Component/Emoji/Resources/bin/Makefile similarity index 100% rename from src/Symfony/Component/Intl/Resources/emoji/Makefile rename to src/Symfony/Component/Emoji/Resources/bin/Makefile diff --git a/src/Symfony/Component/Intl/Resources/emoji/README.md b/src/Symfony/Component/Emoji/Resources/bin/README.md similarity index 100% rename from src/Symfony/Component/Intl/Resources/emoji/README.md rename to src/Symfony/Component/Emoji/Resources/bin/README.md diff --git a/src/Symfony/Component/Intl/Resources/emoji/build.php b/src/Symfony/Component/Emoji/Resources/bin/build.php similarity index 98% rename from src/Symfony/Component/Intl/Resources/emoji/build.php rename to src/Symfony/Component/Emoji/Resources/bin/build.php index a3546815865cf..088838cdd0466 100755 --- a/src/Symfony/Component/Intl/Resources/emoji/build.php +++ b/src/Symfony/Component/Emoji/Resources/bin/build.php @@ -25,7 +25,7 @@ final class Builder { - private const TARGET_DIR = __DIR__.'/../data/transliterator/emoji/'; + private const TARGET_DIR = __DIR__.'/../data/'; public static function getEmojisCodePoints(): array { @@ -233,7 +233,7 @@ public static function saveRules(iterable $rulesByLocale): void sort($firstChars); $quickCheck = '"'.str_replace('%', '\\x', rawurlencode(implode('', $firstChars))).'"'; - $file = dirname(__DIR__, 2).'/Transliterator/EmojiTransliterator.php'; + $file = dirname(__DIR__, 2).'/EmojiTransliterator.php'; file_put_contents($file, preg_replace('/QUICK_CHECK = .*;/m', "QUICK_CHECK = {$quickCheck};", file_get_contents($file))); } diff --git a/src/Symfony/Component/Intl/Resources/emoji/composer.json b/src/Symfony/Component/Emoji/Resources/bin/composer.json similarity index 100% rename from src/Symfony/Component/Intl/Resources/emoji/composer.json rename to src/Symfony/Component/Emoji/Resources/bin/composer.json diff --git a/src/Symfony/Component/Emoji/Resources/bin/compress b/src/Symfony/Component/Emoji/Resources/bin/compress new file mode 100755 index 0000000000000..174cbca59b041 --- /dev/null +++ b/src/Symfony/Component/Emoji/Resources/bin/compress @@ -0,0 +1,36 @@ +#!/usr/bin/env php + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +if ('cli' !== PHP_SAPI) { + throw new Exception('This script must be run from the command line.'); +} +if (!extension_loaded('zlib')) { + throw new Exception('This script requires the zlib extension.'); +} + +$iterator = new RecursiveIteratorIterator( + new RecursiveDirectoryIterator( + dirname(__DIR__).'/data', + FilesystemIterator::CURRENT_AS_FILEINFO | FilesystemIterator::SKIP_DOTS + ) +); + +foreach ($iterator as $file) { + if ('php' !== $file->getExtension()) { + continue; + } + + $data = file_get_contents($file); + file_put_contents('compress.zlib://'.$file.'.gz', $data); + + unlink($file.(filesize($file.'.gz') >= strlen($data) ? '.gz' : '')); +} diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-af.php b/src/Symfony/Component/Emoji/Resources/data/emoji-af.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-af.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-af.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-am.php b/src/Symfony/Component/Emoji/Resources/data/emoji-am.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-am.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-am.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-ar.php b/src/Symfony/Component/Emoji/Resources/data/emoji-ar.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-ar.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-ar.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-ar_sa.php b/src/Symfony/Component/Emoji/Resources/data/emoji-ar_sa.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-ar_sa.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-ar_sa.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-as.php b/src/Symfony/Component/Emoji/Resources/data/emoji-as.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-as.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-as.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-ast.php b/src/Symfony/Component/Emoji/Resources/data/emoji-ast.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-ast.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-ast.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-az.php b/src/Symfony/Component/Emoji/Resources/data/emoji-az.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-az.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-az.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-be.php b/src/Symfony/Component/Emoji/Resources/data/emoji-be.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-be.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-be.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-bew.php b/src/Symfony/Component/Emoji/Resources/data/emoji-bew.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-bew.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-bew.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-bg.php b/src/Symfony/Component/Emoji/Resources/data/emoji-bg.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-bg.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-bg.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-bgn.php b/src/Symfony/Component/Emoji/Resources/data/emoji-bgn.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-bgn.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-bgn.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-bn.php b/src/Symfony/Component/Emoji/Resources/data/emoji-bn.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-bn.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-bn.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-br.php b/src/Symfony/Component/Emoji/Resources/data/emoji-br.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-br.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-br.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-bs.php b/src/Symfony/Component/Emoji/Resources/data/emoji-bs.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-bs.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-bs.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-ca.php b/src/Symfony/Component/Emoji/Resources/data/emoji-ca.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-ca.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-ca.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-ccp.php b/src/Symfony/Component/Emoji/Resources/data/emoji-ccp.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-ccp.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-ccp.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-ceb.php b/src/Symfony/Component/Emoji/Resources/data/emoji-ceb.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-ceb.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-ceb.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-chr.php b/src/Symfony/Component/Emoji/Resources/data/emoji-chr.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-chr.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-chr.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-ckb.php b/src/Symfony/Component/Emoji/Resources/data/emoji-ckb.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-ckb.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-ckb.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-cs.php b/src/Symfony/Component/Emoji/Resources/data/emoji-cs.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-cs.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-cs.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-cv.php b/src/Symfony/Component/Emoji/Resources/data/emoji-cv.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-cv.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-cv.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-cy.php b/src/Symfony/Component/Emoji/Resources/data/emoji-cy.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-cy.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-cy.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-da.php b/src/Symfony/Component/Emoji/Resources/data/emoji-da.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-da.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-da.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-de.php b/src/Symfony/Component/Emoji/Resources/data/emoji-de.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-de.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-de.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-de_ch.php b/src/Symfony/Component/Emoji/Resources/data/emoji-de_ch.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-de_ch.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-de_ch.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-dsb.php b/src/Symfony/Component/Emoji/Resources/data/emoji-dsb.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-dsb.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-dsb.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-el.php b/src/Symfony/Component/Emoji/Resources/data/emoji-el.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-el.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-el.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-en.php b/src/Symfony/Component/Emoji/Resources/data/emoji-en.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-en.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-en.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-en_001.php b/src/Symfony/Component/Emoji/Resources/data/emoji-en_001.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-en_001.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-en_001.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-en_au.php b/src/Symfony/Component/Emoji/Resources/data/emoji-en_au.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-en_au.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-en_au.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-en_ca.php b/src/Symfony/Component/Emoji/Resources/data/emoji-en_ca.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-en_ca.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-en_ca.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-en_gb.php b/src/Symfony/Component/Emoji/Resources/data/emoji-en_gb.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-en_gb.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-en_gb.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-en_in.php b/src/Symfony/Component/Emoji/Resources/data/emoji-en_in.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-en_in.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-en_in.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-es.php b/src/Symfony/Component/Emoji/Resources/data/emoji-es.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-es.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-es.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-es_419.php b/src/Symfony/Component/Emoji/Resources/data/emoji-es_419.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-es_419.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-es_419.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-es_mx.php b/src/Symfony/Component/Emoji/Resources/data/emoji-es_mx.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-es_mx.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-es_mx.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-es_us.php b/src/Symfony/Component/Emoji/Resources/data/emoji-es_us.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-es_us.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-es_us.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-et.php b/src/Symfony/Component/Emoji/Resources/data/emoji-et.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-et.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-et.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-eu.php b/src/Symfony/Component/Emoji/Resources/data/emoji-eu.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-eu.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-eu.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-fa.php b/src/Symfony/Component/Emoji/Resources/data/emoji-fa.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-fa.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-fa.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-ff_adlm.php b/src/Symfony/Component/Emoji/Resources/data/emoji-ff_adlm.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-ff_adlm.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-ff_adlm.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-fi.php b/src/Symfony/Component/Emoji/Resources/data/emoji-fi.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-fi.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-fi.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-fil.php b/src/Symfony/Component/Emoji/Resources/data/emoji-fil.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-fil.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-fil.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-fo.php b/src/Symfony/Component/Emoji/Resources/data/emoji-fo.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-fo.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-fo.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-fr.php b/src/Symfony/Component/Emoji/Resources/data/emoji-fr.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-fr.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-fr.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-fr_ca.php b/src/Symfony/Component/Emoji/Resources/data/emoji-fr_ca.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-fr_ca.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-fr_ca.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-ga.php b/src/Symfony/Component/Emoji/Resources/data/emoji-ga.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-ga.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-ga.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-gd.php b/src/Symfony/Component/Emoji/Resources/data/emoji-gd.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-gd.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-gd.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-github.php b/src/Symfony/Component/Emoji/Resources/data/emoji-github.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-github.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-github.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-gl.php b/src/Symfony/Component/Emoji/Resources/data/emoji-gl.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-gl.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-gl.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-gu.php b/src/Symfony/Component/Emoji/Resources/data/emoji-gu.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-gu.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-gu.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-ha.php b/src/Symfony/Component/Emoji/Resources/data/emoji-ha.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-ha.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-ha.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-ha_ne.php b/src/Symfony/Component/Emoji/Resources/data/emoji-ha_ne.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-ha_ne.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-ha_ne.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-he.php b/src/Symfony/Component/Emoji/Resources/data/emoji-he.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-he.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-he.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-hi.php b/src/Symfony/Component/Emoji/Resources/data/emoji-hi.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-hi.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-hi.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-hi_latn.php b/src/Symfony/Component/Emoji/Resources/data/emoji-hi_latn.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-hi_latn.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-hi_latn.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-hr.php b/src/Symfony/Component/Emoji/Resources/data/emoji-hr.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-hr.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-hr.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-hsb.php b/src/Symfony/Component/Emoji/Resources/data/emoji-hsb.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-hsb.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-hsb.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-hu.php b/src/Symfony/Component/Emoji/Resources/data/emoji-hu.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-hu.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-hu.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-hy.php b/src/Symfony/Component/Emoji/Resources/data/emoji-hy.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-hy.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-hy.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-ia.php b/src/Symfony/Component/Emoji/Resources/data/emoji-ia.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-ia.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-ia.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-id.php b/src/Symfony/Component/Emoji/Resources/data/emoji-id.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-id.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-id.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-ig.php b/src/Symfony/Component/Emoji/Resources/data/emoji-ig.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-ig.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-ig.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-is.php b/src/Symfony/Component/Emoji/Resources/data/emoji-is.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-is.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-is.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-it.php b/src/Symfony/Component/Emoji/Resources/data/emoji-it.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-it.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-it.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-ja.php b/src/Symfony/Component/Emoji/Resources/data/emoji-ja.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-ja.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-ja.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-jv.php b/src/Symfony/Component/Emoji/Resources/data/emoji-jv.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-jv.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-jv.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-ka.php b/src/Symfony/Component/Emoji/Resources/data/emoji-ka.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-ka.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-ka.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-kab.php b/src/Symfony/Component/Emoji/Resources/data/emoji-kab.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-kab.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-kab.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-kk.php b/src/Symfony/Component/Emoji/Resources/data/emoji-kk.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-kk.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-kk.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-kl.php b/src/Symfony/Component/Emoji/Resources/data/emoji-kl.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-kl.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-kl.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-km.php b/src/Symfony/Component/Emoji/Resources/data/emoji-km.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-km.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-km.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-kn.php b/src/Symfony/Component/Emoji/Resources/data/emoji-kn.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-kn.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-kn.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-ko.php b/src/Symfony/Component/Emoji/Resources/data/emoji-ko.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-ko.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-ko.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-kok.php b/src/Symfony/Component/Emoji/Resources/data/emoji-kok.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-kok.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-kok.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-ku.php b/src/Symfony/Component/Emoji/Resources/data/emoji-ku.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-ku.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-ku.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-ky.php b/src/Symfony/Component/Emoji/Resources/data/emoji-ky.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-ky.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-ky.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-lb.php b/src/Symfony/Component/Emoji/Resources/data/emoji-lb.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-lb.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-lb.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-lij.php b/src/Symfony/Component/Emoji/Resources/data/emoji-lij.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-lij.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-lij.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-lo.php b/src/Symfony/Component/Emoji/Resources/data/emoji-lo.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-lo.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-lo.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-lt.php b/src/Symfony/Component/Emoji/Resources/data/emoji-lt.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-lt.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-lt.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-lv.php b/src/Symfony/Component/Emoji/Resources/data/emoji-lv.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-lv.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-lv.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-mi.php b/src/Symfony/Component/Emoji/Resources/data/emoji-mi.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-mi.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-mi.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-mk.php b/src/Symfony/Component/Emoji/Resources/data/emoji-mk.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-mk.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-mk.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-ml.php b/src/Symfony/Component/Emoji/Resources/data/emoji-ml.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-ml.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-ml.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-mn.php b/src/Symfony/Component/Emoji/Resources/data/emoji-mn.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-mn.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-mn.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-mni.php b/src/Symfony/Component/Emoji/Resources/data/emoji-mni.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-mni.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-mni.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-mr.php b/src/Symfony/Component/Emoji/Resources/data/emoji-mr.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-mr.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-mr.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-ms.php b/src/Symfony/Component/Emoji/Resources/data/emoji-ms.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-ms.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-ms.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-mt.php b/src/Symfony/Component/Emoji/Resources/data/emoji-mt.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-mt.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-mt.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-my.php b/src/Symfony/Component/Emoji/Resources/data/emoji-my.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-my.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-my.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-ne.php b/src/Symfony/Component/Emoji/Resources/data/emoji-ne.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-ne.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-ne.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-nl.php b/src/Symfony/Component/Emoji/Resources/data/emoji-nl.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-nl.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-nl.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-nn.php b/src/Symfony/Component/Emoji/Resources/data/emoji-nn.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-nn.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-nn.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-no.php b/src/Symfony/Component/Emoji/Resources/data/emoji-no.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-no.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-no.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-nso.php b/src/Symfony/Component/Emoji/Resources/data/emoji-nso.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-nso.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-nso.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-oc.php b/src/Symfony/Component/Emoji/Resources/data/emoji-oc.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-oc.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-oc.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-or.php b/src/Symfony/Component/Emoji/Resources/data/emoji-or.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-or.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-or.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-pa.php b/src/Symfony/Component/Emoji/Resources/data/emoji-pa.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-pa.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-pa.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-pa_arab.php b/src/Symfony/Component/Emoji/Resources/data/emoji-pa_arab.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-pa_arab.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-pa_arab.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-pcm.php b/src/Symfony/Component/Emoji/Resources/data/emoji-pcm.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-pcm.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-pcm.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-pl.php b/src/Symfony/Component/Emoji/Resources/data/emoji-pl.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-pl.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-pl.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-ps.php b/src/Symfony/Component/Emoji/Resources/data/emoji-ps.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-ps.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-ps.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-pt.php b/src/Symfony/Component/Emoji/Resources/data/emoji-pt.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-pt.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-pt.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-pt_pt.php b/src/Symfony/Component/Emoji/Resources/data/emoji-pt_pt.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-pt_pt.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-pt_pt.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-qu.php b/src/Symfony/Component/Emoji/Resources/data/emoji-qu.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-qu.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-qu.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-quc.php b/src/Symfony/Component/Emoji/Resources/data/emoji-quc.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-quc.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-quc.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-rhg.php b/src/Symfony/Component/Emoji/Resources/data/emoji-rhg.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-rhg.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-rhg.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-rm.php b/src/Symfony/Component/Emoji/Resources/data/emoji-rm.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-rm.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-rm.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-ro.php b/src/Symfony/Component/Emoji/Resources/data/emoji-ro.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-ro.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-ro.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-root.php b/src/Symfony/Component/Emoji/Resources/data/emoji-root.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-root.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-root.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-ru.php b/src/Symfony/Component/Emoji/Resources/data/emoji-ru.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-ru.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-ru.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-rw.php b/src/Symfony/Component/Emoji/Resources/data/emoji-rw.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-rw.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-rw.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-sat.php b/src/Symfony/Component/Emoji/Resources/data/emoji-sat.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-sat.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-sat.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-sc.php b/src/Symfony/Component/Emoji/Resources/data/emoji-sc.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-sc.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-sc.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-sd.php b/src/Symfony/Component/Emoji/Resources/data/emoji-sd.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-sd.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-sd.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-si.php b/src/Symfony/Component/Emoji/Resources/data/emoji-si.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-si.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-si.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-sk.php b/src/Symfony/Component/Emoji/Resources/data/emoji-sk.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-sk.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-sk.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-sl.php b/src/Symfony/Component/Emoji/Resources/data/emoji-sl.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-sl.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-sl.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-slack.php b/src/Symfony/Component/Emoji/Resources/data/emoji-slack.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-slack.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-slack.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-so.php b/src/Symfony/Component/Emoji/Resources/data/emoji-so.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-so.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-so.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-sq.php b/src/Symfony/Component/Emoji/Resources/data/emoji-sq.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-sq.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-sq.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-sr.php b/src/Symfony/Component/Emoji/Resources/data/emoji-sr.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-sr.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-sr.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-sr_cyrl.php b/src/Symfony/Component/Emoji/Resources/data/emoji-sr_cyrl.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-sr_cyrl.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-sr_cyrl.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-sr_cyrl_ba.php b/src/Symfony/Component/Emoji/Resources/data/emoji-sr_cyrl_ba.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-sr_cyrl_ba.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-sr_cyrl_ba.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-sr_latn.php b/src/Symfony/Component/Emoji/Resources/data/emoji-sr_latn.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-sr_latn.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-sr_latn.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-sr_latn_ba.php b/src/Symfony/Component/Emoji/Resources/data/emoji-sr_latn_ba.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-sr_latn_ba.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-sr_latn_ba.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-strip.php b/src/Symfony/Component/Emoji/Resources/data/emoji-strip.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-strip.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-strip.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-sv.php b/src/Symfony/Component/Emoji/Resources/data/emoji-sv.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-sv.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-sv.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-sw.php b/src/Symfony/Component/Emoji/Resources/data/emoji-sw.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-sw.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-sw.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-sw_ke.php b/src/Symfony/Component/Emoji/Resources/data/emoji-sw_ke.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-sw_ke.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-sw_ke.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-ta.php b/src/Symfony/Component/Emoji/Resources/data/emoji-ta.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-ta.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-ta.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-te.php b/src/Symfony/Component/Emoji/Resources/data/emoji-te.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-te.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-te.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-tg.php b/src/Symfony/Component/Emoji/Resources/data/emoji-tg.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-tg.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-tg.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-th.php b/src/Symfony/Component/Emoji/Resources/data/emoji-th.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-th.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-th.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-ti.php b/src/Symfony/Component/Emoji/Resources/data/emoji-ti.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-ti.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-ti.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-tk.php b/src/Symfony/Component/Emoji/Resources/data/emoji-tk.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-tk.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-tk.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-tn.php b/src/Symfony/Component/Emoji/Resources/data/emoji-tn.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-tn.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-tn.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-to.php b/src/Symfony/Component/Emoji/Resources/data/emoji-to.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-to.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-to.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-tr.php b/src/Symfony/Component/Emoji/Resources/data/emoji-tr.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-tr.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-tr.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-ug.php b/src/Symfony/Component/Emoji/Resources/data/emoji-ug.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-ug.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-ug.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-uk.php b/src/Symfony/Component/Emoji/Resources/data/emoji-uk.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-uk.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-uk.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-ur.php b/src/Symfony/Component/Emoji/Resources/data/emoji-ur.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-ur.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-ur.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-uz.php b/src/Symfony/Component/Emoji/Resources/data/emoji-uz.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-uz.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-uz.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-vi.php b/src/Symfony/Component/Emoji/Resources/data/emoji-vi.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-vi.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-vi.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-wo.php b/src/Symfony/Component/Emoji/Resources/data/emoji-wo.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-wo.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-wo.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-xh.php b/src/Symfony/Component/Emoji/Resources/data/emoji-xh.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-xh.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-xh.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-yo.php b/src/Symfony/Component/Emoji/Resources/data/emoji-yo.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-yo.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-yo.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-yo_bj.php b/src/Symfony/Component/Emoji/Resources/data/emoji-yo_bj.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-yo_bj.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-yo_bj.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-yue.php b/src/Symfony/Component/Emoji/Resources/data/emoji-yue.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-yue.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-yue.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-yue_hans.php b/src/Symfony/Component/Emoji/Resources/data/emoji-yue_hans.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-yue_hans.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-yue_hans.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-zh.php b/src/Symfony/Component/Emoji/Resources/data/emoji-zh.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-zh.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-zh.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-zh_hant.php b/src/Symfony/Component/Emoji/Resources/data/emoji-zh_hant.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-zh_hant.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-zh_hant.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-zh_hant_hk.php b/src/Symfony/Component/Emoji/Resources/data/emoji-zh_hant_hk.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-zh_hant_hk.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-zh_hant_hk.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-zu.php b/src/Symfony/Component/Emoji/Resources/data/emoji-zu.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-zu.php rename to src/Symfony/Component/Emoji/Resources/data/emoji-zu.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/github-emoji.php b/src/Symfony/Component/Emoji/Resources/data/github-emoji.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/github-emoji.php rename to src/Symfony/Component/Emoji/Resources/data/github-emoji.php diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/slack-emoji.php b/src/Symfony/Component/Emoji/Resources/data/slack-emoji.php similarity index 100% rename from src/Symfony/Component/Intl/Resources/data/transliterator/emoji/slack-emoji.php rename to src/Symfony/Component/Emoji/Resources/data/slack-emoji.php diff --git a/src/Symfony/Component/Intl/Tests/Transliterator/EmojiTransliteratorTest.php b/src/Symfony/Component/Emoji/Tests/EmojiTransliteratorTest.php similarity index 96% rename from src/Symfony/Component/Intl/Tests/Transliterator/EmojiTransliteratorTest.php rename to src/Symfony/Component/Emoji/Tests/EmojiTransliteratorTest.php index ccc379b09ac2d..12a3682b16769 100644 --- a/src/Symfony/Component/Intl/Tests/Transliterator/EmojiTransliteratorTest.php +++ b/src/Symfony/Component/Emoji/Tests/EmojiTransliteratorTest.php @@ -9,11 +9,11 @@ * file that was distributed with this source code. */ -namespace Symfony\Component\Intl\Tests\Transliterator; +namespace Symfony\Component\Emoji\Tests; use PHPUnit\Framework\TestCase; +use Symfony\Component\Emoji\EmojiTransliterator; use Symfony\Component\Finder\Finder; -use Symfony\Component\Intl\Transliterator\EmojiTransliterator; /** * @requires extension intl @@ -95,7 +95,7 @@ public function testAllTransliterator(string $locale) public static function provideLocaleTest(): iterable { $file = (new Finder()) - ->in(__DIR__.'/../../Resources/data/transliterator/emoji') + ->in(__DIR__.'/../Resources/data') ->name('*.php') ->notName('emoji-strip.php') ->files() diff --git a/src/Symfony/Component/Emoji/Util/GzipStreamWrapper.php b/src/Symfony/Component/Emoji/Util/GzipStreamWrapper.php new file mode 100644 index 0000000000000..1ae158dd9cc38 --- /dev/null +++ b/src/Symfony/Component/Emoji/Util/GzipStreamWrapper.php @@ -0,0 +1,83 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Emoji\Util; + +/** + * @internal + */ +class GzipStreamWrapper +{ + /** @var resource|null */ + public $context; + + /** @var resource */ + private $handle; + private string $path; + + public static function require(string $path): array + { + if (!\extension_loaded('zlib')) { + throw new \LogicException(sprintf('The "zlib" extension is required to load the "%s/%s" map, please enable it in your php.ini file.', basename(\dirname($path)), basename($path))); + } + + if (!\function_exists('opcache_is_script_cached') || !@opcache_is_script_cached($path)) { + stream_wrapper_unregister('file'); + stream_wrapper_register('file', self::class); + } + + return require $path; + } + + public function stream_open(string $path, string $mode): bool + { + stream_wrapper_restore('file'); + $this->path = $path; + + return false !== $this->handle = fopen('compress.zlib://'.$path, $mode); + } + + public function stream_read(int $count): string|false + { + return fread($this->handle, $count); + } + + public function stream_eof(): bool + { + return feof($this->handle); + } + + public function stream_set_option(int $option, int $arg1, int $arg2): bool + { + return match ($option) { + \STREAM_OPTION_BLOCKING => stream_set_blocking($this->handle, $arg1), + \STREAM_OPTION_READ_TIMEOUT => stream_set_timeout($this->handle, $arg1, $arg2), + \STREAM_OPTION_WRITE_BUFFER => 0 === stream_set_write_buffer($this->handle, $arg2), + default => false, + }; + } + + public function stream_stat(): array|false + { + if (!$stat = stat($this->path)) { + return false; + } + + $h = fopen($this->path, 'r'); + fseek($h, -4, \SEEK_END); + $size = unpack('V', fread($h, 4)); + fclose($h); + + $stat[7] = $stat['size'] = end($size); + + return $stat; + } +} diff --git a/src/Symfony/Component/Emoji/composer.json b/src/Symfony/Component/Emoji/composer.json new file mode 100644 index 0000000000000..c4997c1b32b92 --- /dev/null +++ b/src/Symfony/Component/Emoji/composer.json @@ -0,0 +1,34 @@ +{ + "name": "symfony/emoji", + "type": "library", + "description": "Provides access to emoji characters and sequences from the Unicode CLDR", + "keywords": ["cldr", "emoji", "intl", "transliterator", "unicode"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Grégoire Pineau", + "email": "lyrixx@lyrixx.info" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=8.2", + "ext-intl": "*" + }, + "require-dev": { + "symfony/filesystem": "^6.4|^7.0", + "symfony/finder": "^6.4|^7.0", + "symfony/var-exporter": "^6.4|^7.0" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\Emoji\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev" +} diff --git a/src/Symfony/Component/Emoji/phpunit.xml.dist b/src/Symfony/Component/Emoji/phpunit.xml.dist new file mode 100644 index 0000000000000..5c74dab50b3ca --- /dev/null +++ b/src/Symfony/Component/Emoji/phpunit.xml.dist @@ -0,0 +1,31 @@ + + + + + + + + + + ./Tests/ + + + + + + ./ + + + ./Resources + ./Tests + ./vendor + + + diff --git a/src/Symfony/Component/ErrorHandler/DebugClassLoader.php b/src/Symfony/Component/ErrorHandler/DebugClassLoader.php index b7ed7296bfeee..db5d37bb40c75 100644 --- a/src/Symfony/Component/ErrorHandler/DebugClassLoader.php +++ b/src/Symfony/Component/ErrorHandler/DebugClassLoader.php @@ -311,7 +311,7 @@ public function loadClass(string $class): void $this->checkClass($class, $file); } - private function checkClass(string $class, string $file = null): void + private function checkClass(string $class, ?string $file = null): void { $exists = null === $file || class_exists($class, false) || interface_exists($class, false) || trait_exists($class, false); @@ -796,7 +796,7 @@ private function getOwnInterfaces(string $class, ?string $parent): array return $ownInterfaces; } - private function setReturnType(string $types, string $class, string $method, string $filename, ?string $parent, \ReflectionType $returnType = null): void + private function setReturnType(string $types, string $class, string $method, string $filename, ?string $parent, ?\ReflectionType $returnType = null): void { if ('__construct' === $method) { return; diff --git a/src/Symfony/Component/ErrorHandler/Error/FatalError.php b/src/Symfony/Component/ErrorHandler/Error/FatalError.php index e0cf2f505a229..d96543ae6bc59 100644 --- a/src/Symfony/Component/ErrorHandler/Error/FatalError.php +++ b/src/Symfony/Component/ErrorHandler/Error/FatalError.php @@ -20,9 +20,9 @@ public function __construct( string $message, int $code, private array $error, - int $traceOffset = null, + ?int $traceOffset = null, bool $traceArgs = true, - array $trace = null, + ?array $trace = null, ) { parent::__construct($message, $code); diff --git a/src/Symfony/Component/ErrorHandler/ErrorEnhancer/ClassNotFoundErrorEnhancer.php b/src/Symfony/Component/ErrorHandler/ErrorEnhancer/ClassNotFoundErrorEnhancer.php index a98075fe45ef4..b4623cf17cdf7 100644 --- a/src/Symfony/Component/ErrorHandler/ErrorEnhancer/ClassNotFoundErrorEnhancer.php +++ b/src/Symfony/Component/ErrorHandler/ErrorEnhancer/ClassNotFoundErrorEnhancer.php @@ -107,7 +107,8 @@ private function getClassCandidates(string $class): array private function findClassInPath(string $path, string $class, string $prefix): array { - if (!$path = realpath($path.'/'.strtr($prefix, '\\_', '//')) ?: realpath($path.'/'.\dirname(strtr($prefix, '\\_', '//'))) ?: realpath($path)) { + $path = realpath($path.'/'.strtr($prefix, '\\_', '//')) ?: realpath($path.'/'.\dirname(strtr($prefix, '\\_', '//'))) ?: realpath($path); + if (!$path || !is_dir($path)) { return []; } diff --git a/src/Symfony/Component/ErrorHandler/ErrorHandler.php b/src/Symfony/Component/ErrorHandler/ErrorHandler.php index 1ee29e1b8e71a..5bf05431ea080 100644 --- a/src/Symfony/Component/ErrorHandler/ErrorHandler.php +++ b/src/Symfony/Component/ErrorHandler/ErrorHandler.php @@ -108,7 +108,7 @@ class ErrorHandler /** * Registers the error handler. */ - public static function register(self $handler = null, bool $replace = true): self + public static function register(?self $handler = null, bool $replace = true): self { if (null === self::$reservedMemory) { self::$reservedMemory = str_repeat('x', 32768); @@ -179,7 +179,7 @@ public static function call(callable $function, mixed ...$arguments): mixed } } - public function __construct(BufferingLogger $bootstrappingLogger = null, bool $debug = false) + public function __construct(?BufferingLogger $bootstrappingLogger = null, bool $debug = false) { if ($bootstrappingLogger) { $this->bootstrappingLogger = $bootstrappingLogger; @@ -559,7 +559,7 @@ public function handleException(\Throwable $exception): void * * @internal */ - public static function handleFatalError(array $error = null): void + public static function handleFatalError(?array $error = null): void { if (null === self::$reservedMemory) { return; diff --git a/src/Symfony/Component/ErrorHandler/ErrorRenderer/FileLinkFormatter.php b/src/Symfony/Component/ErrorHandler/ErrorRenderer/FileLinkFormatter.php index 570debb8afa53..e82082457ce10 100644 --- a/src/Symfony/Component/ErrorHandler/ErrorRenderer/FileLinkFormatter.php +++ b/src/Symfony/Component/ErrorHandler/ErrorRenderer/FileLinkFormatter.php @@ -30,10 +30,10 @@ class FileLinkFormatter * @param string|\Closure $urlFormat the URL format, or a closure that returns it on-demand */ public function __construct( - string|array $fileLinkFormat = null, + string|array|null $fileLinkFormat = null, private ?RequestStack $requestStack = null, private ?string $baseDir = null, - private null|string|\Closure $urlFormat = null, + private string|\Closure|null $urlFormat = null, ) { $fileLinkFormat ??= $_ENV['SYMFONY_IDE'] ?? $_SERVER['SYMFONY_IDE'] ?? ''; diff --git a/src/Symfony/Component/ErrorHandler/ErrorRenderer/HtmlErrorRenderer.php b/src/Symfony/Component/ErrorHandler/ErrorRenderer/HtmlErrorRenderer.php index 9a60da3e3e431..cfb6b194f53f4 100644 --- a/src/Symfony/Component/ErrorHandler/ErrorRenderer/HtmlErrorRenderer.php +++ b/src/Symfony/Component/ErrorHandler/ErrorRenderer/HtmlErrorRenderer.php @@ -47,8 +47,8 @@ class HtmlErrorRenderer implements ErrorRendererInterface */ public function __construct( bool|callable $debug = false, - string $charset = null, - string|FileLinkFormatter $fileLinkFormat = null, + ?string $charset = null, + string|FileLinkFormatter|null $fileLinkFormat = null, private ?string $projectDir = null, string|callable $outputBuffer = '', private ?LoggerInterface $logger = null, @@ -217,7 +217,7 @@ private function getFileRelative(string $file): ?string * @param int $line The line number * @param string $text Use this text for the link rather than the file path */ - private function formatFile(string $file, int $line, string $text = null): string + private function formatFile(string $file, int $line, ?string $text = null): string { $file = trim($file); diff --git a/src/Symfony/Component/ErrorHandler/ErrorRenderer/SerializerErrorRenderer.php b/src/Symfony/Component/ErrorHandler/ErrorRenderer/SerializerErrorRenderer.php index 88d4c9037085d..c5d918026ad88 100644 --- a/src/Symfony/Component/ErrorHandler/ErrorRenderer/SerializerErrorRenderer.php +++ b/src/Symfony/Component/ErrorHandler/ErrorRenderer/SerializerErrorRenderer.php @@ -36,7 +36,7 @@ class SerializerErrorRenderer implements ErrorRendererInterface public function __construct( private SerializerInterface $serializer, string|callable $format, - ErrorRendererInterface $fallbackErrorRenderer = null, + ?ErrorRendererInterface $fallbackErrorRenderer = null, bool|callable $debug = false, ) { $this->format = \is_string($format) ? $format : $format(...); diff --git a/src/Symfony/Component/ErrorHandler/Exception/FlattenException.php b/src/Symfony/Component/ErrorHandler/Exception/FlattenException.php index ab62b1be367f8..39473151db194 100644 --- a/src/Symfony/Component/ErrorHandler/Exception/FlattenException.php +++ b/src/Symfony/Component/ErrorHandler/Exception/FlattenException.php @@ -42,12 +42,12 @@ class FlattenException private ?string $asString = null; private Data $dataRepresentation; - public static function create(\Exception $exception, int $statusCode = null, array $headers = []): static + public static function create(\Exception $exception, ?int $statusCode = null, array $headers = []): static { return static::createFromThrowable($exception, $statusCode, $headers); } - public static function createFromThrowable(\Throwable $exception, int $statusCode = null, array $headers = []): static + public static function createFromThrowable(\Throwable $exception, ?int $statusCode = null, array $headers = []): static { $e = new static(); $e->setMessage($exception->getMessage()); @@ -85,7 +85,7 @@ public static function createFromThrowable(\Throwable $exception, int $statusCod return $e; } - public static function createWithDataRepresentation(\Throwable $throwable, int $statusCode = null, array $headers = [], VarCloner $cloner = null): static + public static function createWithDataRepresentation(\Throwable $throwable, ?int $statusCode = null, array $headers = [], ?VarCloner $cloner = null): static { $e = static::createFromThrowable($throwable, $statusCode, $headers); diff --git a/src/Symfony/Component/ErrorHandler/Resources/views/error.html.php b/src/Symfony/Component/ErrorHandler/Resources/views/error.html.php index 1085a5adb2821..3fbf28f60b262 100644 --- a/src/Symfony/Component/ErrorHandler/Resources/views/error.html.php +++ b/src/Symfony/Component/ErrorHandler/Resources/views/error.html.php @@ -1,10 +1,10 @@ - - + + An Error Occurred: <?= $statusText; ?> - + diff --git a/src/Symfony/Component/ErrorHandler/Resources/views/exception_full.html.php b/src/Symfony/Component/ErrorHandler/Resources/views/exception_full.html.php index 9d5f6e3366adc..af04db1bd2c97 100644 --- a/src/Symfony/Component/ErrorHandler/Resources/views/exception_full.html.php +++ b/src/Symfony/Component/ErrorHandler/Resources/views/exception_full.html.php @@ -2,11 +2,11 @@ - - - + + + <?= $_message; ?> - + diff --git a/src/Symfony/Component/ErrorHandler/Tests/ErrorHandlerTest.php b/src/Symfony/Component/ErrorHandler/Tests/ErrorHandlerTest.php index 1e48e8a910b6b..f216a8fba63e1 100644 --- a/src/Symfony/Component/ErrorHandler/Tests/ErrorHandlerTest.php +++ b/src/Symfony/Component/ErrorHandler/Tests/ErrorHandlerTest.php @@ -363,7 +363,7 @@ public function testHandleDeprecation() /** * @dataProvider handleExceptionProvider */ - public function testHandleException(string $expectedMessage, \Throwable $exception, string $enhancedMessage = null) + public function testHandleException(string $expectedMessage, \Throwable $exception, ?string $enhancedMessage = null) { try { $logger = $this->createMock(LoggerInterface::class); diff --git a/src/Symfony/Component/EventDispatcher/Debug/TraceableEventDispatcher.php b/src/Symfony/Component/EventDispatcher/Debug/TraceableEventDispatcher.php index f6b96bf69e352..72ce526586584 100644 --- a/src/Symfony/Component/EventDispatcher/Debug/TraceableEventDispatcher.php +++ b/src/Symfony/Component/EventDispatcher/Debug/TraceableEventDispatcher.php @@ -76,7 +76,7 @@ public function removeSubscriber(EventSubscriberInterface $subscriber): void $this->dispatcher->removeSubscriber($subscriber); } - public function getListeners(string $eventName = null): array + public function getListeners(?string $eventName = null): array { return $this->dispatcher->getListeners($eventName); } @@ -96,12 +96,12 @@ public function getListenerPriority(string $eventName, callable|array $listener) return $this->dispatcher->getListenerPriority($eventName, $listener); } - public function hasListeners(string $eventName = null): bool + public function hasListeners(?string $eventName = null): bool { return $this->dispatcher->hasListeners($eventName); } - public function dispatch(object $event, string $eventName = null): object + public function dispatch(object $event, ?string $eventName = null): object { $eventName ??= $event::class; @@ -136,7 +136,7 @@ public function dispatch(object $event, string $eventName = null): object return $event; } - public function getCalledListeners(Request $request = null): array + public function getCalledListeners(?Request $request = null): array { if (null === $this->callStack) { return []; @@ -154,7 +154,7 @@ public function getCalledListeners(Request $request = null): array return $called; } - public function getNotCalledListeners(Request $request = null): array + public function getNotCalledListeners(?Request $request = null): array { try { $allListeners = $this->dispatcher instanceof EventDispatcher ? $this->getListenersWithPriority() : $this->getListenersWithoutPriority(); @@ -196,7 +196,7 @@ public function getNotCalledListeners(Request $request = null): array return $notCalled; } - public function getOrphanedEvents(Request $request = null): array + public function getOrphanedEvents(?Request $request = null): array { if ($request) { return $this->orphanedEvents[spl_object_hash($request)] ?? []; diff --git a/src/Symfony/Component/EventDispatcher/EventDispatcher.php b/src/Symfony/Component/EventDispatcher/EventDispatcher.php index 65d862637423c..43bc16b85b4e5 100644 --- a/src/Symfony/Component/EventDispatcher/EventDispatcher.php +++ b/src/Symfony/Component/EventDispatcher/EventDispatcher.php @@ -42,7 +42,7 @@ public function __construct() } } - public function dispatch(object $event, string $eventName = null): object + public function dispatch(object $event, ?string $eventName = null): object { $eventName ??= $event::class; @@ -59,7 +59,7 @@ public function dispatch(object $event, string $eventName = null): object return $event; } - public function getListeners(string $eventName = null): array + public function getListeners(?string $eventName = null): array { if (null !== $eventName) { if (empty($this->listeners[$eventName])) { @@ -108,7 +108,7 @@ public function getListenerPriority(string $eventName, callable|array $listener) return null; } - public function hasListeners(string $eventName = null): bool + public function hasListeners(?string $eventName = null): bool { if (null !== $eventName) { return !empty($this->listeners[$eventName]); diff --git a/src/Symfony/Component/EventDispatcher/EventDispatcherInterface.php b/src/Symfony/Component/EventDispatcher/EventDispatcherInterface.php index 6d4a5b4d9557b..99f8b1a03a93c 100644 --- a/src/Symfony/Component/EventDispatcher/EventDispatcherInterface.php +++ b/src/Symfony/Component/EventDispatcher/EventDispatcherInterface.php @@ -50,7 +50,7 @@ public function removeSubscriber(EventSubscriberInterface $subscriber): void; * * @return array */ - public function getListeners(string $eventName = null): array; + public function getListeners(?string $eventName = null): array; /** * Gets the listener priority for a specific event. @@ -62,5 +62,5 @@ public function getListenerPriority(string $eventName, callable $listener): ?int /** * Checks whether an event has any registered listeners. */ - public function hasListeners(string $eventName = null): bool; + public function hasListeners(?string $eventName = null): bool; } diff --git a/src/Symfony/Component/EventDispatcher/ImmutableEventDispatcher.php b/src/Symfony/Component/EventDispatcher/ImmutableEventDispatcher.php index f715ecba3519d..a6d078e9b699d 100644 --- a/src/Symfony/Component/EventDispatcher/ImmutableEventDispatcher.php +++ b/src/Symfony/Component/EventDispatcher/ImmutableEventDispatcher.php @@ -23,7 +23,7 @@ public function __construct( ) { } - public function dispatch(object $event, string $eventName = null): object + public function dispatch(object $event, ?string $eventName = null): object { return $this->dispatcher->dispatch($event, $eventName); } @@ -48,7 +48,7 @@ public function removeSubscriber(EventSubscriberInterface $subscriber): never throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.'); } - public function getListeners(string $eventName = null): array + public function getListeners(?string $eventName = null): array { return $this->dispatcher->getListeners($eventName); } @@ -58,7 +58,7 @@ public function getListenerPriority(string $eventName, callable|array $listener) return $this->dispatcher->getListenerPriority($eventName, $listener); } - public function hasListeners(string $eventName = null): bool + public function hasListeners(?string $eventName = null): bool { return $this->dispatcher->hasListeners($eventName); } diff --git a/src/Symfony/Component/ExpressionLanguage/CHANGELOG.md b/src/Symfony/Component/ExpressionLanguage/CHANGELOG.md index f54f943ac15de..f5c26e6905370 100644 --- a/src/Symfony/Component/ExpressionLanguage/CHANGELOG.md +++ b/src/Symfony/Component/ExpressionLanguage/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +7.1 +--- + + * Add support for PHP `min` and `max` functions + 7.0 --- diff --git a/src/Symfony/Component/ExpressionLanguage/ExpressionFunction.php b/src/Symfony/Component/ExpressionLanguage/ExpressionFunction.php index 5bd857ddb41c4..8950e2148a274 100644 --- a/src/Symfony/Component/ExpressionLanguage/ExpressionFunction.php +++ b/src/Symfony/Component/ExpressionLanguage/ExpressionFunction.php @@ -71,7 +71,7 @@ public function getEvaluator(): \Closure * @throws \InvalidArgumentException if given PHP function name is in namespace * and expression function name is not defined */ - public static function fromPhp(string $phpFunctionName, string $expressionFunctionName = null): self + public static function fromPhp(string $phpFunctionName, ?string $expressionFunctionName = null): self { $phpFunctionName = ltrim($phpFunctionName, '\\'); if (!\function_exists($phpFunctionName)) { diff --git a/src/Symfony/Component/ExpressionLanguage/ExpressionLanguage.php b/src/Symfony/Component/ExpressionLanguage/ExpressionLanguage.php index e8200d9b64b25..bd9dbfcc1944c 100644 --- a/src/Symfony/Component/ExpressionLanguage/ExpressionLanguage.php +++ b/src/Symfony/Component/ExpressionLanguage/ExpressionLanguage.php @@ -34,7 +34,7 @@ class ExpressionLanguage /** * @param ExpressionFunctionProviderInterface[] $providers */ - public function __construct(CacheItemPoolInterface $cache = null, array $providers = []) + public function __construct(?CacheItemPoolInterface $cache = null, array $providers = []) { $this->cache = $cache ?? new ArrayAdapter(); $this->registerFunctions(); @@ -140,7 +140,10 @@ public function registerProvider(ExpressionFunctionProviderInterface $provider): */ protected function registerFunctions() { - $this->addFunction(ExpressionFunction::fromPhp('constant')); + $basicPhpFunctions = ['constant', 'min', 'max']; + foreach ($basicPhpFunctions as $function) { + $this->addFunction(ExpressionFunction::fromPhp($function)); + } $this->addFunction(new ExpressionFunction('enum', static fn ($str): string => sprintf("(\constant(\$v = (%s))) instanceof \UnitEnum ? \constant(\$v) : throw new \TypeError(\sprintf('The string \"%%s\" is not the name of a valid enum case.', \$v))", $str), diff --git a/src/Symfony/Component/ExpressionLanguage/Node/ArrayNode.php b/src/Symfony/Component/ExpressionLanguage/Node/ArrayNode.php index 993af3633d9a2..79eade29ca52d 100644 --- a/src/Symfony/Component/ExpressionLanguage/Node/ArrayNode.php +++ b/src/Symfony/Component/ExpressionLanguage/Node/ArrayNode.php @@ -27,7 +27,7 @@ public function __construct() $this->index = -1; } - public function addElement(Node $value, Node $key = null): void + public function addElement(Node $value, ?Node $key = null): void { $key ??= new ConstantNode(++$this->index); diff --git a/src/Symfony/Component/ExpressionLanguage/SyntaxError.php b/src/Symfony/Component/ExpressionLanguage/SyntaxError.php index 0bfd7e9977727..e165dc22a0d72 100644 --- a/src/Symfony/Component/ExpressionLanguage/SyntaxError.php +++ b/src/Symfony/Component/ExpressionLanguage/SyntaxError.php @@ -13,7 +13,7 @@ class SyntaxError extends \LogicException { - public function __construct(string $message, int $cursor = 0, string $expression = '', string $subject = null, array $proposals = null) + public function __construct(string $message, int $cursor = 0, string $expression = '', ?string $subject = null, ?array $proposals = null) { $message = sprintf('%s around position %d', rtrim($message, '.'), $cursor); if ($expression) { diff --git a/src/Symfony/Component/ExpressionLanguage/Tests/ExpressionLanguageTest.php b/src/Symfony/Component/ExpressionLanguage/Tests/ExpressionLanguageTest.php index ed926ffd35073..f7f712d8c5d46 100644 --- a/src/Symfony/Component/ExpressionLanguage/Tests/ExpressionLanguageTest.php +++ b/src/Symfony/Component/ExpressionLanguage/Tests/ExpressionLanguageTest.php @@ -71,13 +71,23 @@ public function testCachedParse() $this->assertSame($savedParsedExpression, $parsedExpression); } - public function testConstantFunction() + /** + * @dataProvider basicPhpFunctionProvider + */ + public function testBasicPhpFunction($expression, $expected, $compiled) { $expressionLanguage = new ExpressionLanguage(); - $this->assertEquals(\PHP_VERSION, $expressionLanguage->evaluate('constant("PHP_VERSION")')); + $this->assertEquals($expected, $expressionLanguage->evaluate($expression)); + $this->assertEquals($compiled, $expressionLanguage->compile($expression)); + } - $expressionLanguage = new ExpressionLanguage(); - $this->assertEquals('\constant("PHP_VERSION")', $expressionLanguage->compile('constant("PHP_VERSION")')); + public static function basicPhpFunctionProvider() + { + return [ + ['constant("PHP_VERSION")', \PHP_VERSION, '\constant("PHP_VERSION")'], + ['min(1,2,3)', 1, '\min(1, 2, 3)'], + ['max(1,2,3)', 3, '\max(1, 2, 3)'], + ]; } public function testEnumFunctionWithConstantThrows() @@ -168,6 +178,14 @@ public function testParseThrowsInsteadOfNotice() $expressionLanguage->parse('node.', ['node']); } + public function testParseReturnsObjectOnAlreadyParsedExpression() + { + $expressionLanguage = new ExpressionLanguage(); + $expression = $expressionLanguage->parse('1 + 1', []); + + $this->assertSame($expression, $expressionLanguage->parse($expression, [])); + } + public static function shortCircuitProviderEvaluate() { $object = new class(static::fail(...)) { @@ -366,7 +384,7 @@ public function testNullSafeCompileFails($expression, $foo) $this->expectException(\ErrorException::class); - set_error_handler(static function (int $errno, string $errstr, string $errfile = null, int $errline = null): bool { + set_error_handler(static function (int $errno, string $errstr, ?string $errfile = null, ?int $errline = null): bool { if ($errno & (\E_WARNING | \E_USER_WARNING) && (str_contains($errstr, 'Attempt to read property') || str_contains($errstr, 'Trying to access'))) { throw new \ErrorException($errstr, 0, $errno, $errfile, $errline); } @@ -440,6 +458,24 @@ public function testRegisterAfterCompile($registerCallback) $registerCallback($el); } + public function testLintDoesntThrowOnValidExpression() + { + $el = new ExpressionLanguage(); + $el->lint('1 + 1', null); + + $this->expectNotToPerformAssertions(); + } + + public function testLintThrowsOnInvalidExpression() + { + $el = new ExpressionLanguage(); + + $this->expectException(SyntaxError::class); + $this->expectExceptionMessage('Unexpected end of expression around position 6 for expression `node.`.'); + + $el->lint('node.', ['node']); + } + public static function getRegisterCallbacks() { return [ diff --git a/src/Symfony/Component/ExpressionLanguage/Tests/ParserTest.php b/src/Symfony/Component/ExpressionLanguage/Tests/ParserTest.php index 58a232eb8145a..e3170ba809fde 100644 --- a/src/Symfony/Component/ExpressionLanguage/Tests/ParserTest.php +++ b/src/Symfony/Component/ExpressionLanguage/Tests/ParserTest.php @@ -37,6 +37,17 @@ public function testParseWithZeroInNames() $parser->parse($lexer->tokenize('foo'), [0]); } + public function testParseUnknownFunction() + { + $parser = new Parser([]); + $tokenized = (new Lexer())->tokenize('foo()'); + + $this->expectException(SyntaxError::class); + $this->expectExceptionMessage('The function "foo" does not exist around position 1 for expression `foo()`.'); + + $parser->parse($tokenized); + } + /** * @dataProvider getParseData */ @@ -284,7 +295,7 @@ public function testNameProposal() /** * @dataProvider getLintData */ - public function testLint($expression, $names, string $exception = null) + public function testLint($expression, $names, ?string $exception = null) { if ($exception) { $this->expectException(SyntaxError::class); @@ -314,6 +325,14 @@ public static function getLintData(): array 'expression' => 'foo.bar', 'names' => null, ], + 'array with trailing comma' => [ + 'expression' => '[value1, value2, value3,]', + 'names' => ['value1', 'value2', 'value3'], + ], + 'hashmap with trailing comma' => [ + 'expression' => '{val1: value1, val2: value2, val3: value3,}', + 'names' => ['value1', 'value2', 'value3'], + ], 'disallow expression without names' => [ 'expression' => 'foo.bar', 'names' => [], @@ -347,6 +366,11 @@ public static function getLintData(): array 'names' => ['foo'], 'exception' => 'Unclosed "[" around position 3 for expression `foo["some_key")`.', ], + 'incorrect hash key' => [ + 'expression' => '{+: value1}', + 'names' => ['value1'], + 'exception' => 'A hash key must be a quoted string, a number, a name, or an expression enclosed in parentheses (unexpected token "operator" of value "+" around position 2 for expression `{+: value1}`.', + ], 'missed array key' => [ 'expression' => 'foo[]', 'names' => ['foo'], diff --git a/src/Symfony/Component/ExpressionLanguage/Token.php b/src/Symfony/Component/ExpressionLanguage/Token.php index 14ed27704f16c..c1a7c107d1f87 100644 --- a/src/Symfony/Component/ExpressionLanguage/Token.php +++ b/src/Symfony/Component/ExpressionLanguage/Token.php @@ -47,7 +47,7 @@ public function __toString(): string /** * Tests the current token for a type and/or a value. */ - public function test(string $type, string $value = null): bool + public function test(string $type, ?string $value = null): bool { return $this->type === $type && (null === $value || $this->value == $value); } diff --git a/src/Symfony/Component/ExpressionLanguage/TokenStream.php b/src/Symfony/Component/ExpressionLanguage/TokenStream.php index da9e3aceca9d9..a7f67351076bc 100644 --- a/src/Symfony/Component/ExpressionLanguage/TokenStream.php +++ b/src/Symfony/Component/ExpressionLanguage/TokenStream.php @@ -56,7 +56,7 @@ public function next(): void /** * @param string|null $message The syntax error message */ - public function expect(string $type, string $value = null, string $message = null): void + public function expect(string $type, ?string $value = null, ?string $message = null): void { $token = $this->current; if (!$token->test($type, $value)) { diff --git a/src/Symfony/Component/Filesystem/Exception/FileNotFoundException.php b/src/Symfony/Component/Filesystem/Exception/FileNotFoundException.php index 48b6408095a13..06b732b1685c8 100644 --- a/src/Symfony/Component/Filesystem/Exception/FileNotFoundException.php +++ b/src/Symfony/Component/Filesystem/Exception/FileNotFoundException.php @@ -19,7 +19,7 @@ */ class FileNotFoundException extends IOException { - public function __construct(string $message = null, int $code = 0, \Throwable $previous = null, string $path = null) + public function __construct(?string $message = null, int $code = 0, ?\Throwable $previous = null, ?string $path = null) { if (null === $message) { if (null === $path) { diff --git a/src/Symfony/Component/Filesystem/Exception/IOException.php b/src/Symfony/Component/Filesystem/Exception/IOException.php index a3c5445534c72..46ab8b4a5a9d5 100644 --- a/src/Symfony/Component/Filesystem/Exception/IOException.php +++ b/src/Symfony/Component/Filesystem/Exception/IOException.php @@ -20,12 +20,12 @@ */ class IOException extends \RuntimeException implements IOExceptionInterface { - private ?string $path; - - public function __construct(string $message, int $code = 0, \Throwable $previous = null, string $path = null) - { - $this->path = $path; - + public function __construct( + string $message, + int $code = 0, + ?\Throwable $previous = null, + private ?string $path = null, + ) { parent::__construct($message, $code, $previous); } diff --git a/src/Symfony/Component/Filesystem/Filesystem.php b/src/Symfony/Component/Filesystem/Filesystem.php index e8ba4958df79d..b7fc701182a4b 100644 --- a/src/Symfony/Component/Filesystem/Filesystem.php +++ b/src/Symfony/Component/Filesystem/Filesystem.php @@ -125,7 +125,7 @@ public function exists(string|iterable $files): bool * * @throws IOException When touch fails */ - public function touch(string|iterable $files, int $time = null, int $atime = null): void + public function touch(string|iterable $files, ?int $time = null, ?int $atime = null): void { foreach ($this->toIterable($files) as $file) { if (!($time ? self::box('touch', $file, $time, $atime) : self::box('touch', $file))) { @@ -508,7 +508,7 @@ public function makePathRelative(string $endPath, string $startPath): string * * @throws IOException When file type is unknown */ - public function mirror(string $originDir, string $targetDir, \Traversable $iterator = null, array $options = []): void + public function mirror(string $originDir, string $targetDir, ?\Traversable $iterator = null, array $options = []): void { $targetDir = rtrim($targetDir, '/\\'); $originDir = rtrim($originDir, '/\\'); diff --git a/src/Symfony/Component/Filesystem/Path.php b/src/Symfony/Component/Filesystem/Path.php index 0650c8e98cb50..80c616049b2d4 100644 --- a/src/Symfony/Component/Filesystem/Path.php +++ b/src/Symfony/Component/Filesystem/Path.php @@ -254,7 +254,7 @@ public static function getRoot(string $path): string * @param string|null $extension if specified, only that extension is cut * off (may contain leading dot) */ - public static function getFilenameWithoutExtension(string $path, string $extension = null): string + public static function getFilenameWithoutExtension(string $path, ?string $extension = null): string { if ('' === $path) { return ''; diff --git a/src/Symfony/Component/Finder/Comparator/Comparator.php b/src/Symfony/Component/Finder/Comparator/Comparator.php index bd685834739d2..c3d40e70e9946 100644 --- a/src/Symfony/Component/Finder/Comparator/Comparator.php +++ b/src/Symfony/Component/Finder/Comparator/Comparator.php @@ -16,16 +16,16 @@ */ class Comparator { - private string $target; private string $operator; - public function __construct(string $target, string $operator = '==') - { + public function __construct( + private string $target, + string $operator = '==', + ) { if (!\in_array($operator, ['>', '<', '>=', '<=', '==', '!='])) { throw new \InvalidArgumentException(sprintf('Invalid operator "%s".', $operator)); } - $this->target = $target; $this->operator = $operator; } diff --git a/src/Symfony/Component/Finder/SplFileInfo.php b/src/Symfony/Component/Finder/SplFileInfo.php index 867e8e81a2084..2afc37826b8c8 100644 --- a/src/Symfony/Component/Finder/SplFileInfo.php +++ b/src/Symfony/Component/Finder/SplFileInfo.php @@ -18,19 +18,17 @@ */ class SplFileInfo extends \SplFileInfo { - private string $relativePath; - private string $relativePathname; - /** * @param string $file The file name * @param string $relativePath The relative path * @param string $relativePathname The relative path name */ - public function __construct(string $file, string $relativePath, string $relativePathname) - { + public function __construct( + string $file, + private string $relativePath, + private string $relativePathname, + ) { parent::__construct($file); - $this->relativePath = $relativePath; - $this->relativePathname = $relativePathname; } /** diff --git a/src/Symfony/Component/Form/AbstractRendererEngine.php b/src/Symfony/Component/Form/AbstractRendererEngine.php index bccdf69cc5922..1968f5a3766c6 100644 --- a/src/Symfony/Component/Form/AbstractRendererEngine.php +++ b/src/Symfony/Component/Form/AbstractRendererEngine.php @@ -25,8 +25,6 @@ abstract class AbstractRendererEngine implements FormRendererEngineInterface, Re */ public const CACHE_KEY_VAR = 'cache_key'; - protected array $defaultThemes; - /** * @var array[] */ @@ -53,9 +51,9 @@ abstract class AbstractRendererEngine implements FormRendererEngineInterface, Re * @param array $defaultThemes The default themes. The type of these * themes is open to the implementation. */ - public function __construct(array $defaultThemes = []) - { - $this->defaultThemes = $defaultThemes; + public function __construct( + protected array $defaultThemes = [], + ) { } public function setTheme(FormView $view, mixed $themes, bool $useDefaultThemes = true): void diff --git a/src/Symfony/Component/Form/Button.php b/src/Symfony/Component/Form/Button.php index 67772ccfe1c6b..f172a7ee31dec 100644 --- a/src/Symfony/Component/Form/Button.php +++ b/src/Symfony/Component/Form/Button.php @@ -26,15 +26,14 @@ class Button implements \IteratorAggregate, FormInterface { private ?FormInterface $parent = null; - private FormConfigInterface $config; private bool $submitted = false; /** * Creates a new button from a form configuration. */ - public function __construct(FormConfigInterface $config) - { - $this->config = $config; + public function __construct( + private FormConfigInterface $config, + ) { } /** @@ -104,7 +103,7 @@ public function getParent(): ?FormInterface * * @throws BadMethodCallException */ - public function add(string|FormInterface $child, string $type = null, array $options = []): static + public function add(string|FormInterface $child, ?string $type = null, array $options = []): static { throw new BadMethodCallException('Buttons cannot have children.'); } @@ -335,7 +334,7 @@ public function isRoot(): bool return null === $this->parent; } - public function createView(FormView $parent = null): FormView + public function createView(?FormView $parent = null): FormView { if (null === $parent && $this->parent) { $parent = $this->parent->createView(); diff --git a/src/Symfony/Component/Form/ButtonBuilder.php b/src/Symfony/Component/Form/ButtonBuilder.php index e640149bb86e7..4f7079142ef1a 100644 --- a/src/Symfony/Component/Form/ButtonBuilder.php +++ b/src/Symfony/Component/Form/ButtonBuilder.php @@ -31,19 +31,19 @@ class ButtonBuilder implements \IteratorAggregate, FormBuilderInterface private ResolvedFormTypeInterface $type; private string $name; private array $attributes = []; - private array $options; /** * @throws InvalidArgumentException if the name is empty */ - public function __construct(?string $name, array $options = []) - { + public function __construct( + ?string $name, + private array $options = [], + ) { if ('' === $name || null === $name) { throw new InvalidArgumentException('Buttons cannot have empty names.'); } $this->name = $name; - $this->options = $options; FormConfigBuilder::validateName($name); } @@ -53,7 +53,7 @@ public function __construct(?string $name, array $options = []) * * @throws BadMethodCallException */ - public function add(string|FormBuilderInterface $child, string $type = null, array $options = []): never + public function add(string|FormBuilderInterface $child, ?string $type = null, array $options = []): never { throw new BadMethodCallException('Buttons cannot have children.'); } @@ -63,7 +63,7 @@ public function add(string|FormBuilderInterface $child, string $type = null, arr * * @throws BadMethodCallException */ - public function create(string $name, string $type = null, array $options = []): never + public function create(string $name, ?string $type = null, array $options = []): never { throw new BadMethodCallException('Buttons cannot have children.'); } diff --git a/src/Symfony/Component/Form/ChoiceList/ArrayChoiceList.php b/src/Symfony/Component/Form/ChoiceList/ArrayChoiceList.php index 5f0d7dc36e2bc..9ac4fd6e5286f 100644 --- a/src/Symfony/Component/Form/ChoiceList/ArrayChoiceList.php +++ b/src/Symfony/Component/Form/ChoiceList/ArrayChoiceList.php @@ -48,7 +48,7 @@ class ArrayChoiceList implements ChoiceListInterface * incrementing integers are used as * values */ - public function __construct(iterable $choices, callable $value = null) + public function __construct(iterable $choices, ?callable $value = null) { if ($choices instanceof \Traversable) { $choices = iterator_to_array($choices); diff --git a/src/Symfony/Component/Form/ChoiceList/Factory/Cache/ChoiceLoader.php b/src/Symfony/Component/Form/ChoiceList/Factory/Cache/ChoiceLoader.php index 70c3f77e869e7..1d64f101c0b2c 100644 --- a/src/Symfony/Component/Form/ChoiceList/Factory/Cache/ChoiceLoader.php +++ b/src/Symfony/Component/Form/ChoiceList/Factory/Cache/ChoiceLoader.php @@ -26,17 +26,17 @@ */ final class ChoiceLoader extends AbstractStaticOption implements ChoiceLoaderInterface { - public function loadChoiceList(callable $value = null): ChoiceListInterface + public function loadChoiceList(?callable $value = null): ChoiceListInterface { return $this->getOption()->loadChoiceList($value); } - public function loadChoicesForValues(array $values, callable $value = null): array + public function loadChoicesForValues(array $values, ?callable $value = null): array { return $this->getOption()->loadChoicesForValues($values, $value); } - public function loadValuesForChoices(array $choices, callable $value = null): array + public function loadValuesForChoices(array $choices, ?callable $value = null): array { return $this->getOption()->loadValuesForChoices($choices, $value); } diff --git a/src/Symfony/Component/Form/ChoiceList/Factory/ChoiceListFactoryInterface.php b/src/Symfony/Component/Form/ChoiceList/Factory/ChoiceListFactoryInterface.php index 7c94ad809ee2d..a9ca6db20e569 100644 --- a/src/Symfony/Component/Form/ChoiceList/Factory/ChoiceListFactoryInterface.php +++ b/src/Symfony/Component/Form/ChoiceList/Factory/ChoiceListFactoryInterface.php @@ -33,7 +33,7 @@ interface ChoiceListFactoryInterface * * @param callable|null $filter The callable filtering the choices */ - public function createListFromChoices(iterable $choices, callable $value = null, callable $filter = null): ChoiceListInterface; + public function createListFromChoices(iterable $choices, ?callable $value = null, ?callable $filter = null): ChoiceListInterface; /** * Creates a choice list that is loaded with the given loader. @@ -44,7 +44,7 @@ public function createListFromChoices(iterable $choices, callable $value = null, * * @param callable|null $filter The callable filtering the choices */ - public function createListFromLoader(ChoiceLoaderInterface $loader, callable $value = null, callable $filter = null): ChoiceListInterface; + public function createListFromLoader(ChoiceLoaderInterface $loader, ?callable $value = null, ?callable $filter = null): ChoiceListInterface; /** * Creates a view for the given choice list. @@ -81,5 +81,5 @@ public function createListFromLoader(ChoiceLoaderInterface $loader, callable $va * on top of the list and in their original position * or only in the top of the list */ - public function createView(ChoiceListInterface $list, array|callable $preferredChoices = null, callable|false $label = null, callable $index = null, callable $groupBy = null, array|callable $attr = null, array|callable $labelTranslationParameters = [], bool $duplicatePreferredChoices = true): ChoiceListView; + public function createView(ChoiceListInterface $list, array|callable|null $preferredChoices = null, callable|false|null $label = null, ?callable $index = null, ?callable $groupBy = null, array|callable|null $attr = null, array|callable $labelTranslationParameters = [], bool $duplicatePreferredChoices = true): ChoiceListView; } diff --git a/src/Symfony/Component/Form/ChoiceList/Factory/DefaultChoiceListFactory.php b/src/Symfony/Component/Form/ChoiceList/Factory/DefaultChoiceListFactory.php index abac8eae311f1..586ac822a8005 100644 --- a/src/Symfony/Component/Form/ChoiceList/Factory/DefaultChoiceListFactory.php +++ b/src/Symfony/Component/Form/ChoiceList/Factory/DefaultChoiceListFactory.php @@ -30,7 +30,7 @@ */ class DefaultChoiceListFactory implements ChoiceListFactoryInterface { - public function createListFromChoices(iterable $choices, callable $value = null, callable $filter = null): ChoiceListInterface + public function createListFromChoices(iterable $choices, ?callable $value = null, ?callable $filter = null): ChoiceListInterface { if ($filter) { // filter the choice list lazily @@ -43,7 +43,7 @@ public function createListFromChoices(iterable $choices, callable $value = null, return new ArrayChoiceList($choices, $value); } - public function createListFromLoader(ChoiceLoaderInterface $loader, callable $value = null, callable $filter = null): ChoiceListInterface + public function createListFromLoader(ChoiceLoaderInterface $loader, ?callable $value = null, ?callable $filter = null): ChoiceListInterface { if ($filter) { $loader = new FilterChoiceLoaderDecorator($loader, $filter); @@ -52,7 +52,7 @@ public function createListFromLoader(ChoiceLoaderInterface $loader, callable $va return new LazyChoiceList($loader, $value); } - public function createView(ChoiceListInterface $list, array|callable $preferredChoices = null, callable|false $label = null, callable $index = null, callable $groupBy = null, array|callable $attr = null, array|callable $labelTranslationParameters = [], bool $duplicatePreferredChoices = true): ChoiceListView + public function createView(ChoiceListInterface $list, array|callable|null $preferredChoices = null, callable|false|null $label = null, ?callable $index = null, ?callable $groupBy = null, array|callable|null $attr = null, array|callable $labelTranslationParameters = [], bool $duplicatePreferredChoices = true): ChoiceListView { $preferredViews = []; $preferredViewsOrder = []; diff --git a/src/Symfony/Component/Form/ChoiceList/Factory/PropertyAccessDecorator.php b/src/Symfony/Component/Form/ChoiceList/Factory/PropertyAccessDecorator.php index 91460774f37b0..c83ef17e9f434 100644 --- a/src/Symfony/Component/Form/ChoiceList/Factory/PropertyAccessDecorator.php +++ b/src/Symfony/Component/Form/ChoiceList/Factory/PropertyAccessDecorator.php @@ -41,7 +41,7 @@ class PropertyAccessDecorator implements ChoiceListFactoryInterface private ChoiceListFactoryInterface $decoratedFactory; private PropertyAccessorInterface $propertyAccessor; - public function __construct(ChoiceListFactoryInterface $decoratedFactory, PropertyAccessorInterface $propertyAccessor = null) + public function __construct(ChoiceListFactoryInterface $decoratedFactory, ?PropertyAccessorInterface $propertyAccessor = null) { $this->decoratedFactory = $decoratedFactory; $this->propertyAccessor = $propertyAccessor ?: PropertyAccess::createPropertyAccessor(); diff --git a/src/Symfony/Component/Form/ChoiceList/LazyChoiceList.php b/src/Symfony/Component/Form/ChoiceList/LazyChoiceList.php index b34d3708ab069..8e0eaaa8fc4ec 100644 --- a/src/Symfony/Component/Form/ChoiceList/LazyChoiceList.php +++ b/src/Symfony/Component/Form/ChoiceList/LazyChoiceList.php @@ -27,8 +27,6 @@ */ class LazyChoiceList implements ChoiceListInterface { - private ChoiceLoaderInterface $loader; - /** * The callable creating string values for each choice. * @@ -43,11 +41,13 @@ class LazyChoiceList implements ChoiceListInterface * The callable receives the choice as first and the array key as the second * argument. * - * @param callable|null $value The callable generating the choice values + * @param callable|null $value The callable creating string values for each choice. + * If null, choices are cast to strings. */ - public function __construct(ChoiceLoaderInterface $loader, callable $value = null) - { - $this->loader = $loader; + public function __construct( + private ChoiceLoaderInterface $loader, + ?callable $value = null, + ) { $this->value = null === $value ? null : $value(...); } diff --git a/src/Symfony/Component/Form/ChoiceList/Loader/AbstractChoiceLoader.php b/src/Symfony/Component/Form/ChoiceList/Loader/AbstractChoiceLoader.php index c585a08a9fb47..749e2fbcef161 100644 --- a/src/Symfony/Component/Form/ChoiceList/Loader/AbstractChoiceLoader.php +++ b/src/Symfony/Component/Form/ChoiceList/Loader/AbstractChoiceLoader.php @@ -24,12 +24,12 @@ abstract class AbstractChoiceLoader implements ChoiceLoaderInterface /** * @final */ - public function loadChoiceList(callable $value = null): ChoiceListInterface + public function loadChoiceList(?callable $value = null): ChoiceListInterface { return new ArrayChoiceList($this->choices ??= $this->loadChoices(), $value); } - public function loadChoicesForValues(array $values, callable $value = null): array + public function loadChoicesForValues(array $values, ?callable $value = null): array { if (!$values) { return []; @@ -38,7 +38,7 @@ public function loadChoicesForValues(array $values, callable $value = null): arr return $this->doLoadChoicesForValues($values, $value); } - public function loadValuesForChoices(array $choices, callable $value = null): array + public function loadValuesForChoices(array $choices, ?callable $value = null): array { if (!$choices) { return []; diff --git a/src/Symfony/Component/Form/ChoiceList/Loader/ChoiceLoaderInterface.php b/src/Symfony/Component/Form/ChoiceList/Loader/ChoiceLoaderInterface.php index 85cc4bddaac98..d5f803c778629 100644 --- a/src/Symfony/Component/Form/ChoiceList/Loader/ChoiceLoaderInterface.php +++ b/src/Symfony/Component/Form/ChoiceList/Loader/ChoiceLoaderInterface.php @@ -34,7 +34,7 @@ interface ChoiceLoaderInterface * @param callable|null $value The callable which generates the values * from choices */ - public function loadChoiceList(callable $value = null): ChoiceListInterface; + public function loadChoiceList(?callable $value = null): ChoiceListInterface; /** * Loads the choices corresponding to the given values. @@ -50,7 +50,7 @@ public function loadChoiceList(callable $value = null): ChoiceListInterface; * values in this array are ignored * @param callable|null $value The callable generating the choice values */ - public function loadChoicesForValues(array $values, callable $value = null): array; + public function loadChoicesForValues(array $values, ?callable $value = null): array; /** * Loads the values corresponding to the given choices. @@ -68,5 +68,5 @@ public function loadChoicesForValues(array $values, callable $value = null): arr * * @return string[] */ - public function loadValuesForChoices(array $choices, callable $value = null): array; + public function loadValuesForChoices(array $choices, ?callable $value = null): array; } diff --git a/src/Symfony/Component/Form/ChoiceList/Loader/FilterChoiceLoaderDecorator.php b/src/Symfony/Component/Form/ChoiceList/Loader/FilterChoiceLoaderDecorator.php index 069941c1e2234..393c73eba8fc1 100644 --- a/src/Symfony/Component/Form/ChoiceList/Loader/FilterChoiceLoaderDecorator.php +++ b/src/Symfony/Component/Form/ChoiceList/Loader/FilterChoiceLoaderDecorator.php @@ -52,12 +52,12 @@ protected function loadChoices(): iterable return $choices ?? []; } - public function loadChoicesForValues(array $values, callable $value = null): array + public function loadChoicesForValues(array $values, ?callable $value = null): array { return array_filter($this->decoratedLoader->loadChoicesForValues($values, $value), $this->filter); } - public function loadValuesForChoices(array $choices, callable $value = null): array + public function loadValuesForChoices(array $choices, ?callable $value = null): array { return $this->decoratedLoader->loadValuesForChoices(array_filter($choices, $this->filter), $value); } diff --git a/src/Symfony/Component/Form/ChoiceList/Loader/IntlCallbackChoiceLoader.php b/src/Symfony/Component/Form/ChoiceList/Loader/IntlCallbackChoiceLoader.php index 448320f9d9fa4..0931d3ef56398 100644 --- a/src/Symfony/Component/Form/ChoiceList/Loader/IntlCallbackChoiceLoader.php +++ b/src/Symfony/Component/Form/ChoiceList/Loader/IntlCallbackChoiceLoader.php @@ -19,12 +19,12 @@ */ class IntlCallbackChoiceLoader extends CallbackChoiceLoader { - public function loadChoicesForValues(array $values, callable $value = null): array + public function loadChoicesForValues(array $values, ?callable $value = null): array { return parent::loadChoicesForValues(array_filter($values), $value); } - public function loadValuesForChoices(array $choices, callable $value = null): array + public function loadValuesForChoices(array $choices, ?callable $value = null): array { $choices = array_filter($choices); diff --git a/src/Symfony/Component/Form/ChoiceList/View/ChoiceGroupView.php b/src/Symfony/Component/Form/ChoiceList/View/ChoiceGroupView.php index 562515012c423..923fab2241069 100644 --- a/src/Symfony/Component/Form/ChoiceList/View/ChoiceGroupView.php +++ b/src/Symfony/Component/Form/ChoiceList/View/ChoiceGroupView.php @@ -20,18 +20,15 @@ */ class ChoiceGroupView implements \IteratorAggregate { - public string $label; - public array $choices; - /** * Creates a new choice group view. * * @param array $choices the choice views in the group */ - public function __construct(string $label, array $choices = []) - { - $this->label = $label; - $this->choices = $choices; + public function __construct( + public string $label, + public array $choices = [], + ) { } /** diff --git a/src/Symfony/Component/Form/ChoiceList/View/ChoiceListView.php b/src/Symfony/Component/Form/ChoiceList/View/ChoiceListView.php index 15afc4a8a5f6d..f64d10e761a43 100644 --- a/src/Symfony/Component/Form/ChoiceList/View/ChoiceListView.php +++ b/src/Symfony/Component/Form/ChoiceList/View/ChoiceListView.php @@ -22,19 +22,16 @@ */ class ChoiceListView { - public array $choices; - public array $preferredChoices; - /** * Creates a new choice list view. * * @param array $choices The choice views * @param array $preferredChoices the preferred choice views */ - public function __construct(array $choices = [], array $preferredChoices = []) - { - $this->choices = $choices; - $this->preferredChoices = $preferredChoices; + public function __construct( + public array $choices = [], + public array $preferredChoices = [], + ) { } /** diff --git a/src/Symfony/Component/Form/ChoiceList/View/ChoiceView.php b/src/Symfony/Component/Form/ChoiceList/View/ChoiceView.php index 52587fd363b2b..d59a7c947b465 100644 --- a/src/Symfony/Component/Form/ChoiceList/View/ChoiceView.php +++ b/src/Symfony/Component/Form/ChoiceList/View/ChoiceView.php @@ -20,20 +20,6 @@ */ class ChoiceView { - public string|TranslatableInterface|false $label; - public string $value; - public mixed $data; - - /** - * Additional attributes for the HTML tag. - */ - public array $attr; - - /** - * Additional parameters used to translate the label. - */ - public array $labelTranslationParameters; - /** * Creates a new choice view. * @@ -43,12 +29,12 @@ class ChoiceView * @param array $attr Additional attributes for the HTML tag * @param array $labelTranslationParameters Additional parameters used to translate the label */ - public function __construct(mixed $data, string $value, string|TranslatableInterface|false $label, array $attr = [], array $labelTranslationParameters = []) - { - $this->data = $data; - $this->value = $value; - $this->label = $label; - $this->attr = $attr; - $this->labelTranslationParameters = $labelTranslationParameters; + public function __construct( + public mixed $data, + public string $value, + public string|TranslatableInterface|false $label, + public array $attr = [], + public array $labelTranslationParameters = [], + ) { } } diff --git a/src/Symfony/Component/Form/Command/DebugCommand.php b/src/Symfony/Component/Form/Command/DebugCommand.php index a208be3e5bf54..4d990122981b0 100644 --- a/src/Symfony/Component/Form/Command/DebugCommand.php +++ b/src/Symfony/Component/Form/Command/DebugCommand.php @@ -35,23 +35,15 @@ #[AsCommand(name: 'debug:form', description: 'Display form type information')] class DebugCommand extends Command { - private FormRegistryInterface $formRegistry; - private array $namespaces; - private array $types; - private array $extensions; - private array $guessers; - private ?FileLinkFormatter $fileLinkFormatter; - - public function __construct(FormRegistryInterface $formRegistry, array $namespaces = ['Symfony\Component\Form\Extension\Core\Type'], array $types = [], array $extensions = [], array $guessers = [], FileLinkFormatter $fileLinkFormatter = null) - { + public function __construct( + private FormRegistryInterface $formRegistry, + private array $namespaces = ['Symfony\Component\Form\Extension\Core\Type'], + private array $types = [], + private array $extensions = [], + private array $guessers = [], + private ?FileLinkFormatter $fileLinkFormatter = null, + ) { parent::__construct(); - - $this->formRegistry = $formRegistry; - $this->namespaces = $namespaces; - $this->types = $types; - $this->extensions = $extensions; - $this->guessers = $guessers; - $this->fileLinkFormatter = $fileLinkFormatter; } protected function configure(): void diff --git a/src/Symfony/Component/Form/Console/Descriptor/TextDescriptor.php b/src/Symfony/Component/Form/Console/Descriptor/TextDescriptor.php index 14003ab2767f4..7b723a0af6ecd 100644 --- a/src/Symfony/Component/Form/Console/Descriptor/TextDescriptor.php +++ b/src/Symfony/Component/Form/Console/Descriptor/TextDescriptor.php @@ -188,7 +188,7 @@ private function normalizeAndSortOptionsColumns(array $options): array return $options; } - private function formatClassLink(string $class, string $text = null): string + private function formatClassLink(string $class, ?string $text = null): string { $text ??= $class; diff --git a/src/Symfony/Component/Form/Console/Helper/DescriptorHelper.php b/src/Symfony/Component/Form/Console/Helper/DescriptorHelper.php index 2f4b271636053..776b9eaa1ceb3 100644 --- a/src/Symfony/Component/Form/Console/Helper/DescriptorHelper.php +++ b/src/Symfony/Component/Form/Console/Helper/DescriptorHelper.php @@ -23,7 +23,7 @@ */ class DescriptorHelper extends BaseDescriptorHelper { - public function __construct(FileLinkFormatter $fileLinkFormatter = null) + public function __construct(?FileLinkFormatter $fileLinkFormatter = null) { $this ->register('txt', new TextDescriptor($fileLinkFormatter)) diff --git a/src/Symfony/Component/Form/Exception/TransformationFailedException.php b/src/Symfony/Component/Form/Exception/TransformationFailedException.php index ceb01f1a9a1b1..3973d70b468ad 100644 --- a/src/Symfony/Component/Form/Exception/TransformationFailedException.php +++ b/src/Symfony/Component/Form/Exception/TransformationFailedException.php @@ -21,7 +21,7 @@ class TransformationFailedException extends RuntimeException private ?string $invalidMessage; private array $invalidMessageParameters; - public function __construct(string $message = '', int $code = 0, \Throwable $previous = null, string $invalidMessage = null, array $invalidMessageParameters = []) + public function __construct(string $message = '', int $code = 0, ?\Throwable $previous = null, ?string $invalidMessage = null, array $invalidMessageParameters = []) { parent::__construct($message, $code, $previous); diff --git a/src/Symfony/Component/Form/Extension/Core/CoreExtension.php b/src/Symfony/Component/Form/Extension/Core/CoreExtension.php index 951bf345c0c42..1640ed05246ac 100644 --- a/src/Symfony/Component/Form/Extension/Core/CoreExtension.php +++ b/src/Symfony/Component/Form/Extension/Core/CoreExtension.php @@ -30,13 +30,14 @@ class CoreExtension extends AbstractExtension { private PropertyAccessorInterface $propertyAccessor; private ChoiceListFactoryInterface $choiceListFactory; - private ?TranslatorInterface $translator; - public function __construct(PropertyAccessorInterface $propertyAccessor = null, ChoiceListFactoryInterface $choiceListFactory = null, TranslatorInterface $translator = null) - { + public function __construct( + ?PropertyAccessorInterface $propertyAccessor = null, + ?ChoiceListFactoryInterface $choiceListFactory = null, + private ?TranslatorInterface $translator = null, + ) { $this->propertyAccessor = $propertyAccessor ?: PropertyAccess::createPropertyAccessor(); $this->choiceListFactory = $choiceListFactory ?? new CachingFactoryDecorator(new PropertyAccessDecorator(new DefaultChoiceListFactory(), $this->propertyAccessor)); - $this->translator = $translator; } protected function loadTypes(): array diff --git a/src/Symfony/Component/Form/Extension/Core/DataAccessor/ChainAccessor.php b/src/Symfony/Component/Form/Extension/Core/DataAccessor/ChainAccessor.php index ac600f16f0d1c..e7b9da09ea5a8 100644 --- a/src/Symfony/Component/Form/Extension/Core/DataAccessor/ChainAccessor.php +++ b/src/Symfony/Component/Form/Extension/Core/DataAccessor/ChainAccessor.php @@ -20,14 +20,12 @@ */ class ChainAccessor implements DataAccessorInterface { - private iterable $accessors; - /** * @param DataAccessorInterface[]|iterable $accessors */ - public function __construct(iterable $accessors) - { - $this->accessors = $accessors; + public function __construct( + private iterable $accessors, + ) { } public function getValue(object|array $data, FormInterface $form): mixed diff --git a/src/Symfony/Component/Form/Extension/Core/DataAccessor/PropertyPathAccessor.php b/src/Symfony/Component/Form/Extension/Core/DataAccessor/PropertyPathAccessor.php index e06f583cbd5a3..c845c10f6d89a 100644 --- a/src/Symfony/Component/Form/Extension/Core/DataAccessor/PropertyPathAccessor.php +++ b/src/Symfony/Component/Form/Extension/Core/DataAccessor/PropertyPathAccessor.php @@ -31,7 +31,7 @@ class PropertyPathAccessor implements DataAccessorInterface { private PropertyAccessorInterface $propertyAccessor; - public function __construct(PropertyAccessorInterface $propertyAccessor = null) + public function __construct(?PropertyAccessorInterface $propertyAccessor = null) { $this->propertyAccessor = $propertyAccessor ?? PropertyAccess::createPropertyAccessor(); } diff --git a/src/Symfony/Component/Form/Extension/Core/DataMapper/DataMapper.php b/src/Symfony/Component/Form/Extension/Core/DataMapper/DataMapper.php index 0404af0844661..f27f9d14bff98 100644 --- a/src/Symfony/Component/Form/Extension/Core/DataMapper/DataMapper.php +++ b/src/Symfony/Component/Form/Extension/Core/DataMapper/DataMapper.php @@ -27,7 +27,7 @@ class DataMapper implements DataMapperInterface { private DataAccessorInterface $dataAccessor; - public function __construct(DataAccessorInterface $dataAccessor = null) + public function __construct(?DataAccessorInterface $dataAccessor = null) { $this->dataAccessor = $dataAccessor ?? new ChainAccessor([ new CallbackAccessor(), diff --git a/src/Symfony/Component/Form/Extension/Core/DataTransformer/ArrayToPartsTransformer.php b/src/Symfony/Component/Form/Extension/Core/DataTransformer/ArrayToPartsTransformer.php index 9256c0a0948a7..828bd811e5d64 100644 --- a/src/Symfony/Component/Form/Extension/Core/DataTransformer/ArrayToPartsTransformer.php +++ b/src/Symfony/Component/Form/Extension/Core/DataTransformer/ArrayToPartsTransformer.php @@ -21,11 +21,9 @@ */ class ArrayToPartsTransformer implements DataTransformerInterface { - private array $partMapping; - - public function __construct(array $partMapping) - { - $this->partMapping = $partMapping; + public function __construct( + private array $partMapping, + ) { } public function transform(mixed $array): mixed diff --git a/src/Symfony/Component/Form/Extension/Core/DataTransformer/BaseDateTimeTransformer.php b/src/Symfony/Component/Form/Extension/Core/DataTransformer/BaseDateTimeTransformer.php index d2a897471495b..8d311b3f8fa10 100644 --- a/src/Symfony/Component/Form/Extension/Core/DataTransformer/BaseDateTimeTransformer.php +++ b/src/Symfony/Component/Form/Extension/Core/DataTransformer/BaseDateTimeTransformer.php @@ -38,7 +38,7 @@ abstract class BaseDateTimeTransformer implements DataTransformerInterface * * @throws InvalidArgumentException if a timezone is not valid */ - public function __construct(string $inputTimezone = null, string $outputTimezone = null) + public function __construct(?string $inputTimezone = null, ?string $outputTimezone = null) { $this->inputTimezone = $inputTimezone ?: date_default_timezone_get(); $this->outputTimezone = $outputTimezone ?: date_default_timezone_get(); diff --git a/src/Symfony/Component/Form/Extension/Core/DataTransformer/ChoiceToValueTransformer.php b/src/Symfony/Component/Form/Extension/Core/DataTransformer/ChoiceToValueTransformer.php index daec9d71953a6..52ee25e93ebfd 100644 --- a/src/Symfony/Component/Form/Extension/Core/DataTransformer/ChoiceToValueTransformer.php +++ b/src/Symfony/Component/Form/Extension/Core/DataTransformer/ChoiceToValueTransformer.php @@ -22,11 +22,9 @@ */ class ChoiceToValueTransformer implements DataTransformerInterface { - private ChoiceListInterface $choiceList; - - public function __construct(ChoiceListInterface $choiceList) - { - $this->choiceList = $choiceList; + public function __construct( + private ChoiceListInterface $choiceList, + ) { } public function transform(mixed $choice): mixed diff --git a/src/Symfony/Component/Form/Extension/Core/DataTransformer/ChoicesToValuesTransformer.php b/src/Symfony/Component/Form/Extension/Core/DataTransformer/ChoicesToValuesTransformer.php index f284ff34f9234..aa223385494ba 100644 --- a/src/Symfony/Component/Form/Extension/Core/DataTransformer/ChoicesToValuesTransformer.php +++ b/src/Symfony/Component/Form/Extension/Core/DataTransformer/ChoicesToValuesTransformer.php @@ -22,11 +22,9 @@ */ class ChoicesToValuesTransformer implements DataTransformerInterface { - private ChoiceListInterface $choiceList; - - public function __construct(ChoiceListInterface $choiceList) - { - $this->choiceList = $choiceList; + public function __construct( + private ChoiceListInterface $choiceList, + ) { } /** diff --git a/src/Symfony/Component/Form/Extension/Core/DataTransformer/DataTransformerChain.php b/src/Symfony/Component/Form/Extension/Core/DataTransformer/DataTransformerChain.php index ec5def469520a..e34be745565cc 100644 --- a/src/Symfony/Component/Form/Extension/Core/DataTransformer/DataTransformerChain.php +++ b/src/Symfony/Component/Form/Extension/Core/DataTransformer/DataTransformerChain.php @@ -21,16 +21,14 @@ */ class DataTransformerChain implements DataTransformerInterface { - protected array $transformers; - /** * Uses the given value transformers to transform values. * * @param DataTransformerInterface[] $transformers */ - public function __construct(array $transformers) - { - $this->transformers = $transformers; + public function __construct( + protected array $transformers, + ) { } /** diff --git a/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateIntervalToArrayTransformer.php b/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateIntervalToArrayTransformer.php index 8638e4a84235e..08c05859798b2 100644 --- a/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateIntervalToArrayTransformer.php +++ b/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateIntervalToArrayTransformer.php @@ -42,16 +42,16 @@ class DateIntervalToArrayTransformer implements DataTransformerInterface self::INVERT => 'r', ]; private array $fields; - private bool $pad; /** * @param string[]|null $fields The date fields * @param bool $pad Whether to use padding */ - public function __construct(array $fields = null, bool $pad = false) - { + public function __construct( + ?array $fields = null, + private bool $pad = false, + ) { $this->fields = $fields ?? ['years', 'months', 'days', 'hours', 'minutes', 'seconds', 'invert']; - $this->pad = $pad; } /** diff --git a/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateIntervalToStringTransformer.php b/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateIntervalToStringTransformer.php index 4160f8f34c06e..e1a3d97246227 100644 --- a/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateIntervalToStringTransformer.php +++ b/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateIntervalToStringTransformer.php @@ -24,8 +24,6 @@ */ class DateIntervalToStringTransformer implements DataTransformerInterface { - private string $format; - /** * Transforms a \DateInterval instance to a string. * @@ -33,9 +31,9 @@ class DateIntervalToStringTransformer implements DataTransformerInterface * * @param string $format The date format */ - public function __construct(string $format = 'P%yY%mM%dDT%hH%iM%sS') - { - $this->format = $format; + public function __construct( + private string $format = 'P%yY%mM%dDT%hH%iM%sS', + ) { } /** diff --git a/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToArrayTransformer.php b/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToArrayTransformer.php index 6675d1c24a590..8c3d2d2266aa8 100644 --- a/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToArrayTransformer.php +++ b/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToArrayTransformer.php @@ -23,7 +23,6 @@ */ class DateTimeToArrayTransformer extends BaseDateTimeTransformer { - private bool $pad; private array $fields; private \DateTimeInterface $referenceDate; @@ -33,12 +32,16 @@ class DateTimeToArrayTransformer extends BaseDateTimeTransformer * @param string[]|null $fields The date fields * @param bool $pad Whether to use padding */ - public function __construct(string $inputTimezone = null, string $outputTimezone = null, array $fields = null, bool $pad = false, \DateTimeInterface $referenceDate = null) - { + public function __construct( + ?string $inputTimezone = null, + ?string $outputTimezone = null, + ?array $fields = null, + private bool $pad = false, + ?\DateTimeInterface $referenceDate = null, + ) { parent::__construct($inputTimezone, $outputTimezone); $this->fields = $fields ?? ['year', 'month', 'day', 'hour', 'minute', 'second']; - $this->pad = $pad; $this->referenceDate = $referenceDate ?? new \DateTimeImmutable('1970-01-01 00:00:00'); } diff --git a/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToHtml5LocalDateTimeTransformer.php b/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToHtml5LocalDateTimeTransformer.php index 2dc157cd83e9e..855b22a499ce2 100644 --- a/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToHtml5LocalDateTimeTransformer.php +++ b/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToHtml5LocalDateTimeTransformer.php @@ -25,7 +25,7 @@ class DateTimeToHtml5LocalDateTimeTransformer extends BaseDateTimeTransformer public const HTML5_FORMAT = 'Y-m-d\\TH:i:s'; public const HTML5_FORMAT_NO_SECONDS = 'Y-m-d\\TH:i'; - public function __construct(string $inputTimezone = null, string $outputTimezone = null, private bool $withSeconds = false) + public function __construct(?string $inputTimezone = null, ?string $outputTimezone = null, private bool $withSeconds = false) { parent::__construct($inputTimezone, $outputTimezone); } diff --git a/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToLocalizedStringTransformer.php b/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToLocalizedStringTransformer.php index 22a5d41b5f88b..b7ea092591d51 100644 --- a/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToLocalizedStringTransformer.php +++ b/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToLocalizedStringTransformer.php @@ -26,8 +26,6 @@ class DateTimeToLocalizedStringTransformer extends BaseDateTimeTransformer { private int $dateFormat; private int $timeFormat; - private ?string $pattern; - private int $calendar; /** * @see BaseDateTimeTransformer::formats for available format options @@ -41,8 +39,14 @@ class DateTimeToLocalizedStringTransformer extends BaseDateTimeTransformer * * @throws UnexpectedTypeException If a format is not supported or if a timezone is not a string */ - public function __construct(string $inputTimezone = null, string $outputTimezone = null, int $dateFormat = null, int $timeFormat = null, int $calendar = \IntlDateFormatter::GREGORIAN, string $pattern = null) - { + public function __construct( + ?string $inputTimezone = null, + ?string $outputTimezone = null, + ?int $dateFormat = null, + ?int $timeFormat = null, + private int $calendar = \IntlDateFormatter::GREGORIAN, + private ?string $pattern = null, + ) { parent::__construct($inputTimezone, $outputTimezone); $dateFormat ??= \IntlDateFormatter::MEDIUM; @@ -58,8 +62,6 @@ public function __construct(string $inputTimezone = null, string $outputTimezone $this->dateFormat = $dateFormat; $this->timeFormat = $timeFormat; - $this->calendar = $calendar; - $this->pattern = $pattern; } /** diff --git a/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToStringTransformer.php b/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToStringTransformer.php index ca0d2e59db120..77b1e75bd49a5 100644 --- a/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToStringTransformer.php +++ b/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToStringTransformer.php @@ -47,7 +47,7 @@ class DateTimeToStringTransformer extends BaseDateTimeTransformer * @param string $format The date format * @param string|null $parseFormat The parse format when different from $format */ - public function __construct(string $inputTimezone = null, string $outputTimezone = null, string $format = 'Y-m-d H:i:s', string $parseFormat = null) + public function __construct(?string $inputTimezone = null, ?string $outputTimezone = null, string $format = 'Y-m-d H:i:s', ?string $parseFormat = null) { parent::__construct($inputTimezone, $outputTimezone); diff --git a/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeZoneToStringTransformer.php b/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeZoneToStringTransformer.php index f7bda175118ba..50767f7a01a5b 100644 --- a/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeZoneToStringTransformer.php +++ b/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeZoneToStringTransformer.php @@ -23,11 +23,9 @@ */ class DateTimeZoneToStringTransformer implements DataTransformerInterface { - private bool $multiple; - - public function __construct(bool $multiple = false) - { - $this->multiple = $multiple; + public function __construct( + private bool $multiple = false, + ) { } public function transform(mixed $dateTimeZone): mixed diff --git a/src/Symfony/Component/Form/Extension/Core/DataTransformer/IntegerToLocalizedStringTransformer.php b/src/Symfony/Component/Form/Extension/Core/DataTransformer/IntegerToLocalizedStringTransformer.php index ee41efc47e596..eb5a2d6ff18e6 100644 --- a/src/Symfony/Component/Form/Extension/Core/DataTransformer/IntegerToLocalizedStringTransformer.php +++ b/src/Symfony/Component/Form/Extension/Core/DataTransformer/IntegerToLocalizedStringTransformer.php @@ -28,7 +28,7 @@ class IntegerToLocalizedStringTransformer extends NumberToLocalizedStringTransfo * @param int|null $roundingMode One of the ROUND_ constants in this class * @param string|null $locale locale used for transforming */ - public function __construct(?bool $grouping = false, ?int $roundingMode = \NumberFormatter::ROUND_DOWN, string $locale = null) + public function __construct(?bool $grouping = false, ?int $roundingMode = \NumberFormatter::ROUND_DOWN, ?string $locale = null) { parent::__construct(0, $grouping, $roundingMode, $locale); } diff --git a/src/Symfony/Component/Form/Extension/Core/DataTransformer/IntlTimeZoneToStringTransformer.php b/src/Symfony/Component/Form/Extension/Core/DataTransformer/IntlTimeZoneToStringTransformer.php index d379164a7a9af..446a95f7082bb 100644 --- a/src/Symfony/Component/Form/Extension/Core/DataTransformer/IntlTimeZoneToStringTransformer.php +++ b/src/Symfony/Component/Form/Extension/Core/DataTransformer/IntlTimeZoneToStringTransformer.php @@ -23,11 +23,9 @@ */ class IntlTimeZoneToStringTransformer implements DataTransformerInterface { - private bool $multiple; - - public function __construct(bool $multiple = false) - { - $this->multiple = $multiple; + public function __construct( + private bool $multiple = false, + ) { } public function transform(mixed $intlTimeZone): mixed diff --git a/src/Symfony/Component/Form/Extension/Core/DataTransformer/MoneyToLocalizedStringTransformer.php b/src/Symfony/Component/Form/Extension/Core/DataTransformer/MoneyToLocalizedStringTransformer.php index 9a768e8979149..d9a75a2604c16 100644 --- a/src/Symfony/Component/Form/Extension/Core/DataTransformer/MoneyToLocalizedStringTransformer.php +++ b/src/Symfony/Component/Form/Extension/Core/DataTransformer/MoneyToLocalizedStringTransformer.php @@ -22,14 +22,18 @@ class MoneyToLocalizedStringTransformer extends NumberToLocalizedStringTransformer { private int $divisor; - private string $modelType; - public function __construct(?int $scale = 2, ?bool $grouping = true, ?int $roundingMode = \NumberFormatter::ROUND_HALFUP, ?int $divisor = 1, string $locale = null, string $modelType = 'float') - { + public function __construct( + ?int $scale = 2, + ?bool $grouping = true, + ?int $roundingMode = \NumberFormatter::ROUND_HALFUP, + ?int $divisor = 1, + ?string $locale = null, + private string $modelType = 'float', + ) { parent::__construct($scale ?? 2, $grouping ?? true, $roundingMode, $locale); $this->divisor = $divisor ?? 1; - $this->modelType = $modelType; } /** diff --git a/src/Symfony/Component/Form/Extension/Core/DataTransformer/NumberToLocalizedStringTransformer.php b/src/Symfony/Component/Form/Extension/Core/DataTransformer/NumberToLocalizedStringTransformer.php index 0693e79797599..6e1c495aeeaef 100644 --- a/src/Symfony/Component/Form/Extension/Core/DataTransformer/NumberToLocalizedStringTransformer.php +++ b/src/Symfony/Component/Form/Extension/Core/DataTransformer/NumberToLocalizedStringTransformer.php @@ -28,15 +28,14 @@ class NumberToLocalizedStringTransformer implements DataTransformerInterface protected bool $grouping; protected int $roundingMode; - private ?int $scale; - private ?string $locale; - - public function __construct(int $scale = null, ?bool $grouping = false, ?int $roundingMode = \NumberFormatter::ROUND_HALFUP, string $locale = null) - { - $this->scale = $scale; + public function __construct( + private ?int $scale = null, + ?bool $grouping = false, + ?int $roundingMode = \NumberFormatter::ROUND_HALFUP, + private ?string $locale = null, + ) { $this->grouping = $grouping ?? false; $this->roundingMode = $roundingMode ?? \NumberFormatter::ROUND_HALFUP; - $this->locale = $locale; } /** diff --git a/src/Symfony/Component/Form/Extension/Core/DataTransformer/PercentToLocalizedStringTransformer.php b/src/Symfony/Component/Form/Extension/Core/DataTransformer/PercentToLocalizedStringTransformer.php index 98d62783c1c00..d4d8aad9d44ba 100644 --- a/src/Symfony/Component/Form/Extension/Core/DataTransformer/PercentToLocalizedStringTransformer.php +++ b/src/Symfony/Component/Form/Extension/Core/DataTransformer/PercentToLocalizedStringTransformer.php @@ -33,10 +33,8 @@ class PercentToLocalizedStringTransformer implements DataTransformerInterface self::INTEGER, ]; - private int $roundingMode; private string $type; private int $scale; - private bool $html5Format; /** * @see self::$types for a list of supported types @@ -46,8 +44,12 @@ class PercentToLocalizedStringTransformer implements DataTransformerInterface * * @throws UnexpectedTypeException if the given value of type is unknown */ - public function __construct(int $scale = null, string $type = null, int $roundingMode = \NumberFormatter::ROUND_HALFUP, bool $html5Format = false) - { + public function __construct( + ?int $scale = null, + ?string $type = null, + private int $roundingMode = \NumberFormatter::ROUND_HALFUP, + private bool $html5Format = false, + ) { $type ??= self::FRACTIONAL; if (!\in_array($type, self::$types, true)) { @@ -56,8 +58,6 @@ public function __construct(int $scale = null, string $type = null, int $roundin $this->type = $type; $this->scale = $scale ?? 0; - $this->roundingMode = $roundingMode; - $this->html5Format = $html5Format; } /** diff --git a/src/Symfony/Component/Form/Extension/Core/DataTransformer/StringToFloatTransformer.php b/src/Symfony/Component/Form/Extension/Core/DataTransformer/StringToFloatTransformer.php index 09b5e51faf786..f47fcb625487a 100644 --- a/src/Symfony/Component/Form/Extension/Core/DataTransformer/StringToFloatTransformer.php +++ b/src/Symfony/Component/Form/Extension/Core/DataTransformer/StringToFloatTransformer.php @@ -19,11 +19,9 @@ */ class StringToFloatTransformer implements DataTransformerInterface { - private ?int $scale; - - public function __construct(int $scale = null) - { - $this->scale = $scale; + public function __construct( + private ?int $scale = null, + ) { } public function transform(mixed $value): ?float diff --git a/src/Symfony/Component/Form/Extension/Core/DataTransformer/ValueToDuplicatesTransformer.php b/src/Symfony/Component/Form/Extension/Core/DataTransformer/ValueToDuplicatesTransformer.php index 2399abf73c7a3..9700b0424e269 100644 --- a/src/Symfony/Component/Form/Extension/Core/DataTransformer/ValueToDuplicatesTransformer.php +++ b/src/Symfony/Component/Form/Extension/Core/DataTransformer/ValueToDuplicatesTransformer.php @@ -21,11 +21,9 @@ */ class ValueToDuplicatesTransformer implements DataTransformerInterface { - private array $keys; - - public function __construct(array $keys) - { - $this->keys = $keys; + public function __construct( + private array $keys, + ) { } /** diff --git a/src/Symfony/Component/Form/Extension/Core/EventListener/FixUrlProtocolListener.php b/src/Symfony/Component/Form/Extension/Core/EventListener/FixUrlProtocolListener.php index d648d6f508d24..97cd3ad474669 100644 --- a/src/Symfony/Component/Form/Extension/Core/EventListener/FixUrlProtocolListener.php +++ b/src/Symfony/Component/Form/Extension/Core/EventListener/FixUrlProtocolListener.php @@ -22,14 +22,12 @@ */ class FixUrlProtocolListener implements EventSubscriberInterface { - private ?string $defaultProtocol; - /** * @param string|null $defaultProtocol The URL scheme to add when there is none or null to not modify the data */ - public function __construct(?string $defaultProtocol = 'http') - { - $this->defaultProtocol = $defaultProtocol; + public function __construct( + private ?string $defaultProtocol = 'http', + ) { } public function onSubmit(FormEvent $event): void diff --git a/src/Symfony/Component/Form/Extension/Core/EventListener/MergeCollectionListener.php b/src/Symfony/Component/Form/Extension/Core/EventListener/MergeCollectionListener.php index 23ee47bd2fadb..61428d5dfaa40 100644 --- a/src/Symfony/Component/Form/Extension/Core/EventListener/MergeCollectionListener.php +++ b/src/Symfony/Component/Form/Extension/Core/EventListener/MergeCollectionListener.php @@ -21,17 +21,14 @@ */ class MergeCollectionListener implements EventSubscriberInterface { - private bool $allowAdd; - private bool $allowDelete; - /** * @param bool $allowAdd Whether values might be added to the collection * @param bool $allowDelete Whether values might be removed from the collection */ - public function __construct(bool $allowAdd = false, bool $allowDelete = false) - { - $this->allowAdd = $allowAdd; - $this->allowDelete = $allowDelete; + public function __construct( + private bool $allowAdd = false, + private bool $allowDelete = false, + ) { } public static function getSubscribedEvents(): array diff --git a/src/Symfony/Component/Form/Extension/Core/EventListener/ResizeFormListener.php b/src/Symfony/Component/Form/Extension/Core/EventListener/ResizeFormListener.php index 641f16525770e..d67efab31e636 100644 --- a/src/Symfony/Component/Form/Extension/Core/EventListener/ResizeFormListener.php +++ b/src/Symfony/Component/Form/Extension/Core/EventListener/ResizeFormListener.php @@ -24,24 +24,21 @@ */ class ResizeFormListener implements EventSubscriberInterface { - protected string $type; - protected array $options; protected array $prototypeOptions; - protected bool $allowAdd; - protected bool $allowDelete; private \Closure|bool $deleteEmpty; - private bool $keepAsList; - public function __construct(string $type, array $options = [], bool $allowAdd = false, bool $allowDelete = false, bool|callable $deleteEmpty = false, array $prototypeOptions = null, bool $keepAsList = false) - { - $this->type = $type; - $this->allowAdd = $allowAdd; - $this->allowDelete = $allowDelete; - $this->options = $options; + public function __construct( + private string $type, + private array $options = [], + private bool $allowAdd = false, + private bool $allowDelete = false, + bool|callable $deleteEmpty = false, + ?array $prototypeOptions = null, + private bool $keepAsList = false, + ) { $this->deleteEmpty = \is_bool($deleteEmpty) ? $deleteEmpty : $deleteEmpty(...); $this->prototypeOptions = $prototypeOptions ?? $options; - $this->keepAsList = $keepAsList; } public static function getSubscribedEvents(): array diff --git a/src/Symfony/Component/Form/Extension/Core/EventListener/TransformationFailureListener.php b/src/Symfony/Component/Form/Extension/Core/EventListener/TransformationFailureListener.php index 570a285aae988..48f0b9ac9feed 100644 --- a/src/Symfony/Component/Form/Extension/Core/EventListener/TransformationFailureListener.php +++ b/src/Symfony/Component/Form/Extension/Core/EventListener/TransformationFailureListener.php @@ -22,11 +22,9 @@ */ class TransformationFailureListener implements EventSubscriberInterface { - private ?TranslatorInterface $translator; - - public function __construct(TranslatorInterface $translator = null) - { - $this->translator = $translator; + public function __construct( + private ?TranslatorInterface $translator = null, + ) { } public static function getSubscribedEvents(): array diff --git a/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php b/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php index 711dc5af4541a..2e9cb7087fb53 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php @@ -53,7 +53,7 @@ class ChoiceType extends AbstractType private ChoiceListFactoryInterface $choiceListFactory; private ?TranslatorInterface $translator; - public function __construct(ChoiceListFactoryInterface $choiceListFactory = null, TranslatorInterface $translator = null) + public function __construct(?ChoiceListFactoryInterface $choiceListFactory = null, ?TranslatorInterface $translator = null) { $this->choiceListFactory = $choiceListFactory ?? new CachingFactoryDecorator( new PropertyAccessDecorator( diff --git a/src/Symfony/Component/Form/Extension/Core/Type/ColorType.php b/src/Symfony/Component/Form/Extension/Core/Type/ColorType.php index 476050fbee987..d67d6dc73b914 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/ColorType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/ColorType.php @@ -26,11 +26,9 @@ class ColorType extends AbstractType */ private const HTML5_PATTERN = '/^#[0-9a-f]{6}$/i'; - private ?TranslatorInterface $translator; - - public function __construct(TranslatorInterface $translator = null) - { - $this->translator = $translator; + public function __construct( + private ?TranslatorInterface $translator = null, + ) { } public function buildForm(FormBuilderInterface $builder, array $options): void diff --git a/src/Symfony/Component/Form/Extension/Core/Type/FileType.php b/src/Symfony/Component/Form/Extension/Core/Type/FileType.php index 948f897f68aa6..b4b4d86d9515c 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/FileType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/FileType.php @@ -35,11 +35,9 @@ class FileType extends AbstractType self::MIB_BYTES => 'MiB', ]; - private ?TranslatorInterface $translator; - - public function __construct(TranslatorInterface $translator = null) - { - $this->translator = $translator; + public function __construct( + private ?TranslatorInterface $translator = null, + ) { } public function buildForm(FormBuilderInterface $builder, array $options): void diff --git a/src/Symfony/Component/Form/Extension/Core/Type/FormType.php b/src/Symfony/Component/Form/Extension/Core/Type/FormType.php index 889d0578669b3..9497bad365823 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/FormType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/FormType.php @@ -30,7 +30,7 @@ class FormType extends BaseType { private DataMapper $dataMapper; - public function __construct(PropertyAccessorInterface $propertyAccessor = null) + public function __construct(?PropertyAccessorInterface $propertyAccessor = null) { $this->dataMapper = new DataMapper(new ChainAccessor([ new CallbackAccessor(), diff --git a/src/Symfony/Component/Form/Extension/Core/Type/TimezoneType.php b/src/Symfony/Component/Form/Extension/Core/Type/TimezoneType.php index 4b64fd881c511..01ce68ce372f9 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/TimezoneType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/TimezoneType.php @@ -106,7 +106,7 @@ private static function getPhpTimezones(string $input): array return $timezones; } - private static function getIntlTimezones(string $input, string $locale = null): array + private static function getIntlTimezones(string $input, ?string $locale = null): array { $timezones = array_flip(Timezones::getNames($locale)); diff --git a/src/Symfony/Component/Form/Extension/Core/Type/TransformationFailureExtension.php b/src/Symfony/Component/Form/Extension/Core/Type/TransformationFailureExtension.php index 5539e57f9f65c..e90cd714e8665 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/TransformationFailureExtension.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/TransformationFailureExtension.php @@ -21,11 +21,9 @@ */ class TransformationFailureExtension extends AbstractTypeExtension { - private ?TranslatorInterface $translator; - - public function __construct(TranslatorInterface $translator = null) - { - $this->translator = $translator; + public function __construct( + private ?TranslatorInterface $translator = null, + ) { } public function buildForm(FormBuilderInterface $builder, array $options): void diff --git a/src/Symfony/Component/Form/Extension/Csrf/CsrfExtension.php b/src/Symfony/Component/Form/Extension/Csrf/CsrfExtension.php index 026bed3604464..33c4616b4cf62 100644 --- a/src/Symfony/Component/Form/Extension/Csrf/CsrfExtension.php +++ b/src/Symfony/Component/Form/Extension/Csrf/CsrfExtension.php @@ -22,15 +22,11 @@ */ class CsrfExtension extends AbstractExtension { - private CsrfTokenManagerInterface $tokenManager; - private ?TranslatorInterface $translator; - private ?string $translationDomain; - - public function __construct(CsrfTokenManagerInterface $tokenManager, TranslatorInterface $translator = null, string $translationDomain = null) - { - $this->tokenManager = $tokenManager; - $this->translator = $translator; - $this->translationDomain = $translationDomain; + public function __construct( + private CsrfTokenManagerInterface $tokenManager, + private ?TranslatorInterface $translator = null, + private ?string $translationDomain = null, + ) { } protected function loadTypeExtensions(): array diff --git a/src/Symfony/Component/Form/Extension/Csrf/EventListener/CsrfValidationListener.php b/src/Symfony/Component/Form/Extension/Csrf/EventListener/CsrfValidationListener.php index dab31fb65f6b0..5aac521c84d40 100644 --- a/src/Symfony/Component/Form/Extension/Csrf/EventListener/CsrfValidationListener.php +++ b/src/Symfony/Component/Form/Extension/Csrf/EventListener/CsrfValidationListener.php @@ -25,12 +25,6 @@ */ class CsrfValidationListener implements EventSubscriberInterface { - private string $fieldName; - private CsrfTokenManagerInterface $tokenManager; - private string $tokenId; - private string $errorMessage; - private ?TranslatorInterface $translator; - private ?string $translationDomain; private ServerParams $serverParams; public static function getSubscribedEvents(): array @@ -40,14 +34,16 @@ public static function getSubscribedEvents(): array ]; } - public function __construct(string $fieldName, CsrfTokenManagerInterface $tokenManager, string $tokenId, string $errorMessage, TranslatorInterface $translator = null, string $translationDomain = null, ServerParams $serverParams = null) + public function __construct( + private string $fieldName, + private CsrfTokenManagerInterface $tokenManager, + private string $tokenId, + private string $errorMessage, + private ?TranslatorInterface $translator = null, + private ?string $translationDomain = null, + ?ServerParams $serverParams = null, + ) { - $this->fieldName = $fieldName; - $this->tokenManager = $tokenManager; - $this->tokenId = $tokenId; - $this->errorMessage = $errorMessage; - $this->translator = $translator; - $this->translationDomain = $translationDomain; $this->serverParams = $serverParams ?? new ServerParams(); } diff --git a/src/Symfony/Component/Form/Extension/Csrf/Type/FormTypeCsrfExtension.php b/src/Symfony/Component/Form/Extension/Csrf/Type/FormTypeCsrfExtension.php index 7096b8957d84a..0ad4daeb3c108 100644 --- a/src/Symfony/Component/Form/Extension/Csrf/Type/FormTypeCsrfExtension.php +++ b/src/Symfony/Component/Form/Extension/Csrf/Type/FormTypeCsrfExtension.php @@ -28,21 +28,14 @@ */ class FormTypeCsrfExtension extends AbstractTypeExtension { - private CsrfTokenManagerInterface $defaultTokenManager; - private bool $defaultEnabled; - private string $defaultFieldName; - private ?TranslatorInterface $translator; - private ?string $translationDomain; - private ?ServerParams $serverParams; - - public function __construct(CsrfTokenManagerInterface $defaultTokenManager, bool $defaultEnabled = true, string $defaultFieldName = '_token', TranslatorInterface $translator = null, string $translationDomain = null, ServerParams $serverParams = null) - { - $this->defaultTokenManager = $defaultTokenManager; - $this->defaultEnabled = $defaultEnabled; - $this->defaultFieldName = $defaultFieldName; - $this->translator = $translator; - $this->translationDomain = $translationDomain; - $this->serverParams = $serverParams; + public function __construct( + private CsrfTokenManagerInterface $defaultTokenManager, + private bool $defaultEnabled = true, + private string $defaultFieldName = '_token', + private ?TranslatorInterface $translator = null, + private ?string $translationDomain = null, + private ?ServerParams $serverParams = null, + ) { } /** diff --git a/src/Symfony/Component/Form/Extension/DataCollector/DataCollectorExtension.php b/src/Symfony/Component/Form/Extension/DataCollector/DataCollectorExtension.php index 50b36bd67f073..9fb84224473b9 100644 --- a/src/Symfony/Component/Form/Extension/DataCollector/DataCollectorExtension.php +++ b/src/Symfony/Component/Form/Extension/DataCollector/DataCollectorExtension.php @@ -21,11 +21,9 @@ */ class DataCollectorExtension extends AbstractExtension { - private FormDataCollectorInterface $dataCollector; - - public function __construct(FormDataCollectorInterface $dataCollector) - { - $this->dataCollector = $dataCollector; + public function __construct( + private FormDataCollectorInterface $dataCollector, + ) { } protected function loadTypeExtensions(): array diff --git a/src/Symfony/Component/Form/Extension/DataCollector/EventListener/DataCollectorListener.php b/src/Symfony/Component/Form/Extension/DataCollector/EventListener/DataCollectorListener.php index f32dc9bc7d493..02cffbeffed7e 100644 --- a/src/Symfony/Component/Form/Extension/DataCollector/EventListener/DataCollectorListener.php +++ b/src/Symfony/Component/Form/Extension/DataCollector/EventListener/DataCollectorListener.php @@ -24,11 +24,9 @@ */ class DataCollectorListener implements EventSubscriberInterface { - private FormDataCollectorInterface $dataCollector; - - public function __construct(FormDataCollectorInterface $dataCollector) - { - $this->dataCollector = $dataCollector; + public function __construct( + private FormDataCollectorInterface $dataCollector, + ) { } public static function getSubscribedEvents(): array diff --git a/src/Symfony/Component/Form/Extension/DataCollector/FormDataCollector.php b/src/Symfony/Component/Form/Extension/DataCollector/FormDataCollector.php index dab72bb309ff5..348be44aaa394 100644 --- a/src/Symfony/Component/Form/Extension/DataCollector/FormDataCollector.php +++ b/src/Symfony/Component/Form/Extension/DataCollector/FormDataCollector.php @@ -33,8 +33,6 @@ */ class FormDataCollector extends DataCollector implements FormDataCollectorInterface { - private FormDataExtractorInterface $dataExtractor; - /** * Stores the collected data per {@link FormInterface} instance. * @@ -62,21 +60,20 @@ class FormDataCollector extends DataCollector implements FormDataCollectorInterf */ private array $formsByView; - public function __construct(FormDataExtractorInterface $dataExtractor) - { + public function __construct( + private FormDataExtractorInterface $dataExtractor, + ) { if (!class_exists(ClassStub::class)) { throw new \LogicException(sprintf('The VarDumper component is needed for using the "%s" class. Install symfony/var-dumper version 3.4 or above.', __CLASS__)); } - $this->dataExtractor = $dataExtractor; - $this->reset(); } /** * Does nothing. The data is collected during the form event listeners. */ - public function collect(Request $request, Response $response, \Throwable $exception = null): void + public function collect(Request $request, Response $response, ?\Throwable $exception = null): void { } diff --git a/src/Symfony/Component/Form/Extension/DataCollector/Proxy/ResolvedTypeDataCollectorProxy.php b/src/Symfony/Component/Form/Extension/DataCollector/Proxy/ResolvedTypeDataCollectorProxy.php index 1e922ff2ea398..90e28a61fb5f3 100644 --- a/src/Symfony/Component/Form/Extension/DataCollector/Proxy/ResolvedTypeDataCollectorProxy.php +++ b/src/Symfony/Component/Form/Extension/DataCollector/Proxy/ResolvedTypeDataCollectorProxy.php @@ -27,13 +27,10 @@ */ class ResolvedTypeDataCollectorProxy implements ResolvedFormTypeInterface { - private ResolvedFormTypeInterface $proxiedType; - private FormDataCollectorInterface $dataCollector; - - public function __construct(ResolvedFormTypeInterface $proxiedType, FormDataCollectorInterface $dataCollector) - { - $this->proxiedType = $proxiedType; - $this->dataCollector = $dataCollector; + public function __construct( + private ResolvedFormTypeInterface $proxiedType, + private FormDataCollectorInterface $dataCollector, + ) { } public function getBlockPrefix(): string @@ -66,7 +63,7 @@ public function createBuilder(FormFactoryInterface $factory, string $name, array return $builder; } - public function createView(FormInterface $form, FormView $parent = null): FormView + public function createView(FormInterface $form, ?FormView $parent = null): FormView { return $this->proxiedType->createView($form, $parent); } diff --git a/src/Symfony/Component/Form/Extension/DataCollector/Proxy/ResolvedTypeFactoryDataCollectorProxy.php b/src/Symfony/Component/Form/Extension/DataCollector/Proxy/ResolvedTypeFactoryDataCollectorProxy.php index eea5bfd4aec00..a052a178294cf 100644 --- a/src/Symfony/Component/Form/Extension/DataCollector/Proxy/ResolvedTypeFactoryDataCollectorProxy.php +++ b/src/Symfony/Component/Form/Extension/DataCollector/Proxy/ResolvedTypeFactoryDataCollectorProxy.php @@ -24,16 +24,13 @@ */ class ResolvedTypeFactoryDataCollectorProxy implements ResolvedFormTypeFactoryInterface { - private ResolvedFormTypeFactoryInterface $proxiedFactory; - private FormDataCollectorInterface $dataCollector; - - public function __construct(ResolvedFormTypeFactoryInterface $proxiedFactory, FormDataCollectorInterface $dataCollector) - { - $this->proxiedFactory = $proxiedFactory; - $this->dataCollector = $dataCollector; + public function __construct( + private ResolvedFormTypeFactoryInterface $proxiedFactory, + private FormDataCollectorInterface $dataCollector, + ) { } - public function createResolvedType(FormTypeInterface $type, array $typeExtensions, ResolvedFormTypeInterface $parent = null): ResolvedFormTypeInterface + public function createResolvedType(FormTypeInterface $type, array $typeExtensions, ?ResolvedFormTypeInterface $parent = null): ResolvedFormTypeInterface { return new ResolvedTypeDataCollectorProxy( $this->proxiedFactory->createResolvedType($type, $typeExtensions, $parent), diff --git a/src/Symfony/Component/Form/Extension/DependencyInjection/DependencyInjectionExtension.php b/src/Symfony/Component/Form/Extension/DependencyInjection/DependencyInjectionExtension.php index 6564bd56574a8..420f26b743f94 100644 --- a/src/Symfony/Component/Form/Extension/DependencyInjection/DependencyInjectionExtension.php +++ b/src/Symfony/Component/Form/Extension/DependencyInjection/DependencyInjectionExtension.php @@ -23,18 +23,15 @@ class DependencyInjectionExtension implements FormExtensionInterface { private ?FormTypeGuesserChain $guesser = null; private bool $guesserLoaded = false; - private ContainerInterface $typeContainer; - private array $typeExtensionServices; - private iterable $guesserServices; /** * @param array> $typeExtensionServices */ - public function __construct(ContainerInterface $typeContainer, array $typeExtensionServices, iterable $guesserServices) - { - $this->typeContainer = $typeContainer; - $this->typeExtensionServices = $typeExtensionServices; - $this->guesserServices = $guesserServices; + public function __construct( + private ContainerInterface $typeContainer, + private array $typeExtensionServices, + private iterable $guesserServices, + ) { } public function getType(string $name): FormTypeInterface diff --git a/src/Symfony/Component/Form/Extension/HttpFoundation/HttpFoundationRequestHandler.php b/src/Symfony/Component/Form/Extension/HttpFoundation/HttpFoundationRequestHandler.php index a627232c90210..d7875e7b0d9a0 100644 --- a/src/Symfony/Component/Form/Extension/HttpFoundation/HttpFoundationRequestHandler.php +++ b/src/Symfony/Component/Form/Extension/HttpFoundation/HttpFoundationRequestHandler.php @@ -31,7 +31,7 @@ class HttpFoundationRequestHandler implements RequestHandlerInterface { private ServerParams $serverParams; - public function __construct(ServerParams $serverParams = null) + public function __construct(?ServerParams $serverParams = null) { $this->serverParams = $serverParams ?? new ServerParams(); } diff --git a/src/Symfony/Component/Form/Extension/HttpFoundation/Type/FormTypeHttpFoundationExtension.php b/src/Symfony/Component/Form/Extension/HttpFoundation/Type/FormTypeHttpFoundationExtension.php index ce90c30528357..5139308bd723f 100644 --- a/src/Symfony/Component/Form/Extension/HttpFoundation/Type/FormTypeHttpFoundationExtension.php +++ b/src/Symfony/Component/Form/Extension/HttpFoundation/Type/FormTypeHttpFoundationExtension.php @@ -24,7 +24,7 @@ class FormTypeHttpFoundationExtension extends AbstractTypeExtension { private RequestHandlerInterface $requestHandler; - public function __construct(RequestHandlerInterface $requestHandler = null) + public function __construct(?RequestHandlerInterface $requestHandler = null) { $this->requestHandler = $requestHandler ?? new HttpFoundationRequestHandler(); } diff --git a/src/Symfony/Component/Form/Extension/Validator/EventListener/ValidationListener.php b/src/Symfony/Component/Form/Extension/Validator/EventListener/ValidationListener.php index d24264f24496b..b3c16dbad23d4 100644 --- a/src/Symfony/Component/Form/Extension/Validator/EventListener/ValidationListener.php +++ b/src/Symfony/Component/Form/Extension/Validator/EventListener/ValidationListener.php @@ -23,18 +23,15 @@ */ class ValidationListener implements EventSubscriberInterface { - private ValidatorInterface $validator; - private ViolationMapperInterface $violationMapper; - public static function getSubscribedEvents(): array { return [FormEvents::POST_SUBMIT => 'validateForm']; } - public function __construct(ValidatorInterface $validator, ViolationMapperInterface $violationMapper) - { - $this->validator = $validator; - $this->violationMapper = $violationMapper; + public function __construct( + private ValidatorInterface $validator, + private ViolationMapperInterface $violationMapper, + ) { } public function validateForm(FormEvent $event): void diff --git a/src/Symfony/Component/Form/Extension/Validator/Type/FormTypeValidatorExtension.php b/src/Symfony/Component/Form/Extension/Validator/Type/FormTypeValidatorExtension.php index e904a4f16ebb7..14029e339dcc8 100644 --- a/src/Symfony/Component/Form/Extension/Validator/Type/FormTypeValidatorExtension.php +++ b/src/Symfony/Component/Form/Extension/Validator/Type/FormTypeValidatorExtension.php @@ -27,13 +27,14 @@ */ class FormTypeValidatorExtension extends BaseValidatorExtension { - private ValidatorInterface $validator; private ViolationMapper $violationMapper; - private bool $legacyErrorMessages; - public function __construct(ValidatorInterface $validator, bool $legacyErrorMessages = true, FormRendererInterface $formRenderer = null, TranslatorInterface $translator = null) - { - $this->validator = $validator; + public function __construct( + private ValidatorInterface $validator, + private bool $legacyErrorMessages = true, + ?FormRendererInterface $formRenderer = null, + ?TranslatorInterface $translator = null, + ) { $this->violationMapper = new ViolationMapper($formRenderer, $translator); } diff --git a/src/Symfony/Component/Form/Extension/Validator/Type/UploadValidatorExtension.php b/src/Symfony/Component/Form/Extension/Validator/Type/UploadValidatorExtension.php index 80e3315ae9acb..7c1e965aba834 100644 --- a/src/Symfony/Component/Form/Extension/Validator/Type/UploadValidatorExtension.php +++ b/src/Symfony/Component/Form/Extension/Validator/Type/UploadValidatorExtension.php @@ -23,13 +23,10 @@ */ class UploadValidatorExtension extends AbstractTypeExtension { - private TranslatorInterface $translator; - private ?string $translationDomain; - - public function __construct(TranslatorInterface $translator, string $translationDomain = null) - { - $this->translator = $translator; - $this->translationDomain = $translationDomain; + public function __construct( + private TranslatorInterface $translator, + private ?string $translationDomain = null, + ) { } public function configureOptions(OptionsResolver $resolver): void diff --git a/src/Symfony/Component/Form/Extension/Validator/ValidatorExtension.php b/src/Symfony/Component/Form/Extension/Validator/ValidatorExtension.php index fe1bd33f5f8d5..522a7696293f6 100644 --- a/src/Symfony/Component/Form/Extension/Validator/ValidatorExtension.php +++ b/src/Symfony/Component/Form/Extension/Validator/ValidatorExtension.php @@ -27,15 +27,12 @@ */ class ValidatorExtension extends AbstractExtension { - private ValidatorInterface $validator; - private ?FormRendererInterface $formRenderer; - private ?TranslatorInterface $translator; - private bool $legacyErrorMessages; - - public function __construct(ValidatorInterface $validator, bool $legacyErrorMessages = true, FormRendererInterface $formRenderer = null, TranslatorInterface $translator = null) - { - $this->legacyErrorMessages = $legacyErrorMessages; - + public function __construct( + private ValidatorInterface $validator, + private bool $legacyErrorMessages = true, + private ?FormRendererInterface $formRenderer = null, + private ?TranslatorInterface $translator = null, + ) { $metadata = $validator->getMetadataFor(\Symfony\Component\Form\Form::class); // Register the form constraints in the validator programmatically. @@ -46,10 +43,6 @@ public function __construct(ValidatorInterface $validator, bool $legacyErrorMess /* @var $metadata ClassMetadata */ $metadata->addConstraint(new Form()); $metadata->addConstraint(new Traverse(false)); - - $this->validator = $validator; - $this->formRenderer = $formRenderer; - $this->translator = $translator; } public function loadTypeGuesser(): ?FormTypeGuesserInterface diff --git a/src/Symfony/Component/Form/Extension/Validator/ValidatorTypeGuesser.php b/src/Symfony/Component/Form/Extension/Validator/ValidatorTypeGuesser.php index 57bccaa39f4e8..72ae8ddd5783a 100644 --- a/src/Symfony/Component/Form/Extension/Validator/ValidatorTypeGuesser.php +++ b/src/Symfony/Component/Form/Extension/Validator/ValidatorTypeGuesser.php @@ -57,11 +57,9 @@ class ValidatorTypeGuesser implements FormTypeGuesserInterface { - private MetadataFactoryInterface $metadataFactory; - - public function __construct(MetadataFactoryInterface $metadataFactory) - { - $this->metadataFactory = $metadataFactory; + public function __construct( + private MetadataFactoryInterface $metadataFactory, + ) { } public function guessType(string $class, string $property): ?TypeGuess diff --git a/src/Symfony/Component/Form/Extension/Validator/ViolationMapper/MappingRule.php b/src/Symfony/Component/Form/Extension/Validator/ViolationMapper/MappingRule.php index 6e33f222979f6..f9a61cc817965 100644 --- a/src/Symfony/Component/Form/Extension/Validator/ViolationMapper/MappingRule.php +++ b/src/Symfony/Component/Form/Extension/Validator/ViolationMapper/MappingRule.php @@ -19,15 +19,11 @@ */ class MappingRule { - private FormInterface $origin; - private string $propertyPath; - private string $targetPath; - - public function __construct(FormInterface $origin, string $propertyPath, string $targetPath) - { - $this->origin = $origin; - $this->propertyPath = $propertyPath; - $this->targetPath = $targetPath; + public function __construct( + private FormInterface $origin, + private string $propertyPath, + private string $targetPath, + ) { } public function getOrigin(): FormInterface diff --git a/src/Symfony/Component/Form/Extension/Validator/ViolationMapper/RelativePath.php b/src/Symfony/Component/Form/Extension/Validator/ViolationMapper/RelativePath.php index 0384edb4449d7..dbf0712e2fa6b 100644 --- a/src/Symfony/Component/Form/Extension/Validator/ViolationMapper/RelativePath.php +++ b/src/Symfony/Component/Form/Extension/Validator/ViolationMapper/RelativePath.php @@ -19,13 +19,12 @@ */ class RelativePath extends PropertyPath { - private FormInterface $root; - - public function __construct(FormInterface $root, string $propertyPath) + public function __construct( + private FormInterface $root, + string $propertyPath, + ) { parent::__construct($propertyPath); - - $this->root = $root; } public function getRoot(): FormInterface diff --git a/src/Symfony/Component/Form/Extension/Validator/ViolationMapper/ViolationMapper.php b/src/Symfony/Component/Form/Extension/Validator/ViolationMapper/ViolationMapper.php index fca010d70e4bf..faca255b5dcbb 100644 --- a/src/Symfony/Component/Form/Extension/Validator/ViolationMapper/ViolationMapper.php +++ b/src/Symfony/Component/Form/Extension/Validator/ViolationMapper/ViolationMapper.php @@ -28,14 +28,12 @@ */ class ViolationMapper implements ViolationMapperInterface { - private ?FormRendererInterface $formRenderer; - private ?TranslatorInterface $translator; private bool $allowNonSynchronized = false; - public function __construct(FormRendererInterface $formRenderer = null, TranslatorInterface $translator = null) - { - $this->formRenderer = $formRenderer; - $this->translator = $translator; + public function __construct( + private ?FormRendererInterface $formRenderer = null, + private ?TranslatorInterface $translator = null, + ) { } public function mapViolation(ConstraintViolation $violation, FormInterface $form, bool $allowNonSynchronized = false): void diff --git a/src/Symfony/Component/Form/Form.php b/src/Symfony/Component/Form/Form.php index 88c43766190a9..ee75311a14a10 100644 --- a/src/Symfony/Component/Form/Form.php +++ b/src/Symfony/Component/Form/Form.php @@ -70,7 +70,6 @@ */ class Form implements \IteratorAggregate, FormInterface, ClearableErrorsInterface { - private FormConfigInterface $config; private ?FormInterface $parent = null; /** @@ -135,8 +134,9 @@ class Form implements \IteratorAggregate, FormInterface, ClearableErrorsInterfac /** * @throws LogicException if a data mapper is not provided for a compound form */ - public function __construct(FormConfigInterface $config) - { + public function __construct( + private FormConfigInterface $config, + ) { // Compound forms always need a data mapper, otherwise calls to // `setData` and `add` will not lead to the correct population of // the child forms. @@ -150,7 +150,6 @@ public function __construct(FormConfigInterface $config) $this->defaultDataSet = true; } - $this->config = $config; $this->children = new OrderedHashMap(); $this->name = $config->getName(); } @@ -715,7 +714,7 @@ public function all(): array return iterator_to_array($this->children); } - public function add(FormInterface|string $child, string $type = null, array $options = []): static + public function add(FormInterface|string $child, ?string $type = null, array $options = []): static { if ($this->submitted) { throw new AlreadySubmittedException('You cannot add children to a submitted form.'); @@ -872,7 +871,7 @@ public function count(): int return \count($this->children); } - public function createView(FormView $parent = null): FormView + public function createView(?FormView $parent = null): FormView { if (null === $parent && $this->parent) { $parent = $this->parent->createView(); diff --git a/src/Symfony/Component/Form/FormBuilder.php b/src/Symfony/Component/Form/FormBuilder.php index 816c38810fc39..58bc9c86d9476 100644 --- a/src/Symfony/Component/Form/FormBuilder.php +++ b/src/Symfony/Component/Form/FormBuilder.php @@ -44,7 +44,7 @@ public function __construct(?string $name, ?string $dataClass, EventDispatcherIn $this->setFormFactory($factory); } - public function add(FormBuilderInterface|string $child, string $type = null, array $options = []): static + public function add(FormBuilderInterface|string $child, ?string $type = null, array $options = []): static { if ($this->locked) { throw new BadMethodCallException('FormBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.'); @@ -66,7 +66,7 @@ public function add(FormBuilderInterface|string $child, string $type = null, arr return $this; } - public function create(string $name, string $type = null, array $options = []): FormBuilderInterface + public function create(string $name, ?string $type = null, array $options = []): FormBuilderInterface { if ($this->locked) { throw new BadMethodCallException('FormBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.'); diff --git a/src/Symfony/Component/Form/FormBuilderInterface.php b/src/Symfony/Component/Form/FormBuilderInterface.php index c00fae46a5b95..08d29303c9ab4 100644 --- a/src/Symfony/Component/Form/FormBuilderInterface.php +++ b/src/Symfony/Component/Form/FormBuilderInterface.php @@ -27,7 +27,7 @@ interface FormBuilderInterface extends \Traversable, \Countable, FormConfigBuild * * @param array $options */ - public function add(string|self $child, string $type = null, array $options = []): static; + public function add(string|self $child, ?string $type = null, array $options = []): static; /** * Creates a form builder. @@ -36,7 +36,7 @@ public function add(string|self $child, string $type = null, array $options = [] * @param string|null $type The type of the form or null if name is a property * @param array $options */ - public function create(string $name, string $type = null, array $options = []): self; + public function create(string $name, ?string $type = null, array $options = []): self; /** * Returns a child by name. diff --git a/src/Symfony/Component/Form/FormConfigBuilder.php b/src/Symfony/Component/Form/FormConfigBuilder.php index 49aa89ab048e1..e0fb01dad96a9 100644 --- a/src/Symfony/Component/Form/FormConfigBuilder.php +++ b/src/Symfony/Component/Form/FormConfigBuilder.php @@ -33,7 +33,6 @@ class FormConfigBuilder implements FormConfigBuilderInterface */ private static NativeRequestHandler $nativeRequestHandler; - private EventDispatcherInterface $dispatcher; private string $name; private ?PropertyPathInterface $propertyPath = null; private bool $mapped = true; @@ -57,7 +56,6 @@ class FormConfigBuilder implements FormConfigBuilderInterface private string $method = 'POST'; private RequestHandlerInterface $requestHandler; private bool $autoInitialize = false; - private array $options; private ?\Closure $isEmptyCallback = null; /** @@ -69,8 +67,12 @@ class FormConfigBuilder implements FormConfigBuilderInterface * @throws InvalidArgumentException if the data class is not a valid class or if * the name contains invalid characters */ - public function __construct(?string $name, ?string $dataClass, EventDispatcherInterface $dispatcher, array $options = []) - { + public function __construct( + ?string $name, + ?string $dataClass, + private EventDispatcherInterface $dispatcher, + private array $options = [], + ) { self::validateName($name); if (null !== $dataClass && !class_exists($dataClass) && !interface_exists($dataClass, false)) { @@ -79,8 +81,6 @@ public function __construct(?string $name, ?string $dataClass, EventDispatcherIn $this->name = (string) $name; $this->dataClass = $dataClass; - $this->dispatcher = $dispatcher; - $this->options = $options; } public function addEventListener(string $eventName, callable $listener, int $priority = 0): static diff --git a/src/Symfony/Component/Form/FormError.php b/src/Symfony/Component/Form/FormError.php index face43bcef850..335d9e21d3eb7 100644 --- a/src/Symfony/Component/Form/FormError.php +++ b/src/Symfony/Component/Form/FormError.php @@ -21,11 +21,6 @@ class FormError { protected string $messageTemplate; - protected array $messageParameters; - protected ?int $messagePluralization; - - private string $message; - private mixed $cause; /** * The form that spawned this error. @@ -45,13 +40,14 @@ class FormError * * @see \Symfony\Component\Translation\Translator */ - public function __construct(string $message, string $messageTemplate = null, array $messageParameters = [], int $messagePluralization = null, mixed $cause = null) - { - $this->message = $message; + public function __construct( + private string $message, + ?string $messageTemplate = null, + protected array $messageParameters = [], + protected ?int $messagePluralization = null, + private mixed $cause = null, + ) { $this->messageTemplate = $messageTemplate ?: $message; - $this->messageParameters = $messageParameters; - $this->messagePluralization = $messagePluralization; - $this->cause = $cause; } /** diff --git a/src/Symfony/Component/Form/FormErrorIterator.php b/src/Symfony/Component/Form/FormErrorIterator.php index 42de0c8358ef5..a614e72c2ee8a 100644 --- a/src/Symfony/Component/Form/FormErrorIterator.php +++ b/src/Symfony/Component/Form/FormErrorIterator.php @@ -42,8 +42,6 @@ class FormErrorIterator implements \RecursiveIterator, \SeekableIterator, \Array */ public const INDENTATION = ' '; - private FormInterface $form; - /** * @var list */ @@ -54,15 +52,16 @@ class FormErrorIterator implements \RecursiveIterator, \SeekableIterator, \Array * * @throws InvalidArgumentException If the errors are invalid */ - public function __construct(FormInterface $form, array $errors) - { + public function __construct( + private FormInterface $form, + array $errors, + ) { foreach ($errors as $error) { if (!($error instanceof FormError || $error instanceof self)) { throw new InvalidArgumentException(sprintf('The errors must be instances of "Symfony\Component\Form\FormError" or "%s". Got: "%s".', __CLASS__, get_debug_type($error))); } } - $this->form = $form; $this->errors = $errors; } diff --git a/src/Symfony/Component/Form/FormEvent.php b/src/Symfony/Component/Form/FormEvent.php index e46fbb6a6aca3..e6a3878f6dccf 100644 --- a/src/Symfony/Component/Form/FormEvent.php +++ b/src/Symfony/Component/Form/FormEvent.php @@ -18,14 +18,10 @@ */ class FormEvent extends Event { - protected mixed $data; - - private FormInterface $form; - - public function __construct(FormInterface $form, mixed $data) - { - $this->form = $form; - $this->data = $data; + public function __construct( + private FormInterface $form, + protected mixed $data, + ) { } /** diff --git a/src/Symfony/Component/Form/FormFactory.php b/src/Symfony/Component/Form/FormFactory.php index 9e1234f8317c9..dcf7b36f28d02 100644 --- a/src/Symfony/Component/Form/FormFactory.php +++ b/src/Symfony/Component/Form/FormFactory.php @@ -16,11 +16,9 @@ class FormFactory implements FormFactoryInterface { - private FormRegistryInterface $registry; - - public function __construct(FormRegistryInterface $registry) - { - $this->registry = $registry; + public function __construct( + private FormRegistryInterface $registry, + ) { } public function create(string $type = FormType::class, mixed $data = null, array $options = []): FormInterface diff --git a/src/Symfony/Component/Form/FormFactoryBuilder.php b/src/Symfony/Component/Form/FormFactoryBuilder.php index 42b8dec9f42ea..90e3bf20ca87b 100644 --- a/src/Symfony/Component/Form/FormFactoryBuilder.php +++ b/src/Symfony/Component/Form/FormFactoryBuilder.php @@ -20,8 +20,6 @@ */ class FormFactoryBuilder implements FormFactoryBuilderInterface { - private bool $forceCoreExtension; - private ResolvedFormTypeFactoryInterface $resolvedTypeFactory; /** @@ -44,9 +42,9 @@ class FormFactoryBuilder implements FormFactoryBuilderInterface */ private array $typeGuessers = []; - public function __construct(bool $forceCoreExtension = false) - { - $this->forceCoreExtension = $forceCoreExtension; + public function __construct( + private bool $forceCoreExtension = false, + ) { } public function setResolvedTypeFactory(ResolvedFormTypeFactoryInterface $resolvedTypeFactory): static diff --git a/src/Symfony/Component/Form/FormInterface.php b/src/Symfony/Component/Form/FormInterface.php index a66cf420c95e9..23392c4931237 100644 --- a/src/Symfony/Component/Form/FormInterface.php +++ b/src/Symfony/Component/Form/FormInterface.php @@ -54,7 +54,7 @@ public function getParent(): ?self; * @throws Exception\LogicException when trying to add a child to a non-compound form * @throws Exception\UnexpectedTypeException if $child or $type has an unexpected type */ - public function add(self|string $child, string $type = null, array $options = []): static; + public function add(self|string $child, ?string $type = null, array $options = []): static; /** * Returns the child with the given name. @@ -285,5 +285,5 @@ public function getRoot(): self; */ public function isRoot(): bool; - public function createView(FormView $parent = null): FormView; + public function createView(?FormView $parent = null): FormView; } diff --git a/src/Symfony/Component/Form/FormRegistry.php b/src/Symfony/Component/Form/FormRegistry.php index ab3f55f9fb908..95a0077378568 100644 --- a/src/Symfony/Component/Form/FormRegistry.php +++ b/src/Symfony/Component/Form/FormRegistry.php @@ -33,8 +33,7 @@ class FormRegistry implements FormRegistryInterface */ private array $types = []; - private FormTypeGuesserInterface|null|false $guesser = false; - private ResolvedFormTypeFactoryInterface $resolvedTypeFactory; + private FormTypeGuesserInterface|false|null $guesser = false; private array $checkedTypes = []; /** @@ -42,8 +41,10 @@ class FormRegistry implements FormRegistryInterface * * @throws UnexpectedTypeException if any extension does not implement FormExtensionInterface */ - public function __construct(array $extensions, ResolvedFormTypeFactoryInterface $resolvedTypeFactory) - { + public function __construct( + array $extensions, + private ResolvedFormTypeFactoryInterface $resolvedTypeFactory, + ) { foreach ($extensions as $extension) { if (!$extension instanceof FormExtensionInterface) { throw new UnexpectedTypeException($extension, FormExtensionInterface::class); @@ -51,7 +52,6 @@ public function __construct(array $extensions, ResolvedFormTypeFactoryInterface } $this->extensions = $extensions; - $this->resolvedTypeFactory = $resolvedTypeFactory; } public function getType(string $name): ResolvedFormTypeInterface diff --git a/src/Symfony/Component/Form/FormRenderer.php b/src/Symfony/Component/Form/FormRenderer.php index c2771d28c9433..a9ffd4f41ed73 100644 --- a/src/Symfony/Component/Form/FormRenderer.php +++ b/src/Symfony/Component/Form/FormRenderer.php @@ -25,16 +25,14 @@ class FormRenderer implements FormRendererInterface { public const CACHE_KEY_VAR = 'unique_block_prefix'; - private FormRendererEngineInterface $engine; - private ?CsrfTokenManagerInterface $csrfTokenManager; private array $blockNameHierarchyMap = []; private array $hierarchyLevelMap = []; private array $variableStack = []; - public function __construct(FormRendererEngineInterface $engine, CsrfTokenManagerInterface $csrfTokenManager = null) - { - $this->engine = $engine; - $this->csrfTokenManager = $csrfTokenManager; + public function __construct( + private FormRendererEngineInterface $engine, + private ?CsrfTokenManagerInterface $csrfTokenManager = null, + ) { } public function getEngine(): FormRendererEngineInterface diff --git a/src/Symfony/Component/Form/FormView.php b/src/Symfony/Component/Form/FormView.php index e9e9e9957b3e4..93804bb8798e8 100644 --- a/src/Symfony/Component/Form/FormView.php +++ b/src/Symfony/Component/Form/FormView.php @@ -29,11 +29,6 @@ class FormView implements \ArrayAccess, \IteratorAggregate, \Countable 'attr' => [], ]; - /** - * The parent view. - */ - public ?self $parent = null; - /** * The child views. * @@ -52,9 +47,12 @@ class FormView implements \ArrayAccess, \IteratorAggregate, \Countable private bool $methodRendered = false; - public function __construct(self $parent = null) - { - $this->parent = $parent; + /** + * @param FormView|null $parent The parent view + */ + public function __construct( + public ?self $parent = null, + ) { } /** diff --git a/src/Symfony/Component/Form/Guess/TypeGuess.php b/src/Symfony/Component/Form/Guess/TypeGuess.php index 8ede78eb8cec6..b62873daa4f8d 100644 --- a/src/Symfony/Component/Form/Guess/TypeGuess.php +++ b/src/Symfony/Component/Form/Guess/TypeGuess.php @@ -19,9 +19,6 @@ */ class TypeGuess extends Guess { - private string $type; - private array $options; - /** * @param string $type The guessed field type * @param array $options The options for creating instances of the @@ -29,12 +26,12 @@ class TypeGuess extends Guess * @param int $confidence The confidence that the guessed class name * is correct */ - public function __construct(string $type, array $options, int $confidence) - { + public function __construct( + private string $type, + private array $options, + int $confidence, + ) { parent::__construct($confidence); - - $this->type = $type; - $this->options = $options; } /** diff --git a/src/Symfony/Component/Form/Guess/ValueGuess.php b/src/Symfony/Component/Form/Guess/ValueGuess.php index 36abe6602d723..2283287472ade 100644 --- a/src/Symfony/Component/Form/Guess/ValueGuess.php +++ b/src/Symfony/Component/Form/Guess/ValueGuess.php @@ -18,16 +18,14 @@ */ class ValueGuess extends Guess { - private string|int|bool|null $value; - /** * @param int $confidence The confidence that the guessed class name is correct */ - public function __construct(string|int|bool|null $value, int $confidence) - { + public function __construct( + private string|int|bool|null $value, + int $confidence, + ) { parent::__construct($confidence); - - $this->value = $value; } /** diff --git a/src/Symfony/Component/Form/NativeRequestHandler.php b/src/Symfony/Component/Form/NativeRequestHandler.php index 8c74bd1ded8ae..4c03df13d5865 100644 --- a/src/Symfony/Component/Form/NativeRequestHandler.php +++ b/src/Symfony/Component/Form/NativeRequestHandler.php @@ -36,7 +36,7 @@ class NativeRequestHandler implements RequestHandlerInterface 'type', ]; - public function __construct(ServerParams $params = null) + public function __construct(?ServerParams $params = null) { $this->serverParams = $params ?? new ServerParams(); } diff --git a/src/Symfony/Component/Form/PreloadedExtension.php b/src/Symfony/Component/Form/PreloadedExtension.php index c8e628d2d20e9..58d8f13b1b93c 100644 --- a/src/Symfony/Component/Form/PreloadedExtension.php +++ b/src/Symfony/Component/Form/PreloadedExtension.php @@ -21,8 +21,6 @@ class PreloadedExtension implements FormExtensionInterface { private array $types = []; - private array $typeExtensions = []; - private ?FormTypeGuesserInterface $typeGuesser; /** * Creates a new preloaded extension. @@ -30,11 +28,11 @@ class PreloadedExtension implements FormExtensionInterface * @param FormTypeInterface[] $types The types that the extension should support * @param FormTypeExtensionInterface[][] $typeExtensions The type extensions that the extension should support */ - public function __construct(array $types, array $typeExtensions, FormTypeGuesserInterface $typeGuesser = null) - { - $this->typeExtensions = $typeExtensions; - $this->typeGuesser = $typeGuesser; - + public function __construct( + array $types, + private array $typeExtensions, + private ?FormTypeGuesserInterface $typeGuesser = null, + ) { foreach ($types as $type) { $this->types[$type::class] = $type; } diff --git a/src/Symfony/Component/Form/ResolvedFormType.php b/src/Symfony/Component/Form/ResolvedFormType.php index 1acdce837567a..964619c396b38 100644 --- a/src/Symfony/Component/Form/ResolvedFormType.php +++ b/src/Symfony/Component/Form/ResolvedFormType.php @@ -23,31 +23,28 @@ */ class ResolvedFormType implements ResolvedFormTypeInterface { - private FormTypeInterface $innerType; - /** * @var FormTypeExtensionInterface[] */ private array $typeExtensions; - private ?ResolvedFormTypeInterface $parent; - private OptionsResolver $optionsResolver; /** * @param FormTypeExtensionInterface[] $typeExtensions */ - public function __construct(FormTypeInterface $innerType, array $typeExtensions = [], ResolvedFormTypeInterface $parent = null) - { + public function __construct( + private FormTypeInterface $innerType, + array $typeExtensions = [], + private ?ResolvedFormTypeInterface $parent = null, + ) { foreach ($typeExtensions as $extension) { if (!$extension instanceof FormTypeExtensionInterface) { throw new UnexpectedTypeException($extension, FormTypeExtensionInterface::class); } } - $this->innerType = $innerType; $this->typeExtensions = $typeExtensions; - $this->parent = $parent; } public function getBlockPrefix(): string @@ -87,7 +84,7 @@ public function createBuilder(FormFactoryInterface $factory, string $name, array return $builder; } - public function createView(FormInterface $form, FormView $parent = null): FormView + public function createView(FormInterface $form, ?FormView $parent = null): FormView { return $this->newView($parent); } @@ -168,7 +165,7 @@ protected function newBuilder(string $name, ?string $dataClass, FormFactoryInter * * Override this method if you want to customize the view class. */ - protected function newView(FormView $parent = null): FormView + protected function newView(?FormView $parent = null): FormView { return new FormView($parent); } diff --git a/src/Symfony/Component/Form/ResolvedFormTypeFactory.php b/src/Symfony/Component/Form/ResolvedFormTypeFactory.php index fd7c4521b28a0..437f9c553ca62 100644 --- a/src/Symfony/Component/Form/ResolvedFormTypeFactory.php +++ b/src/Symfony/Component/Form/ResolvedFormTypeFactory.php @@ -16,7 +16,7 @@ */ class ResolvedFormTypeFactory implements ResolvedFormTypeFactoryInterface { - public function createResolvedType(FormTypeInterface $type, array $typeExtensions, ResolvedFormTypeInterface $parent = null): ResolvedFormTypeInterface + public function createResolvedType(FormTypeInterface $type, array $typeExtensions, ?ResolvedFormTypeInterface $parent = null): ResolvedFormTypeInterface { return new ResolvedFormType($type, $typeExtensions, $parent); } diff --git a/src/Symfony/Component/Form/ResolvedFormTypeFactoryInterface.php b/src/Symfony/Component/Form/ResolvedFormTypeFactoryInterface.php index 8d44f0d24c655..9fd39e7fe24f7 100644 --- a/src/Symfony/Component/Form/ResolvedFormTypeFactoryInterface.php +++ b/src/Symfony/Component/Form/ResolvedFormTypeFactoryInterface.php @@ -30,5 +30,5 @@ interface ResolvedFormTypeFactoryInterface * @throws Exception\UnexpectedTypeException if the types parent {@link FormTypeInterface::getParent()} is not a string * @throws Exception\InvalidArgumentException if the types parent cannot be retrieved from any extension */ - public function createResolvedType(FormTypeInterface $type, array $typeExtensions, ResolvedFormTypeInterface $parent = null): ResolvedFormTypeInterface; + public function createResolvedType(FormTypeInterface $type, array $typeExtensions, ?ResolvedFormTypeInterface $parent = null): ResolvedFormTypeInterface; } diff --git a/src/Symfony/Component/Form/ResolvedFormTypeInterface.php b/src/Symfony/Component/Form/ResolvedFormTypeInterface.php index 821d4d4b4e7b8..690e0d7834684 100644 --- a/src/Symfony/Component/Form/ResolvedFormTypeInterface.php +++ b/src/Symfony/Component/Form/ResolvedFormTypeInterface.php @@ -52,7 +52,7 @@ public function createBuilder(FormFactoryInterface $factory, string $name, array /** * Creates a new form view for a form of this type. */ - public function createView(FormInterface $form, FormView $parent = null): FormView; + public function createView(FormInterface $form, ?FormView $parent = null): FormView; /** * Configures a form builder for the type hierarchy. diff --git a/src/Symfony/Component/Form/ReversedTransformer.php b/src/Symfony/Component/Form/ReversedTransformer.php index b68387908081c..4aa92450a24e2 100644 --- a/src/Symfony/Component/Form/ReversedTransformer.php +++ b/src/Symfony/Component/Form/ReversedTransformer.php @@ -21,11 +21,9 @@ */ class ReversedTransformer implements DataTransformerInterface { - protected DataTransformerInterface $reversedTransformer; - - public function __construct(DataTransformerInterface $reversedTransformer) - { - $this->reversedTransformer = $reversedTransformer; + public function __construct( + protected DataTransformerInterface $reversedTransformer, + ) { } public function transform(mixed $value): mixed diff --git a/src/Symfony/Component/Form/Tests/ChoiceList/Factory/DefaultChoiceListFactoryTest.php b/src/Symfony/Component/Form/Tests/ChoiceList/Factory/DefaultChoiceListFactoryTest.php index 9973c62ae9a3c..e7bf26d1780d3 100644 --- a/src/Symfony/Component/Form/Tests/ChoiceList/Factory/DefaultChoiceListFactoryTest.php +++ b/src/Symfony/Component/Form/Tests/ChoiceList/Factory/DefaultChoiceListFactoryTest.php @@ -729,7 +729,7 @@ public function testPassTranslatableMessageAsLabelDoesntCastItToString() public function testPassTranslatableInterfaceAsLabelDoesntCastItToString() { $message = new class() implements TranslatableInterface { - public function trans(TranslatorInterface $translator, string $locale = null): string + public function trans(TranslatorInterface $translator, ?string $locale = null): string { return 'my_message'; } diff --git a/src/Symfony/Component/Form/Tests/CompoundFormTest.php b/src/Symfony/Component/Form/Tests/CompoundFormTest.php index 8cb4d53d944e9..882e73034c86b 100644 --- a/src/Symfony/Component/Form/Tests/CompoundFormTest.php +++ b/src/Symfony/Component/Form/Tests/CompoundFormTest.php @@ -1117,7 +1117,7 @@ private function createForm(string $name = 'name', bool $compound = true): FormI return $builder->getForm(); } - private function getBuilder(string $name = 'name', string $dataClass = null, array $options = []): FormBuilder + private function getBuilder(string $name = 'name', ?string $dataClass = null, array $options = []): FormBuilder { return new FormBuilder($name, $dataClass, new EventDispatcher(), $this->factory, $options); } diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/BaseDateTimeTransformerTestCase.php b/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/BaseDateTimeTransformerTestCase.php index 7e86f2c069118..8210b22930e50 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/BaseDateTimeTransformerTestCase.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/BaseDateTimeTransformerTestCase.php @@ -31,5 +31,5 @@ public function testConstructFailsIfOutputTimezoneIsInvalid() $this->createDateTimeTransformer(null, 'that_timezone_does_not_exist'); } - abstract protected function createDateTimeTransformer(string $inputTimezone = null, string $outputTimezone = null): BaseDateTimeTransformer; + abstract protected function createDateTimeTransformer(?string $inputTimezone = null, ?string $outputTimezone = null): BaseDateTimeTransformer; } diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeToArrayTransformerTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeToArrayTransformerTest.php index 08e05c58405f2..8ed6114f04cfc 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeToArrayTransformerTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeToArrayTransformerTest.php @@ -536,7 +536,7 @@ public function testReverseTransformWithEmptyStringSecond() ]); } - protected function createDateTimeTransformer(string $inputTimezone = null, string $outputTimezone = null): BaseDateTimeTransformer + protected function createDateTimeTransformer(?string $inputTimezone = null, ?string $outputTimezone = null): BaseDateTimeTransformer { return new DateTimeToArrayTransformer($inputTimezone, $outputTimezone); } diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeToHtml5LocalDateTimeTransformerTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeToHtml5LocalDateTimeTransformerTest.php index 0b33f1584b59e..f2fb15cf0b410 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeToHtml5LocalDateTimeTransformerTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeToHtml5LocalDateTimeTransformerTest.php @@ -120,7 +120,7 @@ public function testReverseTransformExpectsValidDateString() $transformer->reverseTransform('2010-2010-2010'); } - protected function createDateTimeTransformer(string $inputTimezone = null, string $outputTimezone = null): BaseDateTimeTransformer + protected function createDateTimeTransformer(?string $inputTimezone = null, ?string $outputTimezone = null): BaseDateTimeTransformer { return new DateTimeToHtml5LocalDateTimeTransformer($inputTimezone, $outputTimezone); } diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeToLocalizedStringTransformerTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeToLocalizedStringTransformerTest.php index 107d5513d6c03..47f21203251d6 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeToLocalizedStringTransformerTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeToLocalizedStringTransformerTest.php @@ -371,7 +371,7 @@ public function testReverseTransformWrapsIntlErrorsWithExceptionsAndErrorLevel() $transformer->reverseTransform('12345'); } - protected function createDateTimeTransformer(string $inputTimezone = null, string $outputTimezone = null): BaseDateTimeTransformer + protected function createDateTimeTransformer(?string $inputTimezone = null, ?string $outputTimezone = null): BaseDateTimeTransformer { return new DateTimeToLocalizedStringTransformer($inputTimezone, $outputTimezone); } diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeToRfc3339TransformerTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeToRfc3339TransformerTest.php index eec23c5d36cf4..6a4d77039f150 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeToRfc3339TransformerTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeToRfc3339TransformerTest.php @@ -138,7 +138,7 @@ public static function invalidDateStringProvider(): array ]; } - protected function createDateTimeTransformer(string $inputTimezone = null, string $outputTimezone = null): BaseDateTimeTransformer + protected function createDateTimeTransformer(?string $inputTimezone = null, ?string $outputTimezone = null): BaseDateTimeTransformer { return new DateTimeToRfc3339Transformer($inputTimezone, $outputTimezone); } diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeToStringTransformerTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeToStringTransformerTest.php index 56ff98117aee9..66ad9ff416e26 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeToStringTransformerTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeToStringTransformerTest.php @@ -171,7 +171,7 @@ public function testReverseTransformWithNonExistingDate() $reverseTransformer->reverseTransform('2010-04-31'); } - protected function createDateTimeTransformer(string $inputTimezone = null, string $outputTimezone = null): BaseDateTimeTransformer + protected function createDateTimeTransformer(?string $inputTimezone = null, ?string $outputTimezone = null): BaseDateTimeTransformer { return new DateTimeToStringTransformer($inputTimezone, $outputTimezone); } diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeToTimestampTransformerTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeToTimestampTransformerTest.php index bf662d6464bef..183a7f9bd47d7 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeToTimestampTransformerTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeToTimestampTransformerTest.php @@ -115,7 +115,7 @@ public function testReverseTransformExpectsValidTimestamp() $reverseTransformer->reverseTransform('2010-2010-2010'); } - protected function createDateTimeTransformer(string $inputTimezone = null, string $outputTimezone = null): BaseDateTimeTransformer + protected function createDateTimeTransformer(?string $inputTimezone = null, ?string $outputTimezone = null): BaseDateTimeTransformer { return new DateTimeToTimestampTransformer($inputTimezone, $outputTimezone); } diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/StringToFloatTransformerTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/StringToFloatTransformerTest.php index 0ffb0b0ea8941..aaea8b2984d3e 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/StringToFloatTransformerTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/StringToFloatTransformerTest.php @@ -71,7 +71,7 @@ public static function provideReverseTransformations(): array /** * @dataProvider provideReverseTransformations */ - public function testReverseTransform($from, $to, int $scale = null) + public function testReverseTransform($from, $to, ?int $scale = null) { $transformer = new StringToFloatTransformer($scale); diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/Type/CollectionTypeTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/Type/CollectionTypeTest.php index dd92b7c89e11d..08d512caf17ad 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/CollectionTypeTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/CollectionTypeTest.php @@ -120,7 +120,7 @@ public function testResizedDownWithDeleteEmptyCallable() $form = $this->factory->create(static::TESTED_TYPE, null, [ 'entry_type' => AuthorType::class, 'allow_delete' => true, - 'delete_empty' => fn (Author $obj = null) => null === $obj || empty($obj->firstName), + 'delete_empty' => fn (?Author $obj = null) => null === $obj || empty($obj->firstName), ]); $form->setData([new Author('Bob'), new Author('Alice')]); diff --git a/src/Symfony/Component/Form/Tests/Extension/Validator/Constraints/FormValidatorTest.php b/src/Symfony/Component/Form/Tests/Extension/Validator/Constraints/FormValidatorTest.php index e26d31299c389..4e1588a9c7f74 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Validator/Constraints/FormValidatorTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Validator/Constraints/FormValidatorTest.php @@ -707,7 +707,7 @@ protected function createValidator(): FormValidator return new FormValidator(); } - private function getBuilder(string $name = 'name', string $dataClass = null, array $options = []): FormBuilder + private function getBuilder(string $name = 'name', ?string $dataClass = null, array $options = []): FormBuilder { $options = array_replace([ 'constraints' => [], diff --git a/src/Symfony/Component/Form/Tests/SimpleFormTest.php b/src/Symfony/Component/Form/Tests/SimpleFormTest.php index 83880600f81c8..7ded9b8535a0b 100644 --- a/src/Symfony/Component/Form/Tests/SimpleFormTest.php +++ b/src/Symfony/Component/Form/Tests/SimpleFormTest.php @@ -1128,7 +1128,7 @@ private function createForm(): FormInterface return $this->getBuilder()->getForm(); } - private function getBuilder(?string $name = 'name', string $dataClass = null, array $options = []): FormBuilder + private function getBuilder(?string $name = 'name', ?string $dataClass = null, array $options = []): FormBuilder { return new FormBuilder($name, $dataClass, new EventDispatcher(), new FormFactory(new FormRegistry([], new ResolvedFormTypeFactory())), $options); } diff --git a/src/Symfony/Component/Form/Util/OrderedHashMap.php b/src/Symfony/Component/Form/Util/OrderedHashMap.php index 32d08caa8fb6e..c0cab7aac6049 100644 --- a/src/Symfony/Component/Form/Util/OrderedHashMap.php +++ b/src/Symfony/Component/Form/Util/OrderedHashMap.php @@ -71,13 +71,6 @@ */ class OrderedHashMap implements \ArrayAccess, \IteratorAggregate, \Countable { - /** - * The elements of the map, indexed by their keys. - * - * @var TValue[] - */ - private array $elements = []; - /** * The keys of the map in the order in which they were inserted or changed. * @@ -95,11 +88,11 @@ class OrderedHashMap implements \ArrayAccess, \IteratorAggregate, \Countable /** * Creates a new map. * - * @param TValue[] $elements The elements to insert initially + * @param TValue[] $elements The initial elements of the map, indexed by their keys. */ - public function __construct(array $elements = []) - { - $this->elements = $elements; + public function __construct( + private array $elements = [], + ) { // the explicit string type-cast is necessary as digit-only keys would be returned as integers otherwise $this->orderedKeys = array_map(strval(...), array_keys($elements)); } diff --git a/src/Symfony/Component/Form/Util/OrderedHashMapIterator.php b/src/Symfony/Component/Form/Util/OrderedHashMapIterator.php index 4a8eebe61d921..927a28c04a3a4 100644 --- a/src/Symfony/Component/Form/Util/OrderedHashMapIterator.php +++ b/src/Symfony/Component/Form/Util/OrderedHashMapIterator.php @@ -24,14 +24,8 @@ */ class OrderedHashMapIterator implements \Iterator { - /** @var TValue[] */ - private array $elements; - /** @var list */ - private array $orderedKeys; private int $cursor = 0; private int $cursorId; - /** @var array */ - private array $managedCursors; private ?string $key = null; /** @var TValue|null */ private mixed $current = null; @@ -47,11 +41,11 @@ class OrderedHashMapIterator implements \Iterator * {@link OrderedHashMap} instance to support * recognizing the deletion of elements. */ - public function __construct(array &$elements, array &$orderedKeys, array &$managedCursors) - { - $this->elements = &$elements; - $this->orderedKeys = &$orderedKeys; - $this->managedCursors = &$managedCursors; + public function __construct( + private array &$elements, + private array &$orderedKeys, + private array &$managedCursors, + ) { $this->cursorId = \count($managedCursors); $this->managedCursors[$this->cursorId] = &$this->cursor; diff --git a/src/Symfony/Component/Form/Util/ServerParams.php b/src/Symfony/Component/Form/Util/ServerParams.php index eb317ff36a439..2c23efcc88482 100644 --- a/src/Symfony/Component/Form/Util/ServerParams.php +++ b/src/Symfony/Component/Form/Util/ServerParams.php @@ -18,11 +18,9 @@ */ class ServerParams { - private ?RequestStack $requestStack; - - public function __construct(RequestStack $requestStack = null) - { - $this->requestStack = $requestStack; + public function __construct( + private ?RequestStack $requestStack = null, + ) { } /** diff --git a/src/Symfony/Component/HtmlSanitizer/HtmlSanitizer.php b/src/Symfony/Component/HtmlSanitizer/HtmlSanitizer.php index ccc6f69379c3f..1147435a0409c 100644 --- a/src/Symfony/Component/HtmlSanitizer/HtmlSanitizer.php +++ b/src/Symfony/Component/HtmlSanitizer/HtmlSanitizer.php @@ -30,7 +30,7 @@ final class HtmlSanitizer implements HtmlSanitizerInterface */ private array $domVisitors = []; - public function __construct(HtmlSanitizerConfig $config, ParserInterface $parser = null) + public function __construct(HtmlSanitizerConfig $config, ?ParserInterface $parser = null) { $this->config = $config; $this->parser = $parser ?? new MastermindsParser(); diff --git a/src/Symfony/Component/HtmlSanitizer/TextSanitizer/UrlSanitizer.php b/src/Symfony/Component/HtmlSanitizer/TextSanitizer/UrlSanitizer.php index c4643f7b24635..a806981de770f 100644 --- a/src/Symfony/Component/HtmlSanitizer/TextSanitizer/UrlSanitizer.php +++ b/src/Symfony/Component/HtmlSanitizer/TextSanitizer/UrlSanitizer.php @@ -29,7 +29,7 @@ final class UrlSanitizer * * It also transforms the URL to HTTPS if requested. */ - public static function sanitize(?string $input, array $allowedSchemes = null, bool $forceHttps = false, array $allowedHosts = null, bool $allowRelative = false): ?string + public static function sanitize(?string $input, ?array $allowedSchemes = null, bool $forceHttps = false, ?array $allowedHosts = null, bool $allowRelative = false): ?string { if (!$input) { return null; diff --git a/src/Symfony/Component/HttpClient/AmpHttpClient.php b/src/Symfony/Component/HttpClient/AmpHttpClient.php index 341961ee7fa9e..f93aaa81eb065 100644 --- a/src/Symfony/Component/HttpClient/AmpHttpClient.php +++ b/src/Symfony/Component/HttpClient/AmpHttpClient.php @@ -64,7 +64,7 @@ final class AmpHttpClient implements HttpClientInterface, LoggerAwareInterface, * * @see HttpClientInterface::OPTIONS_DEFAULTS for available options */ - public function __construct(array $defaultOptions = [], callable $clientConfigurator = null, int $maxHostConnections = 6, int $maxPendingPushes = 50) + public function __construct(array $defaultOptions = [], ?callable $clientConfigurator = null, int $maxHostConnections = 6, int $maxPendingPushes = 50) { $this->defaultOptions['buffer'] ??= self::shouldBuffer(...); @@ -148,7 +148,7 @@ public function request(string $method, string $url, array $options = []): Respo return new AmpResponse($this->multi, $request, $options, $this->logger); } - public function stream(ResponseInterface|iterable $responses, float $timeout = null): ResponseStreamInterface + public function stream(ResponseInterface|iterable $responses, ?float $timeout = null): ResponseStreamInterface { if ($responses instanceof AmpResponse) { $responses = [$responses]; diff --git a/src/Symfony/Component/HttpClient/AsyncDecoratorTrait.php b/src/Symfony/Component/HttpClient/AsyncDecoratorTrait.php index 912b8250eacee..785c34a37d19c 100644 --- a/src/Symfony/Component/HttpClient/AsyncDecoratorTrait.php +++ b/src/Symfony/Component/HttpClient/AsyncDecoratorTrait.php @@ -30,7 +30,7 @@ trait AsyncDecoratorTrait */ abstract public function request(string $method, string $url, array $options = []): ResponseInterface; - public function stream(ResponseInterface|iterable $responses, float $timeout = null): ResponseStreamInterface + public function stream(ResponseInterface|iterable $responses, ?float $timeout = null): ResponseStreamInterface { if ($responses instanceof AsyncResponse) { $responses = [$responses]; diff --git a/src/Symfony/Component/HttpClient/CHANGELOG.md b/src/Symfony/Component/HttpClient/CHANGELOG.md index 581247bbab847..0b6ceba0851c5 100644 --- a/src/Symfony/Component/HttpClient/CHANGELOG.md +++ b/src/Symfony/Component/HttpClient/CHANGELOG.md @@ -7,6 +7,7 @@ CHANGELOG * Add `HttpOptions::setHeader()` to add or replace a single header * Allow mocking `start_time` info in `MockResponse` * Add `MockResponse::fromFile()` and `JsonMockResponse::fromFile()` methods to help using fixtures files + * Add `ThrottlingHttpClient` to enable limiting the number of requests within a certain period 7.0 --- diff --git a/src/Symfony/Component/HttpClient/CachingHttpClient.php b/src/Symfony/Component/HttpClient/CachingHttpClient.php index 8940c6d3f47e5..fd6a18c3cc1b1 100644 --- a/src/Symfony/Component/HttpClient/CachingHttpClient.php +++ b/src/Symfony/Component/HttpClient/CachingHttpClient.php @@ -105,7 +105,7 @@ public function request(string $method, string $url, array $options = []): Respo return MockResponse::fromRequest($method, $url, $options, $response); } - public function stream(ResponseInterface|iterable $responses, float $timeout = null): ResponseStreamInterface + public function stream(ResponseInterface|iterable $responses, ?float $timeout = null): ResponseStreamInterface { if ($responses instanceof ResponseInterface) { $responses = [$responses]; diff --git a/src/Symfony/Component/HttpClient/Chunk/DataChunk.php b/src/Symfony/Component/HttpClient/Chunk/DataChunk.php index 3507a0cd021d0..70f1b13bc59df 100644 --- a/src/Symfony/Component/HttpClient/Chunk/DataChunk.php +++ b/src/Symfony/Component/HttpClient/Chunk/DataChunk.php @@ -20,13 +20,10 @@ */ class DataChunk implements ChunkInterface { - private int $offset = 0; - private string $content = ''; - - public function __construct(int $offset = 0, string $content = '') - { - $this->offset = $offset; - $this->content = $content; + public function __construct( + private int $offset = 0, + private string $content = '', + ) { } public function isTimeout(): bool diff --git a/src/Symfony/Component/HttpClient/Chunk/ErrorChunk.php b/src/Symfony/Component/HttpClient/Chunk/ErrorChunk.php index 5584438595915..819056a9d83f3 100644 --- a/src/Symfony/Component/HttpClient/Chunk/ErrorChunk.php +++ b/src/Symfony/Component/HttpClient/Chunk/ErrorChunk.php @@ -23,14 +23,13 @@ class ErrorChunk implements ChunkInterface { private bool $didThrow = false; - private int $offset; private string $errorMessage; private ?\Throwable $error = null; - public function __construct(int $offset, \Throwable|string $error) - { - $this->offset = $offset; - + public function __construct( + private int $offset, + \Throwable|string $error, + ) { if (\is_string($error)) { $this->errorMessage = $error; } else { @@ -84,7 +83,7 @@ public function getError(): ?string return $this->errorMessage; } - public function didThrow(bool $didThrow = null): bool + public function didThrow(?bool $didThrow = null): bool { if (null !== $didThrow && $this->didThrow !== $didThrow) { return !$this->didThrow = $didThrow; diff --git a/src/Symfony/Component/HttpClient/CurlHttpClient.php b/src/Symfony/Component/HttpClient/CurlHttpClient.php index 4817ffc794afa..f3bd513b1dbfe 100644 --- a/src/Symfony/Component/HttpClient/CurlHttpClient.php +++ b/src/Symfony/Component/HttpClient/CurlHttpClient.php @@ -317,7 +317,7 @@ public function request(string $method, string $url, array $options = []): Respo return $pushedResponse ?? new CurlResponse($this->multi, $ch, $options, $this->logger, $method, self::createRedirectResolver($options, $host, $port), CurlClientState::$curlVersion['version_number'], $url); } - public function stream(ResponseInterface|iterable $responses, float $timeout = null): ResponseStreamInterface + public function stream(ResponseInterface|iterable $responses, ?float $timeout = null): ResponseStreamInterface { if ($responses instanceof CurlResponse) { $responses = [$responses]; diff --git a/src/Symfony/Component/HttpClient/DataCollector/HttpClientDataCollector.php b/src/Symfony/Component/HttpClient/DataCollector/HttpClientDataCollector.php index 58399890c2654..8e85462737e99 100644 --- a/src/Symfony/Component/HttpClient/DataCollector/HttpClientDataCollector.php +++ b/src/Symfony/Component/HttpClient/DataCollector/HttpClientDataCollector.php @@ -38,7 +38,7 @@ public function registerClient(string $name, TraceableHttpClient $client): void $this->clients[$name] = $client; } - public function collect(Request $request, Response $response, \Throwable $exception = null): void + public function collect(Request $request, Response $response, ?\Throwable $exception = null): void { $this->lateCollect(); } diff --git a/src/Symfony/Component/HttpClient/DecoratorTrait.php b/src/Symfony/Component/HttpClient/DecoratorTrait.php index c100a733e8327..6fcb349243450 100644 --- a/src/Symfony/Component/HttpClient/DecoratorTrait.php +++ b/src/Symfony/Component/HttpClient/DecoratorTrait.php @@ -25,7 +25,7 @@ trait DecoratorTrait { private HttpClientInterface $client; - public function __construct(HttpClientInterface $client = null) + public function __construct(?HttpClientInterface $client = null) { $this->client = $client ?? HttpClient::create(); } @@ -35,7 +35,7 @@ public function request(string $method, string $url, array $options = []): Respo return $this->client->request($method, $url, $options); } - public function stream(ResponseInterface|iterable $responses, float $timeout = null): ResponseStreamInterface + public function stream(ResponseInterface|iterable $responses, ?float $timeout = null): ResponseStreamInterface { return $this->client->stream($responses, $timeout); } diff --git a/src/Symfony/Component/HttpClient/EventSourceHttpClient.php b/src/Symfony/Component/HttpClient/EventSourceHttpClient.php index 853657c770eff..80022eaf26653 100644 --- a/src/Symfony/Component/HttpClient/EventSourceHttpClient.php +++ b/src/Symfony/Component/HttpClient/EventSourceHttpClient.php @@ -33,7 +33,7 @@ final class EventSourceHttpClient implements HttpClientInterface, ResetInterface private float $reconnectionTime; - public function __construct(HttpClientInterface $client = null, float $reconnectionTime = 10.0) + public function __construct(?HttpClientInterface $client = null, float $reconnectionTime = 10.0) { $this->client = $client ?? HttpClient::create(); $this->reconnectionTime = $reconnectionTime; diff --git a/src/Symfony/Component/HttpClient/HttpClientTrait.php b/src/Symfony/Component/HttpClient/HttpClientTrait.php index 193efbff25567..85a1814032468 100644 --- a/src/Symfony/Component/HttpClient/HttpClientTrait.php +++ b/src/Symfony/Component/HttpClient/HttpClientTrait.php @@ -535,7 +535,7 @@ private static function normalizePeerFingerprint(mixed $fingerprint): array /** * @throws InvalidArgumentException When the value cannot be json-encoded */ - private static function jsonEncode(mixed $value, int $flags = null, int $maxDepth = 512): string + private static function jsonEncode(mixed $value, ?int $flags = null, int $maxDepth = 512): string { $flags ??= \JSON_HEX_TAG | \JSON_HEX_APOS | \JSON_HEX_AMP | \JSON_HEX_QUOT | \JSON_PRESERVE_ZERO_FRACTION; diff --git a/src/Symfony/Component/HttpClient/HttplugClient.php b/src/Symfony/Component/HttpClient/HttplugClient.php index 2652f15c7309c..67cf8273d9634 100644 --- a/src/Symfony/Component/HttpClient/HttplugClient.php +++ b/src/Symfony/Component/HttpClient/HttplugClient.php @@ -69,7 +69,7 @@ final class HttplugClient implements ClientInterface, HttpAsyncClient, RequestFa private HttplugWaitLoop $waitLoop; - public function __construct(HttpClientInterface $client = null, ResponseFactoryInterface $responseFactory = null, StreamFactoryInterface $streamFactory = null) + public function __construct(?HttpClientInterface $client = null, ?ResponseFactoryInterface $responseFactory = null, ?StreamFactoryInterface $streamFactory = null) { $this->client = $client ?? HttpClient::create(); $streamFactory ??= $responseFactory instanceof StreamFactoryInterface ? $responseFactory : null; @@ -143,7 +143,7 @@ public function sendAsyncRequest(RequestInterface $request): HttplugPromise * * @return int The number of remaining pending promises */ - public function wait(float $maxDuration = null, float $idleTimeout = null): int + public function wait(?float $maxDuration = null, ?float $idleTimeout = null): int { return $this->waitLoop->wait(null, $maxDuration, $idleTimeout); } @@ -220,7 +220,7 @@ public function reset(): void } } - private function sendPsr7Request(RequestInterface $request, bool $buffer = null): ResponseInterface + private function sendPsr7Request(RequestInterface $request, ?bool $buffer = null): ResponseInterface { try { $body = $request->getBody(); diff --git a/src/Symfony/Component/HttpClient/Internal/AmpBody.php b/src/Symfony/Component/HttpClient/Internal/AmpBody.php index bd995e17d8b81..abf8fbd177afb 100644 --- a/src/Symfony/Component/HttpClient/Internal/AmpBody.php +++ b/src/Symfony/Component/HttpClient/Internal/AmpBody.php @@ -27,7 +27,6 @@ class AmpBody implements RequestBody, InputStream { private ResourceInputStream|\Closure|string $body; private array $info; - private \Closure $onProgress; private ?int $offset = 0; private int $length = -1; private ?int $uploaded = null; @@ -35,10 +34,12 @@ class AmpBody implements RequestBody, InputStream /** * @param \Closure|resource|string $body */ - public function __construct($body, &$info, \Closure $onProgress) - { + public function __construct( + $body, + &$info, + private \Closure $onProgress, + ) { $this->info = &$info; - $this->onProgress = $onProgress; if (\is_resource($body)) { $this->offset = ftell($body); diff --git a/src/Symfony/Component/HttpClient/Internal/AmpClientState.php b/src/Symfony/Component/HttpClient/Internal/AmpClientState.php index 90a002fe1a654..6c47854c05650 100644 --- a/src/Symfony/Component/HttpClient/Internal/AmpClientState.php +++ b/src/Symfony/Component/HttpClient/Internal/AmpClientState.php @@ -47,18 +47,15 @@ final class AmpClientState extends ClientState private array $clients = []; private \Closure $clientConfigurator; - private int $maxHostConnections; - private int $maxPendingPushes; - private ?LoggerInterface $logger; - public function __construct(?callable $clientConfigurator, int $maxHostConnections, int $maxPendingPushes, ?LoggerInterface &$logger) - { + public function __construct( + ?callable $clientConfigurator, + private int $maxHostConnections, + private int $maxPendingPushes, + private ?LoggerInterface &$logger, + ) { $clientConfigurator ??= static fn (PooledHttpClient $client) => new InterceptedHttpClient($client, new RetryRequests(2)); $this->clientConfigurator = $clientConfigurator(...); - - $this->maxHostConnections = $maxHostConnections; - $this->maxPendingPushes = $maxPendingPushes; - $this->logger = &$logger; } /** @@ -150,7 +147,7 @@ private function getClient(array $options): array /** @var resource|null */ public $handle; - public function connect(string $uri, ConnectContext $context = null, CancellationToken $token = null): Promise + public function connect(string $uri, ?ConnectContext $context = null, ?CancellationToken $token = null): Promise { $result = $this->connector->connect($this->uri ?? $uri, $context, $token); $result->onResolve(function ($e, $socket) { diff --git a/src/Symfony/Component/HttpClient/Internal/AmpListener.php b/src/Symfony/Component/HttpClient/Internal/AmpListener.php index 95c3bb0ed68f9..999742511beda 100644 --- a/src/Symfony/Component/HttpClient/Internal/AmpListener.php +++ b/src/Symfony/Component/HttpClient/Internal/AmpListener.php @@ -26,13 +26,16 @@ class AmpListener implements EventListener { private array $info; - private array $pinSha256; - private \Closure $onProgress; - /** @var resource|null */ - private $handle; - public function __construct(array &$info, array $pinSha256, \Closure $onProgress, &$handle) - { + /** + * @param resource|null $handle + */ + public function __construct( + array &$info, + private array $pinSha256, + private \Closure $onProgress, + private &$handle, + ) { $info += [ 'connect_time' => 0.0, 'pretransfer_time' => 0.0, @@ -44,9 +47,6 @@ public function __construct(array &$info, array $pinSha256, \Closure $onProgress ]; $this->info = &$info; - $this->pinSha256 = $pinSha256; - $this->onProgress = $onProgress; - $this->handle = &$handle; } public function startRequest(Request $request): Promise diff --git a/src/Symfony/Component/HttpClient/Internal/AmpResolver.php b/src/Symfony/Component/HttpClient/Internal/AmpResolver.php index 12880236fe56b..aff847524ecf2 100644 --- a/src/Symfony/Component/HttpClient/Internal/AmpResolver.php +++ b/src/Symfony/Component/HttpClient/Internal/AmpResolver.php @@ -25,14 +25,12 @@ */ class AmpResolver implements Dns\Resolver { - private array $dnsMap; - - public function __construct(array &$dnsMap) - { - $this->dnsMap = &$dnsMap; + public function __construct( + private array &$dnsMap, + ) { } - public function resolve(string $name, int $typeRestriction = null): Promise + public function resolve(string $name, ?int $typeRestriction = null): Promise { if (!isset($this->dnsMap[$name]) || !\in_array($typeRestriction, [Record::A, null], true)) { return Dns\resolver()->resolve($name, $typeRestriction); diff --git a/src/Symfony/Component/HttpClient/Internal/Canary.php b/src/Symfony/Component/HttpClient/Internal/Canary.php index b4438d94d0e37..69da0fc004bd7 100644 --- a/src/Symfony/Component/HttpClient/Internal/Canary.php +++ b/src/Symfony/Component/HttpClient/Internal/Canary.php @@ -18,11 +18,9 @@ */ final class Canary { - private \Closure $canceller; - - public function __construct(\Closure $canceller) - { - $this->canceller = $canceller; + public function __construct( + private \Closure $canceller, + ) { } public function cancel(): void diff --git a/src/Symfony/Component/HttpClient/Internal/HttplugWaitLoop.php b/src/Symfony/Component/HttpClient/Internal/HttplugWaitLoop.php index eaccdb8c7cc65..aa172b89b160d 100644 --- a/src/Symfony/Component/HttpClient/Internal/HttplugWaitLoop.php +++ b/src/Symfony/Component/HttpClient/Internal/HttplugWaitLoop.php @@ -30,23 +30,18 @@ */ final class HttplugWaitLoop { - private HttpClientInterface $client; - private ?\SplObjectStorage $promisePool; - private ResponseFactoryInterface $responseFactory; - private StreamFactoryInterface $streamFactory; - /** * @param \SplObjectStorage|null $promisePool */ - public function __construct(HttpClientInterface $client, ?\SplObjectStorage $promisePool, ResponseFactoryInterface $responseFactory, StreamFactoryInterface $streamFactory) - { - $this->client = $client; - $this->promisePool = $promisePool; - $this->responseFactory = $responseFactory; - $this->streamFactory = $streamFactory; + public function __construct( + private HttpClientInterface $client, + private ?\SplObjectStorage $promisePool, + private ResponseFactoryInterface $responseFactory, + private StreamFactoryInterface $streamFactory, + ) { } - public function wait(?ResponseInterface $pendingResponse, float $maxDuration = null, float $idleTimeout = null): int + public function wait(?ResponseInterface $pendingResponse, ?float $maxDuration = null, ?float $idleTimeout = null): int { if (!$this->promisePool) { return 0; diff --git a/src/Symfony/Component/HttpClient/MockHttpClient.php b/src/Symfony/Component/HttpClient/MockHttpClient.php index e4b6c96af918d..a0e08f940cdac 100644 --- a/src/Symfony/Component/HttpClient/MockHttpClient.php +++ b/src/Symfony/Component/HttpClient/MockHttpClient.php @@ -35,7 +35,7 @@ class MockHttpClient implements HttpClientInterface, ResetInterface /** * @param callable|callable[]|ResponseInterface|ResponseInterface[]|iterable|null $responseFactory */ - public function __construct(callable|iterable|ResponseInterface $responseFactory = null, ?string $baseUri = 'https://example.com') + public function __construct(callable|iterable|ResponseInterface|null $responseFactory = null, ?string $baseUri = 'https://example.com') { $this->setResponseFactory($responseFactory); $this->defaultOptions['base_uri'] = $baseUri; @@ -84,7 +84,7 @@ public function request(string $method, string $url, array $options = []): Respo return MockResponse::fromRequest($method, $url, $options, $response); } - public function stream(ResponseInterface|iterable $responses, float $timeout = null): ResponseStreamInterface + public function stream(ResponseInterface|iterable $responses, ?float $timeout = null): ResponseStreamInterface { if ($responses instanceof ResponseInterface) { $responses = [$responses]; diff --git a/src/Symfony/Component/HttpClient/NativeHttpClient.php b/src/Symfony/Component/HttpClient/NativeHttpClient.php index ad6bfde5f4b58..68f1fc247763e 100644 --- a/src/Symfony/Component/HttpClient/NativeHttpClient.php +++ b/src/Symfony/Component/HttpClient/NativeHttpClient.php @@ -269,7 +269,7 @@ public function request(string $method, string $url, array $options = []): Respo return new NativeResponse($this->multi, $context, implode('', $url), $options, $info, $resolver, $onProgress, $this->logger); } - public function stream(ResponseInterface|iterable $responses, float $timeout = null): ResponseStreamInterface + public function stream(ResponseInterface|iterable $responses, ?float $timeout = null): ResponseStreamInterface { if ($responses instanceof NativeResponse) { $responses = [$responses]; diff --git a/src/Symfony/Component/HttpClient/NoPrivateNetworkHttpClient.php b/src/Symfony/Component/HttpClient/NoPrivateNetworkHttpClient.php index 70c172f68678e..7bfe24db20330 100644 --- a/src/Symfony/Component/HttpClient/NoPrivateNetworkHttpClient.php +++ b/src/Symfony/Component/HttpClient/NoPrivateNetworkHttpClient.php @@ -37,7 +37,7 @@ final class NoPrivateNetworkHttpClient implements HttpClientInterface, LoggerAwa * @param string|array|null $subnets String or array of subnets using CIDR notation that will be used by IpUtils. * If null is passed, the standard private subnets will be used. */ - public function __construct(HttpClientInterface $client, string|array $subnets = null) + public function __construct(HttpClientInterface $client, string|array|null $subnets = null) { if (!class_exists(IpUtils::class)) { throw new \LogicException(sprintf('You cannot use "%s" if the HttpFoundation component is not installed. Try running "composer require symfony/http-foundation".', __CLASS__)); @@ -72,7 +72,7 @@ public function request(string $method, string $url, array $options = []): Respo return $this->client->request($method, $url, $options); } - public function stream(ResponseInterface|iterable $responses, float $timeout = null): ResponseStreamInterface + public function stream(ResponseInterface|iterable $responses, ?float $timeout = null): ResponseStreamInterface { return $this->client->stream($responses, $timeout); } diff --git a/src/Symfony/Component/HttpClient/Psr18Client.php b/src/Symfony/Component/HttpClient/Psr18Client.php index 61201465db86c..d46a7b14d19a7 100644 --- a/src/Symfony/Component/HttpClient/Psr18Client.php +++ b/src/Symfony/Component/HttpClient/Psr18Client.php @@ -54,7 +54,7 @@ final class Psr18Client implements ClientInterface, RequestFactoryInterface, Str private ResponseFactoryInterface $responseFactory; private StreamFactoryInterface $streamFactory; - public function __construct(HttpClientInterface $client = null, ResponseFactoryInterface $responseFactory = null, StreamFactoryInterface $streamFactory = null) + public function __construct(?HttpClientInterface $client = null, ?ResponseFactoryInterface $responseFactory = null, ?StreamFactoryInterface $streamFactory = null) { $this->client = $client ?? HttpClient::create(); $streamFactory ??= $responseFactory instanceof StreamFactoryInterface ? $responseFactory : null; diff --git a/src/Symfony/Component/HttpClient/Response/AmpResponse.php b/src/Symfony/Component/HttpClient/Response/AmpResponse.php index 6dfb9a01ece15..6f73c91b46af5 100644 --- a/src/Symfony/Component/HttpClient/Response/AmpResponse.php +++ b/src/Symfony/Component/HttpClient/Response/AmpResponse.php @@ -45,7 +45,6 @@ final class AmpResponse implements ResponseInterface, StreamableInterface private static string $nextId = 'a'; - private AmpClientState $multi; private ?array $options; private \Closure $onProgress; @@ -54,9 +53,12 @@ final class AmpResponse implements ResponseInterface, StreamableInterface /** * @internal */ - public function __construct(AmpClientState $multi, Request $request, array $options, ?LoggerInterface $logger) - { - $this->multi = $multi; + public function __construct( + private AmpClientState $multi, + Request $request, + array $options, + ?LoggerInterface $logger, + ) { $this->options = &$options; $this->logger = $logger; $this->timeout = $options['timeout']; @@ -134,7 +136,7 @@ public function __construct(AmpClientState $multi, Request $request, array $opti }); } - public function getInfo(string $type = null): mixed + public function getInfo(?string $type = null): mixed { return null !== $type ? $this->info[$type] ?? null : $this->info; } @@ -179,7 +181,7 @@ private static function schedule(self $response, array &$runningResponses): void /** * @param AmpClientState $multi */ - private static function perform(ClientState $multi, array &$responses = null): void + private static function perform(ClientState $multi, ?array &$responses = null): void { if ($responses) { foreach ($responses as $response) { diff --git a/src/Symfony/Component/HttpClient/Response/AsyncContext.php b/src/Symfony/Component/HttpClient/Response/AsyncContext.php index eeb7a11ba0c45..4f4d10616c608 100644 --- a/src/Symfony/Component/HttpClient/Response/AsyncContext.php +++ b/src/Symfony/Component/HttpClient/Response/AsyncContext.php @@ -116,7 +116,7 @@ public function cancel(): ChunkInterface /** * Returns the current info of the response. */ - public function getInfo(string $type = null): mixed + public function getInfo(?string $type = null): mixed { if (null !== $type) { return $this->info[$type] ?? $this->response->getInfo($type); @@ -189,7 +189,7 @@ public function replaceResponse(ResponseInterface $response): ResponseInterface * * @param ?callable(ChunkInterface, self): ?\Iterator $passthru */ - public function passthru(callable $passthru = null): void + public function passthru(?callable $passthru = null): void { $this->passthru = $passthru ?? static function ($chunk, $context) { $context->passthru = null; diff --git a/src/Symfony/Component/HttpClient/Response/AsyncResponse.php b/src/Symfony/Component/HttpClient/Response/AsyncResponse.php index 6f9791546d30b..ea27b1c4a120b 100644 --- a/src/Symfony/Component/HttpClient/Response/AsyncResponse.php +++ b/src/Symfony/Component/HttpClient/Response/AsyncResponse.php @@ -45,7 +45,7 @@ class AsyncResponse implements ResponseInterface, StreamableInterface /** * @param ?callable(ChunkInterface, AsyncContext): ?\Iterator $passthru */ - public function __construct(HttpClientInterface $client, string $method, string $url, array $options, callable $passthru = null) + public function __construct(HttpClientInterface $client, string $method, string $url, array $options, ?callable $passthru = null) { $this->client = $client; $this->shouldBuffer = $options['buffer'] ?? true; @@ -58,7 +58,7 @@ public function __construct(HttpClientInterface $client, string $method, string } $this->response = $client->request($method, $url, ['buffer' => false] + $options); $this->passthru = $passthru; - $this->initializer = static function (self $response, float $timeout = null) { + $this->initializer = static function (self $response, ?float $timeout = null) { if (null === $response->shouldBuffer) { return false; } @@ -66,7 +66,7 @@ public function __construct(HttpClientInterface $client, string $method, string while (true) { foreach (self::stream([$response], $timeout) as $chunk) { if ($chunk->isTimeout() && $response->passthru) { - foreach (self::passthru($response->client, $response, new ErrorChunk($response->offset, new TransportException($chunk->getError()))) as $chunk) { + foreach (self::passthru($response->client, $response, new ErrorChunk($response->offset, $chunk->getError())) as $chunk) { if ($chunk->isFirst()) { return false; } @@ -115,7 +115,7 @@ public function getHeaders(bool $throw = true): array return $headers; } - public function getInfo(string $type = null): mixed + public function getInfo(?string $type = null): mixed { if (null !== $type) { return $this->info[$type] ?? $this->response->getInfo($type); @@ -207,7 +207,7 @@ public function __destruct() /** * @internal */ - public static function stream(iterable $responses, float $timeout = null, string $class = null): \Generator + public static function stream(iterable $responses, ?float $timeout = null, ?string $class = null): \Generator { while ($responses) { $wrappedResponses = []; @@ -315,7 +315,7 @@ public static function stream(iterable $responses, float $timeout = null, string /** * @param \SplObjectStorage|null $asyncMap */ - private static function passthru(HttpClientInterface $client, self $r, ChunkInterface $chunk, \SplObjectStorage $asyncMap = null): \Generator + private static function passthru(HttpClientInterface $client, self $r, ChunkInterface $chunk, ?\SplObjectStorage $asyncMap = null): \Generator { $r->stream = null; $response = $r->response; diff --git a/src/Symfony/Component/HttpClient/Response/CurlResponse.php b/src/Symfony/Component/HttpClient/Response/CurlResponse.php index d472aca543554..8858342b7545a 100644 --- a/src/Symfony/Component/HttpClient/Response/CurlResponse.php +++ b/src/Symfony/Component/HttpClient/Response/CurlResponse.php @@ -32,8 +32,6 @@ final class CurlResponse implements ResponseInterface, StreamableInterface } use TransportResponseTrait; - private CurlClientState $multi; - /** * @var resource */ @@ -42,10 +40,16 @@ final class CurlResponse implements ResponseInterface, StreamableInterface /** * @internal */ - public function __construct(CurlClientState $multi, \CurlHandle|string $ch, array $options = null, LoggerInterface $logger = null, string $method = 'GET', callable $resolveRedirect = null, int $curlVersion = null, string $originalUrl = null) - { - $this->multi = $multi; - + public function __construct( + private CurlClientState $multi, + \CurlHandle|string $ch, + ?array $options = null, + ?LoggerInterface $logger = null, + string $method = 'GET', + ?callable $resolveRedirect = null, + ?int $curlVersion = null, + ?string $originalUrl = null, + ) { if ($ch instanceof \CurlHandle) { $this->handle = $ch; $this->debugBuffer = fopen('php://temp', 'w+'); @@ -98,7 +102,6 @@ public function __construct(CurlClientState $multi, \CurlHandle|string $ch, arra $this->info['pause_handler'] = static function (float $duration) use ($ch, $multi, $execCounter) { if (0 < $duration) { if ($execCounter === $multi->execCounter) { - $multi->execCounter = !\is_float($execCounter) ? 1 + $execCounter : \PHP_INT_MIN; curl_multi_remove_handle($multi->handle, $ch); } @@ -193,7 +196,7 @@ public function __construct(CurlClientState $multi, \CurlHandle|string $ch, arra }); } - public function getInfo(string $type = null): mixed + public function getInfo(?string $type = null): mixed { if (!$info = $this->finalInfo) { $info = array_merge($this->info, curl_getinfo($this->handle)); @@ -266,7 +269,7 @@ private static function schedule(self $response, array &$runningResponses): void /** * @param CurlClientState $multi */ - private static function perform(ClientState $multi, array &$responses = null): void + private static function perform(ClientState $multi, ?array &$responses = null): void { if ($multi->performing) { if ($responses) { diff --git a/src/Symfony/Component/HttpClient/Response/HttplugPromise.php b/src/Symfony/Component/HttpClient/Response/HttplugPromise.php index e9dc24041e5fa..2ec04a18e7703 100644 --- a/src/Symfony/Component/HttpClient/Response/HttplugPromise.php +++ b/src/Symfony/Component/HttpClient/Response/HttplugPromise.php @@ -23,14 +23,12 @@ */ final class HttplugPromise implements HttplugPromiseInterface { - private GuzzlePromiseInterface $promise; - - public function __construct(GuzzlePromiseInterface $promise) - { - $this->promise = $promise; + public function __construct( + private GuzzlePromiseInterface $promise, + ) { } - public function then(callable $onFulfilled = null, callable $onRejected = null): self + public function then(?callable $onFulfilled = null, ?callable $onRejected = null): self { return new self($this->promise->then( $this->wrapThenCallback($onFulfilled), diff --git a/src/Symfony/Component/HttpClient/Response/MockResponse.php b/src/Symfony/Component/HttpClient/Response/MockResponse.php index 19041e3070ccd..f57311e385501 100644 --- a/src/Symfony/Component/HttpClient/Response/MockResponse.php +++ b/src/Symfony/Component/HttpClient/Response/MockResponse.php @@ -97,7 +97,7 @@ public function getRequestMethod(): string return $this->requestMethod; } - public function getInfo(string $type = null): mixed + public function getInfo(?string $type = null): mixed { return null !== $type ? $this->info[$type] ?? null : $this->info; } diff --git a/src/Symfony/Component/HttpClient/Response/NativeResponse.php b/src/Symfony/Component/HttpClient/Response/NativeResponse.php index 4d9e3e2176d82..af7b25f296769 100644 --- a/src/Symfony/Component/HttpClient/Response/NativeResponse.php +++ b/src/Symfony/Component/HttpClient/Response/NativeResponse.php @@ -29,11 +29,6 @@ final class NativeResponse implements ResponseInterface, StreamableInterface use CommonResponseTrait; use TransportResponseTrait; - /** - * @var resource - */ - private $context; - private string $url; private \Closure $resolver; private ?\Closure $onProgress; private ?int $remaining = null; @@ -43,18 +38,23 @@ final class NativeResponse implements ResponseInterface, StreamableInterface */ private $buffer; - private NativeClientState $multi; private float $pauseExpiry = 0.0; /** * @internal + * @param $context resource */ - public function __construct(NativeClientState $multi, $context, string $url, array $options, array &$info, callable $resolver, ?callable $onProgress, ?LoggerInterface $logger) - { - $this->multi = $multi; + public function __construct( + private NativeClientState $multi, + private $context, + private string $url, + array $options, + array &$info, + callable $resolver, + ?callable $onProgress, + ?LoggerInterface $logger, + ) { $this->id = $id = (int) $context; - $this->context = $context; - $this->url = $url; $this->logger = $logger; $this->timeout = $options['timeout']; $this->info = &$info; @@ -86,7 +86,7 @@ public function __construct(NativeClientState $multi, $context, string $url, arr }); } - public function getInfo(string $type = null): mixed + public function getInfo(?string $type = null): mixed { if (!$info = $this->finalInfo) { $info = $this->info; @@ -228,7 +228,7 @@ private static function schedule(self $response, array &$runningResponses): void /** * @param NativeClientState $multi */ - private static function perform(ClientState $multi, array &$responses = null): void + private static function perform(ClientState $multi, ?array &$responses = null): void { foreach ($multi->openHandles as $i => [$pauseExpiry, $h, $buffer, $onProgress]) { if ($pauseExpiry) { diff --git a/src/Symfony/Component/HttpClient/Response/ResponseStream.php b/src/Symfony/Component/HttpClient/Response/ResponseStream.php index 3fea27a77d02d..624b2f1e88e84 100644 --- a/src/Symfony/Component/HttpClient/Response/ResponseStream.php +++ b/src/Symfony/Component/HttpClient/Response/ResponseStream.php @@ -20,11 +20,9 @@ */ final class ResponseStream implements ResponseStreamInterface { - private \Generator $generator; - - public function __construct(\Generator $generator) - { - $this->generator = $generator; + public function __construct( + private \Generator $generator, + ) { } public function key(): ResponseInterface diff --git a/src/Symfony/Component/HttpClient/Response/StreamWrapper.php b/src/Symfony/Component/HttpClient/Response/StreamWrapper.php index b5554a8ad2ced..50b99378494ab 100644 --- a/src/Symfony/Component/HttpClient/Response/StreamWrapper.php +++ b/src/Symfony/Component/HttpClient/Response/StreamWrapper.php @@ -45,7 +45,7 @@ class StreamWrapper * * @return resource */ - public static function createResource(ResponseInterface $response, HttpClientInterface $client = null) + public static function createResource(ResponseInterface $response, ?HttpClientInterface $client = null) { if ($response instanceof StreamableInterface) { $stack = debug_backtrace(\DEBUG_BACKTRACE_PROVIDE_OBJECT | \DEBUG_BACKTRACE_IGNORE_ARGS, 2); diff --git a/src/Symfony/Component/HttpClient/Response/TraceableResponse.php b/src/Symfony/Component/HttpClient/Response/TraceableResponse.php index 4944e2525c11d..e382f2393cfdd 100644 --- a/src/Symfony/Component/HttpClient/Response/TraceableResponse.php +++ b/src/Symfony/Component/HttpClient/Response/TraceableResponse.php @@ -31,17 +31,12 @@ */ class TraceableResponse implements ResponseInterface, StreamableInterface { - private HttpClientInterface $client; - private ResponseInterface $response; - private mixed $content; - private ?StopwatchEvent $event; - - public function __construct(HttpClientInterface $client, ResponseInterface $response, &$content, StopwatchEvent $event = null) - { - $this->client = $client; - $this->response = $response; - $this->content = &$content; - $this->event = $event; + public function __construct( + private HttpClientInterface $client, + private ResponseInterface $response, + private mixed &$content, + private ?StopwatchEvent $event = null, + ) { } public function __sleep(): array @@ -134,7 +129,7 @@ public function cancel(): void } } - public function getInfo(string $type = null): mixed + public function getInfo(?string $type = null): mixed { return $this->response->getInfo($type); } diff --git a/src/Symfony/Component/HttpClient/Response/TransportResponseTrait.php b/src/Symfony/Component/HttpClient/Response/TransportResponseTrait.php index ca27178f96205..7b65fd7990464 100644 --- a/src/Symfony/Component/HttpClient/Response/TransportResponseTrait.php +++ b/src/Symfony/Component/HttpClient/Response/TransportResponseTrait.php @@ -139,7 +139,7 @@ private function doDestruct(): void * * @internal */ - public static function stream(iterable $responses, float $timeout = null): \Generator + public static function stream(iterable $responses, ?float $timeout = null): \Generator { $runningResponses = []; diff --git a/src/Symfony/Component/HttpClient/Retry/GenericRetryStrategy.php b/src/Symfony/Component/HttpClient/Retry/GenericRetryStrategy.php index ecfa5cd3c2748..95daf3b6c82b6 100644 --- a/src/Symfony/Component/HttpClient/Retry/GenericRetryStrategy.php +++ b/src/Symfony/Component/HttpClient/Retry/GenericRetryStrategy.php @@ -36,7 +36,6 @@ class GenericRetryStrategy implements RetryStrategyInterface 510 => self::IDEMPOTENT_METHODS, ]; - private array $statusCodes; private int $delayMs; private float $multiplier; private int $maxDelayMs; @@ -49,10 +48,13 @@ class GenericRetryStrategy implements RetryStrategyInterface * @param int $maxDelayMs Maximum delay to allow (0 means no maximum) * @param float $jitter Probability of randomness int delay (0 = none, 1 = 100% random) */ - public function __construct(array $statusCodes = self::DEFAULT_RETRY_STATUS_CODES, int $delayMs = 1000, float $multiplier = 2.0, int $maxDelayMs = 0, float $jitter = 0.1) - { - $this->statusCodes = $statusCodes; - + public function __construct( + private array $statusCodes = self::DEFAULT_RETRY_STATUS_CODES, + int $delayMs = 1000, + float $multiplier = 2.0, + int $maxDelayMs = 0, + float $jitter = 0.1, + ) { if ($delayMs < 0) { throw new InvalidArgumentException(sprintf('Delay must be greater than or equal to zero: "%s" given.', $delayMs)); } diff --git a/src/Symfony/Component/HttpClient/RetryableHttpClient.php b/src/Symfony/Component/HttpClient/RetryableHttpClient.php index b506c9bccfa95..d3b779420ffa9 100644 --- a/src/Symfony/Component/HttpClient/RetryableHttpClient.php +++ b/src/Symfony/Component/HttpClient/RetryableHttpClient.php @@ -39,7 +39,7 @@ class RetryableHttpClient implements HttpClientInterface, ResetInterface /** * @param int $maxRetries The maximum number of times to retry */ - public function __construct(HttpClientInterface $client, RetryStrategyInterface $strategy = null, int $maxRetries = 3, LoggerInterface $logger = null) + public function __construct(HttpClientInterface $client, ?RetryStrategyInterface $strategy = null, int $maxRetries = 3, ?LoggerInterface $logger = null) { $this->client = $client; $this->strategy = $strategy ?? new GenericRetryStrategy(); diff --git a/src/Symfony/Component/HttpClient/ScopingHttpClient.php b/src/Symfony/Component/HttpClient/ScopingHttpClient.php index fd92a8520d48b..5734d3e4e7c5a 100644 --- a/src/Symfony/Component/HttpClient/ScopingHttpClient.php +++ b/src/Symfony/Component/HttpClient/ScopingHttpClient.php @@ -32,7 +32,7 @@ class ScopingHttpClient implements HttpClientInterface, ResetInterface, LoggerAw private array $defaultOptionsByRegexp; private ?string $defaultRegexp; - public function __construct(HttpClientInterface $client, array $defaultOptionsByRegexp, string $defaultRegexp = null) + public function __construct(HttpClientInterface $client, array $defaultOptionsByRegexp, ?string $defaultRegexp = null) { $this->client = $client; $this->defaultOptionsByRegexp = $defaultOptionsByRegexp; @@ -43,7 +43,7 @@ public function __construct(HttpClientInterface $client, array $defaultOptionsBy } } - public static function forBaseUri(HttpClientInterface $client, string $baseUri, array $defaultOptions = [], string $regexp = null): self + public static function forBaseUri(HttpClientInterface $client, string $baseUri, array $defaultOptions = [], ?string $regexp = null): self { $regexp ??= preg_quote(implode('', self::resolveUrl(self::parseUrl('.'), self::parseUrl($baseUri)))); @@ -88,7 +88,7 @@ public function request(string $method, string $url, array $options = []): Respo return $this->client->request($method, $url, $options); } - public function stream(ResponseInterface|iterable $responses, float $timeout = null): ResponseStreamInterface + public function stream(ResponseInterface|iterable $responses, ?float $timeout = null): ResponseStreamInterface { return $this->client->stream($responses, $timeout); } diff --git a/src/Symfony/Component/HttpClient/Tests/AsyncDecoratorTraitTest.php b/src/Symfony/Component/HttpClient/Tests/AsyncDecoratorTraitTest.php index e1c4b7ee34bff..97e4c42a0c79a 100644 --- a/src/Symfony/Component/HttpClient/Tests/AsyncDecoratorTraitTest.php +++ b/src/Symfony/Component/HttpClient/Tests/AsyncDecoratorTraitTest.php @@ -26,7 +26,7 @@ class AsyncDecoratorTraitTest extends NativeHttpClientTest { - protected function getHttpClient(string $testCase, \Closure $chunkFilter = null, HttpClientInterface $decoratedClient = null): HttpClientInterface + protected function getHttpClient(string $testCase, ?\Closure $chunkFilter = null, ?HttpClientInterface $decoratedClient = null): HttpClientInterface { if ('testHandleIsRemovedOnException' === $testCase) { $this->markTestSkipped("AsyncDecoratorTrait doesn't cache handles"); @@ -43,7 +43,7 @@ protected function getHttpClient(string $testCase, \Closure $chunkFilter = null, private ?\Closure $chunkFilter; - public function __construct(HttpClientInterface $client, \Closure $chunkFilter = null) + public function __construct(HttpClientInterface $client, ?\Closure $chunkFilter = null) { $this->chunkFilter = $chunkFilter; $this->client = $client; diff --git a/src/Symfony/Component/HttpClient/Tests/RetryableHttpClientTest.php b/src/Symfony/Component/HttpClient/Tests/RetryableHttpClientTest.php index fcd839da18c67..849d4119f4ae8 100644 --- a/src/Symfony/Component/HttpClient/Tests/RetryableHttpClientTest.php +++ b/src/Symfony/Component/HttpClient/Tests/RetryableHttpClientTest.php @@ -13,6 +13,7 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\HttpClient\Exception\ServerException; +use Symfony\Component\HttpClient\Exception\TimeoutException; use Symfony\Component\HttpClient\HttpClient; use Symfony\Component\HttpClient\MockHttpClient; use Symfony\Component\HttpClient\NativeHttpClient; @@ -21,6 +22,7 @@ use Symfony\Component\HttpClient\Retry\GenericRetryStrategy; use Symfony\Component\HttpClient\RetryableHttpClient; use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface; +use Symfony\Contracts\HttpClient\Test\TestHttpServer; class RetryableHttpClientTest extends TestCase { @@ -245,6 +247,35 @@ public function testRetryOnErrorAssertContent() self::assertSame('Test out content', $response->getContent(), 'Content should be buffered'); } + /** + * @testWith ["GET"] + * ["POST"] + * ["PUT"] + * ["PATCH"] + * ["DELETE"] + */ + public function testRetryOnHeaderTimeout(string $method) + { + $client = HttpClient::create(); + + if ($client instanceof NativeHttpClient) { + $this->markTestSkipped('NativeHttpClient cannot timeout before receiving headers'); + } + + TestHttpServer::start(); + + $client = new RetryableHttpClient($client); + $response = $client->request($method, 'http://localhost:8057/timeout-header', ['timeout' => 0.1]); + + try { + $response->getStatusCode(); + $this->fail(TimeoutException::class.' expected'); + } catch (TimeoutException $e) { + } + + $this->assertSame('Idle timeout reached for "http://localhost:8057/timeout-header".', $response->getInfo('error')); + } + public function testRetryWithMultipleBaseUris() { $client = new RetryableHttpClient( diff --git a/src/Symfony/Component/HttpClient/Tests/ThrottlingHttpClientTest.php b/src/Symfony/Component/HttpClient/Tests/ThrottlingHttpClientTest.php new file mode 100644 index 0000000000000..b63c5bab63a3e --- /dev/null +++ b/src/Symfony/Component/HttpClient/Tests/ThrottlingHttpClientTest.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpClient\MockHttpClient; +use Symfony\Component\HttpClient\Response\MockResponse; +use Symfony\Component\HttpClient\ThrottlingHttpClient; +use Symfony\Component\RateLimiter\RateLimiterFactory; +use Symfony\Component\RateLimiter\Storage\InMemoryStorage; + +class ThrottlingHttpClientTest extends TestCase +{ + public function testThrottling() + { + $failPauseHandler = static function (float $duration) { + self::fail(sprintf('The pause handler should\'t have been called, but it was called with %f.', $duration)); + }; + + $pauseHandler = static fn (float $expectedDuration) => function (float $duration) use ($expectedDuration) { + self::assertEqualsWithDelta($expectedDuration, $duration, 1); + }; + + $rateLimiterFactory = new RateLimiterFactory([ + 'id' => 'token_bucket', + 'policy' => 'token_bucket', + 'limit' => 2, + 'rate' => ['interval' => '5 seconds', 'amount' => 2], + ], new InMemoryStorage()); + + $client = new ThrottlingHttpClient( + new MockHttpClient([ + new MockResponse('', ['http_code' => 200, 'pause_handler' => $failPauseHandler]), + new MockResponse('', ['http_code' => 200, 'pause_handler' => $failPauseHandler]), + new MockResponse('', ['http_code' => 200, 'pause_handler' => $pauseHandler(5)]), + new MockResponse('', ['http_code' => 200, 'pause_handler' => $pauseHandler(5)]), + new MockResponse('', ['http_code' => 200, 'pause_handler' => $pauseHandler(10)]), + ]), + $rateLimiterFactory->create(), + ); + + $client->request('GET', 'http://example.com/foo'); + $client->request('GET', 'http://example.com/bar'); + $client->request('GET', 'http://example.com/baz'); + $client->request('GET', 'http://example.com/qux'); + $client->request('GET', 'http://example.com/corge'); + } +} diff --git a/src/Symfony/Component/HttpClient/ThrottlingHttpClient.php b/src/Symfony/Component/HttpClient/ThrottlingHttpClient.php new file mode 100644 index 0000000000000..66fc173053771 --- /dev/null +++ b/src/Symfony/Component/HttpClient/ThrottlingHttpClient.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient; + +use Symfony\Component\RateLimiter\LimiterInterface; +use Symfony\Contracts\HttpClient\HttpClientInterface; +use Symfony\Contracts\HttpClient\ResponseInterface; +use Symfony\Contracts\Service\ResetInterface; + +/** + * Limits the number of requests within a certain period. + */ +class ThrottlingHttpClient implements HttpClientInterface, ResetInterface +{ + use DecoratorTrait { + reset as private traitReset; + } + + public function __construct( + HttpClientInterface $client, + private readonly LimiterInterface $rateLimiter, + ) { + $this->client = $client; + } + + public function request(string $method, string $url, array $options = []): ResponseInterface + { + $response = $this->client->request($method, $url, $options); + + if (0 < $waitDuration = $this->rateLimiter->reserve()->getWaitDuration()) { + $response->getInfo('pause_handler')($waitDuration); + } + + return $response; + } + + public function reset(): void + { + $this->traitReset(); + $this->rateLimiter->reset(); + } +} diff --git a/src/Symfony/Component/HttpClient/TraceableHttpClient.php b/src/Symfony/Component/HttpClient/TraceableHttpClient.php index 974e9f6f00646..9f1bd515e0914 100644 --- a/src/Symfony/Component/HttpClient/TraceableHttpClient.php +++ b/src/Symfony/Component/HttpClient/TraceableHttpClient.php @@ -30,7 +30,7 @@ final class TraceableHttpClient implements HttpClientInterface, ResetInterface, private ?Stopwatch $stopwatch; private \ArrayObject $tracedRequests; - public function __construct(HttpClientInterface $client, Stopwatch $stopwatch = null) + public function __construct(HttpClientInterface $client, ?Stopwatch $stopwatch = null) { $this->client = $client; $this->stopwatch = $stopwatch; @@ -66,7 +66,7 @@ public function request(string $method, string $url, array $options = []): Respo return new TraceableResponse($this->client, $this->client->request($method, $url, $options), $content, $this->stopwatch?->start("$method $url", 'http_client')); } - public function stream(ResponseInterface|iterable $responses, float $timeout = null): ResponseStreamInterface + public function stream(ResponseInterface|iterable $responses, ?float $timeout = null): ResponseStreamInterface { if ($responses instanceof TraceableResponse) { $responses = [$responses]; diff --git a/src/Symfony/Component/HttpClient/UriTemplateHttpClient.php b/src/Symfony/Component/HttpClient/UriTemplateHttpClient.php index 55ae724f12207..2767ed3687eaf 100644 --- a/src/Symfony/Component/HttpClient/UriTemplateHttpClient.php +++ b/src/Symfony/Component/HttpClient/UriTemplateHttpClient.php @@ -22,7 +22,7 @@ class UriTemplateHttpClient implements HttpClientInterface, ResetInterface /** * @param (\Closure(string $url, array $vars): string)|null $expander */ - public function __construct(HttpClientInterface $client = null, private ?\Closure $expander = null, private array $defaultVars = []) + public function __construct(?HttpClientInterface $client = null, private ?\Closure $expander = null, private array $defaultVars = []) { $this->client = $client ?? HttpClient::create(); } diff --git a/src/Symfony/Component/HttpClient/composer.json b/src/Symfony/Component/HttpClient/composer.json index 278a9b23601f9..5f56f21db99ba 100644 --- a/src/Symfony/Component/HttpClient/composer.json +++ b/src/Symfony/Component/HttpClient/composer.json @@ -40,6 +40,7 @@ "symfony/http-kernel": "^6.4|^7.0", "symfony/messenger": "^6.4|^7.0", "symfony/process": "^6.4|^7.0", + "symfony/rate-limiter": "^6.4|^7.0", "symfony/stopwatch": "^6.4|^7.0" }, "conflict": { diff --git a/src/Symfony/Component/HttpFoundation/BinaryFileResponse.php b/src/Symfony/Component/HttpFoundation/BinaryFileResponse.php index 844153745b2cb..6a466f5711d7c 100644 --- a/src/Symfony/Component/HttpFoundation/BinaryFileResponse.php +++ b/src/Symfony/Component/HttpFoundation/BinaryFileResponse.php @@ -42,7 +42,7 @@ class BinaryFileResponse extends Response * @param bool $autoEtag Whether the ETag header should be automatically set * @param bool $autoLastModified Whether the Last-Modified header should be automatically set */ - public function __construct(\SplFileInfo|string $file, int $status = 200, array $headers = [], bool $public = true, string $contentDisposition = null, bool $autoEtag = false, bool $autoLastModified = true) + public function __construct(\SplFileInfo|string $file, int $status = 200, array $headers = [], bool $public = true, ?string $contentDisposition = null, bool $autoEtag = false, bool $autoLastModified = true) { parent::__construct(null, $status, $headers); @@ -60,7 +60,7 @@ public function __construct(\SplFileInfo|string $file, int $status = 200, array * * @throws FileException */ - public function setFile(\SplFileInfo|string $file, string $contentDisposition = null, bool $autoEtag = false, bool $autoLastModified = true): static + public function setFile(\SplFileInfo|string $file, ?string $contentDisposition = null, bool $autoEtag = false, bool $autoLastModified = true): static { if (!$file instanceof File) { if ($file instanceof \SplFileInfo) { diff --git a/src/Symfony/Component/HttpFoundation/CHANGELOG.md b/src/Symfony/Component/HttpFoundation/CHANGELOG.md index d4d07411f70e7..a26edc4626a77 100644 --- a/src/Symfony/Component/HttpFoundation/CHANGELOG.md +++ b/src/Symfony/Component/HttpFoundation/CHANGELOG.md @@ -5,6 +5,8 @@ CHANGELOG --- * Add `UploadedFile::getClientOriginalPath()` + * Add `QueryParameterRequestMatcher` + * Add `HeaderRequestMatcher` 7.0 --- diff --git a/src/Symfony/Component/HttpFoundation/Cookie.php b/src/Symfony/Component/HttpFoundation/Cookie.php index b8982f75fc8d2..402ae41797630 100644 --- a/src/Symfony/Component/HttpFoundation/Cookie.php +++ b/src/Symfony/Component/HttpFoundation/Cookie.php @@ -76,7 +76,7 @@ public static function fromString(string $cookie, bool $decode = false): static * * @param self::SAMESITE_*|''|null $sameSite */ - public static function create(string $name, string $value = null, int|string|\DateTimeInterface $expire = 0, ?string $path = '/', string $domain = null, bool $secure = null, bool $httpOnly = true, bool $raw = false, ?string $sameSite = self::SAMESITE_LAX, bool $partitioned = false): self + public static function create(string $name, ?string $value = null, int|string|\DateTimeInterface $expire = 0, ?string $path = '/', ?string $domain = null, ?bool $secure = null, bool $httpOnly = true, bool $raw = false, ?string $sameSite = self::SAMESITE_LAX, bool $partitioned = false): self { return new self($name, $value, $expire, $path, $domain, $secure, $httpOnly, $raw, $sameSite, $partitioned); } @@ -94,7 +94,7 @@ public static function create(string $name, string $value = null, int|string|\Da * * @throws \InvalidArgumentException */ - public function __construct(string $name, string $value = null, int|string|\DateTimeInterface $expire = 0, ?string $path = '/', string $domain = null, bool $secure = null, bool $httpOnly = true, bool $raw = false, ?string $sameSite = self::SAMESITE_LAX, bool $partitioned = false) + public function __construct(string $name, ?string $value = null, int|string|\DateTimeInterface $expire = 0, ?string $path = '/', ?string $domain = null, ?bool $secure = null, bool $httpOnly = true, bool $raw = false, ?string $sameSite = self::SAMESITE_LAX, bool $partitioned = false) { // from PHP source code if ($raw && false !== strpbrk($name, self::RESERVED_CHARS_LIST)) { diff --git a/src/Symfony/Component/HttpFoundation/Exception/SessionNotFoundException.php b/src/Symfony/Component/HttpFoundation/Exception/SessionNotFoundException.php index 94b0cb69aae1f..80a21bf151c8e 100644 --- a/src/Symfony/Component/HttpFoundation/Exception/SessionNotFoundException.php +++ b/src/Symfony/Component/HttpFoundation/Exception/SessionNotFoundException.php @@ -20,7 +20,7 @@ */ class SessionNotFoundException extends \LogicException implements RequestExceptionInterface { - public function __construct(string $message = 'There is currently no session available.', int $code = 0, \Throwable $previous = null) + public function __construct(string $message = 'There is currently no session available.', int $code = 0, ?\Throwable $previous = null) { parent::__construct($message, $code, $previous); } diff --git a/src/Symfony/Component/HttpFoundation/File/File.php b/src/Symfony/Component/HttpFoundation/File/File.php index e8ce4bcf8075b..34ca5a53774ae 100644 --- a/src/Symfony/Component/HttpFoundation/File/File.php +++ b/src/Symfony/Component/HttpFoundation/File/File.php @@ -82,7 +82,7 @@ public function getMimeType(): ?string * * @throws FileException if the target file could not be created */ - public function move(string $directory, string $name = null): self + public function move(string $directory, ?string $name = null): self { $target = $this->getTargetFile($directory, $name); @@ -112,7 +112,7 @@ public function getContent(): string return $content; } - protected function getTargetFile(string $directory, string $name = null): self + protected function getTargetFile(string $directory, ?string $name = null): self { if (!is_dir($directory)) { if (false === @mkdir($directory, 0777, true) && !is_dir($directory)) { diff --git a/src/Symfony/Component/HttpFoundation/File/UploadedFile.php b/src/Symfony/Component/HttpFoundation/File/UploadedFile.php index b0a01f30f68b6..74e929f9b89e5 100644 --- a/src/Symfony/Component/HttpFoundation/File/UploadedFile.php +++ b/src/Symfony/Component/HttpFoundation/File/UploadedFile.php @@ -61,7 +61,7 @@ class UploadedFile extends File * @throws FileException If file_uploads is disabled * @throws FileNotFoundException If the file does not exist */ - public function __construct(string $path, string $originalName, string $mimeType = null, int $error = null, bool $test = false) + public function __construct(string $path, string $originalName, ?string $mimeType = null, ?int $error = null, bool $test = false) { $this->originalName = $this->getName($originalName); $this->originalPath = strtr($originalName, '\\', '/'); @@ -175,7 +175,7 @@ public function isValid(): bool * * @throws FileException if, for any reason, the file could not have been moved */ - public function move(string $directory, string $name = null): File + public function move(string $directory, ?string $name = null): File { if ($this->isValid()) { if ($this->test) { diff --git a/src/Symfony/Component/HttpFoundation/HeaderBag.php b/src/Symfony/Component/HttpFoundation/HeaderBag.php index 40750d1c0910b..4bab3764b1f51 100644 --- a/src/Symfony/Component/HttpFoundation/HeaderBag.php +++ b/src/Symfony/Component/HttpFoundation/HeaderBag.php @@ -65,7 +65,7 @@ public function __toString(): string * * @return ($key is null ? array> : list) */ - public function all(string $key = null): array + public function all(?string $key = null): array { if (null !== $key) { return $this->headers[strtr($key, self::UPPER, self::LOWER)] ?? []; @@ -106,7 +106,7 @@ public function add(array $headers): void /** * Returns the first header by name or the default one. */ - public function get(string $key, string $default = null): ?string + public function get(string $key, ?string $default = null): ?string { $headers = $this->all($key); @@ -187,7 +187,7 @@ public function remove(string $key): void * * @throws \RuntimeException When the HTTP header is not parseable */ - public function getDate(string $key, \DateTimeInterface $default = null): ?\DateTimeImmutable + public function getDate(string $key, ?\DateTimeInterface $default = null): ?\DateTimeImmutable { if (null === $value = $this->get($key)) { return null !== $default ? \DateTimeImmutable::createFromInterface($default) : null; diff --git a/src/Symfony/Component/HttpFoundation/InputBag.php b/src/Symfony/Component/HttpFoundation/InputBag.php index 343adbcce6273..78903f2101eeb 100644 --- a/src/Symfony/Component/HttpFoundation/InputBag.php +++ b/src/Symfony/Component/HttpFoundation/InputBag.php @@ -84,7 +84,7 @@ public function set(string $key, mixed $value): void * * @return ?T */ - public function getEnum(string $key, string $class, \BackedEnum $default = null): ?\BackedEnum + public function getEnum(string $key, string $class, ?\BackedEnum $default = null): ?\BackedEnum { try { return parent::getEnum($key, $class, $default); diff --git a/src/Symfony/Component/HttpFoundation/ParameterBag.php b/src/Symfony/Component/HttpFoundation/ParameterBag.php index 004b59a919213..4cfc5b3a8fc6b 100644 --- a/src/Symfony/Component/HttpFoundation/ParameterBag.php +++ b/src/Symfony/Component/HttpFoundation/ParameterBag.php @@ -35,7 +35,7 @@ public function __construct(array $parameters = []) * * @param string|null $key The name of the parameter to return or null to get them all */ - public function all(string $key = null): array + public function all(?string $key = null): array { if (null === $key) { return $this->parameters; @@ -161,7 +161,7 @@ public function getBoolean(string $key, bool $default = false): bool * * @return ?T */ - public function getEnum(string $key, string $class, \BackedEnum $default = null): ?\BackedEnum + public function getEnum(string $key, string $class, ?\BackedEnum $default = null): ?\BackedEnum { $value = $this->get($key); diff --git a/src/Symfony/Component/HttpFoundation/Request.php b/src/Symfony/Component/HttpFoundation/Request.php index a31f1aace29d5..919e9e01ff9cd 100644 --- a/src/Symfony/Component/HttpFoundation/Request.php +++ b/src/Symfony/Component/HttpFoundation/Request.php @@ -395,7 +395,7 @@ public static function setFactory(?callable $callable): void * @param array|null $files The FILES parameters * @param array|null $server The SERVER parameters */ - public function duplicate(array $query = null, array $request = null, array $attributes = null, array $cookies = null, array $files = null, array $server = null): static + public function duplicate(?array $query = null, ?array $request = null, ?array $attributes = null, ?array $cookies = null, ?array $files = null, ?array $server = null): static { $dup = clone $this; if (null !== $query) { @@ -1524,7 +1524,7 @@ public function getPreferredFormat(?string $default = 'html'): ?string * * @param string[] $locales An array of ordered available locales */ - public function getPreferredLanguage(array $locales = null): ?string + public function getPreferredLanguage(?array $locales = null): ?string { $preferredLanguages = $this->getLanguages(); @@ -1917,7 +1917,7 @@ public function isFromTrustedProxy(): bool * getPort(), isSecure(), getHost(), getClientIps(), getBaseUrl() etc. Thus, we try to cache the results for * best performance. */ - private function getTrustedValues(int $type, string $ip = null): array + private function getTrustedValues(int $type, ?string $ip = null): array { $cacheKey = $type."\0".((self::$trustedHeaderSet & $type) ? $this->headers->get(self::TRUSTED_HEADERS[$type]) : ''); $cacheKey .= "\0".$ip."\0".$this->headers->get(self::TRUSTED_HEADERS[self::HEADER_FORWARDED]); diff --git a/src/Symfony/Component/HttpFoundation/RequestMatcher/HeaderRequestMatcher.php b/src/Symfony/Component/HttpFoundation/RequestMatcher/HeaderRequestMatcher.php new file mode 100644 index 0000000000000..8617a8aca40b9 --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/RequestMatcher/HeaderRequestMatcher.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\RequestMatcher; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestMatcherInterface; + +/** + * Checks the presence of HTTP headers in a Request. + * + * @author Alexandre Daubois + */ +class HeaderRequestMatcher implements RequestMatcherInterface +{ + /** + * @var string[] + */ + private array $headers; + + /** + * @param string[]|string $headers A header or a list of headers + * Strings can contain a comma-delimited list of headers + */ + public function __construct(array|string $headers) + { + $this->headers = array_reduce((array) $headers, static fn (array $headers, string $header) => array_merge($headers, preg_split('/\s*,\s*/', $header)), []); + } + + public function matches(Request $request): bool + { + if (!$this->headers) { + return true; + } + + foreach ($this->headers as $header) { + if (!$request->headers->has($header)) { + return false; + } + } + + return true; + } +} diff --git a/src/Symfony/Component/HttpFoundation/RequestMatcher/QueryParameterRequestMatcher.php b/src/Symfony/Component/HttpFoundation/RequestMatcher/QueryParameterRequestMatcher.php new file mode 100644 index 0000000000000..86161e7c031dc --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/RequestMatcher/QueryParameterRequestMatcher.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\RequestMatcher; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestMatcherInterface; + +/** + * Checks the presence of HTTP query parameters of a Request. + * + * @author Alexandre Daubois + */ +class QueryParameterRequestMatcher implements RequestMatcherInterface +{ + /** + * @var string[] + */ + private array $parameters; + + /** + * @param string[]|string $parameters A parameter or a list of parameters + * Strings can contain a comma-delimited list of query parameters + */ + public function __construct(array|string $parameters) + { + $this->parameters = array_reduce(array_map(strtolower(...), (array) $parameters), static fn (array $parameters, string $parameter) => array_merge($parameters, preg_split('/\s*,\s*/', $parameter)), []); + } + + public function matches(Request $request): bool + { + if (!$this->parameters) { + return true; + } + + return 0 === \count(array_diff_assoc($this->parameters, $request->query->keys())); + } +} diff --git a/src/Symfony/Component/HttpFoundation/Response.php b/src/Symfony/Component/HttpFoundation/Response.php index efe61358f1478..19f8f5e1e84f5 100644 --- a/src/Symfony/Component/HttpFoundation/Response.php +++ b/src/Symfony/Component/HttpFoundation/Response.php @@ -311,7 +311,7 @@ public function prepare(Request $request): static * * @return $this */ - public function sendHeaders(int $statusCode = null): static + public function sendHeaders(?int $statusCode = null): static { // headers have already been sent by the developer if (headers_sent()) { @@ -471,7 +471,7 @@ public function getProtocolVersion(): string * * @final */ - public function setStatusCode(int $code, string $text = null): static + public function setStatusCode(int $code, ?string $text = null): static { $this->statusCode = $code; if ($this->isInvalid()) { @@ -1249,7 +1249,7 @@ public function isNotFound(): bool * * @final */ - public function isRedirect(string $location = null): bool + public function isRedirect(?string $location = null): bool { return \in_array($this->statusCode, [201, 301, 302, 303, 307, 308]) && (null === $location ?: $location == $this->headers->get('Location')); } diff --git a/src/Symfony/Component/HttpFoundation/ResponseHeaderBag.php b/src/Symfony/Component/HttpFoundation/ResponseHeaderBag.php index 2b5f6712837cf..c5a28f579519a 100644 --- a/src/Symfony/Component/HttpFoundation/ResponseHeaderBag.php +++ b/src/Symfony/Component/HttpFoundation/ResponseHeaderBag.php @@ -80,7 +80,7 @@ public function replace(array $headers = []): void } } - public function all(string $key = null): array + public function all(?string $key = null): array { $headers = parent::all(); @@ -166,7 +166,7 @@ public function setCookie(Cookie $cookie): void /** * Removes a cookie from the array, but does not unset it in the browser. */ - public function removeCookie(string $name, ?string $path = '/', string $domain = null): void + public function removeCookie(string $name, ?string $path = '/', ?string $domain = null): void { $path ??= '/'; @@ -216,10 +216,14 @@ public function getCookies(string $format = self::COOKIES_FLAT): array /** * Clears a cookie in the browser. + * + * @param bool $partitioned */ - public function clearCookie(string $name, ?string $path = '/', string $domain = null, bool $secure = false, bool $httpOnly = true, string $sameSite = null): void + public function clearCookie(string $name, ?string $path = '/', ?string $domain = null, bool $secure = false, bool $httpOnly = true, ?string $sameSite = null /* , bool $partitioned = false */): void { - $this->setCookie(new Cookie($name, null, 1, $path, $domain, $secure, $httpOnly, false, $sameSite)); + $partitioned = 6 < \func_num_args() ? \func_get_arg(6) : false; + + $this->setCookie(new Cookie($name, null, 1, $path, $domain, $secure, $httpOnly, false, $sameSite, $partitioned)); } /** diff --git a/src/Symfony/Component/HttpFoundation/ServerBag.php b/src/Symfony/Component/HttpFoundation/ServerBag.php index 3e912cb8004eb..09fc386643bbb 100644 --- a/src/Symfony/Component/HttpFoundation/ServerBag.php +++ b/src/Symfony/Component/HttpFoundation/ServerBag.php @@ -29,7 +29,7 @@ public function getHeaders(): array foreach ($this->parameters as $key => $value) { if (str_starts_with($key, 'HTTP_')) { $headers[substr($key, 5)] = $value; - } elseif (\in_array($key, ['CONTENT_TYPE', 'CONTENT_LENGTH', 'CONTENT_MD5'], true)) { + } elseif (\in_array($key, ['CONTENT_TYPE', 'CONTENT_LENGTH', 'CONTENT_MD5'], true) && '' !== $value) { $headers[$key] = $value; } } diff --git a/src/Symfony/Component/HttpFoundation/Session/Session.php b/src/Symfony/Component/HttpFoundation/Session/Session.php index f212b52e49662..f25e81052e1cd 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Session.php +++ b/src/Symfony/Component/HttpFoundation/Session/Session.php @@ -40,7 +40,7 @@ class Session implements FlashBagAwareSessionInterface, \IteratorAggregate, \Cou private int $usageIndex = 0; private ?\Closure $usageReporter; - public function __construct(SessionStorageInterface $storage = null, AttributeBagInterface $attributes = null, FlashBagInterface $flashes = null, callable $usageReporter = null) + public function __construct(?SessionStorageInterface $storage = null, ?AttributeBagInterface $attributes = null, ?FlashBagInterface $flashes = null, ?callable $usageReporter = null) { $this->storage = $storage ?? new NativeSessionStorage(); $this->usageReporter = null === $usageReporter ? null : $usageReporter(...); @@ -142,14 +142,14 @@ public function isEmpty(): bool return true; } - public function invalidate(int $lifetime = null): bool + public function invalidate(?int $lifetime = null): bool { $this->storage->clear(); return $this->migrate(true, $lifetime); } - public function migrate(bool $destroy = false, int $lifetime = null): bool + public function migrate(bool $destroy = false, ?int $lifetime = null): bool { return $this->storage->regenerate($destroy, $lifetime); } diff --git a/src/Symfony/Component/HttpFoundation/Session/SessionFactory.php b/src/Symfony/Component/HttpFoundation/Session/SessionFactory.php index cdb6af51e7e16..c06ed4b7d84f4 100644 --- a/src/Symfony/Component/HttpFoundation/Session/SessionFactory.php +++ b/src/Symfony/Component/HttpFoundation/Session/SessionFactory.php @@ -26,7 +26,7 @@ class SessionFactory implements SessionFactoryInterface private SessionStorageFactoryInterface $storageFactory; private ?\Closure $usageReporter; - public function __construct(RequestStack $requestStack, SessionStorageFactoryInterface $storageFactory, callable $usageReporter = null) + public function __construct(RequestStack $requestStack, SessionStorageFactoryInterface $storageFactory, ?callable $usageReporter = null) { $this->requestStack = $requestStack; $this->storageFactory = $storageFactory; diff --git a/src/Symfony/Component/HttpFoundation/Session/SessionInterface.php b/src/Symfony/Component/HttpFoundation/Session/SessionInterface.php index f8ce12e228f85..3e29ba42c9724 100644 --- a/src/Symfony/Component/HttpFoundation/Session/SessionInterface.php +++ b/src/Symfony/Component/HttpFoundation/Session/SessionInterface.php @@ -58,7 +58,7 @@ public function setName(string $name): void; * to expire with browser session. Time is in seconds, and is * not a Unix timestamp. */ - public function invalidate(int $lifetime = null): bool; + public function invalidate(?int $lifetime = null): bool; /** * Migrates the current session to a new session id while maintaining all @@ -70,7 +70,7 @@ public function invalidate(int $lifetime = null): bool; * to expire with browser session. Time is in seconds, and is * not a Unix timestamp. */ - public function migrate(bool $destroy = false, int $lifetime = null): bool; + public function migrate(bool $destroy = false, ?int $lifetime = null): bool; /** * Force the session to be saved and closed. diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/NativeFileSessionHandler.php b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/NativeFileSessionHandler.php index f6e73f9e6ce62..f8c6151a4f436 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/NativeFileSessionHandler.php +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/NativeFileSessionHandler.php @@ -28,7 +28,7 @@ class NativeFileSessionHandler extends \SessionHandler * @throws \InvalidArgumentException On invalid $savePath * @throws \RuntimeException When failing to create the save directory */ - public function __construct(string $savePath = null) + public function __construct(?string $savePath = null) { $baseDir = $savePath ??= \ini_get('session.save_path'); diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/PdoSessionHandler.php b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/PdoSessionHandler.php index c09c9cb0c12fc..aa8ab5690ae62 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/PdoSessionHandler.php +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/PdoSessionHandler.php @@ -151,7 +151,7 @@ class PdoSessionHandler extends AbstractSessionHandler * * @throws \InvalidArgumentException When PDO error mode is not PDO::ERRMODE_EXCEPTION */ - public function __construct(#[\SensitiveParameter] \PDO|string $pdoOrDsn = null, #[\SensitiveParameter] array $options = []) + public function __construct(#[\SensitiveParameter] \PDO|string|null $pdoOrDsn = null, #[\SensitiveParameter] array $options = []) { if ($pdoOrDsn instanceof \PDO) { if (\PDO::ERRMODE_EXCEPTION !== $pdoOrDsn->getAttribute(\PDO::ATTR_ERRMODE)) { @@ -181,7 +181,7 @@ public function __construct(#[\SensitiveParameter] \PDO|string $pdoOrDsn = null, /** * Adds the Table to the Schema if it doesn't exist. */ - public function configureSchema(Schema $schema, \Closure $isSameDatabase = null): void + public function configureSchema(Schema $schema, ?\Closure $isSameDatabase = null): void { if ($schema->hasTable($this->table) || ($isSameDatabase && !$isSameDatabase($this->getConnection()->exec(...)))) { return; diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/MetadataBag.php b/src/Symfony/Component/HttpFoundation/Session/Storage/MetadataBag.php index 9bcaafc4e7def..3e80f7dd80b6a 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Storage/MetadataBag.php +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/MetadataBag.php @@ -75,7 +75,7 @@ public function getLifetime(): int * to expire with browser session. Time is in seconds, and is * not a Unix timestamp. */ - public function stampNew(int $lifetime = null): void + public function stampNew(?int $lifetime = null): void { $this->stampCreated($lifetime); } @@ -124,7 +124,7 @@ public function setName(string $name): void $this->name = $name; } - private function stampCreated(int $lifetime = null): void + private function stampCreated(?int $lifetime = null): void { $timeStamp = time(); $this->meta[self::CREATED] = $this->meta[self::UPDATED] = $this->lastUsed = $timeStamp; diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/MockArraySessionStorage.php b/src/Symfony/Component/HttpFoundation/Session/Storage/MockArraySessionStorage.php index 8ba28835d77ca..f4f5132a1407e 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Storage/MockArraySessionStorage.php +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/MockArraySessionStorage.php @@ -39,7 +39,7 @@ class MockArraySessionStorage implements SessionStorageInterface */ protected array $bags = []; - public function __construct(string $name = 'MOCKSESSID', MetadataBag $metaBag = null) + public function __construct(string $name = 'MOCKSESSID', ?MetadataBag $metaBag = null) { $this->name = $name; $this->setMetadataBag($metaBag); @@ -65,7 +65,7 @@ public function start(): bool return true; } - public function regenerate(bool $destroy = false, int $lifetime = null): bool + public function regenerate(bool $destroy = false, ?int $lifetime = null): bool { if (!$this->started) { $this->start(); diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/MockFileSessionStorage.php b/src/Symfony/Component/HttpFoundation/Session/Storage/MockFileSessionStorage.php index d5ca171066632..48dd74dfbb255 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Storage/MockFileSessionStorage.php +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/MockFileSessionStorage.php @@ -30,7 +30,7 @@ class MockFileSessionStorage extends MockArraySessionStorage /** * @param string|null $savePath Path of directory to save session files */ - public function __construct(string $savePath = null, string $name = 'MOCKSESSID', MetadataBag $metaBag = null) + public function __construct(?string $savePath = null, string $name = 'MOCKSESSID', ?MetadataBag $metaBag = null) { $savePath ??= sys_get_temp_dir(); @@ -60,7 +60,7 @@ public function start(): bool return true; } - public function regenerate(bool $destroy = false, int $lifetime = null): bool + public function regenerate(bool $destroy = false, ?int $lifetime = null): bool { if (!$this->started) { $this->start(); diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/MockFileSessionStorageFactory.php b/src/Symfony/Component/HttpFoundation/Session/Storage/MockFileSessionStorageFactory.php index 8ecf943dcb39b..6727cf14fc52b 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Storage/MockFileSessionStorageFactory.php +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/MockFileSessionStorageFactory.php @@ -28,7 +28,7 @@ class MockFileSessionStorageFactory implements SessionStorageFactoryInterface /** * @see MockFileSessionStorage constructor. */ - public function __construct(string $savePath = null, string $name = 'MOCKSESSID', MetadataBag $metaBag = null) + public function __construct(?string $savePath = null, string $name = 'MOCKSESSID', ?MetadataBag $metaBag = null) { $this->savePath = $savePath; $this->name = $name; diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/NativeSessionStorage.php b/src/Symfony/Component/HttpFoundation/Session/Storage/NativeSessionStorage.php index 4f6a9ba0d0482..d32292ae02c59 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Storage/NativeSessionStorage.php +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/NativeSessionStorage.php @@ -73,7 +73,7 @@ class NativeSessionStorage implements SessionStorageInterface * trans_sid_hosts, $_SERVER['HTTP_HOST'] * trans_sid_tags, "a=href,area=href,frame=src,form=" */ - public function __construct(array $options = [], AbstractProxy|\SessionHandlerInterface $handler = null, MetadataBag $metaBag = null) + public function __construct(array $options = [], AbstractProxy|\SessionHandlerInterface|null $handler = null, ?MetadataBag $metaBag = null) { if (!\extension_loaded('session')) { throw new \LogicException('PHP extension "session" is required.'); @@ -182,7 +182,7 @@ public function setName(string $name): void $this->saveHandler->setName($name); } - public function regenerate(bool $destroy = false, int $lifetime = null): bool + public function regenerate(bool $destroy = false, ?int $lifetime = null): bool { // Cannot regenerate the session ID for non-active sessions. if (\PHP_SESSION_ACTIVE !== session_status()) { @@ -382,7 +382,7 @@ public function setSaveHandler(AbstractProxy|\SessionHandlerInterface|null $save * PHP takes the return value from the read() handler, unserializes it * and populates $_SESSION with the result automatically. */ - protected function loadSession(array &$session = null): void + protected function loadSession(?array &$session = null): void { if (null === $session) { $session = &$_SESSION; diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/NativeSessionStorageFactory.php b/src/Symfony/Component/HttpFoundation/Session/Storage/NativeSessionStorageFactory.php index 08901284c33a2..6463a4c1b19db 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Storage/NativeSessionStorageFactory.php +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/NativeSessionStorageFactory.php @@ -30,7 +30,7 @@ class NativeSessionStorageFactory implements SessionStorageFactoryInterface /** * @see NativeSessionStorage constructor. */ - public function __construct(array $options = [], AbstractProxy|\SessionHandlerInterface $handler = null, MetadataBag $metaBag = null, bool $secure = false) + public function __construct(array $options = [], AbstractProxy|\SessionHandlerInterface|null $handler = null, ?MetadataBag $metaBag = null, bool $secure = false) { $this->options = $options; $this->handler = $handler; diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/PhpBridgeSessionStorage.php b/src/Symfony/Component/HttpFoundation/Session/Storage/PhpBridgeSessionStorage.php index 2583aeb0bc682..8a8c50c933438 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Storage/PhpBridgeSessionStorage.php +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/PhpBridgeSessionStorage.php @@ -20,7 +20,7 @@ */ class PhpBridgeSessionStorage extends NativeSessionStorage { - public function __construct(AbstractProxy|\SessionHandlerInterface $handler = null, MetadataBag $metaBag = null) + public function __construct(AbstractProxy|\SessionHandlerInterface|null $handler = null, ?MetadataBag $metaBag = null) { if (!\extension_loaded('session')) { throw new \LogicException('PHP extension "session" is required.'); diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/PhpBridgeSessionStorageFactory.php b/src/Symfony/Component/HttpFoundation/Session/Storage/PhpBridgeSessionStorageFactory.php index 5cc73802422f3..aa4f800d3af1c 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Storage/PhpBridgeSessionStorageFactory.php +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/PhpBridgeSessionStorageFactory.php @@ -26,7 +26,7 @@ class PhpBridgeSessionStorageFactory implements SessionStorageFactoryInterface private ?MetadataBag $metaBag; private bool $secure; - public function __construct(AbstractProxy|\SessionHandlerInterface $handler = null, MetadataBag $metaBag = null, bool $secure = false) + public function __construct(AbstractProxy|\SessionHandlerInterface|null $handler = null, ?MetadataBag $metaBag = null, bool $secure = false) { $this->handler = $handler; $this->metaBag = $metaBag; diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/SessionStorageInterface.php b/src/Symfony/Component/HttpFoundation/Session/Storage/SessionStorageInterface.php index 18cfec1b66ad1..c51850de73085 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Storage/SessionStorageInterface.php +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/SessionStorageInterface.php @@ -80,7 +80,7 @@ public function setName(string $name): void; * * @throws \RuntimeException If an error occurs while regenerating this storage */ - public function regenerate(bool $destroy = false, int $lifetime = null): bool; + public function regenerate(bool $destroy = false, ?int $lifetime = null): bool; /** * Force the session to be saved and closed. diff --git a/src/Symfony/Component/HttpFoundation/StreamedResponse.php b/src/Symfony/Component/HttpFoundation/StreamedResponse.php index e6a0c0fef50dd..3acaade17d645 100644 --- a/src/Symfony/Component/HttpFoundation/StreamedResponse.php +++ b/src/Symfony/Component/HttpFoundation/StreamedResponse.php @@ -34,7 +34,7 @@ class StreamedResponse extends Response /** * @param int $status The HTTP status code (200 "OK" by default) */ - public function __construct(callable $callback = null, int $status = 200, array $headers = []) + public function __construct(?callable $callback = null, int $status = 200, array $headers = []) { parent::__construct(null, $status, $headers); @@ -73,7 +73,7 @@ public function getCallback(): ?\Closure * * @return $this */ - public function sendHeaders(int $statusCode = null): static + public function sendHeaders(?int $statusCode = null): static { if ($this->headersSent) { return $this; diff --git a/src/Symfony/Component/HttpFoundation/Test/Constraint/ResponseCookieValueSame.php b/src/Symfony/Component/HttpFoundation/Test/Constraint/ResponseCookieValueSame.php index 417efc77a6688..768007b9593d5 100644 --- a/src/Symfony/Component/HttpFoundation/Test/Constraint/ResponseCookieValueSame.php +++ b/src/Symfony/Component/HttpFoundation/Test/Constraint/ResponseCookieValueSame.php @@ -22,7 +22,7 @@ final class ResponseCookieValueSame extends Constraint private string $path; private ?string $domain; - public function __construct(string $name, string $value, string $path = '/', string $domain = null) + public function __construct(string $name, string $value, string $path = '/', ?string $domain = null) { $this->name = $name; $this->value = $value; diff --git a/src/Symfony/Component/HttpFoundation/Test/Constraint/ResponseHasCookie.php b/src/Symfony/Component/HttpFoundation/Test/Constraint/ResponseHasCookie.php index 73393d386fbce..8eccea9d147d5 100644 --- a/src/Symfony/Component/HttpFoundation/Test/Constraint/ResponseHasCookie.php +++ b/src/Symfony/Component/HttpFoundation/Test/Constraint/ResponseHasCookie.php @@ -21,7 +21,7 @@ final class ResponseHasCookie extends Constraint private string $path; private ?string $domain; - public function __construct(string $name, string $path = '/', string $domain = null) + public function __construct(string $name, string $path = '/', ?string $domain = null) { $this->name = $name; $this->path = $path; diff --git a/src/Symfony/Component/HttpFoundation/Tests/HeaderUtilsTest.php b/src/Symfony/Component/HttpFoundation/Tests/HeaderUtilsTest.php index befa4aea035a5..3279b9a53b47d 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/HeaderUtilsTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/HeaderUtilsTest.php @@ -149,7 +149,7 @@ public static function provideMakeDispositionFail() /** * @dataProvider provideParseQuery */ - public function testParseQuery(string $query, string $expected = null) + public function testParseQuery(string $query, ?string $expected = null) { $this->assertSame($expected ?? $query, http_build_query(HeaderUtils::parseQuery($query), '', '&')); } diff --git a/src/Symfony/Component/HttpFoundation/Tests/RequestMatcher/HeaderRequestMatcherTest.php b/src/Symfony/Component/HttpFoundation/Tests/RequestMatcher/HeaderRequestMatcherTest.php new file mode 100644 index 0000000000000..47a5c7ee83ae4 --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/Tests/RequestMatcher/HeaderRequestMatcherTest.php @@ -0,0 +1,84 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Tests\RequestMatcher; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestMatcher\HeaderRequestMatcher; + +class HeaderRequestMatcherTest extends TestCase +{ + /** + * @dataProvider getDataForArray + */ + public function testArray(array $headers, bool $matches) + { + $matcher = new HeaderRequestMatcher(['x-foo', 'bar']); + + $request = Request::create('https://example.com'); + foreach ($headers as $k => $v) { + $request->headers->set($k, $v); + } + + $this->assertSame($matches, $matcher->matches($request)); + } + + /** + * @dataProvider getDataForArray + */ + public function testCommaSeparatedString(array $headers, bool $matches) + { + $matcher = new HeaderRequestMatcher('x-foo, bar'); + + $request = Request::create('https://example.com'); + foreach ($headers as $k => $v) { + $request->headers->set($k, $v); + } + + $this->assertSame($matches, $matcher->matches($request)); + } + + /** + * @dataProvider getDataForSingleString + */ + public function testSingleString(array $headers, bool $matches) + { + $matcher = new HeaderRequestMatcher('x-foo'); + + $request = Request::create('https://example.com'); + foreach ($headers as $k => $v) { + $request->headers->set($k, $v); + } + + $this->assertSame($matches, $matcher->matches($request)); + } + + public static function getDataForArray(): \Generator + { + yield 'Superfluous header' => [['X-Foo' => 'foo', 'bar' => 'bar', 'baz' => 'baz'], true]; + yield 'Exact match' => [['X-Foo' => 'foo', 'bar' => 'bar'], true]; + yield 'Case insensitivity' => [['x-foo' => 'foo', 'BAR' => 'bar'], true]; + yield 'Only one header matching' => [['bar' => 'bar', 'baz' => 'baz'], false]; + yield 'Only one header' => [['X-foo' => 'foo'], false]; + yield 'Header name as a value' => [['X-foo'], false]; + yield 'Empty headers' => [[], false]; + } + + public static function getDataForSingleString(): \Generator + { + yield 'Superfluous header' => [['X-Foo' => 'foo', 'bar' => 'bar'], true]; + yield 'Exact match' => [['X-foo' => 'foo'], true]; + yield 'Case insensitivity' => [['x-foo' => 'foo'], true]; + yield 'Header name as a value' => [['X-foo'], false]; + yield 'Empty headers' => [[], false]; + } +} diff --git a/src/Symfony/Component/HttpFoundation/Tests/RequestMatcher/QueryParameterRequestMatcherTest.php b/src/Symfony/Component/HttpFoundation/Tests/RequestMatcher/QueryParameterRequestMatcherTest.php new file mode 100644 index 0000000000000..202ca649ab05f --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/Tests/RequestMatcher/QueryParameterRequestMatcherTest.php @@ -0,0 +1,67 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Tests\RequestMatcher; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestMatcher\QueryParameterRequestMatcher; + +class QueryParameterRequestMatcherTest extends TestCase +{ + /** + * @dataProvider getDataForArray + */ + public function testArray(string $uri, bool $matches) + { + $matcher = new QueryParameterRequestMatcher(['foo', 'bar']); + $request = Request::create($uri); + $this->assertSame($matches, $matcher->matches($request)); + } + + /** + * @dataProvider getDataForArray + */ + public function testCommaSeparatedString(string $uri, bool $matches) + { + $matcher = new QueryParameterRequestMatcher('foo, bar'); + $request = Request::create($uri); + $this->assertSame($matches, $matcher->matches($request)); + } + + /** + * @dataProvider getDataForSingleString + */ + public function testSingleString(string $uri, bool $matches) + { + $matcher = new QueryParameterRequestMatcher('foo'); + $request = Request::create($uri); + $this->assertSame($matches, $matcher->matches($request)); + } + + public static function getDataForArray(): \Generator + { + yield ['https://example.com?foo=&bar=', true]; + yield ['https://example.com?foo=foo1&bar=bar1', true]; + yield ['https://example.com?foo=foo1&bar=bar1&baz=baz1', true]; + yield ['https://example.com?foo=', false]; + yield ['https://example.com', false]; + } + + public static function getDataForSingleString(): \Generator + { + yield ['https://example.com?foo=&bar=', true]; + yield ['https://example.com?foo=foo1', true]; + yield ['https://example.com?foo=', true]; + yield ['https://example.com?bar=bar1&baz=baz1', false]; + yield ['https://example.com', false]; + } +} diff --git a/src/Symfony/Component/HttpFoundation/Tests/ResponseHeaderBagTest.php b/src/Symfony/Component/HttpFoundation/Tests/ResponseHeaderBagTest.php index 8165e43740a66..9e61dd684e60f 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/ResponseHeaderBagTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/ResponseHeaderBagTest.php @@ -136,6 +136,14 @@ public function testClearCookieSamesite() $this->assertSetCookieHeader('foo=deleted; expires='.gmdate('D, d M Y H:i:s T', time() - 31536001).'; Max-Age=0; path=/; secure; samesite=none', $bag); } + public function testClearCookiePartitioned() + { + $bag = new ResponseHeaderBag([]); + + $bag->clearCookie('foo', '/', null, true, false, 'none', true); + $this->assertSetCookieHeader('foo=deleted; expires='.gmdate('D, d M Y H:i:s T', time() - 31536001).'; Max-Age=0; path=/; secure; samesite=none; partitioned', $bag); + } + public function testReplace() { $bag = new ResponseHeaderBag([]); diff --git a/src/Symfony/Component/HttpFoundation/Tests/ServerBagTest.php b/src/Symfony/Component/HttpFoundation/Tests/ServerBagTest.php index e26714bc4640a..3d675c5127868 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/ServerBagTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/ServerBagTest.php @@ -177,4 +177,20 @@ public function testItDoesNotOverwriteTheAuthorizationHeaderIfItIsAlreadySet() 'PHP_AUTH_PW' => '', ], $bag->getHeaders()); } + + /** + * An HTTP request without content-type and content-length will result in + * the variables $_SERVER['CONTENT_TYPE'] and $_SERVER['CONTENT_LENGTH'] + * containing an empty string in PHP. + */ + public function testRequestWithoutContentTypeAndContentLength() + { + $bag = new ServerBag([ + 'CONTENT_TYPE' => '', + 'CONTENT_LENGTH' => '', + 'HTTP_USER_AGENT' => 'foo', + ]); + + $this->assertSame(['USER_AGENT' => 'foo'], $bag->getHeaders()); + } } diff --git a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/PdoSessionHandlerTest.php b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/PdoSessionHandlerTest.php index cd34c72e34342..ede4703aa04f7 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/PdoSessionHandlerTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/PdoSessionHandlerTest.php @@ -408,7 +408,7 @@ class MockPdo extends \PDO private ?string $driverName; private bool|int $errorMode; - public function __construct(string $driverName = null, int $errorMode = null) + public function __construct(?string $driverName = null, ?int $errorMode = null) { $this->driverName = $driverName; $this->errorMode = null !== $errorMode ?: \PDO::ERRMODE_EXCEPTION; diff --git a/src/Symfony/Component/HttpKernel/CacheWarmer/CacheWarmerAggregate.php b/src/Symfony/Component/HttpKernel/CacheWarmer/CacheWarmerAggregate.php index beb98133862c1..b96d7c3d7617f 100644 --- a/src/Symfony/Component/HttpKernel/CacheWarmer/CacheWarmerAggregate.php +++ b/src/Symfony/Component/HttpKernel/CacheWarmer/CacheWarmerAggregate.php @@ -31,7 +31,7 @@ class CacheWarmerAggregate implements CacheWarmerInterface /** * @param iterable $warmers */ - public function __construct(iterable $warmers = [], bool $debug = false, string $deprecationLogsFilepath = null) + public function __construct(iterable $warmers = [], bool $debug = false, ?string $deprecationLogsFilepath = null) { $this->warmers = $warmers; $this->debug = $debug; @@ -48,7 +48,7 @@ public function enableOnlyOptionalWarmers(): void $this->onlyOptionalsEnabled = $this->optionalsEnabled = true; } - public function warmUp(string $cacheDir, string $buildDir = null, SymfonyStyle $io = null): array + public function warmUp(string $cacheDir, ?string $buildDir = null, ?SymfonyStyle $io = null): array { if ($collectDeprecations = $this->debug && !\defined('PHPUNIT_COMPOSER_INSTALL')) { $collectedLogs = []; diff --git a/src/Symfony/Component/HttpKernel/CacheWarmer/WarmableInterface.php b/src/Symfony/Component/HttpKernel/CacheWarmer/WarmableInterface.php index abef0f0134cdd..7ffe3c0dfde4d 100644 --- a/src/Symfony/Component/HttpKernel/CacheWarmer/WarmableInterface.php +++ b/src/Symfony/Component/HttpKernel/CacheWarmer/WarmableInterface.php @@ -26,5 +26,5 @@ interface WarmableInterface * * @return string[] A list of classes or files to preload */ - public function warmUp(string $cacheDir, string $buildDir = null): array; + public function warmUp(string $cacheDir, ?string $buildDir = null): array; } diff --git a/src/Symfony/Component/HttpKernel/Config/FileLocator.php b/src/Symfony/Component/HttpKernel/Config/FileLocator.php index f81f91925bbe5..fb6bb10f1f1b7 100644 --- a/src/Symfony/Component/HttpKernel/Config/FileLocator.php +++ b/src/Symfony/Component/HttpKernel/Config/FileLocator.php @@ -30,7 +30,7 @@ public function __construct(KernelInterface $kernel) parent::__construct(); } - public function locate(string $file, string $currentPath = null, bool $first = true): string|array + public function locate(string $file, ?string $currentPath = null, bool $first = true): string|array { if (isset($file[0]) && '@' === $file[0]) { $resource = $this->kernel->locateResource($file); diff --git a/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver.php b/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver.php index d4c790e8eab9d..dbd47b73db36f 100644 --- a/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver.php +++ b/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver.php @@ -38,14 +38,14 @@ final class ArgumentResolver implements ArgumentResolverInterface /** * @param iterable $argumentValueResolvers */ - public function __construct(ArgumentMetadataFactoryInterface $argumentMetadataFactory = null, iterable $argumentValueResolvers = [], ContainerInterface $namedResolvers = null) + public function __construct(?ArgumentMetadataFactoryInterface $argumentMetadataFactory = null, iterable $argumentValueResolvers = [], ?ContainerInterface $namedResolvers = null) { $this->argumentMetadataFactory = $argumentMetadataFactory ?? new ArgumentMetadataFactory(); $this->argumentValueResolvers = $argumentValueResolvers ?: self::getDefaultArgumentValueResolvers(); $this->namedResolvers = $namedResolvers; } - public function getArguments(Request $request, callable $controller, \ReflectionFunctionAbstract $reflector = null): array + public function getArguments(Request $request, callable $controller, ?\ReflectionFunctionAbstract $reflector = null): array { $arguments = []; diff --git a/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/RequestPayloadValueResolver.php b/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/RequestPayloadValueResolver.php index d48c01649fa1d..efa78dfb05878 100644 --- a/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/RequestPayloadValueResolver.php +++ b/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/RequestPayloadValueResolver.php @@ -109,11 +109,15 @@ public function onKernelControllerArguments(ControllerArgumentsEvent $event): vo } catch (PartialDenormalizationException $e) { $trans = $this->translator ? $this->translator->trans(...) : fn ($m, $p) => strtr($m, $p); foreach ($e->getErrors() as $error) { - $parameters = ['{{ type }}' => implode('|', $error->getExpectedTypes())]; + $parameters = []; + $template = 'This value was of an unexpected type.'; + if ($expectedTypes = $error->getExpectedTypes()) { + $template = 'This value should be of type {{ type }}.'; + $parameters['{{ type }}'] = implode('|', $expectedTypes); + } if ($error->canUseMessageForUser()) { $parameters['hint'] = $error->getMessage(); } - $template = 'This value should be of type {{ type }}.'; $message = $trans($template, $parameters, 'validators'); $violations->add(new ConstraintViolation($message, $template, $parameters, null, $error->getPath(), null)); } diff --git a/src/Symfony/Component/HttpKernel/Controller/ArgumentResolverInterface.php b/src/Symfony/Component/HttpKernel/Controller/ArgumentResolverInterface.php index a1f999fd49ee1..2090a599288df 100644 --- a/src/Symfony/Component/HttpKernel/Controller/ArgumentResolverInterface.php +++ b/src/Symfony/Component/HttpKernel/Controller/ArgumentResolverInterface.php @@ -26,5 +26,5 @@ interface ArgumentResolverInterface * * @throws \RuntimeException When no value could be provided for a required argument */ - public function getArguments(Request $request, callable $controller, \ReflectionFunctionAbstract $reflector = null): array; + public function getArguments(Request $request, callable $controller, ?\ReflectionFunctionAbstract $reflector = null): array; } diff --git a/src/Symfony/Component/HttpKernel/Controller/ContainerControllerResolver.php b/src/Symfony/Component/HttpKernel/Controller/ContainerControllerResolver.php index 5e1531de525ee..44b75860d4a4b 100644 --- a/src/Symfony/Component/HttpKernel/Controller/ContainerControllerResolver.php +++ b/src/Symfony/Component/HttpKernel/Controller/ContainerControllerResolver.php @@ -25,7 +25,7 @@ class ContainerControllerResolver extends ControllerResolver { protected ContainerInterface $container; - public function __construct(ContainerInterface $container, LoggerInterface $logger = null) + public function __construct(ContainerInterface $container, ?LoggerInterface $logger = null) { $this->container = $container; diff --git a/src/Symfony/Component/HttpKernel/Controller/ControllerResolver.php b/src/Symfony/Component/HttpKernel/Controller/ControllerResolver.php index 6c78668be81f6..c1622b96e2cfe 100644 --- a/src/Symfony/Component/HttpKernel/Controller/ControllerResolver.php +++ b/src/Symfony/Component/HttpKernel/Controller/ControllerResolver.php @@ -29,7 +29,7 @@ class ControllerResolver implements ControllerResolverInterface private array $allowedControllerTypes = []; private array $allowedControllerAttributes = [AsController::class => AsController::class]; - public function __construct(LoggerInterface $logger = null) + public function __construct(?LoggerInterface $logger = null) { $this->logger = $logger; } diff --git a/src/Symfony/Component/HttpKernel/Controller/TraceableArgumentResolver.php b/src/Symfony/Component/HttpKernel/Controller/TraceableArgumentResolver.php index a71d8db5c9bee..8d182fa4199db 100644 --- a/src/Symfony/Component/HttpKernel/Controller/TraceableArgumentResolver.php +++ b/src/Symfony/Component/HttpKernel/Controller/TraceableArgumentResolver.php @@ -28,7 +28,7 @@ public function __construct(ArgumentResolverInterface $resolver, Stopwatch $stop $this->stopwatch = $stopwatch; } - public function getArguments(Request $request, callable $controller, \ReflectionFunctionAbstract $reflector = null): array + public function getArguments(Request $request, callable $controller, ?\ReflectionFunctionAbstract $reflector = null): array { $e = $this->stopwatch->start('controller.get_arguments'); diff --git a/src/Symfony/Component/HttpKernel/ControllerMetadata/ArgumentMetadata.php b/src/Symfony/Component/HttpKernel/ControllerMetadata/ArgumentMetadata.php index a352090eac842..dd6c8be86fec6 100644 --- a/src/Symfony/Component/HttpKernel/ControllerMetadata/ArgumentMetadata.php +++ b/src/Symfony/Component/HttpKernel/ControllerMetadata/ArgumentMetadata.php @@ -106,7 +106,7 @@ public function getDefaultValue(): mixed * * @return array */ - public function getAttributes(string $name = null, int $flags = 0): array + public function getAttributes(?string $name = null, int $flags = 0): array { if (!$name) { return $this->attributes; diff --git a/src/Symfony/Component/HttpKernel/ControllerMetadata/ArgumentMetadataFactory.php b/src/Symfony/Component/HttpKernel/ControllerMetadata/ArgumentMetadataFactory.php index cb7f0a78c4ae1..7eafdc94b0738 100644 --- a/src/Symfony/Component/HttpKernel/ControllerMetadata/ArgumentMetadataFactory.php +++ b/src/Symfony/Component/HttpKernel/ControllerMetadata/ArgumentMetadataFactory.php @@ -18,7 +18,7 @@ */ final class ArgumentMetadataFactory implements ArgumentMetadataFactoryInterface { - public function createArgumentMetadata(string|object|array $controller, \ReflectionFunctionAbstract $reflector = null): array + public function createArgumentMetadata(string|object|array $controller, ?\ReflectionFunctionAbstract $reflector = null): array { $arguments = []; $reflector ??= new \ReflectionFunction($controller(...)); diff --git a/src/Symfony/Component/HttpKernel/ControllerMetadata/ArgumentMetadataFactoryInterface.php b/src/Symfony/Component/HttpKernel/ControllerMetadata/ArgumentMetadataFactoryInterface.php index f54c7e2ba8bae..4f4bc0786639e 100644 --- a/src/Symfony/Component/HttpKernel/ControllerMetadata/ArgumentMetadataFactoryInterface.php +++ b/src/Symfony/Component/HttpKernel/ControllerMetadata/ArgumentMetadataFactoryInterface.php @@ -21,5 +21,5 @@ interface ArgumentMetadataFactoryInterface /** * @return ArgumentMetadata[] */ - public function createArgumentMetadata(string|object|array $controller, \ReflectionFunctionAbstract $reflector = null): array; + public function createArgumentMetadata(string|object|array $controller, ?\ReflectionFunctionAbstract $reflector = null): array; } diff --git a/src/Symfony/Component/HttpKernel/DataCollector/AjaxDataCollector.php b/src/Symfony/Component/HttpKernel/DataCollector/AjaxDataCollector.php index 016ef2eceb2cc..3c8d2f0f607af 100644 --- a/src/Symfony/Component/HttpKernel/DataCollector/AjaxDataCollector.php +++ b/src/Symfony/Component/HttpKernel/DataCollector/AjaxDataCollector.php @@ -21,7 +21,7 @@ */ class AjaxDataCollector extends DataCollector { - public function collect(Request $request, Response $response, \Throwable $exception = null): void + public function collect(Request $request, Response $response, ?\Throwable $exception = null): void { // all collecting is done client side } diff --git a/src/Symfony/Component/HttpKernel/DataCollector/ConfigDataCollector.php b/src/Symfony/Component/HttpKernel/DataCollector/ConfigDataCollector.php index 8b231b2961aa3..7c8f469eab880 100644 --- a/src/Symfony/Component/HttpKernel/DataCollector/ConfigDataCollector.php +++ b/src/Symfony/Component/HttpKernel/DataCollector/ConfigDataCollector.php @@ -35,7 +35,7 @@ public function setKernel(KernelInterface $kernel): void $this->kernel = $kernel; } - public function collect(Request $request, Response $response, \Throwable $exception = null): void + public function collect(Request $request, Response $response, ?\Throwable $exception = null): void { $eom = \DateTimeImmutable::createFromFormat('d/m/Y', '01/'.Kernel::END_OF_MAINTENANCE); $eol = \DateTimeImmutable::createFromFormat('d/m/Y', '01/'.Kernel::END_OF_LIFE); diff --git a/src/Symfony/Component/HttpKernel/DataCollector/DataCollectorInterface.php b/src/Symfony/Component/HttpKernel/DataCollector/DataCollectorInterface.php index 8df94ccb8fa23..5e8593d07c3b1 100644 --- a/src/Symfony/Component/HttpKernel/DataCollector/DataCollectorInterface.php +++ b/src/Symfony/Component/HttpKernel/DataCollector/DataCollectorInterface.php @@ -27,7 +27,7 @@ interface DataCollectorInterface extends ResetInterface * * @return void */ - public function collect(Request $request, Response $response, \Throwable $exception = null); + public function collect(Request $request, Response $response, ?\Throwable $exception = null); /** * Returns the name of the collector. diff --git a/src/Symfony/Component/HttpKernel/DataCollector/DumpDataCollector.php b/src/Symfony/Component/HttpKernel/DataCollector/DumpDataCollector.php index ce02b545bf17a..0a46a8cd4e8a8 100644 --- a/src/Symfony/Component/HttpKernel/DataCollector/DumpDataCollector.php +++ b/src/Symfony/Component/HttpKernel/DataCollector/DumpDataCollector.php @@ -44,7 +44,7 @@ class DumpDataCollector extends DataCollector implements DataDumperInterface private mixed $sourceContextProvider; private bool $webMode; - public function __construct(Stopwatch $stopwatch = null, string|FileLinkFormatter $fileLinkFormat = null, string $charset = null, RequestStack $requestStack = null, DataDumperInterface|Connection $dumper = null, bool $webMode = null) + public function __construct(?Stopwatch $stopwatch = null, string|FileLinkFormatter|null $fileLinkFormat = null, ?string $charset = null, ?RequestStack $requestStack = null, DataDumperInterface|Connection|null $dumper = null, ?bool $webMode = null) { $fileLinkFormat = $fileLinkFormat ?: \ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format'); $this->stopwatch = $stopwatch; @@ -100,7 +100,7 @@ public function dump(Data $data): ?string return null; } - public function collect(Request $request, Response $response, \Throwable $exception = null): void + public function collect(Request $request, Response $response, ?\Throwable $exception = null): void { if (!$this->dataCount) { $this->data = []; diff --git a/src/Symfony/Component/HttpKernel/DataCollector/EventDataCollector.php b/src/Symfony/Component/HttpKernel/DataCollector/EventDataCollector.php index a6524ea045d01..3a94dbc3231d1 100644 --- a/src/Symfony/Component/HttpKernel/DataCollector/EventDataCollector.php +++ b/src/Symfony/Component/HttpKernel/DataCollector/EventDataCollector.php @@ -36,7 +36,7 @@ class EventDataCollector extends DataCollector implements LateDataCollectorInter * @param iterable|EventDispatcherInterface|null $dispatchers */ public function __construct( - iterable|EventDispatcherInterface $dispatchers = null, + iterable|EventDispatcherInterface|null $dispatchers = null, private ?RequestStack $requestStack = null, private string $defaultDispatcher = 'event_dispatcher', ) { @@ -46,7 +46,7 @@ public function __construct( $this->dispatchers = $dispatchers ?? []; } - public function collect(Request $request, Response $response, \Throwable $exception = null): void + public function collect(Request $request, Response $response, ?\Throwable $exception = null): void { $this->currentRequest = $this->requestStack && $this->requestStack->getMainRequest() !== $request ? $request : null; $this->data = []; @@ -86,7 +86,7 @@ public function getData(): array|Data /** * @see TraceableEventDispatcher */ - public function setCalledListeners(array $listeners, string $dispatcher = null): void + public function setCalledListeners(array $listeners, ?string $dispatcher = null): void { $this->data[$dispatcher ?? $this->defaultDispatcher]['called_listeners'] = $listeners; } @@ -94,7 +94,7 @@ public function setCalledListeners(array $listeners, string $dispatcher = null): /** * @see TraceableEventDispatcher */ - public function getCalledListeners(string $dispatcher = null): array|Data + public function getCalledListeners(?string $dispatcher = null): array|Data { return $this->data[$dispatcher ?? $this->defaultDispatcher]['called_listeners'] ?? []; } @@ -102,7 +102,7 @@ public function getCalledListeners(string $dispatcher = null): array|Data /** * @see TraceableEventDispatcher */ - public function setNotCalledListeners(array $listeners, string $dispatcher = null): void + public function setNotCalledListeners(array $listeners, ?string $dispatcher = null): void { $this->data[$dispatcher ?? $this->defaultDispatcher]['not_called_listeners'] = $listeners; } @@ -110,7 +110,7 @@ public function setNotCalledListeners(array $listeners, string $dispatcher = nul /** * @see TraceableEventDispatcher */ - public function getNotCalledListeners(string $dispatcher = null): array|Data + public function getNotCalledListeners(?string $dispatcher = null): array|Data { return $this->data[$dispatcher ?? $this->defaultDispatcher]['not_called_listeners'] ?? []; } @@ -120,7 +120,7 @@ public function getNotCalledListeners(string $dispatcher = null): array|Data * * @see TraceableEventDispatcher */ - public function setOrphanedEvents(array $events, string $dispatcher = null): void + public function setOrphanedEvents(array $events, ?string $dispatcher = null): void { $this->data[$dispatcher ?? $this->defaultDispatcher]['orphaned_events'] = $events; } @@ -128,7 +128,7 @@ public function setOrphanedEvents(array $events, string $dispatcher = null): voi /** * @see TraceableEventDispatcher */ - public function getOrphanedEvents(string $dispatcher = null): array|Data + public function getOrphanedEvents(?string $dispatcher = null): array|Data { return $this->data[$dispatcher ?? $this->defaultDispatcher]['orphaned_events'] ?? []; } diff --git a/src/Symfony/Component/HttpKernel/DataCollector/ExceptionDataCollector.php b/src/Symfony/Component/HttpKernel/DataCollector/ExceptionDataCollector.php index 16a29adc18d2a..80156bc8d5662 100644 --- a/src/Symfony/Component/HttpKernel/DataCollector/ExceptionDataCollector.php +++ b/src/Symfony/Component/HttpKernel/DataCollector/ExceptionDataCollector.php @@ -22,7 +22,7 @@ */ class ExceptionDataCollector extends DataCollector { - public function collect(Request $request, Response $response, \Throwable $exception = null): void + public function collect(Request $request, Response $response, ?\Throwable $exception = null): void { if (null !== $exception) { $this->data = [ diff --git a/src/Symfony/Component/HttpKernel/DataCollector/LoggerDataCollector.php b/src/Symfony/Component/HttpKernel/DataCollector/LoggerDataCollector.php index eb2b9c85ca061..cf17e7a7396e1 100644 --- a/src/Symfony/Component/HttpKernel/DataCollector/LoggerDataCollector.php +++ b/src/Symfony/Component/HttpKernel/DataCollector/LoggerDataCollector.php @@ -32,14 +32,14 @@ class LoggerDataCollector extends DataCollector implements LateDataCollectorInte private ?RequestStack $requestStack; private ?array $processedLogs = null; - public function __construct(object $logger = null, string $containerPathPrefix = null, RequestStack $requestStack = null) + public function __construct(?object $logger = null, ?string $containerPathPrefix = null, ?RequestStack $requestStack = null) { $this->logger = DebugLoggerConfigurator::getDebugLogger($logger); $this->containerPathPrefix = $containerPathPrefix; $this->requestStack = $requestStack; } - public function collect(Request $request, Response $response, \Throwable $exception = null): void + public function collect(Request $request, Response $response, ?\Throwable $exception = null): void { $this->currentRequest = $this->requestStack && $this->requestStack->getMainRequest() !== $request ? $request : null; } @@ -199,7 +199,7 @@ private function getContainerDeprecationLogs(): array return $logs; } - private function getContainerCompilerLogs(string $compilerLogsFilepath = null): array + private function getContainerCompilerLogs(?string $compilerLogsFilepath = null): array { if (!$compilerLogsFilepath || !is_file($compilerLogsFilepath)) { return []; diff --git a/src/Symfony/Component/HttpKernel/DataCollector/MemoryDataCollector.php b/src/Symfony/Component/HttpKernel/DataCollector/MemoryDataCollector.php index 8b88943675c98..9715f94eef295 100644 --- a/src/Symfony/Component/HttpKernel/DataCollector/MemoryDataCollector.php +++ b/src/Symfony/Component/HttpKernel/DataCollector/MemoryDataCollector.php @@ -26,7 +26,7 @@ public function __construct() $this->reset(); } - public function collect(Request $request, Response $response, \Throwable $exception = null): void + public function collect(Request $request, Response $response, ?\Throwable $exception = null): void { $this->updateMemoryUsage(); } diff --git a/src/Symfony/Component/HttpKernel/DataCollector/RequestDataCollector.php b/src/Symfony/Component/HttpKernel/DataCollector/RequestDataCollector.php index 55f2f95604266..531ce0b94a999 100644 --- a/src/Symfony/Component/HttpKernel/DataCollector/RequestDataCollector.php +++ b/src/Symfony/Component/HttpKernel/DataCollector/RequestDataCollector.php @@ -38,13 +38,13 @@ class RequestDataCollector extends DataCollector implements EventSubscriberInter private array $sessionUsages = []; private ?RequestStack $requestStack; - public function __construct(RequestStack $requestStack = null) + public function __construct(?RequestStack $requestStack = null) { $this->controllers = new \SplObjectStorage(); $this->requestStack = $requestStack; } - public function collect(Request $request, Response $response, \Throwable $exception = null): void + public function collect(Request $request, Response $response, ?\Throwable $exception = null): void { // attributes are serialized and as they can be anything, they need to be converted to strings. $attributes = []; diff --git a/src/Symfony/Component/HttpKernel/DataCollector/RouterDataCollector.php b/src/Symfony/Component/HttpKernel/DataCollector/RouterDataCollector.php index 38c58bd86c6c9..cb7b233f5df6a 100644 --- a/src/Symfony/Component/HttpKernel/DataCollector/RouterDataCollector.php +++ b/src/Symfony/Component/HttpKernel/DataCollector/RouterDataCollector.php @@ -34,7 +34,7 @@ public function __construct() /** * @final */ - public function collect(Request $request, Response $response, \Throwable $exception = null): void + public function collect(Request $request, Response $response, ?\Throwable $exception = null): void { if ($response instanceof RedirectResponse) { $this->data['redirect'] = true; diff --git a/src/Symfony/Component/HttpKernel/DataCollector/TimeDataCollector.php b/src/Symfony/Component/HttpKernel/DataCollector/TimeDataCollector.php index a8b7ead94073f..9799a1333dec1 100644 --- a/src/Symfony/Component/HttpKernel/DataCollector/TimeDataCollector.php +++ b/src/Symfony/Component/HttpKernel/DataCollector/TimeDataCollector.php @@ -27,14 +27,14 @@ class TimeDataCollector extends DataCollector implements LateDataCollectorInterf private ?KernelInterface $kernel; private ?Stopwatch $stopwatch; - public function __construct(KernelInterface $kernel = null, Stopwatch $stopwatch = null) + public function __construct(?KernelInterface $kernel = null, ?Stopwatch $stopwatch = null) { $this->kernel = $kernel; $this->stopwatch = $stopwatch; $this->data = ['events' => [], 'stopwatch_installed' => false, 'start_time' => 0]; } - public function collect(Request $request, Response $response, \Throwable $exception = null): void + public function collect(Request $request, Response $response, ?\Throwable $exception = null): void { if (null !== $this->kernel) { $startTime = $this->kernel->getStartTime(); diff --git a/src/Symfony/Component/HttpKernel/Debug/ErrorHandlerConfigurator.php b/src/Symfony/Component/HttpKernel/Debug/ErrorHandlerConfigurator.php index 49f188c22d9d0..5b3e1cdddf22d 100644 --- a/src/Symfony/Component/HttpKernel/Debug/ErrorHandlerConfigurator.php +++ b/src/Symfony/Component/HttpKernel/Debug/ErrorHandlerConfigurator.php @@ -36,7 +36,7 @@ class ErrorHandlerConfigurator * @param bool $scream Enables/disables screaming mode, where even silenced errors are logged * @param bool $scope Enables/disables scoping mode */ - public function __construct(LoggerInterface $logger = null, array|int|null $levels = \E_ALL, ?int $throwAt = \E_ALL, bool $scream = true, bool $scope = true, LoggerInterface $deprecationLogger = null) + public function __construct(?LoggerInterface $logger = null, array|int|null $levels = \E_ALL, ?int $throwAt = \E_ALL, bool $scream = true, bool $scope = true, ?LoggerInterface $deprecationLogger = null) { $this->logger = $logger; $this->levels = $levels ?? \E_ALL; diff --git a/src/Symfony/Component/HttpKernel/DependencyInjection/Extension.php b/src/Symfony/Component/HttpKernel/DependencyInjection/Extension.php index ea8ae3acb6bfb..a70edad6875cc 100644 --- a/src/Symfony/Component/HttpKernel/DependencyInjection/Extension.php +++ b/src/Symfony/Component/HttpKernel/DependencyInjection/Extension.php @@ -24,6 +24,8 @@ abstract class Extension extends BaseExtension /** * Gets the annotated classes to cache. + * + * @return string[] */ public function getAnnotatedClassesToCompile(): array { @@ -33,7 +35,7 @@ public function getAnnotatedClassesToCompile(): array /** * Adds annotated classes to the class cache. * - * @param array $annotatedClasses An array of class patterns + * @param string[] $annotatedClasses An array of class patterns */ public function addAnnotatedClassesToCompile(array $annotatedClasses): void { diff --git a/src/Symfony/Component/HttpKernel/Event/ControllerArgumentsEvent.php b/src/Symfony/Component/HttpKernel/Event/ControllerArgumentsEvent.php index c90b7706f24a4..4c804ccf19548 100644 --- a/src/Symfony/Component/HttpKernel/Event/ControllerArgumentsEvent.php +++ b/src/Symfony/Component/HttpKernel/Event/ControllerArgumentsEvent.php @@ -52,7 +52,7 @@ public function getController(): callable /** * @param array>|null $attributes */ - public function setController(callable $controller, array $attributes = null): void + public function setController(callable $controller, ?array $attributes = null): void { $this->controllerEvent->setController($controller, $attributes); unset($this->namedArguments); @@ -102,7 +102,7 @@ public function getNamedArguments(): array * * @psalm-return (T is null ? array> : list) */ - public function getAttributes(string $className = null): array + public function getAttributes(?string $className = null): array { return $this->controllerEvent->getAttributes($className); } diff --git a/src/Symfony/Component/HttpKernel/Event/ControllerEvent.php b/src/Symfony/Component/HttpKernel/Event/ControllerEvent.php index 182798a8d5c3d..90bf8d270ab91 100644 --- a/src/Symfony/Component/HttpKernel/Event/ControllerEvent.php +++ b/src/Symfony/Component/HttpKernel/Event/ControllerEvent.php @@ -51,7 +51,7 @@ public function getControllerReflector(): \ReflectionFunctionAbstract /** * @param array>|null $attributes */ - public function setController(callable $controller, array $attributes = null): void + public function setController(callable $controller, ?array $attributes = null): void { if (null !== $attributes) { $this->attributes = $attributes; @@ -87,7 +87,7 @@ public function setController(callable $controller, array $attributes = null): v * * @psalm-return (T is null ? array> : list) */ - public function getAttributes(string $className = null): array + public function getAttributes(?string $className = null): array { if (isset($this->attributes)) { return null === $className ? $this->attributes : $this->attributes[$className] ?? []; diff --git a/src/Symfony/Component/HttpKernel/Event/ViewEvent.php b/src/Symfony/Component/HttpKernel/Event/ViewEvent.php index bf96985b29547..4d963aea1f3f9 100644 --- a/src/Symfony/Component/HttpKernel/Event/ViewEvent.php +++ b/src/Symfony/Component/HttpKernel/Event/ViewEvent.php @@ -28,7 +28,7 @@ final class ViewEvent extends RequestEvent public readonly ?ControllerArgumentsEvent $controllerArgumentsEvent; private mixed $controllerResult; - public function __construct(HttpKernelInterface $kernel, Request $request, int $requestType, mixed $controllerResult, ControllerArgumentsEvent $controllerArgumentsEvent = null) + public function __construct(HttpKernelInterface $kernel, Request $request, int $requestType, mixed $controllerResult, ?ControllerArgumentsEvent $controllerArgumentsEvent = null) { parent::__construct($kernel, $request, $requestType); diff --git a/src/Symfony/Component/HttpKernel/EventListener/AbstractSessionListener.php b/src/Symfony/Component/HttpKernel/EventListener/AbstractSessionListener.php index 2f012ab522d86..1ce4905376639 100644 --- a/src/Symfony/Component/HttpKernel/EventListener/AbstractSessionListener.php +++ b/src/Symfony/Component/HttpKernel/EventListener/AbstractSessionListener.php @@ -55,7 +55,7 @@ abstract class AbstractSessionListener implements EventSubscriberInterface, Rese /** * @internal */ - public function __construct(ContainerInterface $container = null, bool $debug = false, array $sessionOptions = []) + public function __construct(?ContainerInterface $container = null, bool $debug = false, array $sessionOptions = []) { $this->container = $container; $this->debug = $debug; diff --git a/src/Symfony/Component/HttpKernel/EventListener/DebugHandlersListener.php b/src/Symfony/Component/HttpKernel/EventListener/DebugHandlersListener.php index ce746bd125dce..df1443b4fadd6 100644 --- a/src/Symfony/Component/HttpKernel/EventListener/DebugHandlersListener.php +++ b/src/Symfony/Component/HttpKernel/EventListener/DebugHandlersListener.php @@ -41,7 +41,7 @@ class DebugHandlersListener implements EventSubscriberInterface * @param bool $webMode * @param callable|null $exceptionHandler A handler that must support \Throwable instances that will be called on Exception */ - public function __construct(callable $exceptionHandler = null, bool|LoggerInterface $webMode = null) + public function __construct(?callable $exceptionHandler = null, bool|LoggerInterface|null $webMode = null) { if ($webMode instanceof LoggerInterface) { // BC with Symfony 5 @@ -58,7 +58,7 @@ public function __construct(callable $exceptionHandler = null, bool|LoggerInterf /** * Configures the error handler. */ - public function configure(object $event = null): void + public function configure(?object $event = null): void { if ($event instanceof ConsoleEvent && $this->webMode) { return; diff --git a/src/Symfony/Component/HttpKernel/EventListener/DumpListener.php b/src/Symfony/Component/HttpKernel/EventListener/DumpListener.php index 72fc5ca6b4af6..76e71c28bbc59 100644 --- a/src/Symfony/Component/HttpKernel/EventListener/DumpListener.php +++ b/src/Symfony/Component/HttpKernel/EventListener/DumpListener.php @@ -29,7 +29,7 @@ class DumpListener implements EventSubscriberInterface private DataDumperInterface $dumper; private ?Connection $connection; - public function __construct(ClonerInterface $cloner, DataDumperInterface $dumper, Connection $connection = null) + public function __construct(ClonerInterface $cloner, DataDumperInterface $dumper, ?Connection $connection = null) { $this->cloner = $cloner; $this->dumper = $dumper; @@ -42,7 +42,7 @@ public function configure(): void $dumper = $this->dumper; $connection = $this->connection; - VarDumper::setHandler(static function ($var, string $label = null) use ($cloner, $dumper, $connection) { + VarDumper::setHandler(static function ($var, ?string $label = null) use ($cloner, $dumper, $connection) { $data = $cloner->cloneVar($var); if (null !== $label) { $data = $data->withContext(['label' => $label]); diff --git a/src/Symfony/Component/HttpKernel/EventListener/ErrorListener.php b/src/Symfony/Component/HttpKernel/EventListener/ErrorListener.php index 3934d9abe4280..8f27c412f9ead 100644 --- a/src/Symfony/Component/HttpKernel/EventListener/ErrorListener.php +++ b/src/Symfony/Component/HttpKernel/EventListener/ErrorListener.php @@ -44,7 +44,7 @@ class ErrorListener implements EventSubscriberInterface /** * @param array|null}> $exceptionsMapping */ - public function __construct(string|object|array|null $controller, LoggerInterface $logger = null, bool $debug = false, array $exceptionsMapping = []) + public function __construct(string|object|array|null $controller, ?LoggerInterface $logger = null, bool $debug = false, array $exceptionsMapping = []) { $this->controller = $controller; $this->logger = $logger; @@ -168,7 +168,7 @@ public static function getSubscribedEvents(): array /** * Logs an exception. */ - protected function logException(\Throwable $exception, string $message, string $logLevel = null): void + protected function logException(\Throwable $exception, string $message, ?string $logLevel = null): void { if (null === $this->logger) { return; diff --git a/src/Symfony/Component/HttpKernel/EventListener/LocaleListener.php b/src/Symfony/Component/HttpKernel/EventListener/LocaleListener.php index 65a3bfde46db1..9feaa0b4f8814 100644 --- a/src/Symfony/Component/HttpKernel/EventListener/LocaleListener.php +++ b/src/Symfony/Component/HttpKernel/EventListener/LocaleListener.php @@ -35,7 +35,7 @@ class LocaleListener implements EventSubscriberInterface private bool $useAcceptLanguageHeader; private array $enabledLocales; - public function __construct(RequestStack $requestStack, string $defaultLocale = 'en', RequestContextAwareInterface $router = null, bool $useAcceptLanguageHeader = false, array $enabledLocales = []) + public function __construct(RequestStack $requestStack, string $defaultLocale = 'en', ?RequestContextAwareInterface $router = null, bool $useAcceptLanguageHeader = false, array $enabledLocales = []) { $this->defaultLocale = $defaultLocale; $this->requestStack = $requestStack; diff --git a/src/Symfony/Component/HttpKernel/EventListener/ProfilerListener.php b/src/Symfony/Component/HttpKernel/EventListener/ProfilerListener.php index 716d939fd023a..5d16823fceddd 100644 --- a/src/Symfony/Component/HttpKernel/EventListener/ProfilerListener.php +++ b/src/Symfony/Component/HttpKernel/EventListener/ProfilerListener.php @@ -48,7 +48,7 @@ class ProfilerListener implements EventSubscriberInterface * @param bool $onlyException True if the profiler only collects data when an exception occurs, false otherwise * @param bool $onlyMainRequests True if the profiler only collects data when the request is the main request, false otherwise */ - public function __construct(Profiler $profiler, RequestStack $requestStack, RequestMatcherInterface $matcher = null, bool $onlyException = false, bool $onlyMainRequests = false, string $collectParameter = null) + public function __construct(Profiler $profiler, RequestStack $requestStack, ?RequestMatcherInterface $matcher = null, bool $onlyException = false, bool $onlyMainRequests = false, ?string $collectParameter = null) { $this->profiler = $profiler; $this->matcher = $matcher; diff --git a/src/Symfony/Component/HttpKernel/EventListener/RouterListener.php b/src/Symfony/Component/HttpKernel/EventListener/RouterListener.php index 85c0562a79b8e..f4406ade4923e 100644 --- a/src/Symfony/Component/HttpKernel/EventListener/RouterListener.php +++ b/src/Symfony/Component/HttpKernel/EventListener/RouterListener.php @@ -53,7 +53,7 @@ class RouterListener implements EventSubscriberInterface * * @throws \InvalidArgumentException */ - public function __construct(UrlMatcherInterface|RequestMatcherInterface $matcher, RequestStack $requestStack, RequestContext $context = null, LoggerInterface $logger = null, string $projectDir = null, bool $debug = true) + public function __construct(UrlMatcherInterface|RequestMatcherInterface $matcher, RequestStack $requestStack, ?RequestContext $context = null, ?LoggerInterface $logger = null, ?string $projectDir = null, bool $debug = true) { if (null === $context && !$matcher instanceof RequestContextAwareInterface) { throw new \InvalidArgumentException('You must either pass a RequestContext or the matcher must implement RequestContextAwareInterface.'); @@ -159,7 +159,7 @@ private function createWelcomeResponse(): Response $docVersion = substr(Kernel::VERSION, 0, 3); ob_start(); - include_once \dirname(__DIR__).'/Resources/welcome.html.php'; + include \dirname(__DIR__).'/Resources/welcome.html.php'; return new Response(ob_get_clean(), Response::HTTP_NOT_FOUND); } diff --git a/src/Symfony/Component/HttpKernel/EventListener/SurrogateListener.php b/src/Symfony/Component/HttpKernel/EventListener/SurrogateListener.php index 17bdf2b392789..a702a68f84640 100644 --- a/src/Symfony/Component/HttpKernel/EventListener/SurrogateListener.php +++ b/src/Symfony/Component/HttpKernel/EventListener/SurrogateListener.php @@ -28,7 +28,7 @@ class SurrogateListener implements EventSubscriberInterface { private ?SurrogateInterface $surrogate; - public function __construct(SurrogateInterface $surrogate = null) + public function __construct(?SurrogateInterface $surrogate = null) { $this->surrogate = $surrogate; } diff --git a/src/Symfony/Component/HttpKernel/Exception/AccessDeniedHttpException.php b/src/Symfony/Component/HttpKernel/Exception/AccessDeniedHttpException.php index 78e8fe37d69e8..0f9ea715c0482 100644 --- a/src/Symfony/Component/HttpKernel/Exception/AccessDeniedHttpException.php +++ b/src/Symfony/Component/HttpKernel/Exception/AccessDeniedHttpException.php @@ -17,7 +17,7 @@ */ class AccessDeniedHttpException extends HttpException { - public function __construct(string $message = '', \Throwable $previous = null, int $code = 0, array $headers = []) + public function __construct(string $message = '', ?\Throwable $previous = null, int $code = 0, array $headers = []) { parent::__construct(403, $message, $previous, $headers, $code); } diff --git a/src/Symfony/Component/HttpKernel/Exception/BadRequestHttpException.php b/src/Symfony/Component/HttpKernel/Exception/BadRequestHttpException.php index c920fbd0d6286..57a7a2583e615 100644 --- a/src/Symfony/Component/HttpKernel/Exception/BadRequestHttpException.php +++ b/src/Symfony/Component/HttpKernel/Exception/BadRequestHttpException.php @@ -16,7 +16,7 @@ */ class BadRequestHttpException extends HttpException { - public function __construct(string $message = '', \Throwable $previous = null, int $code = 0, array $headers = []) + public function __construct(string $message = '', ?\Throwable $previous = null, int $code = 0, array $headers = []) { parent::__construct(400, $message, $previous, $headers, $code); } diff --git a/src/Symfony/Component/HttpKernel/Exception/ConflictHttpException.php b/src/Symfony/Component/HttpKernel/Exception/ConflictHttpException.php index a5a6f8405c187..997c4a68165b0 100644 --- a/src/Symfony/Component/HttpKernel/Exception/ConflictHttpException.php +++ b/src/Symfony/Component/HttpKernel/Exception/ConflictHttpException.php @@ -16,7 +16,7 @@ */ class ConflictHttpException extends HttpException { - public function __construct(string $message = '', \Throwable $previous = null, int $code = 0, array $headers = []) + public function __construct(string $message = '', ?\Throwable $previous = null, int $code = 0, array $headers = []) { parent::__construct(409, $message, $previous, $headers, $code); } diff --git a/src/Symfony/Component/HttpKernel/Exception/GoneHttpException.php b/src/Symfony/Component/HttpKernel/Exception/GoneHttpException.php index 2893f05cbc74e..c40d597cc042d 100644 --- a/src/Symfony/Component/HttpKernel/Exception/GoneHttpException.php +++ b/src/Symfony/Component/HttpKernel/Exception/GoneHttpException.php @@ -16,7 +16,7 @@ */ class GoneHttpException extends HttpException { - public function __construct(string $message = '', \Throwable $previous = null, int $code = 0, array $headers = []) + public function __construct(string $message = '', ?\Throwable $previous = null, int $code = 0, array $headers = []) { parent::__construct(410, $message, $previous, $headers, $code); } diff --git a/src/Symfony/Component/HttpKernel/Exception/HttpException.php b/src/Symfony/Component/HttpKernel/Exception/HttpException.php index 7eaf049e9302c..a7db43bb4c08f 100644 --- a/src/Symfony/Component/HttpKernel/Exception/HttpException.php +++ b/src/Symfony/Component/HttpKernel/Exception/HttpException.php @@ -21,7 +21,7 @@ class HttpException extends \RuntimeException implements HttpExceptionInterface private int $statusCode; private array $headers; - public function __construct(int $statusCode, string $message = '', \Throwable $previous = null, array $headers = [], int $code = 0) + public function __construct(int $statusCode, string $message = '', ?\Throwable $previous = null, array $headers = [], int $code = 0) { $this->statusCode = $statusCode; $this->headers = $headers; @@ -29,7 +29,7 @@ public function __construct(int $statusCode, string $message = '', \Throwable $p parent::__construct($message, $code, $previous); } - public static function fromStatusCode(int $statusCode, string $message = '', \Throwable $previous = null, array $headers = [], int $code = 0): self + public static function fromStatusCode(int $statusCode, string $message = '', ?\Throwable $previous = null, array $headers = [], int $code = 0): self { return match ($statusCode) { 400 => new BadRequestHttpException($message, $previous, $code, $headers), diff --git a/src/Symfony/Component/HttpKernel/Exception/LengthRequiredHttpException.php b/src/Symfony/Component/HttpKernel/Exception/LengthRequiredHttpException.php index a3dd8b3cd73f1..ca8741e409f17 100644 --- a/src/Symfony/Component/HttpKernel/Exception/LengthRequiredHttpException.php +++ b/src/Symfony/Component/HttpKernel/Exception/LengthRequiredHttpException.php @@ -16,7 +16,7 @@ */ class LengthRequiredHttpException extends HttpException { - public function __construct(string $message = '', \Throwable $previous = null, int $code = 0, array $headers = []) + public function __construct(string $message = '', ?\Throwable $previous = null, int $code = 0, array $headers = []) { parent::__construct(411, $message, $previous, $headers, $code); } diff --git a/src/Symfony/Component/HttpKernel/Exception/LockedHttpException.php b/src/Symfony/Component/HttpKernel/Exception/LockedHttpException.php index 069619bfc294b..3f05c2277bbdb 100644 --- a/src/Symfony/Component/HttpKernel/Exception/LockedHttpException.php +++ b/src/Symfony/Component/HttpKernel/Exception/LockedHttpException.php @@ -16,7 +16,7 @@ */ class LockedHttpException extends HttpException { - public function __construct(string $message = '', \Throwable $previous = null, int $code = 0, array $headers = []) + public function __construct(string $message = '', ?\Throwable $previous = null, int $code = 0, array $headers = []) { parent::__construct(423, $message, $previous, $headers, $code); } diff --git a/src/Symfony/Component/HttpKernel/Exception/MethodNotAllowedHttpException.php b/src/Symfony/Component/HttpKernel/Exception/MethodNotAllowedHttpException.php index cfbaf5cb02e4f..33572e461bdbb 100644 --- a/src/Symfony/Component/HttpKernel/Exception/MethodNotAllowedHttpException.php +++ b/src/Symfony/Component/HttpKernel/Exception/MethodNotAllowedHttpException.php @@ -19,7 +19,7 @@ class MethodNotAllowedHttpException extends HttpException /** * @param string[] $allow An array of allowed methods */ - public function __construct(array $allow, string $message = '', \Throwable $previous = null, int $code = 0, array $headers = []) + public function __construct(array $allow, string $message = '', ?\Throwable $previous = null, int $code = 0, array $headers = []) { $headers['Allow'] = strtoupper(implode(', ', $allow)); diff --git a/src/Symfony/Component/HttpKernel/Exception/NotAcceptableHttpException.php b/src/Symfony/Component/HttpKernel/Exception/NotAcceptableHttpException.php index ec2bb596fc0ea..13e9c2312f492 100644 --- a/src/Symfony/Component/HttpKernel/Exception/NotAcceptableHttpException.php +++ b/src/Symfony/Component/HttpKernel/Exception/NotAcceptableHttpException.php @@ -16,7 +16,7 @@ */ class NotAcceptableHttpException extends HttpException { - public function __construct(string $message = '', \Throwable $previous = null, int $code = 0, array $headers = []) + public function __construct(string $message = '', ?\Throwable $previous = null, int $code = 0, array $headers = []) { parent::__construct(406, $message, $previous, $headers, $code); } diff --git a/src/Symfony/Component/HttpKernel/Exception/NotFoundHttpException.php b/src/Symfony/Component/HttpKernel/Exception/NotFoundHttpException.php index 0e78fcc155cd8..e1b489eed2e42 100644 --- a/src/Symfony/Component/HttpKernel/Exception/NotFoundHttpException.php +++ b/src/Symfony/Component/HttpKernel/Exception/NotFoundHttpException.php @@ -16,7 +16,7 @@ */ class NotFoundHttpException extends HttpException { - public function __construct(string $message = '', \Throwable $previous = null, int $code = 0, array $headers = []) + public function __construct(string $message = '', ?\Throwable $previous = null, int $code = 0, array $headers = []) { parent::__construct(404, $message, $previous, $headers, $code); } diff --git a/src/Symfony/Component/HttpKernel/Exception/PreconditionFailedHttpException.php b/src/Symfony/Component/HttpKernel/Exception/PreconditionFailedHttpException.php index 4431f89d03776..8ec710e41f4cf 100644 --- a/src/Symfony/Component/HttpKernel/Exception/PreconditionFailedHttpException.php +++ b/src/Symfony/Component/HttpKernel/Exception/PreconditionFailedHttpException.php @@ -16,7 +16,7 @@ */ class PreconditionFailedHttpException extends HttpException { - public function __construct(string $message = '', \Throwable $previous = null, int $code = 0, array $headers = []) + public function __construct(string $message = '', ?\Throwable $previous = null, int $code = 0, array $headers = []) { parent::__construct(412, $message, $previous, $headers, $code); } diff --git a/src/Symfony/Component/HttpKernel/Exception/PreconditionRequiredHttpException.php b/src/Symfony/Component/HttpKernel/Exception/PreconditionRequiredHttpException.php index f75afd3706ad8..848876939aa68 100644 --- a/src/Symfony/Component/HttpKernel/Exception/PreconditionRequiredHttpException.php +++ b/src/Symfony/Component/HttpKernel/Exception/PreconditionRequiredHttpException.php @@ -18,7 +18,7 @@ */ class PreconditionRequiredHttpException extends HttpException { - public function __construct(string $message = '', \Throwable $previous = null, int $code = 0, array $headers = []) + public function __construct(string $message = '', ?\Throwable $previous = null, int $code = 0, array $headers = []) { parent::__construct(428, $message, $previous, $headers, $code); } diff --git a/src/Symfony/Component/HttpKernel/Exception/ServiceUnavailableHttpException.php b/src/Symfony/Component/HttpKernel/Exception/ServiceUnavailableHttpException.php index d4862bd109d51..842271dc92e66 100644 --- a/src/Symfony/Component/HttpKernel/Exception/ServiceUnavailableHttpException.php +++ b/src/Symfony/Component/HttpKernel/Exception/ServiceUnavailableHttpException.php @@ -19,7 +19,7 @@ class ServiceUnavailableHttpException extends HttpException /** * @param int|string|null $retryAfter The number of seconds or HTTP-date after which the request may be retried */ - public function __construct(int|string $retryAfter = null, string $message = '', \Throwable $previous = null, int $code = 0, array $headers = []) + public function __construct(int|string|null $retryAfter = null, string $message = '', ?\Throwable $previous = null, int $code = 0, array $headers = []) { if ($retryAfter) { $headers['Retry-After'] = $retryAfter; diff --git a/src/Symfony/Component/HttpKernel/Exception/TooManyRequestsHttpException.php b/src/Symfony/Component/HttpKernel/Exception/TooManyRequestsHttpException.php index b71fb2508f217..2f749aa262a41 100644 --- a/src/Symfony/Component/HttpKernel/Exception/TooManyRequestsHttpException.php +++ b/src/Symfony/Component/HttpKernel/Exception/TooManyRequestsHttpException.php @@ -21,7 +21,7 @@ class TooManyRequestsHttpException extends HttpException /** * @param int|string|null $retryAfter The number of seconds or HTTP-date after which the request may be retried */ - public function __construct(int|string $retryAfter = null, string $message = '', \Throwable $previous = null, int $code = 0, array $headers = []) + public function __construct(int|string|null $retryAfter = null, string $message = '', ?\Throwable $previous = null, int $code = 0, array $headers = []) { if ($retryAfter) { $headers['Retry-After'] = $retryAfter; diff --git a/src/Symfony/Component/HttpKernel/Exception/UnauthorizedHttpException.php b/src/Symfony/Component/HttpKernel/Exception/UnauthorizedHttpException.php index c86686128100a..de8f314b4f5bf 100644 --- a/src/Symfony/Component/HttpKernel/Exception/UnauthorizedHttpException.php +++ b/src/Symfony/Component/HttpKernel/Exception/UnauthorizedHttpException.php @@ -19,7 +19,7 @@ class UnauthorizedHttpException extends HttpException /** * @param string $challenge WWW-Authenticate challenge string */ - public function __construct(string $challenge, string $message = '', \Throwable $previous = null, int $code = 0, array $headers = []) + public function __construct(string $challenge, string $message = '', ?\Throwable $previous = null, int $code = 0, array $headers = []) { $headers['WWW-Authenticate'] = $challenge; diff --git a/src/Symfony/Component/HttpKernel/Exception/UnprocessableEntityHttpException.php b/src/Symfony/Component/HttpKernel/Exception/UnprocessableEntityHttpException.php index d58af6c2b6677..162aa30d6bf91 100644 --- a/src/Symfony/Component/HttpKernel/Exception/UnprocessableEntityHttpException.php +++ b/src/Symfony/Component/HttpKernel/Exception/UnprocessableEntityHttpException.php @@ -16,7 +16,7 @@ */ class UnprocessableEntityHttpException extends HttpException { - public function __construct(string $message = '', \Throwable $previous = null, int $code = 0, array $headers = []) + public function __construct(string $message = '', ?\Throwable $previous = null, int $code = 0, array $headers = []) { parent::__construct(422, $message, $previous, $headers, $code); } diff --git a/src/Symfony/Component/HttpKernel/Exception/UnsupportedMediaTypeHttpException.php b/src/Symfony/Component/HttpKernel/Exception/UnsupportedMediaTypeHttpException.php index 3060f1f91810a..736337bab06d5 100644 --- a/src/Symfony/Component/HttpKernel/Exception/UnsupportedMediaTypeHttpException.php +++ b/src/Symfony/Component/HttpKernel/Exception/UnsupportedMediaTypeHttpException.php @@ -16,7 +16,7 @@ */ class UnsupportedMediaTypeHttpException extends HttpException { - public function __construct(string $message = '', \Throwable $previous = null, int $code = 0, array $headers = []) + public function __construct(string $message = '', ?\Throwable $previous = null, int $code = 0, array $headers = []) { parent::__construct(415, $message, $previous, $headers, $code); } diff --git a/src/Symfony/Component/HttpKernel/Fragment/AbstractSurrogateFragmentRenderer.php b/src/Symfony/Component/HttpKernel/Fragment/AbstractSurrogateFragmentRenderer.php index 14a3f3cf24133..e6419e922d320 100644 --- a/src/Symfony/Component/HttpKernel/Fragment/AbstractSurrogateFragmentRenderer.php +++ b/src/Symfony/Component/HttpKernel/Fragment/AbstractSurrogateFragmentRenderer.php @@ -34,7 +34,7 @@ abstract class AbstractSurrogateFragmentRenderer extends RoutableFragmentRendere * * @param FragmentRendererInterface $inlineStrategy The inline strategy to use when the surrogate is not supported */ - public function __construct(?SurrogateInterface $surrogate, FragmentRendererInterface $inlineStrategy, UriSigner $signer = null) + public function __construct(?SurrogateInterface $surrogate, FragmentRendererInterface $inlineStrategy, ?UriSigner $signer = null) { $this->surrogate = $surrogate; $this->inlineStrategy = $inlineStrategy; diff --git a/src/Symfony/Component/HttpKernel/Fragment/FragmentUriGenerator.php b/src/Symfony/Component/HttpKernel/Fragment/FragmentUriGenerator.php index aeef41546e011..59423293e8071 100644 --- a/src/Symfony/Component/HttpKernel/Fragment/FragmentUriGenerator.php +++ b/src/Symfony/Component/HttpKernel/Fragment/FragmentUriGenerator.php @@ -28,14 +28,14 @@ final class FragmentUriGenerator implements FragmentUriGeneratorInterface private ?UriSigner $signer; private ?RequestStack $requestStack; - public function __construct(string $fragmentPath, UriSigner $signer = null, RequestStack $requestStack = null) + public function __construct(string $fragmentPath, ?UriSigner $signer = null, ?RequestStack $requestStack = null) { $this->fragmentPath = $fragmentPath; $this->signer = $signer; $this->requestStack = $requestStack; } - public function generate(ControllerReference $controller, Request $request = null, bool $absolute = false, bool $strict = true, bool $sign = true): string + public function generate(ControllerReference $controller, ?Request $request = null, bool $absolute = false, bool $strict = true, bool $sign = true): string { if (null === $request && (null === $this->requestStack || null === $request = $this->requestStack->getCurrentRequest())) { throw new \LogicException('Generating a fragment URL can only be done when handling a Request.'); diff --git a/src/Symfony/Component/HttpKernel/Fragment/FragmentUriGeneratorInterface.php b/src/Symfony/Component/HttpKernel/Fragment/FragmentUriGeneratorInterface.php index 968c002b90896..6b1317c3a73bc 100644 --- a/src/Symfony/Component/HttpKernel/Fragment/FragmentUriGeneratorInterface.php +++ b/src/Symfony/Component/HttpKernel/Fragment/FragmentUriGeneratorInterface.php @@ -28,5 +28,5 @@ interface FragmentUriGeneratorInterface * @param bool $strict Whether to allow non-scalar attributes or not * @param bool $sign Whether to sign the URL or not */ - public function generate(ControllerReference $controller, Request $request = null, bool $absolute = false, bool $strict = true, bool $sign = true): string; + public function generate(ControllerReference $controller, ?Request $request = null, bool $absolute = false, bool $strict = true, bool $sign = true): string; } diff --git a/src/Symfony/Component/HttpKernel/Fragment/HIncludeFragmentRenderer.php b/src/Symfony/Component/HttpKernel/Fragment/HIncludeFragmentRenderer.php index d5b6c4cd3c22a..edcf9938c4c93 100644 --- a/src/Symfony/Component/HttpKernel/Fragment/HIncludeFragmentRenderer.php +++ b/src/Symfony/Component/HttpKernel/Fragment/HIncludeFragmentRenderer.php @@ -32,7 +32,7 @@ class HIncludeFragmentRenderer extends RoutableFragmentRenderer /** * @param string|null $globalDefaultTemplate The global default content (it can be a template name or the content) */ - public function __construct(Environment $twig = null, UriSigner $signer = null, string $globalDefaultTemplate = null, string $charset = 'utf-8') + public function __construct(?Environment $twig = null, ?UriSigner $signer = null, ?string $globalDefaultTemplate = null, string $charset = 'utf-8') { $this->twig = $twig; $this->globalDefaultTemplate = $globalDefaultTemplate; diff --git a/src/Symfony/Component/HttpKernel/Fragment/InlineFragmentRenderer.php b/src/Symfony/Component/HttpKernel/Fragment/InlineFragmentRenderer.php index 6b815a87ba91a..db20eb78b9031 100644 --- a/src/Symfony/Component/HttpKernel/Fragment/InlineFragmentRenderer.php +++ b/src/Symfony/Component/HttpKernel/Fragment/InlineFragmentRenderer.php @@ -30,7 +30,7 @@ class InlineFragmentRenderer extends RoutableFragmentRenderer private HttpKernelInterface $kernel; private ?EventDispatcherInterface $dispatcher; - public function __construct(HttpKernelInterface $kernel, EventDispatcherInterface $dispatcher = null) + public function __construct(HttpKernelInterface $kernel, ?EventDispatcherInterface $dispatcher = null) { $this->kernel = $kernel; $this->dispatcher = $dispatcher; diff --git a/src/Symfony/Component/HttpKernel/HttpCache/Esi.php b/src/Symfony/Component/HttpKernel/HttpCache/Esi.php index 3c72ceee0853e..2cff07aaf949a 100644 --- a/src/Symfony/Component/HttpKernel/HttpCache/Esi.php +++ b/src/Symfony/Component/HttpKernel/HttpCache/Esi.php @@ -39,7 +39,7 @@ public function addSurrogateControl(Response $response): void } } - public function renderIncludeTag(string $uri, string $alt = null, bool $ignoreErrors = true, string $comment = ''): string + public function renderIncludeTag(string $uri, ?string $alt = null, bool $ignoreErrors = true, string $comment = ''): string { $html = sprintf('', $uri, diff --git a/src/Symfony/Component/HttpKernel/HttpCache/HttpCache.php b/src/Symfony/Component/HttpKernel/HttpCache/HttpCache.php index 654537110fefc..6175a52712d15 100644 --- a/src/Symfony/Component/HttpKernel/HttpCache/HttpCache.php +++ b/src/Symfony/Component/HttpKernel/HttpCache/HttpCache.php @@ -84,7 +84,7 @@ class HttpCache implements HttpKernelInterface, TerminableInterface * This setting is overridden by the stale-if-error HTTP Cache-Control extension * (see RFC 5861). */ - public function __construct(HttpKernelInterface $kernel, StoreInterface $store, SurrogateInterface $surrogate = null, array $options = []) + public function __construct(HttpKernelInterface $kernel, StoreInterface $store, ?SurrogateInterface $surrogate = null, array $options = []) { $this->store = $store; $this->kernel = $kernel; @@ -452,7 +452,7 @@ protected function fetch(Request $request, bool $catch = false): Response * @param bool $catch Whether to catch exceptions or not * @param Response|null $entry A Response instance (the stale entry if present, null otherwise) */ - protected function forward(Request $request, bool $catch = false, Response $entry = null): Response + protected function forward(Request $request, bool $catch = false, ?Response $entry = null): Response { $this->surrogate?->addSurrogateCapability($request); diff --git a/src/Symfony/Component/HttpKernel/HttpCache/ResponseCacheStrategy.php b/src/Symfony/Component/HttpKernel/HttpCache/ResponseCacheStrategy.php index 52c34fbdc8e7e..c7d32f6bfa461 100644 --- a/src/Symfony/Component/HttpKernel/HttpCache/ResponseCacheStrategy.php +++ b/src/Symfony/Component/HttpKernel/HttpCache/ResponseCacheStrategy.php @@ -37,7 +37,7 @@ class ResponseCacheStrategy implements ResponseCacheStrategyInterface private int $embeddedResponses = 0; private bool $isNotCacheableResponseEmbedded = false; private int $age = 0; - private \DateTimeInterface|null|false $lastModified = null; + private \DateTimeInterface|false|null $lastModified = null; private array $flagDirectives = [ 'no-cache' => null, 'no-store' => null, diff --git a/src/Symfony/Component/HttpKernel/HttpCache/Ssi.php b/src/Symfony/Component/HttpKernel/HttpCache/Ssi.php index 433a6a6ea1bc2..66d51d76b4f75 100644 --- a/src/Symfony/Component/HttpKernel/HttpCache/Ssi.php +++ b/src/Symfony/Component/HttpKernel/HttpCache/Ssi.php @@ -33,7 +33,7 @@ public function addSurrogateControl(Response $response): void } } - public function renderIncludeTag(string $uri, string $alt = null, bool $ignoreErrors = true, string $comment = ''): string + public function renderIncludeTag(string $uri, ?string $alt = null, bool $ignoreErrors = true, string $comment = ''): string { return sprintf('', $uri); } diff --git a/src/Symfony/Component/HttpKernel/HttpCache/Store.php b/src/Symfony/Component/HttpKernel/HttpCache/Store.php index b6a2e76bd59c5..e885abc81551f 100644 --- a/src/Symfony/Component/HttpKernel/HttpCache/Store.php +++ b/src/Symfony/Component/HttpKernel/HttpCache/Store.php @@ -467,7 +467,7 @@ private function persistResponse(Response $response): array /** * Restores a Response from the HTTP headers and body. */ - private function restoreResponse(array $headers, string $path = null): ?Response + private function restoreResponse(array $headers, ?string $path = null): ?Response { $status = $headers['X-Status'][0]; unset($headers['X-Status']); diff --git a/src/Symfony/Component/HttpKernel/HttpCache/SurrogateInterface.php b/src/Symfony/Component/HttpKernel/HttpCache/SurrogateInterface.php index 33d207fb1bb9f..45b358b7b3f35 100644 --- a/src/Symfony/Component/HttpKernel/HttpCache/SurrogateInterface.php +++ b/src/Symfony/Component/HttpKernel/HttpCache/SurrogateInterface.php @@ -54,7 +54,7 @@ public function needsParsing(Response $response): bool; * @param string|null $alt An alternate URI * @param string $comment A comment to add as an esi:include tag */ - public function renderIncludeTag(string $uri, string $alt = null, bool $ignoreErrors = true, string $comment = ''): string; + public function renderIncludeTag(string $uri, ?string $alt = null, bool $ignoreErrors = true, string $comment = ''): string; /** * Replaces a Response Surrogate tags with the included resource content. diff --git a/src/Symfony/Component/HttpKernel/HttpClientKernel.php b/src/Symfony/Component/HttpKernel/HttpClientKernel.php index 1d8c30278108a..7c719e8e61e30 100644 --- a/src/Symfony/Component/HttpKernel/HttpClientKernel.php +++ b/src/Symfony/Component/HttpKernel/HttpClientKernel.php @@ -33,7 +33,7 @@ final class HttpClientKernel implements HttpKernelInterface { private HttpClientInterface $client; - public function __construct(HttpClientInterface $client = null) + public function __construct(?HttpClientInterface $client = null) { if (null === $client && !class_exists(HttpClient::class)) { throw new \LogicException(sprintf('You cannot use "%s" as the HttpClient component is not installed. Try running "composer require symfony/http-client".', __CLASS__)); diff --git a/src/Symfony/Component/HttpKernel/HttpKernel.php b/src/Symfony/Component/HttpKernel/HttpKernel.php index 0be34ff652ab3..be59335dc1d4d 100644 --- a/src/Symfony/Component/HttpKernel/HttpKernel.php +++ b/src/Symfony/Component/HttpKernel/HttpKernel.php @@ -58,7 +58,7 @@ class HttpKernel implements HttpKernelInterface, TerminableInterface private bool $handleAllThrowables; private bool $terminating = false; - public function __construct(EventDispatcherInterface $dispatcher, ControllerResolverInterface $resolver, RequestStack $requestStack = null, ArgumentResolverInterface $argumentResolver = null, bool $handleAllThrowables = false) + public function __construct(EventDispatcherInterface $dispatcher, ControllerResolverInterface $resolver, ?RequestStack $requestStack = null, ?ArgumentResolverInterface $argumentResolver = null, bool $handleAllThrowables = false) { $this->dispatcher = $dispatcher; $this->resolver = $resolver; @@ -121,7 +121,7 @@ public function terminate(Request $request, Response $response): void /** * @internal */ - public function terminateWithException(\Throwable $exception, Request $request = null): void + public function terminateWithException(\Throwable $exception, ?Request $request = null): void { if (!$request ??= $this->requestStack->getMainRequest()) { throw $exception; diff --git a/src/Symfony/Component/HttpKernel/HttpKernelBrowser.php b/src/Symfony/Component/HttpKernel/HttpKernelBrowser.php index 83761ee69860f..c9235c6668cd4 100644 --- a/src/Symfony/Component/HttpKernel/HttpKernelBrowser.php +++ b/src/Symfony/Component/HttpKernel/HttpKernelBrowser.php @@ -36,7 +36,7 @@ class HttpKernelBrowser extends AbstractBrowser /** * @param array $server The server parameters (equivalent of $_SERVER) */ - public function __construct(HttpKernelInterface $kernel, array $server = [], History $history = null, CookieJar $cookieJar = null) + public function __construct(HttpKernelInterface $kernel, array $server = [], ?History $history = null, ?CookieJar $cookieJar = null) { // These class properties must be set before calling the parent constructor, as it may depend on it. $this->kernel = $kernel; diff --git a/src/Symfony/Component/HttpKernel/Kernel.php b/src/Symfony/Component/HttpKernel/Kernel.php index 11950a5a522d6..b4b8663c4b22f 100644 --- a/src/Symfony/Component/HttpKernel/Kernel.php +++ b/src/Symfony/Component/HttpKernel/Kernel.php @@ -314,6 +314,8 @@ public function getCharset(): string /** * Gets the patterns defining the classes to parse and cache for annotations. + * + * @return string[] */ public function getAnnotatedClassesToCompile(): array { @@ -534,6 +536,8 @@ protected function initializeContainer(): void /** * Returns the kernel parameters. + * + * @return array */ protected function getKernelParameters(): array { diff --git a/src/Symfony/Component/HttpKernel/Log/DebugLoggerConfigurator.php b/src/Symfony/Component/HttpKernel/Log/DebugLoggerConfigurator.php index 537c1004083f4..e036f398eced7 100644 --- a/src/Symfony/Component/HttpKernel/Log/DebugLoggerConfigurator.php +++ b/src/Symfony/Component/HttpKernel/Log/DebugLoggerConfigurator.php @@ -20,7 +20,7 @@ class DebugLoggerConfigurator { private ?object $processor = null; - public function __construct(callable $processor, bool $enable = null) + public function __construct(callable $processor, ?bool $enable = null) { if ($enable ?? !\in_array(\PHP_SAPI, ['cli', 'phpdbg', 'embed'], true)) { $this->processor = \is_object($processor) ? $processor : $processor(...); diff --git a/src/Symfony/Component/HttpKernel/Log/DebugLoggerInterface.php b/src/Symfony/Component/HttpKernel/Log/DebugLoggerInterface.php index b35769d3d0268..50bb08d7f5d15 100644 --- a/src/Symfony/Component/HttpKernel/Log/DebugLoggerInterface.php +++ b/src/Symfony/Component/HttpKernel/Log/DebugLoggerInterface.php @@ -33,12 +33,12 @@ interface DebugLoggerInterface * timestamp_rfc3339: string, * }> */ - public function getLogs(Request $request = null): array; + public function getLogs(?Request $request = null): array; /** * Returns the number of errors. */ - public function countErrors(Request $request = null): int; + public function countErrors(?Request $request = null): int; /** * Removes all log records. diff --git a/src/Symfony/Component/HttpKernel/Log/Logger.php b/src/Symfony/Component/HttpKernel/Log/Logger.php index 11d35b7e2f9fd..6b7a90d4291ac 100644 --- a/src/Symfony/Component/HttpKernel/Log/Logger.php +++ b/src/Symfony/Component/HttpKernel/Log/Logger.php @@ -57,7 +57,7 @@ class Logger extends AbstractLogger implements DebugLoggerInterface /** * @param string|resource|null $output */ - public function __construct(string $minLevel = null, $output = null, callable $formatter = null, private readonly ?RequestStack $requestStack = null, bool $debug = false) + public function __construct(?string $minLevel = null, $output = null, ?callable $formatter = null, private readonly ?RequestStack $requestStack = null, bool $debug = false) { if (null === $minLevel) { $minLevel = null === $output || 'php://stdout' === $output || 'php://stderr' === $output ? LogLevel::ERROR : LogLevel::WARNING; @@ -112,7 +112,7 @@ public function log($level, $message, array $context = []): void } } - public function getLogs(Request $request = null): array + public function getLogs(?Request $request = null): array { if ($request) { return $this->logs[spl_object_id($request)] ?? []; @@ -121,7 +121,7 @@ public function getLogs(Request $request = null): array return array_merge(...array_values($this->logs)); } - public function countErrors(Request $request = null): int + public function countErrors(?Request $request = null): int { if ($request) { return $this->errorCount[spl_object_id($request)] ?? 0; diff --git a/src/Symfony/Component/HttpKernel/Profiler/FileProfilerStorage.php b/src/Symfony/Component/HttpKernel/Profiler/FileProfilerStorage.php index d740598c896f6..16d99504aa9d6 100644 --- a/src/Symfony/Component/HttpKernel/Profiler/FileProfilerStorage.php +++ b/src/Symfony/Component/HttpKernel/Profiler/FileProfilerStorage.php @@ -42,7 +42,7 @@ public function __construct(string $dsn) } } - public function find(?string $ip, ?string $url, ?int $limit, ?string $method, int $start = null, int $end = null, string $statusCode = null, \Closure $filter = null): array + public function find(?string $ip, ?string $url, ?int $limit, ?string $method, ?int $start = null, ?int $end = null, ?string $statusCode = null, ?\Closure $filter = null): array { $file = $this->getIndexFilename(); @@ -262,7 +262,7 @@ protected function readLineFromFile($file): mixed return '' === $line ? null : $line; } - protected function createProfileFromData(string $token, array $data, Profile $parent = null): Profile + protected function createProfileFromData(string $token, array $data, ?Profile $parent = null): Profile { $profile = new Profile($token); $profile->setIp($data['ip']); @@ -290,7 +290,7 @@ protected function createProfileFromData(string $token, array $data, Profile $pa return $profile; } - private function doRead($token, Profile $profile = null): ?Profile + private function doRead($token, ?Profile $profile = null): ?Profile { if (!$token || !file_exists($file = $this->getFilename($token))) { return null; diff --git a/src/Symfony/Component/HttpKernel/Profiler/Profiler.php b/src/Symfony/Component/HttpKernel/Profiler/Profiler.php index 44fef547f49e0..e67e247c0954c 100644 --- a/src/Symfony/Component/HttpKernel/Profiler/Profiler.php +++ b/src/Symfony/Component/HttpKernel/Profiler/Profiler.php @@ -37,7 +37,7 @@ class Profiler implements ResetInterface private bool $initiallyEnabled = true; private bool $enabled = true; - public function __construct(ProfilerStorageInterface $storage, LoggerInterface $logger = null, bool $enable = true) + public function __construct(ProfilerStorageInterface $storage, ?LoggerInterface $logger = null, bool $enable = true) { $this->storage = $storage; $this->logger = $logger; @@ -122,7 +122,7 @@ public function purge(): void * * @see https://php.net/datetime.formats for the supported date/time formats */ - public function find(?string $ip, ?string $url, ?int $limit, ?string $method, ?string $start, ?string $end, string $statusCode = null, \Closure $filter = null): array + public function find(?string $ip, ?string $url, ?int $limit, ?string $method, ?string $start, ?string $end, ?string $statusCode = null, ?\Closure $filter = null): array { return $this->storage->find($ip, $url, $limit, $method, $this->getTimestamp($start), $this->getTimestamp($end), $statusCode, $filter); } @@ -130,7 +130,7 @@ public function find(?string $ip, ?string $url, ?int $limit, ?string $method, ?s /** * Collects data for the given Response. */ - public function collect(Request $request, Response $response, \Throwable $exception = null): ?Profile + public function collect(Request $request, Response $response, ?\Throwable $exception = null): ?Profile { if (false === $this->enabled) { return null; diff --git a/src/Symfony/Component/HttpKernel/Profiler/ProfilerStorageInterface.php b/src/Symfony/Component/HttpKernel/Profiler/ProfilerStorageInterface.php index 1060df3868d9d..eed353d17181a 100644 --- a/src/Symfony/Component/HttpKernel/Profiler/ProfilerStorageInterface.php +++ b/src/Symfony/Component/HttpKernel/Profiler/ProfilerStorageInterface.php @@ -35,7 +35,7 @@ interface ProfilerStorageInterface * @param string|null $statusCode The response status code * @param \Closure|null $filter A filter to apply on the list of tokens */ - public function find(?string $ip, ?string $url, ?int $limit, ?string $method, int $start = null, int $end = null, string $statusCode = null, \Closure $filter = null): array; + public function find(?string $ip, ?string $url, ?int $limit, ?string $method, ?int $start = null, ?int $end = null, ?string $statusCode = null, ?\Closure $filter = null): array; /** * Reads data associated with the given token. diff --git a/src/Symfony/Component/HttpKernel/Resources/welcome.html.php b/src/Symfony/Component/HttpKernel/Resources/welcome.html.php index 83602bfe93d9f..24f68806b8254 100644 --- a/src/Symfony/Component/HttpKernel/Resources/welcome.html.php +++ b/src/Symfony/Component/HttpKernel/Resources/welcome.html.php @@ -1,10 +1,125 @@ + +SVG; + +// SVG icons from the Tabler Icons project +// MIT License - Copyright (c) 2020-2023 Paweł Kuna +// https://github.com/tabler/tabler-icons/blob/master/LICENSE + +$renderBoxIconSvg = << + + + + + + +SVG; + +$renderFolderIconSvg = << + + + +SVG; + +$renderInfoIconSvg = << + + + + + +SVG; + +$renderNextStepIconSvg = << + + + + + +SVG; + +$renderLearnIconSvg = << +SVG; + +$renderCommunityIconSvg = << +SVG; + +$renderUpdatesIconSvg = << +SVG; + +$renderWavesSvg = << +SVG; +?> - - + + Welcome to Symfony! - + - - - - - - - - - - - - - - - - SVG; - } - ?> diff --git a/src/Symfony/Component/HttpKernel/Tests/CacheWarmer/CacheWarmerTest.php b/src/Symfony/Component/HttpKernel/Tests/CacheWarmer/CacheWarmerTest.php index c1d6650fc3183..13ae22dbfbcef 100644 --- a/src/Symfony/Component/HttpKernel/Tests/CacheWarmer/CacheWarmerTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/CacheWarmer/CacheWarmerTest.php @@ -54,7 +54,7 @@ public function __construct(string $file) $this->file = $file; } - public function warmUp(string $cacheDir, string $buildDir = null): array + public function warmUp(string $cacheDir, ?string $buildDir = null): array { $this->writeCacheFile($this->file, 'content'); diff --git a/src/Symfony/Component/HttpKernel/Tests/Controller/ArgumentResolver/RequestPayloadValueResolverTest.php b/src/Symfony/Component/HttpKernel/Tests/Controller/ArgumentResolver/RequestPayloadValueResolverTest.php index 238e53d7ef481..e6175b33e75b2 100644 --- a/src/Symfony/Component/HttpKernel/Tests/Controller/ArgumentResolver/RequestPayloadValueResolverTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/Controller/ArgumentResolver/RequestPayloadValueResolverTest.php @@ -23,9 +23,12 @@ use Symfony\Component\HttpKernel\HttpKernelInterface; use Symfony\Component\Serializer\Encoder\JsonEncoder; use Symfony\Component\Serializer\Encoder\XmlEncoder; +use Symfony\Component\Serializer\Exception\NotNormalizableValueException; use Symfony\Component\Serializer\Exception\PartialDenormalizationException; +use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; use Symfony\Component\Serializer\Serializer; +use Symfony\Component\Serializer\SerializerInterface; use Symfony\Component\Validator\Constraints as Assert; use Symfony\Component\Validator\ConstraintViolation; use Symfony\Component\Validator\ConstraintViolationList; @@ -334,6 +337,34 @@ public function testRequestContentValidationPassed() $this->assertEquals([$payload], $event->getArguments()); } + /** + * @testWith [null] + * [[]] + */ + public function testRequestContentWithUntypedErrors(?array $types) + { + $this->expectException(HttpException::class); + $this->expectExceptionMessage('This value was of an unexpected type.'); + $serializer = $this->createMock(SerializerDenormalizer::class); + + if (null === $types) { + $exception = new NotNormalizableValueException('Error with no types'); + } else { + $exception = NotNormalizableValueException::createForUnexpectedDataType('Error with no types', '', []); + } + $serializer->method('deserialize')->willThrowException(new PartialDenormalizationException([], [$exception])); + + $resolver = new RequestPayloadValueResolver($serializer, $this->createMock(ValidatorInterface::class)); + $request = Request::create('/', 'POST', server: ['CONTENT_TYPE' => 'application/json'], content: '{"price": 50}'); + + $arguments = $resolver->resolve($request, new ArgumentMetadata('valid', RequestPayload::class, false, false, null, false, [ + MapRequestPayload::class => new MapRequestPayload(), + ])); + $event = new ControllerArgumentsEvent($this->createMock(HttpKernelInterface::class), function () {}, $arguments, $request, HttpKernelInterface::MAIN_REQUEST); + + $resolver->onKernelControllerArguments($event); + } + public function testQueryStringValidationPassed() { $payload = new RequestPayload(50); @@ -703,6 +734,10 @@ public function __construct(public readonly float $price) } } +interface SerializerDenormalizer extends SerializerInterface, DenormalizerInterface +{ +} + class QueryPayload { public function __construct(public readonly float $page) diff --git a/src/Symfony/Component/HttpKernel/Tests/Controller/ArgumentResolverTest.php b/src/Symfony/Component/HttpKernel/Tests/Controller/ArgumentResolverTest.php index 284aba4c0c035..c8e97d53216f1 100644 --- a/src/Symfony/Component/HttpKernel/Tests/Controller/ArgumentResolverTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/Controller/ArgumentResolverTest.php @@ -32,7 +32,7 @@ class ArgumentResolverTest extends TestCase { - public static function getResolver(array $chainableResolvers = [], array $namedResolvers = null): ArgumentResolver + public static function getResolver(array $chainableResolvers = [], ?array $namedResolvers = null): ArgumentResolver { if (null !== $namedResolvers) { $namedResolvers = new ServiceLocator(array_map(fn ($resolver) => fn () => $resolver, $namedResolvers)); diff --git a/src/Symfony/Component/HttpKernel/Tests/Controller/ContainerControllerResolverTest.php b/src/Symfony/Component/HttpKernel/Tests/Controller/ContainerControllerResolverTest.php index 16d14d0f5ab48..675ea5b298517 100644 --- a/src/Symfony/Component/HttpKernel/Tests/Controller/ContainerControllerResolverTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/Controller/ContainerControllerResolverTest.php @@ -23,16 +23,8 @@ public function testGetControllerService() { $service = new ControllerTestService('foo'); - $container = $this->createMockContainer(); - $container->expects($this->once()) - ->method('has') - ->with('foo') - ->willReturn(true); - $container->expects($this->once()) - ->method('get') - ->with('foo') - ->willReturn($service) - ; + $container = new Container(); + $container->set('foo', $service); $resolver = $this->createControllerResolver(null, $container); $request = Request::create('/'); @@ -48,17 +40,8 @@ public function testGetControllerInvokableService() { $service = new InvokableControllerService('bar'); - $container = $this->createMockContainer(); - $container->expects($this->once()) - ->method('has') - ->with('foo') - ->willReturn(true) - ; - $container->expects($this->once()) - ->method('get') - ->with('foo') - ->willReturn($service) - ; + $container = new Container(); + $container->set('foo', $service); $resolver = $this->createControllerResolver(null, $container); $request = Request::create('/'); @@ -73,17 +56,8 @@ public function testGetControllerInvokableServiceWithClassNameAsName() { $service = new InvokableControllerService('bar'); - $container = $this->createMockContainer(); - $container->expects($this->once()) - ->method('has') - ->with(InvokableControllerService::class) - ->willReturn(true) - ; - $container->expects($this->once()) - ->method('get') - ->with(InvokableControllerService::class) - ->willReturn($service) - ; + $container = new Container(); + $container->set(InvokableControllerService::class, $service); $resolver = $this->createControllerResolver(null, $container); $request = Request::create('/'); @@ -102,9 +76,8 @@ public function testInstantiateControllerWhenControllerStartsWithABackslash($con $service = new ControllerTestService('foo'); $class = ControllerTestService::class; - $container = $this->createMockContainer(); - $container->expects($this->once())->method('has')->with($class)->willReturn(true); - $container->expects($this->once())->method('get')->with($class)->willReturn($service); + $container = new Container(); + $container->set($class, $service); $resolver = $this->createControllerResolver(null, $container); $request = Request::create('/'); @@ -195,19 +168,14 @@ public static function getUndefinedControllers(): array return $tests; } - protected function createControllerResolver(LoggerInterface $logger = null, ContainerInterface $container = null) + protected function createControllerResolver(?LoggerInterface $logger = null, ?ContainerInterface $container = null) { if (!$container) { - $container = $this->createMockContainer(); + $container = new Container(); } return new ContainerControllerResolver($container, $logger); } - - protected function createMockContainer() - { - return $this->createMock(ContainerInterface::class); - } } class InvokableControllerService diff --git a/src/Symfony/Component/HttpKernel/Tests/Controller/ControllerResolverTest.php b/src/Symfony/Component/HttpKernel/Tests/Controller/ControllerResolverTest.php index 25ff02f380a5c..9693b273d4512 100644 --- a/src/Symfony/Component/HttpKernel/Tests/Controller/ControllerResolverTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/Controller/ControllerResolverTest.php @@ -251,7 +251,7 @@ public function testAllowedAsControllerAttribute() $this->assertSame($controller, $resolver->getController($request)); } - protected function createControllerResolver(LoggerInterface $logger = null) + protected function createControllerResolver(?LoggerInterface $logger = null) { return new ControllerResolver($logger); } diff --git a/src/Symfony/Component/HttpKernel/Tests/Controller/TraceableArgumentResolverTest.php b/src/Symfony/Component/HttpKernel/Tests/Controller/TraceableArgumentResolverTest.php index ddef52bfa702a..71c9b799c0cde 100644 --- a/src/Symfony/Component/HttpKernel/Tests/Controller/TraceableArgumentResolverTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/Controller/TraceableArgumentResolverTest.php @@ -29,7 +29,7 @@ public function testStopwatchEventIsStoppedWhenResolverThrows() $stopwatch->method('start')->willReturn($stopwatchEvent); $resolver = new class() implements ArgumentResolverInterface { - public function getArguments(Request $request, callable $controller, \ReflectionFunctionAbstract $reflector = null): array + public function getArguments(Request $request, callable $controller, ?\ReflectionFunctionAbstract $reflector = null): array { throw new \Exception(); } diff --git a/src/Symfony/Component/HttpKernel/Tests/ControllerMetadata/ArgumentMetadataFactoryTest.php b/src/Symfony/Component/HttpKernel/Tests/ControllerMetadata/ArgumentMetadataFactoryTest.php index 8c3d93d5aed24..9c6fd03fb796d 100644 --- a/src/Symfony/Component/HttpKernel/Tests/ControllerMetadata/ArgumentMetadataFactoryTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/ControllerMetadata/ArgumentMetadataFactoryTest.php @@ -155,7 +155,7 @@ public function signature1(self $foo, array $bar, callable $baz) { } - public function signature2(self $foo = null, FakeClassThatDoesNotExist $bar = null, ImportedAndFake $baz = null) + public function signature2(?self $foo = null, ?FakeClassThatDoesNotExist $bar = null, ?ImportedAndFake $baz = null) { } @@ -167,7 +167,7 @@ public function signature4($foo = 'default', $bar = 500, $baz = []) { } - public function signature5(array $foo = null, $bar = null) + public function signature5(?array $foo = null, $bar = null) { } } diff --git a/src/Symfony/Component/HttpKernel/Tests/DependencyInjection/FragmentRendererPassTest.php b/src/Symfony/Component/HttpKernel/Tests/DependencyInjection/FragmentRendererPassTest.php index ab0efe32f56e8..89e2eed6e8a64 100644 --- a/src/Symfony/Component/HttpKernel/Tests/DependencyInjection/FragmentRendererPassTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/DependencyInjection/FragmentRendererPassTest.php @@ -60,7 +60,7 @@ public function testValidContentRenderer() class RendererService implements FragmentRendererInterface { - public function render($uri, Request $request = null, array $options = []): Response + public function render($uri, ?Request $request = null, array $options = []): Response { } diff --git a/src/Symfony/Component/HttpKernel/Tests/DependencyInjection/LazyLoadingFragmentHandlerTest.php b/src/Symfony/Component/HttpKernel/Tests/DependencyInjection/LazyLoadingFragmentHandlerTest.php index 3a9ef486df19d..6ec35006c5951 100644 --- a/src/Symfony/Component/HttpKernel/Tests/DependencyInjection/LazyLoadingFragmentHandlerTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/DependencyInjection/LazyLoadingFragmentHandlerTest.php @@ -12,7 +12,7 @@ namespace Symfony\Component\HttpKernel\Tests\DependencyInjection; use PHPUnit\Framework\TestCase; -use Psr\Container\ContainerInterface; +use Symfony\Component\DependencyInjection\Container; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\HttpFoundation\Response; @@ -30,9 +30,8 @@ public function testRender() $requestStack = new RequestStack(); $requestStack->push(Request::create('/')); - $container = $this->createMock(ContainerInterface::class); - $container->expects($this->once())->method('has')->with('foo')->willReturn(true); - $container->expects($this->once())->method('get')->willReturn($renderer); + $container = new Container(); + $container->set('foo', $renderer); $handler = new LazyLoadingFragmentHandler($container, $requestStack, false); diff --git a/src/Symfony/Component/HttpKernel/Tests/DependencyInjection/RegisterControllerArgumentLocatorsPassTest.php b/src/Symfony/Component/HttpKernel/Tests/DependencyInjection/RegisterControllerArgumentLocatorsPassTest.php index 72be002e9d099..34f6bf10ee8b1 100644 --- a/src/Symfony/Component/HttpKernel/Tests/DependencyInjection/RegisterControllerArgumentLocatorsPassTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/DependencyInjection/RegisterControllerArgumentLocatorsPassTest.php @@ -578,7 +578,7 @@ class ContainerAwareRegisterTestController { protected ?ContainerInterface $container; - public function setContainer(ContainerInterface $container = null): void + public function setContainer(?ContainerInterface $container = null): void { $this->container = $container; } @@ -608,11 +608,11 @@ public function fooAction(\Acme\NonExistentClass $nonExistent) class NonExistentClassOptionalController { - public function fooAction(NonExistentClass $nonExistent = null) + public function fooAction(?NonExistentClass $nonExistent = null) { } - public function barAction(NonExistentClass $nonExistent = null, $bar) + public function barAction(?NonExistentClass $nonExistent, $bar) { } } @@ -686,7 +686,7 @@ public function fooAction( #[AutowireCallable(service: 'some.id', method: 'bar')] FooInterface $autowireCallable, #[Autowire(service: 'invalid.id')] - \stdClass $service2 = null, + ?\stdClass $service2 = null, ) { } } diff --git a/src/Symfony/Component/HttpKernel/Tests/DependencyInjection/RemoveEmptyControllerArgumentLocatorsPassTest.php b/src/Symfony/Component/HttpKernel/Tests/DependencyInjection/RemoveEmptyControllerArgumentLocatorsPassTest.php index b9dd84d592fa6..8c99b882d32ca 100644 --- a/src/Symfony/Component/HttpKernel/Tests/DependencyInjection/RemoveEmptyControllerArgumentLocatorsPassTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/DependencyInjection/RemoveEmptyControllerArgumentLocatorsPassTest.php @@ -81,7 +81,7 @@ public function testInvoke() class RemoveTestController1 { - public function fooAction(\stdClass $bar, ClassNotInContainer $baz = null) + public function fooAction(\stdClass $bar, ?ClassNotInContainer $baz = null) { } } @@ -92,7 +92,7 @@ public function setTestCase(TestCase $test) { } - public function fooAction(ClassNotInContainer $bar = null) + public function fooAction(?ClassNotInContainer $bar = null) { } } diff --git a/src/Symfony/Component/HttpKernel/Tests/EventListener/ErrorListenerTest.php b/src/Symfony/Component/HttpKernel/Tests/EventListener/ErrorListenerTest.php index 7b9e7ce0667e8..754587e053f70 100644 --- a/src/Symfony/Component/HttpKernel/Tests/EventListener/ErrorListenerTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/EventListener/ErrorListenerTest.php @@ -319,7 +319,7 @@ public static function exceptionWithAttributeProvider() class TestLogger extends Logger implements DebugLoggerInterface { - public function countErrors(Request $request = null): int + public function countErrors(?Request $request = null): int { return \count($this->logs['critical']); } diff --git a/src/Symfony/Component/HttpKernel/Tests/EventListener/RouterListenerTest.php b/src/Symfony/Component/HttpKernel/Tests/EventListener/RouterListenerTest.php index dab6c5d4ede06..e68461a18cfaf 100644 --- a/src/Symfony/Component/HttpKernel/Tests/EventListener/RouterListenerTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/EventListener/RouterListenerTest.php @@ -178,7 +178,7 @@ public function testNoRoutingConfigurationResponse() $requestMatcher = $this->createMock(RequestMatcherInterface::class); $requestMatcher - ->expects($this->once()) + ->expects($this->exactly(2)) ->method('matchRequest') ->willThrowException(new NoConfigurationException()) ; @@ -189,6 +189,11 @@ public function testNoRoutingConfigurationResponse() $kernel = new HttpKernel($dispatcher, new ControllerResolver(), $requestStack, new ArgumentResolver()); $request = Request::create('http://localhost/'); + + $response = $kernel->handle($request); + $this->assertSame(404, $response->getStatusCode()); + $this->assertStringContainsString('Welcome', $response->getContent()); + $response = $kernel->handle($request); $this->assertSame(404, $response->getStatusCode()); $this->assertStringContainsString('Welcome', $response->getContent()); diff --git a/src/Symfony/Component/HttpKernel/Tests/Exception/AccessDeniedHttpExceptionTest.php b/src/Symfony/Component/HttpKernel/Tests/Exception/AccessDeniedHttpExceptionTest.php index a810255b1eb02..4ce91afac8933 100644 --- a/src/Symfony/Component/HttpKernel/Tests/Exception/AccessDeniedHttpExceptionTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/Exception/AccessDeniedHttpExceptionTest.php @@ -16,7 +16,7 @@ class AccessDeniedHttpExceptionTest extends HttpExceptionTest { - protected function createException(string $message = '', \Throwable $previous = null, int $code = 0, array $headers = []): HttpException + protected function createException(string $message = '', ?\Throwable $previous = null, int $code = 0, array $headers = []): HttpException { return new AccessDeniedHttpException($message, $previous, $code, $headers); } diff --git a/src/Symfony/Component/HttpKernel/Tests/Exception/BadRequestHttpExceptionTest.php b/src/Symfony/Component/HttpKernel/Tests/Exception/BadRequestHttpExceptionTest.php index 2e09653fa7eaf..4dfb2cbc01268 100644 --- a/src/Symfony/Component/HttpKernel/Tests/Exception/BadRequestHttpExceptionTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/Exception/BadRequestHttpExceptionTest.php @@ -16,7 +16,7 @@ class BadRequestHttpExceptionTest extends HttpExceptionTest { - protected function createException(string $message = '', \Throwable $previous = null, int $code = 0, array $headers = []): HttpException + protected function createException(string $message = '', ?\Throwable $previous = null, int $code = 0, array $headers = []): HttpException { return new BadRequestHttpException($message, $previous, $code, $headers); } diff --git a/src/Symfony/Component/HttpKernel/Tests/Exception/ConflictHttpExceptionTest.php b/src/Symfony/Component/HttpKernel/Tests/Exception/ConflictHttpExceptionTest.php index dbab2acff555d..4f0b554511522 100644 --- a/src/Symfony/Component/HttpKernel/Tests/Exception/ConflictHttpExceptionTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/Exception/ConflictHttpExceptionTest.php @@ -16,7 +16,7 @@ class ConflictHttpExceptionTest extends HttpExceptionTest { - protected function createException(string $message = '', \Throwable $previous = null, int $code = 0, array $headers = []): HttpException + protected function createException(string $message = '', ?\Throwable $previous = null, int $code = 0, array $headers = []): HttpException { return new ConflictHttpException($message, $previous, $code, $headers); } diff --git a/src/Symfony/Component/HttpKernel/Tests/Exception/GoneHttpExceptionTest.php b/src/Symfony/Component/HttpKernel/Tests/Exception/GoneHttpExceptionTest.php index 2582ab71b33f0..775db75b92c41 100644 --- a/src/Symfony/Component/HttpKernel/Tests/Exception/GoneHttpExceptionTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/Exception/GoneHttpExceptionTest.php @@ -16,7 +16,7 @@ class GoneHttpExceptionTest extends HttpExceptionTest { - protected function createException(string $message = '', \Throwable $previous = null, int $code = 0, array $headers = []): HttpException + protected function createException(string $message = '', ?\Throwable $previous = null, int $code = 0, array $headers = []): HttpException { return new GoneHttpException($message, $previous, $code, $headers); } diff --git a/src/Symfony/Component/HttpKernel/Tests/Exception/HttpExceptionTest.php b/src/Symfony/Component/HttpKernel/Tests/Exception/HttpExceptionTest.php index 11636bbb60bd5..b31bd75a79f10 100644 --- a/src/Symfony/Component/HttpKernel/Tests/Exception/HttpExceptionTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/Exception/HttpExceptionTest.php @@ -95,7 +95,7 @@ public static function provideStatusCode() ]; } - protected function createException(string $message = '', \Throwable $previous = null, int $code = 0, array $headers = []): HttpException + protected function createException(string $message = '', ?\Throwable $previous = null, int $code = 0, array $headers = []): HttpException { return new HttpException(200, $message, $previous, $headers, $code); } diff --git a/src/Symfony/Component/HttpKernel/Tests/Exception/LengthRequiredHttpExceptionTest.php b/src/Symfony/Component/HttpKernel/Tests/Exception/LengthRequiredHttpExceptionTest.php index 5525870e1e324..4e1c3f645ffa3 100644 --- a/src/Symfony/Component/HttpKernel/Tests/Exception/LengthRequiredHttpExceptionTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/Exception/LengthRequiredHttpExceptionTest.php @@ -16,7 +16,7 @@ class LengthRequiredHttpExceptionTest extends HttpExceptionTest { - protected function createException(string $message = '', \Throwable $previous = null, int $code = 0, array $headers = []): HttpException + protected function createException(string $message = '', ?\Throwable $previous = null, int $code = 0, array $headers = []): HttpException { return new LengthRequiredHttpException($message, $previous, $code, $headers); } diff --git a/src/Symfony/Component/HttpKernel/Tests/Exception/LockedHttpExceptionTest.php b/src/Symfony/Component/HttpKernel/Tests/Exception/LockedHttpExceptionTest.php index e13dead8de42d..989fdb6318ae7 100644 --- a/src/Symfony/Component/HttpKernel/Tests/Exception/LockedHttpExceptionTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/Exception/LockedHttpExceptionTest.php @@ -16,7 +16,7 @@ class LockedHttpExceptionTest extends HttpExceptionTest { - protected function createException(string $message = '', \Throwable $previous = null, int $code = 0, array $headers = []): HttpException + protected function createException(string $message = '', ?\Throwable $previous = null, int $code = 0, array $headers = []): HttpException { return new LockedHttpException($message, $previous, $code, $headers); } diff --git a/src/Symfony/Component/HttpKernel/Tests/Exception/MethodNotAllowedHttpExceptionTest.php b/src/Symfony/Component/HttpKernel/Tests/Exception/MethodNotAllowedHttpExceptionTest.php index 61ecb84da4f73..a5cc1f70e1d8e 100644 --- a/src/Symfony/Component/HttpKernel/Tests/Exception/MethodNotAllowedHttpExceptionTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/Exception/MethodNotAllowedHttpExceptionTest.php @@ -45,7 +45,7 @@ public function testHeadersSetter($headers) $this->assertSame($headers, $exception->getHeaders()); } - protected function createException(string $message = '', \Throwable $previous = null, int $code = 0, array $headers = []): HttpException + protected function createException(string $message = '', ?\Throwable $previous = null, int $code = 0, array $headers = []): HttpException { return new MethodNotAllowedHttpException(['get'], $message, $previous, $code, $headers); } diff --git a/src/Symfony/Component/HttpKernel/Tests/Exception/NotAcceptableHttpExceptionTest.php b/src/Symfony/Component/HttpKernel/Tests/Exception/NotAcceptableHttpExceptionTest.php index 6df823ada0584..97c460b5cd1a2 100644 --- a/src/Symfony/Component/HttpKernel/Tests/Exception/NotAcceptableHttpExceptionTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/Exception/NotAcceptableHttpExceptionTest.php @@ -16,7 +16,7 @@ class NotAcceptableHttpExceptionTest extends HttpExceptionTest { - protected function createException(string $message = '', \Throwable $previous = null, int $code = 0, array $headers = []): HttpException + protected function createException(string $message = '', ?\Throwable $previous = null, int $code = 0, array $headers = []): HttpException { return new NotAcceptableHttpException($message, $previous, $code, $headers); } diff --git a/src/Symfony/Component/HttpKernel/Tests/Exception/NotFoundHttpExceptionTest.php b/src/Symfony/Component/HttpKernel/Tests/Exception/NotFoundHttpExceptionTest.php index 8152a727fd215..45fee0457a192 100644 --- a/src/Symfony/Component/HttpKernel/Tests/Exception/NotFoundHttpExceptionTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/Exception/NotFoundHttpExceptionTest.php @@ -16,7 +16,7 @@ class NotFoundHttpExceptionTest extends HttpExceptionTest { - protected function createException(string $message = '', \Throwable $previous = null, int $code = 0, array $headers = []): HttpException + protected function createException(string $message = '', ?\Throwable $previous = null, int $code = 0, array $headers = []): HttpException { return new NotFoundHttpException($message, $previous, $code, $headers); } diff --git a/src/Symfony/Component/HttpKernel/Tests/Exception/PreconditionFailedHttpExceptionTest.php b/src/Symfony/Component/HttpKernel/Tests/Exception/PreconditionFailedHttpExceptionTest.php index d215792875e38..f7750d9a631bb 100644 --- a/src/Symfony/Component/HttpKernel/Tests/Exception/PreconditionFailedHttpExceptionTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/Exception/PreconditionFailedHttpExceptionTest.php @@ -16,7 +16,7 @@ class PreconditionFailedHttpExceptionTest extends HttpExceptionTest { - protected function createException(string $message = '', \Throwable $previous = null, int $code = 0, array $headers = []): HttpException + protected function createException(string $message = '', ?\Throwable $previous = null, int $code = 0, array $headers = []): HttpException { return new PreconditionFailedHttpException($message, $previous, $code, $headers); } diff --git a/src/Symfony/Component/HttpKernel/Tests/Exception/PreconditionRequiredHttpExceptionTest.php b/src/Symfony/Component/HttpKernel/Tests/Exception/PreconditionRequiredHttpExceptionTest.php index 452b226c49c6a..6373d2718f1e9 100644 --- a/src/Symfony/Component/HttpKernel/Tests/Exception/PreconditionRequiredHttpExceptionTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/Exception/PreconditionRequiredHttpExceptionTest.php @@ -16,7 +16,7 @@ class PreconditionRequiredHttpExceptionTest extends HttpExceptionTest { - protected function createException(string $message = '', \Throwable $previous = null, int $code = 0, array $headers = []): HttpException + protected function createException(string $message = '', ?\Throwable $previous = null, int $code = 0, array $headers = []): HttpException { return new PreconditionRequiredHttpException($message, $previous, $code, $headers); } diff --git a/src/Symfony/Component/HttpKernel/Tests/Exception/ServiceUnavailableHttpExceptionTest.php b/src/Symfony/Component/HttpKernel/Tests/Exception/ServiceUnavailableHttpExceptionTest.php index 4f0aa3a45827f..34172b446a343 100644 --- a/src/Symfony/Component/HttpKernel/Tests/Exception/ServiceUnavailableHttpExceptionTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/Exception/ServiceUnavailableHttpExceptionTest.php @@ -45,7 +45,7 @@ public function testHeadersSetter($headers) $this->assertSame($headers, $exception->getHeaders()); } - protected function createException(string $message = '', \Throwable $previous = null, int $code = 0, array $headers = []): HttpException + protected function createException(string $message = '', ?\Throwable $previous = null, int $code = 0, array $headers = []): HttpException { return new ServiceUnavailableHttpException(null, $message, $previous, $code, $headers); } diff --git a/src/Symfony/Component/HttpKernel/Tests/Exception/TooManyRequestsHttpExceptionTest.php b/src/Symfony/Component/HttpKernel/Tests/Exception/TooManyRequestsHttpExceptionTest.php index 4dc2e41ea5428..995e56d5540e8 100644 --- a/src/Symfony/Component/HttpKernel/Tests/Exception/TooManyRequestsHttpExceptionTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/Exception/TooManyRequestsHttpExceptionTest.php @@ -45,7 +45,7 @@ public function testHeadersSetter($headers) $this->assertSame($headers, $exception->getHeaders()); } - protected function createException(string $message = '', \Throwable $previous = null, int $code = 0, array $headers = []): HttpException + protected function createException(string $message = '', ?\Throwable $previous = null, int $code = 0, array $headers = []): HttpException { return new TooManyRequestsHttpException(null, $message, $previous, $code, $headers); } diff --git a/src/Symfony/Component/HttpKernel/Tests/Exception/UnauthorizedHttpExceptionTest.php b/src/Symfony/Component/HttpKernel/Tests/Exception/UnauthorizedHttpExceptionTest.php index dda2777c91878..3797ce0dd0204 100644 --- a/src/Symfony/Component/HttpKernel/Tests/Exception/UnauthorizedHttpExceptionTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/Exception/UnauthorizedHttpExceptionTest.php @@ -45,7 +45,7 @@ public function testHeadersSetter($headers) $this->assertSame($headers, $exception->getHeaders()); } - protected function createException(string $message = '', \Throwable $previous = null, int $code = 0, array $headers = []): HttpException + protected function createException(string $message = '', ?\Throwable $previous = null, int $code = 0, array $headers = []): HttpException { return new UnauthorizedHttpException('Challenge', $message, $previous, $code, $headers); } diff --git a/src/Symfony/Component/HttpKernel/Tests/Exception/UnprocessableEntityHttpExceptionTest.php b/src/Symfony/Component/HttpKernel/Tests/Exception/UnprocessableEntityHttpExceptionTest.php index 8b4ece20ee2da..6d5c309088417 100644 --- a/src/Symfony/Component/HttpKernel/Tests/Exception/UnprocessableEntityHttpExceptionTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/Exception/UnprocessableEntityHttpExceptionTest.php @@ -16,7 +16,7 @@ class UnprocessableEntityHttpExceptionTest extends HttpExceptionTest { - protected function createException(string $message = '', \Throwable $previous = null, int $code = 0, array $headers = []): HttpException + protected function createException(string $message = '', ?\Throwable $previous = null, int $code = 0, array $headers = []): HttpException { return new UnprocessableEntityHttpException($message, $previous, $code, $headers); } diff --git a/src/Symfony/Component/HttpKernel/Tests/Exception/UnsupportedMediaTypeHttpExceptionTest.php b/src/Symfony/Component/HttpKernel/Tests/Exception/UnsupportedMediaTypeHttpExceptionTest.php index 0295d61e0a49b..2407b0a85bd7a 100644 --- a/src/Symfony/Component/HttpKernel/Tests/Exception/UnsupportedMediaTypeHttpExceptionTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/Exception/UnsupportedMediaTypeHttpExceptionTest.php @@ -16,7 +16,7 @@ class UnsupportedMediaTypeHttpExceptionTest extends HttpExceptionTest { - protected function createException(string $message = '', \Throwable $previous = null, int $code = 0, array $headers = []): HttpException + protected function createException(string $message = '', ?\Throwable $previous = null, int $code = 0, array $headers = []): HttpException { return new UnsupportedMediaTypeHttpException($message, $previous, $code, $headers); } diff --git a/src/Symfony/Component/HttpKernel/Tests/HttpCache/HttpCacheTestCase.php b/src/Symfony/Component/HttpKernel/Tests/HttpCache/HttpCacheTestCase.php index 02a34a7a4cf1d..26a29f16b2b75 100644 --- a/src/Symfony/Component/HttpKernel/Tests/HttpCache/HttpCacheTestCase.php +++ b/src/Symfony/Component/HttpKernel/Tests/HttpCache/HttpCacheTestCase.php @@ -146,7 +146,7 @@ public function getMetaStorageValues() } // A basic response with 200 status code and a tiny body. - public function setNextResponse($statusCode = 200, array $headers = [], $body = 'Hello World', \Closure $customizer = null, EventDispatcher $eventDispatcher = null) + public function setNextResponse($statusCode = 200, array $headers = [], $body = 'Hello World', ?\Closure $customizer = null, ?EventDispatcher $eventDispatcher = null) { $this->kernel = new TestHttpKernel($body, $statusCode, $headers, $customizer, $eventDispatcher); } diff --git a/src/Symfony/Component/HttpKernel/Tests/HttpCache/TestHttpKernel.php b/src/Symfony/Component/HttpKernel/Tests/HttpCache/TestHttpKernel.php index f60099140b056..8eb08f3058113 100644 --- a/src/Symfony/Component/HttpKernel/Tests/HttpCache/TestHttpKernel.php +++ b/src/Symfony/Component/HttpKernel/Tests/HttpCache/TestHttpKernel.php @@ -29,7 +29,7 @@ class TestHttpKernel extends HttpKernel implements ControllerResolverInterface, protected bool $catch = false; protected array $backendRequest; - public function __construct($body, $status, $headers, \Closure $customizer = null, EventDispatcher $eventDispatcher = null) + public function __construct($body, $status, $headers, ?\Closure $customizer = null, ?EventDispatcher $eventDispatcher = null) { $this->body = $body; $this->status = $status; @@ -72,7 +72,7 @@ public function getController(Request $request): callable|false return $this->callController(...); } - public function getArguments(Request $request, callable $controller, \ReflectionFunctionAbstract $reflector = null): array + public function getArguments(Request $request, callable $controller, ?\ReflectionFunctionAbstract $reflector = null): array { return [$request]; } diff --git a/src/Symfony/Component/HttpKernel/Tests/HttpCache/TestMultipleHttpKernel.php b/src/Symfony/Component/HttpKernel/Tests/HttpCache/TestMultipleHttpKernel.php index 56503cb013f58..bc8e83baa0183 100644 --- a/src/Symfony/Component/HttpKernel/Tests/HttpCache/TestMultipleHttpKernel.php +++ b/src/Symfony/Component/HttpKernel/Tests/HttpCache/TestMultipleHttpKernel.php @@ -55,7 +55,7 @@ public function getController(Request $request): callable|false return $this->callController(...); } - public function getArguments(Request $request, callable $controller, \ReflectionFunctionAbstract $reflector = null): array + public function getArguments(Request $request, callable $controller, ?\ReflectionFunctionAbstract $reflector = null): array { return [$request]; } diff --git a/src/Symfony/Component/HttpKernel/Tests/HttpKernelTest.php b/src/Symfony/Component/HttpKernel/Tests/HttpKernelTest.php index 618b7fbb63d0a..58e6ab51077e2 100644 --- a/src/Symfony/Component/HttpKernel/Tests/HttpKernelTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/HttpKernelTest.php @@ -495,7 +495,7 @@ public function testInconsistentClientIpsOnMainRequests() Request::setTrustedProxies([], -1); } - private function getHttpKernel(EventDispatcherInterface $eventDispatcher, $controller = null, RequestStack $requestStack = null, array $arguments = [], bool $handleAllThrowables = false) + private function getHttpKernel(EventDispatcherInterface $eventDispatcher, $controller = null, ?RequestStack $requestStack = null, array $arguments = [], bool $handleAllThrowables = false) { $controller ??= fn () => new Response('Hello'); diff --git a/src/Symfony/Component/HttpKernel/Tests/KernelTest.php b/src/Symfony/Component/HttpKernel/Tests/KernelTest.php index 0a77f2c8e1852..fe233043576ed 100644 --- a/src/Symfony/Component/HttpKernel/Tests/KernelTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/KernelTest.php @@ -631,7 +631,7 @@ public function getProjectDir(): string return __DIR__.'/Fixtures'; } - public function warmUp(string $cacheDir, string $buildDir = null): array + public function warmUp(string $cacheDir, ?string $buildDir = null): array { $this->warmedUp = true; $this->warmedUpBuildDir = $buildDir; diff --git a/src/Symfony/Component/HttpKernel/Tests/TestHttpKernel.php b/src/Symfony/Component/HttpKernel/Tests/TestHttpKernel.php index 56054c3a46641..2a8199b0516fe 100644 --- a/src/Symfony/Component/HttpKernel/Tests/TestHttpKernel.php +++ b/src/Symfony/Component/HttpKernel/Tests/TestHttpKernel.php @@ -30,7 +30,7 @@ public function getController(Request $request): callable|false return $this->callController(...); } - public function getArguments(Request $request, callable $controller, \ReflectionFunctionAbstract $reflector = null): array + public function getArguments(Request $request, callable $controller, ?\ReflectionFunctionAbstract $reflector = null): array { return [$request]; } diff --git a/src/Symfony/Component/Intl/CHANGELOG.md b/src/Symfony/Component/Intl/CHANGELOG.md index 1f372c4e46545..ed7abd49647ea 100644 --- a/src/Symfony/Component/Intl/CHANGELOG.md +++ b/src/Symfony/Component/Intl/CHANGELOG.md @@ -1,10 +1,16 @@ CHANGELOG ========= +7.1 +--- + + * Move all emoji code & data to a new `symfony/emoji` component + * Deprecate `EmojiTransliterator` in favor of `Symfony\Component\Emoji\EmojiTransliterator` + 6.4 --- - * Add support for ISO-3166-1 numeric codes with `Countries::getNumericCode()`, `Countries::getNumericCodes()`, + * Add support for ISO-3166-1 numeric codes with `Countries::getNumericCode()`, `Countries::getNumericCodes()`, `Countries::numericCodeExists()` and `Countries::getAlpha2FromNumeric()` 6.3 diff --git a/src/Symfony/Component/Intl/Countries.php b/src/Symfony/Component/Intl/Countries.php index fd17c3f090818..256bd54ebcafa 100644 --- a/src/Symfony/Component/Intl/Countries.php +++ b/src/Symfony/Component/Intl/Countries.php @@ -127,7 +127,7 @@ public static function numericCodeExists(string $numericCode): bool * * @throws MissingResourceException if the country code does not exist */ - public static function getName(string $country, string $displayLocale = null): string + public static function getName(string $country, ?string $displayLocale = null): string { return self::readEntry(['Names', $country], $displayLocale); } @@ -137,7 +137,7 @@ public static function getName(string $country, string $displayLocale = null): s * * @throws MissingResourceException if the country code does not exist */ - public static function getAlpha3Name(string $alpha3Code, string $displayLocale = null): string + public static function getAlpha3Name(string $alpha3Code, ?string $displayLocale = null): string { return self::getName(self::getAlpha2Code($alpha3Code), $displayLocale); } @@ -147,7 +147,7 @@ public static function getAlpha3Name(string $alpha3Code, string $displayLocale = * * @return array */ - public static function getNames(string $displayLocale = null): array + public static function getNames(?string $displayLocale = null): array { return self::asort(self::readEntry(['Names'], $displayLocale), $displayLocale); } @@ -159,7 +159,7 @@ public static function getNames(string $displayLocale = null): array * * @return array */ - public static function getAlpha3Names(string $displayLocale = null): array + public static function getAlpha3Names(?string $displayLocale = null): array { $alpha2Names = self::getNames($displayLocale); $alpha3Names = []; diff --git a/src/Symfony/Component/Intl/Currencies.php b/src/Symfony/Component/Intl/Currencies.php index 2f52576d0fa46..ce7fe5e2a84b7 100644 --- a/src/Symfony/Component/Intl/Currencies.php +++ b/src/Symfony/Component/Intl/Currencies.php @@ -50,7 +50,7 @@ public static function exists(string $currency): bool /** * @throws MissingResourceException if the currency code does not exist */ - public static function getName(string $currency, string $displayLocale = null): string + public static function getName(string $currency, ?string $displayLocale = null): string { return self::readEntry(['Names', $currency, self::INDEX_NAME], $displayLocale); } @@ -58,7 +58,7 @@ public static function getName(string $currency, string $displayLocale = null): /** * @return string[] */ - public static function getNames(string $displayLocale = null): array + public static function getNames(?string $displayLocale = null): array { // ==================================================================== // For reference: It is NOT possible to return names indexed by @@ -82,7 +82,7 @@ public static function getNames(string $displayLocale = null): array /** * @throws MissingResourceException if the currency code does not exist */ - public static function getSymbol(string $currency, string $displayLocale = null): string + public static function getSymbol(string $currency, ?string $displayLocale = null): string { return self::readEntry(['Names', $currency, self::INDEX_SYMBOL], $displayLocale); } diff --git a/src/Symfony/Component/Intl/Data/Generator/TimezoneDataGenerator.php b/src/Symfony/Component/Intl/Data/Generator/TimezoneDataGenerator.php index a0573e36f39c4..9de6a0cd6beab 100644 --- a/src/Symfony/Component/Intl/Data/Generator/TimezoneDataGenerator.php +++ b/src/Symfony/Component/Intl/Data/Generator/TimezoneDataGenerator.php @@ -159,7 +159,7 @@ private function generateZones(BundleEntryReaderInterface $reader, string $tempD $regionFormat = $reader->readEntry($tempDir, $locale, ['zoneStrings', 'regionFormat']); $fallbackFormat = $reader->readEntry($tempDir, $locale, ['zoneStrings', 'fallbackFormat']); - $resolveName = function (string $id, string $city = null) use ($reader, $tempDir, $locale, $regionFormat, $fallbackFormat): ?string { + $resolveName = function (string $id, ?string $city = null) use ($reader, $tempDir, $locale, $regionFormat, $fallbackFormat): ?string { // Resolve default name as described per http://cldr.unicode.org/translation/timezones if (isset($this->zoneToCountryMapping[$id])) { try { diff --git a/src/Symfony/Component/Intl/Languages.php b/src/Symfony/Component/Intl/Languages.php index e00b89fa2cf52..bff314b98e669 100644 --- a/src/Symfony/Component/Intl/Languages.php +++ b/src/Symfony/Component/Intl/Languages.php @@ -56,7 +56,7 @@ public static function exists(string $language): bool * * @throws MissingResourceException if the language code does not exist */ - public static function getName(string $language, string $displayLocale = null): string + public static function getName(string $language, ?string $displayLocale = null): string { try { return self::readEntry(['Names', $language], $displayLocale); @@ -78,7 +78,7 @@ public static function getName(string $language, string $displayLocale = null): * * @return array */ - public static function getNames(string $displayLocale = null): array + public static function getNames(?string $displayLocale = null): array { return self::asort(self::readEntry(['Names'], $displayLocale), $displayLocale); } @@ -137,7 +137,7 @@ public static function alpha3CodeExists(string $language): bool * * @throws MissingResourceException if the country code does not exists */ - public static function getAlpha3Name(string $language, string $displayLocale = null): string + public static function getAlpha3Name(string $language, ?string $displayLocale = null): string { try { return self::getName(self::getAlpha2Code($language), $displayLocale); @@ -157,7 +157,7 @@ public static function getAlpha3Name(string $language, string $displayLocale = n * * @return array */ - public static function getAlpha3Names(string $displayLocale = null): array + public static function getAlpha3Names(?string $displayLocale = null): array { $alpha2Names = self::getNames($displayLocale); $alpha3Names = []; diff --git a/src/Symfony/Component/Intl/Locales.php b/src/Symfony/Component/Intl/Locales.php index 10d3dcef85abf..ea4f50a776eef 100644 --- a/src/Symfony/Component/Intl/Locales.php +++ b/src/Symfony/Component/Intl/Locales.php @@ -51,7 +51,7 @@ public static function exists(string $locale): bool /** * @throws MissingResourceException if the locale does not exist */ - public static function getName(string $locale, string $displayLocale = null): string + public static function getName(string $locale, ?string $displayLocale = null): string { try { return self::readEntry(['Names', $locale], $displayLocale); @@ -67,7 +67,7 @@ public static function getName(string $locale, string $displayLocale = null): st /** * @return string[] */ - public static function getNames(string $displayLocale = null): array + public static function getNames(?string $displayLocale = null): array { return self::asort(self::readEntry(['Names'], $displayLocale), $displayLocale); } diff --git a/src/Symfony/Component/Intl/README.md b/src/Symfony/Component/Intl/README.md index a5d55ea40b63d..333e2fe80a812 100644 --- a/src/Symfony/Component/Intl/README.md +++ b/src/Symfony/Component/Intl/README.md @@ -3,6 +3,10 @@ Intl Component The Intl component provides access to the localization data of the ICU library. +If you have the zlib extension enabled, you can compress the data by running: + + php vendor/symfony/intl/Resources/bin/compress + Resources --------- diff --git a/src/Symfony/Component/Intl/ResourceBundle.php b/src/Symfony/Component/Intl/ResourceBundle.php index f1bc48259ee97..be5017cef94a7 100644 --- a/src/Symfony/Component/Intl/ResourceBundle.php +++ b/src/Symfony/Component/Intl/ResourceBundle.php @@ -43,7 +43,7 @@ abstract protected static function getPath(): string; * @return mixed returns an array or {@link \ArrayAccess} instance for * complex data and a scalar value for simple data */ - final protected static function readEntry(array $indices, string $locale = null, bool $fallback = true): mixed + final protected static function readEntry(array $indices, ?string $locale = null, bool $fallback = true): mixed { if (!isset(self::$entryReader)) { self::$entryReader = new BundleEntryReader(new BufferedBundleReader( @@ -58,7 +58,7 @@ final protected static function readEntry(array $indices, string $locale = null, return self::$entryReader->readEntry(static::getPath(), $locale ?? \Locale::getDefault(), $indices, $fallback); } - final protected static function asort(iterable $list, string $locale = null): array + final protected static function asort(iterable $list, ?string $locale = null): array { if ($list instanceof \Traversable) { $list = iterator_to_array($list); diff --git a/src/Symfony/Component/Intl/Scripts.php b/src/Symfony/Component/Intl/Scripts.php index d21ed6fa6e811..27e460671c54f 100644 --- a/src/Symfony/Component/Intl/Scripts.php +++ b/src/Symfony/Component/Intl/Scripts.php @@ -43,7 +43,7 @@ public static function exists(string $script): bool /** * @throws MissingResourceException if the script code does not exist */ - public static function getName(string $script, string $displayLocale = null): string + public static function getName(string $script, ?string $displayLocale = null): string { return self::readEntry(['Names', $script], $displayLocale); } @@ -51,7 +51,7 @@ public static function getName(string $script, string $displayLocale = null): st /** * @return string[] */ - public static function getNames(string $displayLocale = null): array + public static function getNames(?string $displayLocale = null): array { return self::asort(self::readEntry(['Names'], $displayLocale), $displayLocale); } diff --git a/src/Symfony/Component/Intl/Timezones.php b/src/Symfony/Component/Intl/Timezones.php index 0f9f4e3d92e2a..53d054f2c9c8e 100644 --- a/src/Symfony/Component/Intl/Timezones.php +++ b/src/Symfony/Component/Intl/Timezones.php @@ -49,7 +49,7 @@ public static function exists(string $timezone): bool /** * @throws MissingResourceException if the timezone identifier does not exist or is an alias */ - public static function getName(string $timezone, string $displayLocale = null): string + public static function getName(string $timezone, ?string $displayLocale = null): string { return self::readEntry(['Names', $timezone], $displayLocale); } @@ -57,7 +57,7 @@ public static function getName(string $timezone, string $displayLocale = null): /** * @return string[] */ - public static function getNames(string $displayLocale = null): array + public static function getNames(?string $displayLocale = null): array { return self::asort(self::readEntry(['Names'], $displayLocale), $displayLocale); } @@ -66,14 +66,14 @@ public static function getNames(string $displayLocale = null): array * @throws \Exception if the timezone identifier does not exist * @throws RuntimeException if there's no timezone DST transition information available */ - public static function getRawOffset(string $timezone, int $timestamp = null): int + public static function getRawOffset(string $timezone, ?int $timestamp = null): int { $dateTimeImmutable = new \DateTimeImmutable(date('Y-m-d H:i:s', $timestamp ?? time()), new \DateTimeZone($timezone)); return $dateTimeImmutable->getOffset(); } - public static function getGmtOffset(string $timezone, int $timestamp = null, string $displayLocale = null): string + public static function getGmtOffset(string $timezone, ?int $timestamp = null, ?string $displayLocale = null): string { $offset = self::getRawOffset($timezone, $timestamp); $abs = abs($offset); diff --git a/src/Symfony/Component/Intl/Transliterator/EmojiTransliterator.php b/src/Symfony/Component/Intl/Transliterator/EmojiTransliterator.php index daa114316af74..7c36fe6abd7b9 100644 --- a/src/Symfony/Component/Intl/Transliterator/EmojiTransliterator.php +++ b/src/Symfony/Component/Intl/Transliterator/EmojiTransliterator.php @@ -11,133 +11,21 @@ namespace Symfony\Component\Intl\Transliterator; -use Symfony\Component\Intl\Util\GzipStreamWrapper; +use Symfony\Component\Emoji\EmojiTransliterator as EmojiEmojiTransliterator; -if (!class_exists(\Transliterator::class)) { - throw new \LogicException(sprintf('You cannot use the "%s\EmojiTransliterator" class as the "intl" extension is not installed. See https://php.net/intl.', __NAMESPACE__)); -} else { - /** - * @internal - */ - trait EmojiTransliteratorTrait - { - private array $map; - private \Transliterator $transliterator; - - public static function create(string $id, int $direction = self::FORWARD): self - { - $id = strtolower($id); - - if (!isset(self::REVERSEABLE_IDS[$id]) && !str_starts_with($id, 'emoji-')) { - $id = 'emoji-'.$id; - } - - if (self::REVERSE === $direction) { - if (!isset(self::REVERSEABLE_IDS[$id])) { - // Create a failing reverse-transliterator to populate intl_get_error_*() - \Transliterator::createFromRules('A > B')->createInverse(); - - throw new \IntlException(intl_get_error_message(), intl_get_error_code()); - } - $id = self::REVERSEABLE_IDS[$id]; - } - - $file = \dirname(__DIR__)."/Resources/data/transliterator/emoji/{$id}.php"; - if (!preg_match('/^[a-z0-9@_\\.\\-]*$/', $id) || !is_file($file) && !is_file($file .= '.gz')) { - \Transliterator::create($id); // Populate intl_get_error_*() - - throw new \IntlException(intl_get_error_message(), intl_get_error_code()); - } - - static $maps; - - // Create an instance of \Transliterator with a custom id; that's the only way - static $newInstance; - $instance = ($newInstance ??= (new \ReflectionClass(self::class))->newInstanceWithoutConstructor(...))(); - $instance->id = $id; - $instance->map = $maps[$id] ??= str_ends_with($file, '.gz') ? GzipStreamWrapper::require($file) : require $file; - - return $instance; - } - - public function createInverse(): self - { - return self::create($this->id, self::REVERSE); - } - - public function getErrorCode(): int|false - { - return $this->transliterator?->getErrorCode() ?? 0; - } - - public function getErrorMessage(): string|false - { - return $this->transliterator?->getErrorMessage() ?? false; - } - - public static function listIDs(): array - { - static $ids = []; +trigger_deprecation('symfony/intl', '7.1', 'The "%s" class is deprecated, use "%s" instead.', EmojiTransliterator::class, EmojiEmojiTransliterator::class); - if ($ids) { - return $ids; - } - - foreach (scandir(\dirname(__DIR__).'/Resources/data/transliterator/emoji/') as $file) { - if (str_ends_with($file, '.php.gz')) { - $ids[] = substr($file, 0, -7); - } elseif (str_ends_with($file, '.php')) { - $ids[] = substr($file, 0, -4); - } - } - - return $ids; - } - - public function transliterate(string $string, int $start = 0, int $end = -1): string|false - { - $quickCheck = ':' === array_key_first($this->map)[0] ? ':' : self::QUICK_CHECK; - - if (0 === $start && -1 === $end && preg_match('//u', $string)) { - return \strlen($string) === strcspn($string, $quickCheck) ? $string : strtr($string, $this->map); - } - - // Here we rely on intl to validate the $string, $start and $end arguments - // and to slice the string. Slicing is done by replacing the part if $string - // between $start and $end by a unique cookie that can be reliably used to - // identify which part of $string should be transliterated. - - static $cookie; - static $transliterator; - - $cookie ??= hash('xxh128', random_bytes(8)); - $this->transliterator ??= clone $transliterator ??= \Transliterator::createFromRules('[:any:]* > '.$cookie); - - if (false === $result = $this->transliterator->transliterate($string, $start, $end)) { - return false; - } - - $parts = explode($cookie, $result); - $start = \strlen($parts[0]); - $length = -\strlen($parts[1]) ?: null; - $string = substr($string, $start, $length); - - return $parts[0].(\strlen($string) === strcspn($string, $quickCheck) ? $string : strtr($string, $this->map)).$parts[1]; - } - } +if (!class_exists(EmojiEmojiTransliterator::class)) { + throw new \LogicException(sprintf('You cannot use the "%s" if the Emoji component is not available. Try running "composer require symfony/emoji".', EmojiEmojiTransliterator::class)); } -final class EmojiTransliterator extends \Transliterator -{ - use EmojiTransliteratorTrait; - - private const QUICK_CHECK = "\xA9\xAE\xE2\xE3\xF0"; - private const REVERSEABLE_IDS = [ - 'emoji-github' => 'github-emoji', - 'emoji-slack' => 'slack-emoji', - 'github-emoji' => 'emoji-github', - 'slack-emoji' => 'emoji-slack', - ]; +class_alias(EmojiEmojiTransliterator::class, EmojiTransliterator::class); - public readonly string $id; +if (false) { + /** + * @deprecated since Symfony 7.1, use Symfony\Component\Emoji\EmojiTransliterator instead + */ + class EmojiTransliterator extends \Transliterator + { + } } diff --git a/src/Symfony/Component/Intl/Util/GitRepository.php b/src/Symfony/Component/Intl/Util/GitRepository.php index acbe8c49d697d..1f8870cc6d555 100644 --- a/src/Symfony/Component/Intl/Util/GitRepository.php +++ b/src/Symfony/Component/Intl/Util/GitRepository.php @@ -69,7 +69,7 @@ public function getLastAuthoredDate(): \DateTimeImmutable return new \DateTimeImmutable($this->getLastLine($this->execInPath('git log -1 --format="%ai"'))); } - public function getLastTag(callable $filter = null): string + public function getLastTag(?callable $filter = null): string { $tags = $this->execInPath('git tag -l --sort=v:refname'); @@ -90,7 +90,7 @@ private function execInPath(string $command): array return self::exec(sprintf('cd %s && %s', escapeshellarg($this->path), $command)); } - private static function exec(string $command, string $customErrorMessage = null): array + private static function exec(string $command, ?string $customErrorMessage = null): array { exec(sprintf('%s 2>&1', $command), $output, $result); diff --git a/src/Symfony/Component/Intl/Util/IcuVersion.php b/src/Symfony/Component/Intl/Util/IcuVersion.php index b75987f77b18a..22c15ae238bb9 100644 --- a/src/Symfony/Component/Intl/Util/IcuVersion.php +++ b/src/Symfony/Component/Intl/Util/IcuVersion.php @@ -48,7 +48,7 @@ class IcuVersion * * @see normalize() */ - public static function compare(string $version1, string $version2, string $operator, int $precision = null): bool + public static function compare(string $version1, string $version2, string $operator, ?int $precision = null): bool { $version1 = self::normalize($version1, $precision); $version2 = self::normalize($version2, $precision); diff --git a/src/Symfony/Component/Intl/Util/IntlTestHelper.php b/src/Symfony/Component/Intl/Util/IntlTestHelper.php index f937f2ebb4a0d..dd875f3cbf0e7 100644 --- a/src/Symfony/Component/Intl/Util/IntlTestHelper.php +++ b/src/Symfony/Component/Intl/Util/IntlTestHelper.php @@ -30,7 +30,7 @@ class IntlTestHelper /** * Should be called before tests that work fine with the stub implementation. */ - public static function requireIntl(TestCase $testCase, string $minimumIcuVersion = null): void + public static function requireIntl(TestCase $testCase, ?string $minimumIcuVersion = null): void { $minimumIcuVersion ??= Intl::getIcuStubVersion(); @@ -62,7 +62,7 @@ public static function requireIntl(TestCase $testCase, string $minimumIcuVersion * Should be called before tests that require a feature-complete intl * implementation. */ - public static function requireFullIntl(TestCase $testCase, string $minimumIcuVersion = null): void + public static function requireFullIntl(TestCase $testCase, ?string $minimumIcuVersion = null): void { // We only run tests if the intl extension is loaded... if (!Intl::isExtensionLoaded()) { diff --git a/src/Symfony/Component/Intl/Util/Version.php b/src/Symfony/Component/Intl/Util/Version.php index 8d72c3af29af2..f30709ec8fb41 100644 --- a/src/Symfony/Component/Intl/Util/Version.php +++ b/src/Symfony/Component/Intl/Util/Version.php @@ -38,7 +38,7 @@ class Version * * @see normalize() */ - public static function compare(string $version1, string $version2, string $operator, int $precision = null): bool + public static function compare(string $version1, string $version2, string $operator, ?int $precision = null): bool { $version1 = self::normalize($version1, $precision); $version2 = self::normalize($version2, $precision); diff --git a/src/Symfony/Component/Intl/composer.json b/src/Symfony/Component/Intl/composer.json index 360918baa6df9..f1d1fbf9bfe24 100644 --- a/src/Symfony/Component/Intl/composer.json +++ b/src/Symfony/Component/Intl/composer.json @@ -24,13 +24,16 @@ } ], "require": { - "php": ">=8.2" + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3" }, "require-dev": { "symfony/filesystem": "^6.4|^7.0", - "symfony/finder": "^6.4|^7.0", "symfony/var-exporter": "^6.4|^7.0" }, + "conflict": { + "symfony/string": "<7.1" + }, "autoload": { "psr-4": { "Symfony\\Component\\Intl\\": "" }, "exclude-from-classmap": [ diff --git a/src/Symfony/Component/Ldap/Adapter/ConnectionInterface.php b/src/Symfony/Component/Ldap/Adapter/ConnectionInterface.php index 25000d2174f96..0379473f420f9 100644 --- a/src/Symfony/Component/Ldap/Adapter/ConnectionInterface.php +++ b/src/Symfony/Component/Ldap/Adapter/ConnectionInterface.php @@ -34,5 +34,5 @@ public function isBound(): bool; * @throws ConnectionTimeoutException When the connection can't be created because of an LDAP_TIMEOUT error * @throws InvalidCredentialsException When the connection can't be created because of an LDAP_INVALID_CREDENTIALS error */ - public function bind(string $dn = null, #[\SensitiveParameter] string $password = null): void; + public function bind(?string $dn = null, #[\SensitiveParameter] ?string $password = null): void; } diff --git a/src/Symfony/Component/Ldap/Adapter/ExtLdap/Connection.php b/src/Symfony/Component/Ldap/Adapter/ExtLdap/Connection.php index f8b41b18012d9..077ea5c99feec 100644 --- a/src/Symfony/Component/Ldap/Adapter/ExtLdap/Connection.php +++ b/src/Symfony/Component/Ldap/Adapter/ExtLdap/Connection.php @@ -62,7 +62,7 @@ public function isBound(): bool /** * @param string $password WARNING: When the LDAP server allows unauthenticated binds, a blank $password will always be valid */ - public function bind(string $dn = null, #[\SensitiveParameter] string $password = null): void + public function bind(?string $dn = null, #[\SensitiveParameter] ?string $password = null): void { if (!$this->connection) { $this->connect(); diff --git a/src/Symfony/Component/Ldap/Ldap.php b/src/Symfony/Component/Ldap/Ldap.php index c44578a2aa72d..cd5cf6213e912 100644 --- a/src/Symfony/Component/Ldap/Ldap.php +++ b/src/Symfony/Component/Ldap/Ldap.php @@ -29,7 +29,7 @@ public function __construct(AdapterInterface $adapter) $this->adapter = $adapter; } - public function bind(string $dn = null, #[\SensitiveParameter] string $password = null): void + public function bind(?string $dn = null, #[\SensitiveParameter] ?string $password = null): void { $this->adapter->getConnection()->bind($dn, $password); } diff --git a/src/Symfony/Component/Ldap/LdapInterface.php b/src/Symfony/Component/Ldap/LdapInterface.php index 2ba0ce980fed1..da9dce8e4116d 100644 --- a/src/Symfony/Component/Ldap/LdapInterface.php +++ b/src/Symfony/Component/Ldap/LdapInterface.php @@ -30,7 +30,7 @@ interface LdapInterface * * @throws ConnectionException if dn / password could not be bound */ - public function bind(string $dn = null, #[\SensitiveParameter] string $password = null): void; + public function bind(?string $dn = null, #[\SensitiveParameter] ?string $password = null): void; /** * Queries a ldap server for entries matching the given criteria. diff --git a/src/Symfony/Component/Ldap/Security/LdapAuthenticator.php b/src/Symfony/Component/Ldap/Security/LdapAuthenticator.php index 518da3428a4c5..a1743daf836ed 100644 --- a/src/Symfony/Component/Ldap/Security/LdapAuthenticator.php +++ b/src/Symfony/Component/Ldap/Security/LdapAuthenticator.php @@ -79,7 +79,7 @@ public function onAuthenticationFailure(Request $request, AuthenticationExceptio return $this->authenticator->onAuthenticationFailure($request, $exception); } - public function start(Request $request, AuthenticationException $authException = null): Response + public function start(Request $request, ?AuthenticationException $authException = null): Response { if (!$this->authenticator instanceof AuthenticationEntryPointInterface) { throw new NotAnEntryPointException(sprintf('Decorated authenticator "%s" does not implement interface "%s".', get_debug_type($this->authenticator), AuthenticationEntryPointInterface::class)); diff --git a/src/Symfony/Component/Ldap/Security/LdapBadge.php b/src/Symfony/Component/Ldap/Security/LdapBadge.php index f51ff4ee8cf6b..f6e45a95abecb 100644 --- a/src/Symfony/Component/Ldap/Security/LdapBadge.php +++ b/src/Symfony/Component/Ldap/Security/LdapBadge.php @@ -31,7 +31,7 @@ class LdapBadge implements BadgeInterface private string $searchPassword; private ?string $queryString; - public function __construct(string $ldapServiceId, string $dnString = '{user_identifier}', string $searchDn = '', string $searchPassword = '', string $queryString = null) + public function __construct(string $ldapServiceId, string $dnString = '{user_identifier}', string $searchDn = '', string $searchPassword = '', ?string $queryString = null) { $this->ldapServiceId = $ldapServiceId; $this->dnString = $dnString; diff --git a/src/Symfony/Component/Ldap/Security/LdapUserProvider.php b/src/Symfony/Component/Ldap/Security/LdapUserProvider.php index 8adbb415e1b91..34d18c4fa8cb9 100644 --- a/src/Symfony/Component/Ldap/Security/LdapUserProvider.php +++ b/src/Symfony/Component/Ldap/Security/LdapUserProvider.php @@ -45,7 +45,7 @@ class LdapUserProvider implements UserProviderInterface, PasswordUpgraderInterfa private ?string $passwordAttribute; private array $extraFields; - public function __construct(LdapInterface $ldap, string $baseDn, string $searchDn = null, #[\SensitiveParameter] string $searchPassword = null, array $defaultRoles = [], string $uidKey = null, string $filter = null, string $passwordAttribute = null, array $extraFields = []) + public function __construct(LdapInterface $ldap, string $baseDn, ?string $searchDn = null, #[\SensitiveParameter] ?string $searchPassword = null, array $defaultRoles = [], ?string $uidKey = null, ?string $filter = null, ?string $passwordAttribute = null, array $extraFields = []) { $uidKey ??= 'sAMAccountName'; $filter ??= '({uid_key}={user_identifier})'; diff --git a/src/Symfony/Component/Lock/Lock.php b/src/Symfony/Component/Lock/Lock.php index 1d1f4c3aad5d4..30d1c353a0bbc 100644 --- a/src/Symfony/Component/Lock/Lock.php +++ b/src/Symfony/Component/Lock/Lock.php @@ -175,7 +175,7 @@ public function acquireRead(bool $blocking = false): bool } } - public function refresh(float $ttl = null): void + public function refresh(?float $ttl = null): void { if (!$ttl ??= $this->ttl) { throw new InvalidArgumentException('You have to define an expiration duration.'); diff --git a/src/Symfony/Component/Lock/LockInterface.php b/src/Symfony/Component/Lock/LockInterface.php index fd4ac35143fad..563ea245c5443 100644 --- a/src/Symfony/Component/Lock/LockInterface.php +++ b/src/Symfony/Component/Lock/LockInterface.php @@ -39,7 +39,7 @@ public function acquire(bool $blocking = false): bool; * @throws LockConflictedException If the lock is acquired by someone else * @throws LockAcquiringException If the lock cannot be refreshed */ - public function refresh(float $ttl = null): void; + public function refresh(?float $ttl = null): void; /** * Returns whether or not the lock is acquired. diff --git a/src/Symfony/Component/Lock/NoLock.php b/src/Symfony/Component/Lock/NoLock.php index 50a4f1da4d085..450428313d0e2 100644 --- a/src/Symfony/Component/Lock/NoLock.php +++ b/src/Symfony/Component/Lock/NoLock.php @@ -26,7 +26,7 @@ public function acquire(bool $blocking = false): bool return true; } - public function refresh(float $ttl = null): void + public function refresh(?float $ttl = null): void { } diff --git a/src/Symfony/Component/Lock/Store/FlockStore.php b/src/Symfony/Component/Lock/Store/FlockStore.php index 9f259d74ea2c4..add33d22a4ac9 100644 --- a/src/Symfony/Component/Lock/Store/FlockStore.php +++ b/src/Symfony/Component/Lock/Store/FlockStore.php @@ -37,7 +37,7 @@ class FlockStore implements BlockingStoreInterface, SharedLockStoreInterface * * @throws LockStorageException If the lock directory doesn’t exist or is not writable */ - public function __construct(string $lockPath = null) + public function __construct(?string $lockPath = null) { if (!is_dir($lockPath ??= sys_get_temp_dir())) { if (false === @mkdir($lockPath, 0777, true) && !is_dir($lockPath)) { diff --git a/src/Symfony/Component/Lock/Tests/Store/DoctrineDbalStoreTest.php b/src/Symfony/Component/Lock/Tests/Store/DoctrineDbalStoreTest.php index bf5f878352e92..3a89fb24288da 100644 --- a/src/Symfony/Component/Lock/Tests/Store/DoctrineDbalStoreTest.php +++ b/src/Symfony/Component/Lock/Tests/Store/DoctrineDbalStoreTest.php @@ -72,7 +72,7 @@ public function testAbortAfterExpiration() /** * @dataProvider provideDsnWithSQLite */ - public function testDsnWithSQLite(string $dsn, string $file = null) + public function testDsnWithSQLite(string $dsn, ?string $file = null) { $key = new Key(uniqid(__METHOD__, true)); diff --git a/src/Symfony/Component/Lock/Tests/Store/PdoStoreTest.php b/src/Symfony/Component/Lock/Tests/Store/PdoStoreTest.php index 18db0b4beea63..94253275745f0 100644 --- a/src/Symfony/Component/Lock/Tests/Store/PdoStoreTest.php +++ b/src/Symfony/Component/Lock/Tests/Store/PdoStoreTest.php @@ -72,7 +72,7 @@ public function testInvalidTtlConstruct() /** * @dataProvider provideDsnWithSQLite */ - public function testDsnWithSQLite(string $dsn, string $file = null) + public function testDsnWithSQLite(string $dsn, ?string $file = null) { $key = new Key(uniqid(__METHOD__, true)); diff --git a/src/Symfony/Component/Mailer/Bridge/Amazon/Transport/SesHttpAsyncAwsTransport.php b/src/Symfony/Component/Mailer/Bridge/Amazon/Transport/SesHttpAsyncAwsTransport.php index 5d3762c9770e3..908c32c6b4bc7 100644 --- a/src/Symfony/Component/Mailer/Bridge/Amazon/Transport/SesHttpAsyncAwsTransport.php +++ b/src/Symfony/Component/Mailer/Bridge/Amazon/Transport/SesHttpAsyncAwsTransport.php @@ -30,8 +30,8 @@ class SesHttpAsyncAwsTransport extends AbstractTransport { public function __construct( protected SesClient $sesClient, - EventDispatcherInterface $dispatcher = null, - LoggerInterface $logger = null, + ?EventDispatcherInterface $dispatcher = null, + ?LoggerInterface $logger = null, ) { parent::__construct($dispatcher, $logger); } diff --git a/src/Symfony/Component/Mailer/Bridge/Amazon/Transport/SesSmtpTransport.php b/src/Symfony/Component/Mailer/Bridge/Amazon/Transport/SesSmtpTransport.php index 7d702f13a6470..da85b5c04557c 100644 --- a/src/Symfony/Component/Mailer/Bridge/Amazon/Transport/SesSmtpTransport.php +++ b/src/Symfony/Component/Mailer/Bridge/Amazon/Transport/SesSmtpTransport.php @@ -28,7 +28,7 @@ class SesSmtpTransport extends EsmtpTransport /** * @param string|null $region Amazon SES region */ - public function __construct(string $username, #[\SensitiveParameter] string $password, string $region = null, EventDispatcherInterface $dispatcher = null, LoggerInterface $logger = null, string $host = 'default') + public function __construct(string $username, #[\SensitiveParameter] string $password, ?string $region = null, ?EventDispatcherInterface $dispatcher = null, ?LoggerInterface $logger = null, string $host = 'default') { if ('default' === $host) { $host = sprintf('email-smtp.%s.amazonaws.com', $region ?: 'eu-west-1'); @@ -40,7 +40,7 @@ public function __construct(string $username, #[\SensitiveParameter] string $pas $this->setPassword($password); } - public function send(RawMessage $message, Envelope $envelope = null): ?SentMessage + public function send(RawMessage $message, ?Envelope $envelope = null): ?SentMessage { if ($message instanceof Message) { $this->addSesHeaders($message); diff --git a/src/Symfony/Component/Mailer/Bridge/Azure/Tests/Transport/AzureApiTransportTest.php b/src/Symfony/Component/Mailer/Bridge/Azure/Tests/Transport/AzureApiTransportTest.php index 196cdb7b6b1b7..1af179a4dd5a9 100644 --- a/src/Symfony/Component/Mailer/Bridge/Azure/Tests/Transport/AzureApiTransportTest.php +++ b/src/Symfony/Component/Mailer/Bridge/Azure/Tests/Transport/AzureApiTransportTest.php @@ -121,7 +121,7 @@ public function testTagAndMetadataHeaders() public function testItDoesNotAllowToAddResourceNameWithDot() { $this->expectException(\Exception::class); - $this->expectExceptionMessage('Resource name cannot contain or end with a dot'); + $this->expectExceptionMessage('Resource name must not end with a dot "."'); new AzureApiTransport('KEY', 'ACS_RESOURCE_NAME.'); } diff --git a/src/Symfony/Component/Mailer/Bridge/Azure/Transport/AzureApiTransport.php b/src/Symfony/Component/Mailer/Bridge/Azure/Transport/AzureApiTransport.php index 8fc5573993281..ed096a55ca6a8 100644 --- a/src/Symfony/Component/Mailer/Bridge/Azure/Transport/AzureApiTransport.php +++ b/src/Symfony/Component/Mailer/Bridge/Azure/Transport/AzureApiTransport.php @@ -39,12 +39,12 @@ public function __construct( private string $resourceName, private bool $disableTracking = false, private string $apiVersion = '2023-03-31', - HttpClientInterface $client = null, - EventDispatcherInterface $dispatcher = null, - LoggerInterface $logger = null, + ?HttpClientInterface $client = null, + ?EventDispatcherInterface $dispatcher = null, + ?LoggerInterface $logger = null, ) { - if (str_contains($resourceName, '.') || str_ends_with($resourceName, '.')) { - throw new \Exception('Resource name cannot contain or end with a dot.'); + if (str_ends_with($resourceName, '.')) { + throw new \Exception('Resource name must not end with a dot ".".'); } parent::__construct($client, $dispatcher, $logger); diff --git a/src/Symfony/Component/Mailer/Bridge/Brevo/Transport/BrevoApiTransport.php b/src/Symfony/Component/Mailer/Bridge/Brevo/Transport/BrevoApiTransport.php index 470ca226ef557..ac5b3df1f37be 100644 --- a/src/Symfony/Component/Mailer/Bridge/Brevo/Transport/BrevoApiTransport.php +++ b/src/Symfony/Component/Mailer/Bridge/Brevo/Transport/BrevoApiTransport.php @@ -34,9 +34,9 @@ final class BrevoApiTransport extends AbstractApiTransport { public function __construct( #[\SensitiveParameter] private string $key, - HttpClientInterface $client = null, - EventDispatcherInterface $dispatcher = null, - LoggerInterface $logger = null, + ?HttpClientInterface $client = null, + ?EventDispatcherInterface $dispatcher = null, + ?LoggerInterface $logger = null, ) { parent::__construct($client, $dispatcher, $logger); } diff --git a/src/Symfony/Component/Mailer/Bridge/Brevo/Transport/BrevoSmtpTransport.php b/src/Symfony/Component/Mailer/Bridge/Brevo/Transport/BrevoSmtpTransport.php index e6c9a751c7458..dd1e81f30bc49 100644 --- a/src/Symfony/Component/Mailer/Bridge/Brevo/Transport/BrevoSmtpTransport.php +++ b/src/Symfony/Component/Mailer/Bridge/Brevo/Transport/BrevoSmtpTransport.php @@ -20,7 +20,7 @@ */ final class BrevoSmtpTransport extends EsmtpTransport { - public function __construct(string $username, #[\SensitiveParameter] string $password, EventDispatcherInterface $dispatcher = null, LoggerInterface $logger = null) + public function __construct(string $username, #[\SensitiveParameter] string $password, ?EventDispatcherInterface $dispatcher = null, ?LoggerInterface $logger = null) { parent::__construct('smtp-relay.brevo.com', 465, true, $dispatcher, $logger); diff --git a/src/Symfony/Component/Mailer/Bridge/Google/Transport/GmailSmtpTransport.php b/src/Symfony/Component/Mailer/Bridge/Google/Transport/GmailSmtpTransport.php index 8a2e9086df763..47354064c0239 100644 --- a/src/Symfony/Component/Mailer/Bridge/Google/Transport/GmailSmtpTransport.php +++ b/src/Symfony/Component/Mailer/Bridge/Google/Transport/GmailSmtpTransport.php @@ -20,7 +20,7 @@ */ class GmailSmtpTransport extends EsmtpTransport { - public function __construct(string $username, #[\SensitiveParameter] string $password, EventDispatcherInterface $dispatcher = null, LoggerInterface $logger = null) + public function __construct(string $username, #[\SensitiveParameter] string $password, ?EventDispatcherInterface $dispatcher = null, ?LoggerInterface $logger = null) { parent::__construct('smtp.gmail.com', 465, true, $dispatcher, $logger); diff --git a/src/Symfony/Component/Mailer/Bridge/Infobip/Transport/InfobipApiTransport.php b/src/Symfony/Component/Mailer/Bridge/Infobip/Transport/InfobipApiTransport.php index 2bb4b6fb491c9..222e222d13081 100644 --- a/src/Symfony/Component/Mailer/Bridge/Infobip/Transport/InfobipApiTransport.php +++ b/src/Symfony/Component/Mailer/Bridge/Infobip/Transport/InfobipApiTransport.php @@ -43,9 +43,9 @@ final class InfobipApiTransport extends AbstractApiTransport public function __construct( #[\SensitiveParameter] private string $key, - HttpClientInterface $client = null, - EventDispatcherInterface $dispatcher = null, - LoggerInterface $logger = null, + ?HttpClientInterface $client = null, + ?EventDispatcherInterface $dispatcher = null, + ?LoggerInterface $logger = null, ) { parent::__construct($client, $dispatcher, $logger); } diff --git a/src/Symfony/Component/Mailer/Bridge/Infobip/Transport/InfobipSmtpTransport.php b/src/Symfony/Component/Mailer/Bridge/Infobip/Transport/InfobipSmtpTransport.php index 61f53a13d707f..1072be228914a 100644 --- a/src/Symfony/Component/Mailer/Bridge/Infobip/Transport/InfobipSmtpTransport.php +++ b/src/Symfony/Component/Mailer/Bridge/Infobip/Transport/InfobipSmtpTransport.php @@ -17,7 +17,7 @@ final class InfobipSmtpTransport extends EsmtpTransport { - public function __construct(#[\SensitiveParameter] string $key, EventDispatcherInterface $dispatcher = null, LoggerInterface $logger = null) + public function __construct(#[\SensitiveParameter] string $key, ?EventDispatcherInterface $dispatcher = null, ?LoggerInterface $logger = null) { parent::__construct('smtp-api.infobip.com', 587, false, $dispatcher, $logger); diff --git a/src/Symfony/Component/Mailer/Bridge/MailPace/Transport/MailPaceApiTransport.php b/src/Symfony/Component/Mailer/Bridge/MailPace/Transport/MailPaceApiTransport.php index afdd0d767d91d..e275df21b2f2e 100644 --- a/src/Symfony/Component/Mailer/Bridge/MailPace/Transport/MailPaceApiTransport.php +++ b/src/Symfony/Component/Mailer/Bridge/MailPace/Transport/MailPaceApiTransport.php @@ -33,9 +33,9 @@ final class MailPaceApiTransport extends AbstractApiTransport public function __construct( #[\SensitiveParameter] private string $key, - HttpClientInterface $client = null, - EventDispatcherInterface $dispatcher = null, - LoggerInterface $logger = null, + ?HttpClientInterface $client = null, + ?EventDispatcherInterface $dispatcher = null, + ?LoggerInterface $logger = null, ) { parent::__construct($client, $dispatcher, $logger); } diff --git a/src/Symfony/Component/Mailer/Bridge/MailPace/Transport/MailPaceSmtpTransport.php b/src/Symfony/Component/Mailer/Bridge/MailPace/Transport/MailPaceSmtpTransport.php index 09c82b99809f2..ee88fcf0c3201 100644 --- a/src/Symfony/Component/Mailer/Bridge/MailPace/Transport/MailPaceSmtpTransport.php +++ b/src/Symfony/Component/Mailer/Bridge/MailPace/Transport/MailPaceSmtpTransport.php @@ -25,7 +25,7 @@ */ final class MailPaceSmtpTransport extends EsmtpTransport { - public function __construct(#[\SensitiveParameter] string $id, EventDispatcherInterface $dispatcher = null, LoggerInterface $logger = null) + public function __construct(#[\SensitiveParameter] string $id, ?EventDispatcherInterface $dispatcher = null, ?LoggerInterface $logger = null) { parent::__construct('smtp.mailpace.com', 587, false, $dispatcher, $logger); @@ -33,7 +33,7 @@ public function __construct(#[\SensitiveParameter] string $id, EventDispatcherIn $this->setPassword($id); } - public function send(RawMessage $message, Envelope $envelope = null): ?SentMessage + public function send(RawMessage $message, ?Envelope $envelope = null): ?SentMessage { if ($message instanceof Message) { $this->addMailPaceHeaders($message); diff --git a/src/Symfony/Component/Mailer/Bridge/Mailchimp/Transport/MandrillApiTransport.php b/src/Symfony/Component/Mailer/Bridge/Mailchimp/Transport/MandrillApiTransport.php index aa2d68d3843e4..976442bcfc064 100644 --- a/src/Symfony/Component/Mailer/Bridge/Mailchimp/Transport/MandrillApiTransport.php +++ b/src/Symfony/Component/Mailer/Bridge/Mailchimp/Transport/MandrillApiTransport.php @@ -34,9 +34,9 @@ class MandrillApiTransport extends AbstractApiTransport public function __construct( #[\SensitiveParameter] private string $key, - HttpClientInterface $client = null, - EventDispatcherInterface $dispatcher = null, - LoggerInterface $logger = null, + ?HttpClientInterface $client = null, + ?EventDispatcherInterface $dispatcher = null, + ?LoggerInterface $logger = null, ) { parent::__construct($client, $dispatcher, $logger); } diff --git a/src/Symfony/Component/Mailer/Bridge/Mailchimp/Transport/MandrillHeadersTrait.php b/src/Symfony/Component/Mailer/Bridge/Mailchimp/Transport/MandrillHeadersTrait.php index 73c3dbd010640..faa7e0bee4d88 100644 --- a/src/Symfony/Component/Mailer/Bridge/Mailchimp/Transport/MandrillHeadersTrait.php +++ b/src/Symfony/Component/Mailer/Bridge/Mailchimp/Transport/MandrillHeadersTrait.php @@ -23,7 +23,7 @@ */ trait MandrillHeadersTrait { - public function send(RawMessage $message, Envelope $envelope = null): ?SentMessage + public function send(RawMessage $message, ?Envelope $envelope = null): ?SentMessage { if ($message instanceof Message) { $this->addMandrillHeaders($message); diff --git a/src/Symfony/Component/Mailer/Bridge/Mailchimp/Transport/MandrillHttpTransport.php b/src/Symfony/Component/Mailer/Bridge/Mailchimp/Transport/MandrillHttpTransport.php index 17b686cbe684a..200daa60cc954 100644 --- a/src/Symfony/Component/Mailer/Bridge/Mailchimp/Transport/MandrillHttpTransport.php +++ b/src/Symfony/Component/Mailer/Bridge/Mailchimp/Transport/MandrillHttpTransport.php @@ -33,9 +33,9 @@ class MandrillHttpTransport extends AbstractHttpTransport public function __construct( #[\SensitiveParameter] private string $key, - HttpClientInterface $client = null, - EventDispatcherInterface $dispatcher = null, - LoggerInterface $logger = null, + ?HttpClientInterface $client = null, + ?EventDispatcherInterface $dispatcher = null, + ?LoggerInterface $logger = null, ) { parent::__construct($client, $dispatcher, $logger); } diff --git a/src/Symfony/Component/Mailer/Bridge/Mailchimp/Transport/MandrillSmtpTransport.php b/src/Symfony/Component/Mailer/Bridge/Mailchimp/Transport/MandrillSmtpTransport.php index 6afaaf775ba31..b2667db4bcb73 100644 --- a/src/Symfony/Component/Mailer/Bridge/Mailchimp/Transport/MandrillSmtpTransport.php +++ b/src/Symfony/Component/Mailer/Bridge/Mailchimp/Transport/MandrillSmtpTransport.php @@ -22,7 +22,7 @@ class MandrillSmtpTransport extends EsmtpTransport { use MandrillHeadersTrait; - public function __construct(string $username, #[\SensitiveParameter] string $password, EventDispatcherInterface $dispatcher = null, LoggerInterface $logger = null) + public function __construct(string $username, #[\SensitiveParameter] string $password, ?EventDispatcherInterface $dispatcher = null, ?LoggerInterface $logger = null) { parent::__construct('smtp.mandrillapp.com', 587, false, $dispatcher, $logger); diff --git a/src/Symfony/Component/Mailer/Bridge/MailerSend/CHANGELOG.md b/src/Symfony/Component/Mailer/Bridge/MailerSend/CHANGELOG.md index 1f2c8f86cde72..45273cc83c340 100644 --- a/src/Symfony/Component/Mailer/Bridge/MailerSend/CHANGELOG.md +++ b/src/Symfony/Component/Mailer/Bridge/MailerSend/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +7.1 +--- + + * Add support for `RemoteEvent` and `Webhook` + 6.3 --- diff --git a/src/Symfony/Component/Mailer/Bridge/MailerSend/RemoteEvent/MailerSendPayloadConverter.php b/src/Symfony/Component/Mailer/Bridge/MailerSend/RemoteEvent/MailerSendPayloadConverter.php new file mode 100644 index 0000000000000..7ef67b1d5dacd --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/MailerSend/RemoteEvent/MailerSendPayloadConverter.php @@ -0,0 +1,105 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\Bridge\MailerSend\RemoteEvent; + +use Symfony\Component\RemoteEvent\Event\Mailer\AbstractMailerEvent; +use Symfony\Component\RemoteEvent\Event\Mailer\MailerDeliveryEvent; +use Symfony\Component\RemoteEvent\Event\Mailer\MailerEngagementEvent; +use Symfony\Component\RemoteEvent\Exception\ParseException; +use Symfony\Component\RemoteEvent\PayloadConverterInterface; + +/** + * @author WoutervanderLoop.nl + */ +final class MailerSendPayloadConverter implements PayloadConverterInterface +{ + public function convert(array $payload): AbstractMailerEvent + { + if (\in_array($payload['type'], ['activity.sent', 'activity.delivered', 'activity.soft_bounced', 'activity.hard_bounced'], true)) { + $name = match ($payload['type']) { + 'activity.sent' => MailerDeliveryEvent::RECEIVED, + 'activity.delivered' => MailerDeliveryEvent::DELIVERED, + 'activity.soft_bounced', 'activity.hard_bounced' => MailerDeliveryEvent::BOUNCE, + }; + $event = new MailerDeliveryEvent($name, $this->getMessageId($payload), $payload); + $event->setReason($this->getReason($payload)); + } else { + $name = match ($payload['type']) { + 'activity.clicked', 'activity.clicked_unique' => MailerEngagementEvent::CLICK, + 'activity.unsubscribed' => MailerEngagementEvent::UNSUBSCRIBE, + 'activity.opened', 'activity.opened_unique' => MailerEngagementEvent::OPEN, + 'activity.spam_complaint' => MailerEngagementEvent::SPAM, + default => throw new ParseException(sprintf('Unsupported event "%s".', $payload['type'])), + }; + $event = new MailerEngagementEvent($name, $this->getMessageId($payload), $payload); + } + + if (!$date = \DateTimeImmutable::createFromFormat('Y-m-d\TH:i:s.u\Z', $payload['created_at'])) { + throw new ParseException(sprintf('Invalid date "%s".', $payload['created_at'])); + } + + $event->setDate($date); + $event->setRecipientEmail($this->getRecipientEmail($payload)); + $event->setMetadata($this->getMetadata($payload)); + $event->setTags($this->getTags($payload)); + + return $event; + } + + private function getMessageId(array $payload): string + { + return $payload['data']['email']['message']['id']; + } + + private function getRecipientEmail(array $payload): string + { + return $payload['data']['email']['recipient']['email']; + } + + private function getReason(array $payload): string + { + if (isset($payload['data']['morph']['readable_reason'])) { + return $payload['data']['morph']['readable_reason']; + } + + if (isset($payload['data']['morph']['reason'])) { + return $payload['data']['morph']['reason']; + } + + return ''; + } + + private function getTags(array $payload): array + { + return $payload['data']['email']['tags'] ?? []; + } + + private function getMetadata(array $payload): array + { + $morphObject = $payload['data']['morph']['object'] ?? null; + + return match ($morphObject) { + 'open' => [ + 'ip' => $payload['data']['morph']['ip'] ?? null + ], + 'click' => [ + 'ip' => $payload['data']['morph']['ip'] ?? null, + 'url' => $payload['data']['morph']['url'] ?? null, + ], + 'recipient_unsubscribe' => [ + 'reason' => $payload['data']['morph']['reason'] ?? null, + 'readable_reason' => $payload['data']['morph']['readable_reason'] ?? null, + ], + default => [], + }; + } +} diff --git a/src/Symfony/Component/Mailer/Bridge/MailerSend/Tests/Webhook/Fixtures/clicked.json b/src/Symfony/Component/Mailer/Bridge/MailerSend/Tests/Webhook/Fixtures/clicked.json new file mode 100644 index 0000000000000..2857de9c0b64d --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/MailerSend/Tests/Webhook/Fixtures/clicked.json @@ -0,0 +1,42 @@ +{ + "type": "activity.clicked", + "domain_id": "7z3m5jgrogdpyo6n", + "created_at": "2024-01-01T12:00:00.000000Z", + "webhook_id": "7z3m5jgrogdpyo6n", + "url": "https://www.mailersend.com/webhook", + "data": { + "object": "activity", + "id": "62f114f8165fe0d8db0288e5", + "type": "clicked", + "created_at": "2024-01-01T12:00:00.000000Z", + "email": { + "object": "email", + "id": "62f114f7165fe0d8db0288e2", + "created_at": "2024-01-01T12:00:00.000000Z", + "from": "test@mailersend.com", + "subject": "Test subject", + "status": "delivered", + "tags": ["test-tag"], + "headers": null, + "message": { + "object": "message", + "id": "62fb66bef54a112e920b5493", + "created_at": "2024-01-01T12:00:00.000000Z" + }, + "recipient": { + "object": "recipient", + "id": "62c69be104270ee9c0074d32", + "email": "test@example.com", + "created_at": "2024-01-01T12:00:00.000000Z" + } + }, + "morph": { + "object": "click", + "id": "62fb9215f2481f74e3085356", + "created_at": "2024-01-01T12:00:00.000000Z", + "ip": "127.0.0.1", + "url": "https://www.mailersend.com" + }, + "template_id": "0z76k5jg0o3yeg2d" + } +} diff --git a/src/Symfony/Component/Mailer/Bridge/MailerSend/Tests/Webhook/Fixtures/clicked.php b/src/Symfony/Component/Mailer/Bridge/MailerSend/Tests/Webhook/Fixtures/clicked.php new file mode 100644 index 0000000000000..5ca285288014a --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/MailerSend/Tests/Webhook/Fixtures/clicked.php @@ -0,0 +1,14 @@ +setRecipientEmail('test@example.com'); +$wh->setTags(["test-tag"]); +$wh->setMetadata([ + 'ip' => '127.0.0.1', + 'url' => 'https://www.mailersend.com' +]); +$wh->setDate(\DateTimeImmutable::createFromFormat('Y-m-d\TH:i:s.u\Z', '2024-01-01T12:00:00.000000Z')); + +return $wh; diff --git a/src/Symfony/Component/Mailer/Bridge/MailerSend/Tests/Webhook/Fixtures/clicked_unique.json b/src/Symfony/Component/Mailer/Bridge/MailerSend/Tests/Webhook/Fixtures/clicked_unique.json new file mode 100644 index 0000000000000..9cb5ed17af3b8 --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/MailerSend/Tests/Webhook/Fixtures/clicked_unique.json @@ -0,0 +1,42 @@ +{ + "type": "activity.clicked_unique", + "domain_id": "7z3m5jgrogdpyo6n", + "created_at": "2024-01-01T12:00:00.000000Z", + "webhook_id": "7z3m5jgrogdpyo6n", + "url": "https://www.mailersend.com/webhook", + "data": { + "object": "activity", + "id": "62f114f8165fe0d8db0288e5", + "type": "clicked_unique", + "created_at": "2024-01-01T12:00:00.000000Z", + "email": { + "object": "email", + "id": "62f114f7165fe0d8db0288e2", + "created_at": "2024-01-01T12:00:00.000000Z", + "from": "test@mailersend.com", + "subject": "Test subject", + "status": "delivered", + "tags": ["test-tag"], + "headers": null, + "message": { + "object": "message", + "id": "62fb66bef54a112e920b5493", + "created_at": "2024-01-01T12:00:00.000000Z" + }, + "recipient": { + "object": "recipient", + "id": "62c69be104270ee9c0074d32", + "email": "test@example.com", + "created_at": "2024-01-01T12:00:00.000000Z" + } + }, + "morph": { + "object": "click", + "id": "62fb9215f2481f74e3085356", + "created_at": "2024-01-01T12:00:00.000000Z", + "ip": "127.0.0.1", + "url": "https://www.mailersend.com" + }, + "template_id": "0z76k5jg0o3yeg2d" + } +} diff --git a/src/Symfony/Component/Mailer/Bridge/MailerSend/Tests/Webhook/Fixtures/clicked_unique.php b/src/Symfony/Component/Mailer/Bridge/MailerSend/Tests/Webhook/Fixtures/clicked_unique.php new file mode 100644 index 0000000000000..5ca285288014a --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/MailerSend/Tests/Webhook/Fixtures/clicked_unique.php @@ -0,0 +1,14 @@ +setRecipientEmail('test@example.com'); +$wh->setTags(["test-tag"]); +$wh->setMetadata([ + 'ip' => '127.0.0.1', + 'url' => 'https://www.mailersend.com' +]); +$wh->setDate(\DateTimeImmutable::createFromFormat('Y-m-d\TH:i:s.u\Z', '2024-01-01T12:00:00.000000Z')); + +return $wh; diff --git a/src/Symfony/Component/Mailer/Bridge/MailerSend/Tests/Webhook/Fixtures/delivered.json b/src/Symfony/Component/Mailer/Bridge/MailerSend/Tests/Webhook/Fixtures/delivered.json new file mode 100644 index 0000000000000..eb924779ee895 --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/MailerSend/Tests/Webhook/Fixtures/delivered.json @@ -0,0 +1,36 @@ +{ + "type": "activity.delivered", + "domain_id": "7z3m5jgrogdpyo6n", + "created_at": "2024-01-01T12:00:00.000000Z", + "webhook_id": "7z3m5jgrogdpyo6n", + "url": "https://www.mailersend.com/webhook", + "data": { + "object": "activity", + "id": "62f114f8165fe0d8db0288e5", + "type": "delivered", + "created_at": "2024-01-01T12:00:00.000000Z", + "email": { + "object": "email", + "id": "62f114f7165fe0d8db0288e2", + "created_at": "2024-01-01T12:00:00.000000Z", + "from": "test@mailersend.com", + "subject": "Test subject", + "status": "delivered", + "tags": ["test-tag"], + "headers": null, + "message": { + "object": "message", + "id": "62fb66bef54a112e920b5493", + "created_at": "2024-01-01T12:00:00.000000Z" + }, + "recipient": { + "object": "recipient", + "id": "62c69be104270ee9c0074d32", + "email": "test@example.com", + "created_at": "2024-01-01T12:00:00.000000Z" + } + }, + "morph": null, + "template_id": "0z76k5jg0o3yeg2d" + } +} diff --git a/src/Symfony/Component/Mailer/Bridge/MailerSend/Tests/Webhook/Fixtures/delivered.php b/src/Symfony/Component/Mailer/Bridge/MailerSend/Tests/Webhook/Fixtures/delivered.php new file mode 100644 index 0000000000000..62558144c06aa --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/MailerSend/Tests/Webhook/Fixtures/delivered.php @@ -0,0 +1,12 @@ +setRecipientEmail('test@example.com'); +$wh->setTags(["test-tag"]); +$wh->setMetadata([]); +$wh->setReason(''); +$wh->setDate(\DateTimeImmutable::createFromFormat('Y-m-d\TH:i:s.u\Z', '2024-01-01T12:00:00.000000Z')); + +return $wh; diff --git a/src/Symfony/Component/Mailer/Bridge/MailerSend/Tests/Webhook/Fixtures/hard_bounced.json b/src/Symfony/Component/Mailer/Bridge/MailerSend/Tests/Webhook/Fixtures/hard_bounced.json new file mode 100644 index 0000000000000..d59a3bcefee38 --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/MailerSend/Tests/Webhook/Fixtures/hard_bounced.json @@ -0,0 +1,39 @@ +{ + "type": "activity.hard_bounced", + "domain_id": "7z3m5jgrogdpyo6n", + "created_at": "2024-01-01T12:00:00.000000Z", + "webhook_id": "7z3m5jgrogdpyo6n", + "url": "https://www.mailersend.com/webhook", + "data": { + "object": "activity", + "id": "62f114f8165fe0d8db0288e5", + "type": "hard_bounced", + "created_at": "2024-01-01T12:00:00.000000Z", + "email": { + "object": "email", + "id": "62f114f7165fe0d8db0288e2", + "created_at": "2024-01-01T12:00:00.000000Z", + "from": "test@mailersend.com", + "subject": "Test subject", + "status": "rejected", + "tags": ["test-tag"], + "headers": null, + "message": { + "object": "message", + "id": "62fb66bef54a112e920b5493", + "created_at": "2024-01-01T12:00:00.000000Z" + }, + "recipient": { + "object": "recipient", + "id": "62c69be104270ee9c0074d32", + "email": "test@example.com", + "created_at": "2024-01-01T12:00:00.000000Z" + } + }, + "morph": { + "object": "recipient_bounce", + "reason": "Host or domain name not found" + }, + "template_id": "0z76k5jg0o3yeg2d" + } +} diff --git a/src/Symfony/Component/Mailer/Bridge/MailerSend/Tests/Webhook/Fixtures/hard_bounced.php b/src/Symfony/Component/Mailer/Bridge/MailerSend/Tests/Webhook/Fixtures/hard_bounced.php new file mode 100644 index 0000000000000..06dd15ff42b70 --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/MailerSend/Tests/Webhook/Fixtures/hard_bounced.php @@ -0,0 +1,12 @@ +setRecipientEmail('test@example.com'); +$wh->setTags(["test-tag"]); +$wh->setMetadata([]); +$wh->setReason('Host or domain name not found'); +$wh->setDate(\DateTimeImmutable::createFromFormat('Y-m-d\TH:i:s.u\Z', '2024-01-01T12:00:00.000000Z')); + +return $wh; diff --git a/src/Symfony/Component/Mailer/Bridge/MailerSend/Tests/Webhook/Fixtures/opened.json b/src/Symfony/Component/Mailer/Bridge/MailerSend/Tests/Webhook/Fixtures/opened.json new file mode 100644 index 0000000000000..9b5aac7905bca --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/MailerSend/Tests/Webhook/Fixtures/opened.json @@ -0,0 +1,41 @@ +{ + "type": "activity.opened", + "domain_id": "7z3m5jgrogdpyo6n", + "created_at": "2024-01-01T12:00:00.000000Z", + "webhook_id": "7z3m5jgrogdpyo6n", + "url": "https://www.mailersend.com/webhook", + "data": { + "object": "activity", + "id": "62f114f8165fe0d8db0288e5", + "type": "opened", + "created_at": "2024-01-01T12:00:00.000000Z", + "email": { + "object": "email", + "id": "62f114f7165fe0d8db0288e2", + "created_at": "2024-01-01T12:00:00.000000Z", + "from": "test@mailersend.com", + "subject": "Test subject", + "status": "delivered", + "tags": ["test-tag"], + "headers": null, + "message": { + "object": "message", + "id": "62fb66bef54a112e920b5493", + "created_at": "2024-01-01T12:00:00.000000Z" + }, + "recipient": { + "object": "recipient", + "id": "62c69be104270ee9c0074d32", + "email": "test@example.com", + "created_at": "2024-01-01T12:00:00.000000Z" + } + }, + "morph": { + "object": "open", + "id": "62fb9151f2481f74e3085352", + "created_at": "2024-01-01T12:00:00.000000Z", + "ip": "127.0.0.1" + }, + "template_id": "0z76k5jg0o3yeg2d" + } +} diff --git a/src/Symfony/Component/Mailer/Bridge/MailerSend/Tests/Webhook/Fixtures/opened.php b/src/Symfony/Component/Mailer/Bridge/MailerSend/Tests/Webhook/Fixtures/opened.php new file mode 100644 index 0000000000000..8deb1ca7ceb03 --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/MailerSend/Tests/Webhook/Fixtures/opened.php @@ -0,0 +1,13 @@ +setRecipientEmail('test@example.com'); +$wh->setTags(["test-tag"]); +$wh->setMetadata([ + 'ip' => '127.0.0.1' +]); +$wh->setDate(\DateTimeImmutable::createFromFormat('Y-m-d\TH:i:s.u\Z', '2024-01-01T12:00:00.000000Z')); + +return $wh; diff --git a/src/Symfony/Component/Mailer/Bridge/MailerSend/Tests/Webhook/Fixtures/opened_unique.json b/src/Symfony/Component/Mailer/Bridge/MailerSend/Tests/Webhook/Fixtures/opened_unique.json new file mode 100644 index 0000000000000..7323512a83927 --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/MailerSend/Tests/Webhook/Fixtures/opened_unique.json @@ -0,0 +1,41 @@ +{ + "type": "activity.opened_unique", + "domain_id": "7z3m5jgrogdpyo6n", + "created_at": "2024-01-01T12:00:00.000000Z", + "webhook_id": "7z3m5jgrogdpyo6n", + "url": "https://www.mailersend.com/webhook", + "data": { + "object": "activity", + "id": "62f114f8165fe0d8db0288e5", + "type": "opened_unique", + "created_at": "2024-01-01T12:00:00.000000Z", + "email": { + "object": "email", + "id": "62f114f7165fe0d8db0288e2", + "created_at": "2024-01-01T12:00:00.000000Z", + "from": "test@mailersend.com", + "subject": "Test subject", + "status": "delivered", + "tags": ["test-tag"], + "headers": null, + "message": { + "object": "message", + "id": "62fb66bef54a112e920b5493", + "created_at": "2024-01-01T12:00:00.000000Z" + }, + "recipient": { + "object": "recipient", + "id": "62c69be104270ee9c0074d32", + "email": "test@example.com", + "created_at": "2024-01-01T12:00:00.000000Z" + } + }, + "morph": { + "object": "open", + "id": "62fb9151f2481f74e3085352", + "created_at": "2024-01-01T12:00:00.000000Z", + "ip": "127.0.0.1" + }, + "template_id": "0z76k5jg0o3yeg2d" + } +} diff --git a/src/Symfony/Component/Mailer/Bridge/MailerSend/Tests/Webhook/Fixtures/opened_unique.php b/src/Symfony/Component/Mailer/Bridge/MailerSend/Tests/Webhook/Fixtures/opened_unique.php new file mode 100644 index 0000000000000..8deb1ca7ceb03 --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/MailerSend/Tests/Webhook/Fixtures/opened_unique.php @@ -0,0 +1,13 @@ +setRecipientEmail('test@example.com'); +$wh->setTags(["test-tag"]); +$wh->setMetadata([ + 'ip' => '127.0.0.1' +]); +$wh->setDate(\DateTimeImmutable::createFromFormat('Y-m-d\TH:i:s.u\Z', '2024-01-01T12:00:00.000000Z')); + +return $wh; diff --git a/src/Symfony/Component/Mailer/Bridge/MailerSend/Tests/Webhook/Fixtures/sent.json b/src/Symfony/Component/Mailer/Bridge/MailerSend/Tests/Webhook/Fixtures/sent.json new file mode 100644 index 0000000000000..3d846979eeb94 --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/MailerSend/Tests/Webhook/Fixtures/sent.json @@ -0,0 +1,36 @@ +{ + "type": "activity.sent", + "domain_id": "7z3m5jgrogdpyo6n", + "created_at": "2024-01-01T12:00:00.000000Z", + "webhook_id": "7z3m5jgrogdpyo6n", + "url": "https://www.mailersend.com/webhook", + "data": { + "object": "activity", + "id": "62f114f8165fe0d8db0288e5", + "type": "sent", + "created_at": "2024-01-01T12:00:00.000000Z", + "email": { + "object": "email", + "id": "62f114f7165fe0d8db0288e2", + "created_at": "2024-01-01T12:00:00.000000Z", + "from": "test@mailersend.com", + "subject": "Test subject", + "status": "sent", + "tags": ["test-tag"], + "headers": null, + "message": { + "object": "message", + "id": "62fb66bef54a112e920b5493", + "created_at": "2024-01-01T12:00:00.000000Z" + }, + "recipient": { + "object": "recipient", + "id": "62c69be104270ee9c0074d32", + "email": "test@example.com", + "created_at": "2024-01-01T12:00:00.000000Z" + } + }, + "morph": null, + "template_id": "0z76k5jg0o3yeg2d" + } +} diff --git a/src/Symfony/Component/Mailer/Bridge/MailerSend/Tests/Webhook/Fixtures/sent.php b/src/Symfony/Component/Mailer/Bridge/MailerSend/Tests/Webhook/Fixtures/sent.php new file mode 100644 index 0000000000000..519813c9a39c5 --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/MailerSend/Tests/Webhook/Fixtures/sent.php @@ -0,0 +1,12 @@ +setRecipientEmail('test@example.com'); +$wh->setTags(["test-tag"]); +$wh->setMetadata([]); +$wh->setReason(''); +$wh->setDate(\DateTimeImmutable::createFromFormat('Y-m-d\TH:i:s.u\Z', '2024-01-01T12:00:00.000000Z')); + +return $wh; diff --git a/src/Symfony/Component/Mailer/Bridge/MailerSend/Tests/Webhook/Fixtures/soft_bounced.json b/src/Symfony/Component/Mailer/Bridge/MailerSend/Tests/Webhook/Fixtures/soft_bounced.json new file mode 100644 index 0000000000000..27a3facf73c56 --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/MailerSend/Tests/Webhook/Fixtures/soft_bounced.json @@ -0,0 +1,39 @@ +{ + "type": "activity.soft_bounced", + "domain_id": "7z3m5jgrogdpyo6n", + "created_at": "2024-01-01T12:00:00.000000Z", + "webhook_id": "7z3m5jgrogdpyo6n", + "url": "https://www.mailersend.com/webhook", + "data": { + "object": "activity", + "id": "62f114f8165fe0d8db0288e5", + "type": "soft_bounced", + "created_at": "2024-01-01T12:00:00.000000Z", + "email": { + "object": "email", + "id": "62f114f7165fe0d8db0288e2", + "created_at": "2024-01-01T12:00:00.000000Z", + "from": "test@mailersend.com", + "subject": "Test subject", + "status": "rejected", + "tags": ["test-tag"], + "headers": null, + "message": { + "object": "message", + "id": "62fb66bef54a112e920b5493", + "created_at": "2024-01-01T12:00:00.000000Z" + }, + "recipient": { + "object": "recipient", + "id": "62c69be104270ee9c0074d32", + "email": "test@example.com", + "created_at": "2024-01-01T12:00:00.000000Z" + } + }, + "morph": { + "object": "recipient_bounce", + "reason": "Unknown reason" + }, + "template_id": "0z76k5jg0o3yeg2d" + } +} diff --git a/src/Symfony/Component/Mailer/Bridge/MailerSend/Tests/Webhook/Fixtures/soft_bounced.php b/src/Symfony/Component/Mailer/Bridge/MailerSend/Tests/Webhook/Fixtures/soft_bounced.php new file mode 100644 index 0000000000000..0973e3cd637f6 --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/MailerSend/Tests/Webhook/Fixtures/soft_bounced.php @@ -0,0 +1,12 @@ +setRecipientEmail('test@example.com'); +$wh->setTags(["test-tag"]); +$wh->setMetadata([]); +$wh->setReason('Unknown reason'); +$wh->setDate(\DateTimeImmutable::createFromFormat('Y-m-d\TH:i:s.u\Z', '2024-01-01T12:00:00.000000Z')); + +return $wh; diff --git a/src/Symfony/Component/Mailer/Bridge/MailerSend/Tests/Webhook/Fixtures/spam_complaint.json b/src/Symfony/Component/Mailer/Bridge/MailerSend/Tests/Webhook/Fixtures/spam_complaint.json new file mode 100644 index 0000000000000..a63d796ecd2bc --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/MailerSend/Tests/Webhook/Fixtures/spam_complaint.json @@ -0,0 +1,38 @@ +{ + "type": "activity.spam_complaint", + "domain_id": "7z3m5jgrogdpyo6n", + "created_at": "2024-01-01T12:00:00.000000Z", + "webhook_id": "7z3m5jgrogdpyo6n", + "url": "https://www.mailersend.com/webhook", + "data": { + "object": "activity", + "id": "62f114f8165fe0d8db0288e5", + "type": "spam_complaint", + "created_at": "2024-01-01T12:00:00.000000Z", + "email": { + "object": "email", + "id": "62f114f7165fe0d8db0288e2", + "created_at": "2024-01-01T12:00:00.000000Z", + "from": "test@mailersend.com", + "subject": "Test subject", + "status": "delivered", + "tags": ["test-tag"], + "headers": null, + "message": { + "object": "message", + "id": "62fb66bef54a112e920b5493", + "created_at": "2024-01-01T12:00:00.000000Z" + }, + "recipient": { + "object": "recipient", + "id": "62c69be104270ee9c0074d32", + "email": "test@example.com", + "created_at": "2024-01-01T12:00:00.000000Z" + } + }, + "morph": { + "object": "spam_complaint" + }, + "template_id": "0z76k5jg0o3yeg2d" + } +} diff --git a/src/Symfony/Component/Mailer/Bridge/MailerSend/Tests/Webhook/Fixtures/spam_complaint.php b/src/Symfony/Component/Mailer/Bridge/MailerSend/Tests/Webhook/Fixtures/spam_complaint.php new file mode 100644 index 0000000000000..7bd73b9fa97d1 --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/MailerSend/Tests/Webhook/Fixtures/spam_complaint.php @@ -0,0 +1,11 @@ +setRecipientEmail('test@example.com'); +$wh->setTags(["test-tag"]); +$wh->setMetadata([]); +$wh->setDate(\DateTimeImmutable::createFromFormat('Y-m-d\TH:i:s.u\Z', '2024-01-01T12:00:00.000000Z')); + +return $wh; diff --git a/src/Symfony/Component/Mailer/Bridge/MailerSend/Tests/Webhook/Fixtures/unsubscribed.json b/src/Symfony/Component/Mailer/Bridge/MailerSend/Tests/Webhook/Fixtures/unsubscribed.json new file mode 100644 index 0000000000000..4c7c7ee22cb68 --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/MailerSend/Tests/Webhook/Fixtures/unsubscribed.json @@ -0,0 +1,40 @@ +{ + "type": "activity.unsubscribed", + "domain_id": "7z3m5jgrogdpyo6n", + "created_at": "2024-01-01T12:00:00.000000Z", + "webhook_id": "7z3m5jgrogdpyo6n", + "url": "https://www.mailersend.com/webhook", + "data": { + "object": "activity", + "id": "62f114f8165fe0d8db0288e5", + "type": "unsubscribed", + "created_at": "2024-01-01T12:00:00.000000Z", + "email": { + "object": "email", + "id": "62f114f7165fe0d8db0288e2", + "created_at": "2024-01-01T12:00:00.000000Z", + "from": "test@mailersend.com", + "subject": "Test subject", + "status": "delivered", + "tags": ["test-tag"], + "headers": null, + "message": { + "object": "message", + "id": "62fb66bef54a112e920b5493", + "created_at": "2024-01-01T12:00:00.000000Z" + }, + "recipient": { + "object": "recipient", + "id": "62c69be104270ee9c0074d32", + "email": "test@example.com", + "created_at": "2024-01-01T12:00:00.000000Z" + } + }, + "morph": { + "object": "recipient_unsubscribe", + "reason": "NO_LONGER_WANT", + "readable_reason": "I no longer want to receive these emails" + }, + "template_id": "0z76k5jg0o3yeg2d" + } +} diff --git a/src/Symfony/Component/Mailer/Bridge/MailerSend/Tests/Webhook/Fixtures/unsubscribed.php b/src/Symfony/Component/Mailer/Bridge/MailerSend/Tests/Webhook/Fixtures/unsubscribed.php new file mode 100644 index 0000000000000..45e7a63beb16b --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/MailerSend/Tests/Webhook/Fixtures/unsubscribed.php @@ -0,0 +1,14 @@ +setRecipientEmail('test@example.com'); +$wh->setTags(["test-tag"]); +$wh->setMetadata([ + 'reason' => 'NO_LONGER_WANT', + 'readable_reason' => 'I no longer want to receive these emails' +]); +$wh->setDate(\DateTimeImmutable::createFromFormat('Y-m-d\TH:i:s.u\Z', '2024-01-01T12:00:00.000000Z')); + +return $wh; diff --git a/src/Symfony/Component/Mailer/Bridge/MailerSend/Tests/Webhook/MailerSendMissingSignatureRequestParserTest.php b/src/Symfony/Component/Mailer/Bridge/MailerSend/Tests/Webhook/MailerSendMissingSignatureRequestParserTest.php new file mode 100644 index 0000000000000..010b451a1462e --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/MailerSend/Tests/Webhook/MailerSendMissingSignatureRequestParserTest.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\Bridge\MailerSend\Tests\Webhook; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\Mailer\Bridge\MailerSend\RemoteEvent\MailerSendPayloadConverter; +use Symfony\Component\Mailer\Bridge\MailerSend\Webhook\MailerSendRequestParser; +use Symfony\Component\Webhook\Client\RequestParserInterface; +use Symfony\Component\Webhook\Exception\RejectWebhookException; +use Symfony\Component\Webhook\Test\AbstractRequestParserTestCase; + +class MailerSendMissingSignatureRequestParserTest extends AbstractRequestParserTestCase +{ + protected function createRequestParser(): RequestParserInterface + { + $this->expectException(RejectWebhookException::class); + $this->expectExceptionMessage('Signature is required.'); + + return new MailerSendRequestParser(new MailerSendPayloadConverter()); + } + + public static function getPayloads(): iterable + { + $filename = 'sent.json'; + $currentDir = \dirname((new \ReflectionClass(static::class))->getFileName()); + + yield $filename => [ + file_get_contents($currentDir . '/Fixtures/sent.json'), + include($currentDir . '/Fixtures/sent.php'), + ]; + } + + protected function getSecret(): string + { + return 'GvLY88Uyj70jQm3fUwYyWmAaiz98wWim'; + } + + protected function createRequest(string $payload): Request + { + return Request::create('/', 'POST', [], [], [], [ + 'Content-Type' => 'application/json', + ], $payload); + } +} diff --git a/src/Symfony/Component/Mailer/Bridge/MailerSend/Tests/Webhook/MailerSendRequestParserTest.php b/src/Symfony/Component/Mailer/Bridge/MailerSend/Tests/Webhook/MailerSendRequestParserTest.php new file mode 100644 index 0000000000000..1f8230b97ce64 --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/MailerSend/Tests/Webhook/MailerSendRequestParserTest.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\Bridge\MailerSend\Tests\Webhook; + +use Symfony\Component\Mailer\Bridge\MailerSend\RemoteEvent\MailerSendPayloadConverter; +use Symfony\Component\Mailer\Bridge\MailerSend\Webhook\MailerSendRequestParser; +use Symfony\Component\Webhook\Client\RequestParserInterface; +use Symfony\Component\Webhook\Test\AbstractRequestParserTestCase; + +class MailerSendRequestParserTest extends AbstractRequestParserTestCase +{ + protected function createRequestParser(): RequestParserInterface + { + return new MailerSendRequestParser(new MailerSendPayloadConverter()); + } +} diff --git a/src/Symfony/Component/Mailer/Bridge/MailerSend/Tests/Webhook/MailerSendSignedRequestParserTest.php b/src/Symfony/Component/Mailer/Bridge/MailerSend/Tests/Webhook/MailerSendSignedRequestParserTest.php new file mode 100644 index 0000000000000..9553e989752e1 --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/MailerSend/Tests/Webhook/MailerSendSignedRequestParserTest.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\Bridge\MailerSend\Tests\Webhook; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\Mailer\Bridge\MailerSend\RemoteEvent\MailerSendPayloadConverter; +use Symfony\Component\Mailer\Bridge\MailerSend\Webhook\MailerSendRequestParser; +use Symfony\Component\Webhook\Client\RequestParserInterface; +use Symfony\Component\Webhook\Exception\RejectWebhookException; +use Symfony\Component\Webhook\Test\AbstractRequestParserTestCase; + +class MailerSendSignedRequestParserTest extends AbstractRequestParserTestCase +{ + protected function createRequestParser(): RequestParserInterface + { + return new MailerSendRequestParser(new MailerSendPayloadConverter()); + } + + public static function getPayloads(): iterable + { + $filename = 'sent.json'; + $currentDir = \dirname((new \ReflectionClass(static::class))->getFileName()); + + yield $filename => [ + file_get_contents($currentDir . '/Fixtures/sent.json'), + include($currentDir . '/Fixtures/sent.php'), + ]; + } + + protected function getSecret(): string + { + return 'GvLY88Uyj70jQm3fUwYyWmAaiz98wWim'; + } + + protected function createRequest(string $payload): Request + { + return Request::create( + uri: '/', + method: 'POST', + server: [ + 'Content-Type' => 'application/json', + 'HTTP_Signature' => 'e60f87b019f0aaae29042b14762991765ebb0cd6f6d42884af9fccca4cbd16e7' + ], + content: str_replace("\n", "", $payload) + ); + } +} diff --git a/src/Symfony/Component/Mailer/Bridge/MailerSend/Tests/Webhook/MailerSendWrongSecretRequestParserTest.php b/src/Symfony/Component/Mailer/Bridge/MailerSend/Tests/Webhook/MailerSendWrongSecretRequestParserTest.php new file mode 100644 index 0000000000000..fe64118cba141 --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/MailerSend/Tests/Webhook/MailerSendWrongSecretRequestParserTest.php @@ -0,0 +1,59 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\Bridge\MailerSend\Tests\Webhook; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\Mailer\Bridge\MailerSend\RemoteEvent\MailerSendPayloadConverter; +use Symfony\Component\Mailer\Bridge\MailerSend\Webhook\MailerSendRequestParser; +use Symfony\Component\Webhook\Client\RequestParserInterface; +use Symfony\Component\Webhook\Exception\RejectWebhookException; +use Symfony\Component\Webhook\Test\AbstractRequestParserTestCase; + +class MailerSendWrongSecretRequestParserTest extends AbstractRequestParserTestCase +{ + protected function createRequestParser(): RequestParserInterface + { + $this->expectException(RejectWebhookException::class); + $this->expectExceptionMessage('Signature is wrong.'); + + return new MailerSendRequestParser(new MailerSendPayloadConverter()); + } + + public static function getPayloads(): iterable + { + $filename = 'sent.json'; + $currentDir = \dirname((new \ReflectionClass(static::class))->getFileName()); + + yield $filename => [ + file_get_contents($currentDir . '/Fixtures/sent.json'), + include($currentDir . '/Fixtures/sent.php'), + ]; + } + + protected function getSecret(): string + { + return 'wrong_secret'; + } + + protected function createRequest(string $payload): Request + { + return Request::create( + uri: '/', + method: 'POST', + server: [ + 'Content-Type' => 'application/json', + 'HTTP_Signature' => 'e60f87b019f0aaae29042b14762991765ebb0cd6f6d42884af9fccca4cbd16e7' + ], + content: str_replace("\n", "", $payload) + ); + } +} diff --git a/src/Symfony/Component/Mailer/Bridge/MailerSend/Transport/MailerSendApiTransport.php b/src/Symfony/Component/Mailer/Bridge/MailerSend/Transport/MailerSendApiTransport.php index 7b22c24f71a05..cef9603258cbf 100644 --- a/src/Symfony/Component/Mailer/Bridge/MailerSend/Transport/MailerSendApiTransport.php +++ b/src/Symfony/Component/Mailer/Bridge/MailerSend/Transport/MailerSendApiTransport.php @@ -31,9 +31,9 @@ final class MailerSendApiTransport extends AbstractApiTransport { public function __construct( #[\SensitiveParameter] private string $key, - HttpClientInterface $client = null, - EventDispatcherInterface $dispatcher = null, - LoggerInterface $logger = null, + ?HttpClientInterface $client = null, + ?EventDispatcherInterface $dispatcher = null, + ?LoggerInterface $logger = null, ) { parent::__construct($client, $dispatcher, $logger); } diff --git a/src/Symfony/Component/Mailer/Bridge/MailerSend/Transport/MailerSendSmtpTransport.php b/src/Symfony/Component/Mailer/Bridge/MailerSend/Transport/MailerSendSmtpTransport.php index 2d418fdaf6160..84e2553a627cc 100644 --- a/src/Symfony/Component/Mailer/Bridge/MailerSend/Transport/MailerSendSmtpTransport.php +++ b/src/Symfony/Component/Mailer/Bridge/MailerSend/Transport/MailerSendSmtpTransport.php @@ -20,7 +20,7 @@ */ final class MailerSendSmtpTransport extends EsmtpTransport { - public function __construct(string $username, #[\SensitiveParameter] string $password, EventDispatcherInterface $dispatcher = null, LoggerInterface $logger = null) + public function __construct(string $username, #[\SensitiveParameter] string $password, ?EventDispatcherInterface $dispatcher = null, ?LoggerInterface $logger = null) { parent::__construct('smtp.mailersend.net', 587, true, $dispatcher, $logger); diff --git a/src/Symfony/Component/Mailer/Bridge/MailerSend/Webhook/MailerSendRequestParser.php b/src/Symfony/Component/Mailer/Bridge/MailerSend/Webhook/MailerSendRequestParser.php new file mode 100644 index 0000000000000..959b321da2da7 --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/MailerSend/Webhook/MailerSendRequestParser.php @@ -0,0 +1,75 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\Bridge\MailerSend\Webhook; + +use Symfony\Component\HttpFoundation\ChainRequestMatcher; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestMatcher\IsJsonRequestMatcher; +use Symfony\Component\HttpFoundation\RequestMatcher\MethodRequestMatcher; +use Symfony\Component\HttpFoundation\RequestMatcherInterface; +use Symfony\Component\Mailer\Bridge\MailerSend\RemoteEvent\MailerSendPayloadConverter; +use Symfony\Component\RemoteEvent\Event\Mailer\AbstractMailerEvent; +use Symfony\Component\RemoteEvent\Exception\ParseException; +use Symfony\Component\Webhook\Client\AbstractRequestParser; +use Symfony\Component\Webhook\Exception\RejectWebhookException; + +final class MailerSendRequestParser extends AbstractRequestParser +{ + public function __construct( + private readonly MailerSendPayloadConverter $converter, + ) { + } + + protected function getRequestMatcher(): RequestMatcherInterface + { + return new ChainRequestMatcher([ + new MethodRequestMatcher('POST'), + new IsJsonRequestMatcher(), + ]); + } + + protected function doParse(Request $request, #[\SensitiveParameter] string $secret): ?AbstractMailerEvent + { + if ($secret) { + if (!$request->headers->get('Signature')) { + throw new RejectWebhookException(406, 'Signature is required.'); + } + + $this->validateSignature( + $request->headers->get('Signature'), + $request->getContent(), + $secret, + ); + } + + $content = $request->toArray(); + if (!isset($content['type'], $content['data']['email']['message']['id'], $content['data']['email']['recipient']['email'])) { + throw new RejectWebhookException(406, 'Payload is malformed.'); + } + + try { + return $this->converter->convert($content); + } catch (ParseException $e) { + throw new RejectWebhookException(406, $e->getMessage(), $e); + } + } + + private function validateSignature(string $signature, string $payload, #[\SensitiveParameter] string $secret): void + { + // see https://developers.mailersend.com/api/v1/webhooks.html#security + $computedSignature = hash_hmac('sha256', $payload, $secret); + + if (!hash_equals($signature, $computedSignature)) { + throw new RejectWebhookException(406, 'Signature is wrong.'); + } + } +} diff --git a/src/Symfony/Component/Mailer/Bridge/MailerSend/composer.json b/src/Symfony/Component/Mailer/Bridge/MailerSend/composer.json index 144c50ac44149..4a9b3946f2723 100644 --- a/src/Symfony/Component/Mailer/Bridge/MailerSend/composer.json +++ b/src/Symfony/Component/Mailer/Bridge/MailerSend/composer.json @@ -24,7 +24,8 @@ "symfony/mailer": "^6.4|^7.0" }, "require-dev": { - "symfony/http-client": "^6.4|^7.0" + "symfony/http-client": "^6.4|^7.0", + "symfony/webhook": "^6.3|^7.0" }, "autoload": { "psr-4": { "Symfony\\Component\\Mailer\\Bridge\\MailerSend\\": "" }, diff --git a/src/Symfony/Component/Mailer/Bridge/Mailgun/RemoteEvent/MailgunPayloadConverter.php b/src/Symfony/Component/Mailer/Bridge/Mailgun/RemoteEvent/MailgunPayloadConverter.php index cb3f323861030..02e04104ac6b9 100644 --- a/src/Symfony/Component/Mailer/Bridge/Mailgun/RemoteEvent/MailgunPayloadConverter.php +++ b/src/Symfony/Component/Mailer/Bridge/Mailgun/RemoteEvent/MailgunPayloadConverter.php @@ -75,7 +75,7 @@ private function matchFailedEvent(array $payload): string private function getReason(array $payload): string { - if ('' !== $payload['delivery-status']['description']) { + if ('' !== ($payload['delivery-status']['description'] ?? '')) { return $payload['delivery-status']['description']; } if ('' !== $payload['delivery-status']['message']) { diff --git a/src/Symfony/Component/Mailer/Bridge/Mailgun/Tests/Transport/MailgunApiTransportTest.php b/src/Symfony/Component/Mailer/Bridge/Mailgun/Tests/Transport/MailgunApiTransportTest.php index 0cf6084937441..4e4ab66140447 100644 --- a/src/Symfony/Component/Mailer/Bridge/Mailgun/Tests/Transport/MailgunApiTransportTest.php +++ b/src/Symfony/Component/Mailer/Bridge/Mailgun/Tests/Transport/MailgunApiTransportTest.php @@ -268,4 +268,17 @@ public function testTagAndMetadataHeaders() $this->assertArrayHasKey('v:Client-ID', $payload); $this->assertSame('12345', $payload['v:Client-ID']); } + + public function testEnvelopeSenderHeaderIsCorrectlyEncoded() + { + $email = new Email(); + $envelope = new Envelope(new Address('alice@system.com', 'Žluťoučký Kůň'), [new Address('bob@system.com')]); + + $transport = new MailgunApiTransport('ACCESS_KEY', 'DOMAIN'); + $method = new \ReflectionMethod(MailgunApiTransport::class, 'getPayload'); + $payload = $method->invoke($transport, $email, $envelope); + + $this->assertArrayHasKey('h:Sender', $payload); + $this->assertSame('=?utf-8?Q?=C5=BDlu=C5=A5ou=C4=8Dk=C3=BD_K=C5=AF=C5=88?= ', $payload['h:Sender']); + } } diff --git a/src/Symfony/Component/Mailer/Bridge/Mailgun/Tests/Webhook/Fixtures/permanent_failure_no_description.json b/src/Symfony/Component/Mailer/Bridge/Mailgun/Tests/Webhook/Fixtures/permanent_failure_no_description.json new file mode 100644 index 0000000000000..6f613a0efa3d3 --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/Mailgun/Tests/Webhook/Fixtures/permanent_failure_no_description.json @@ -0,0 +1,58 @@ +{ + "signature": { + "token": "fff0ebc59c0516f4ce0823212a2f45e5c67814420b99fe558b", + "timestamp": "1661590666", + "signature": "a0cd251821d8fd56a2130b541becc8e441023574e602fe9829a9ab9fbfdea33f" + }, + "event-data": { + "id": "G9Bn5sl1TC6nu79C8C0bwg", + "timestamp": 1521233195.375624, + "log-level": "error", + "event": "failed", + "severity": "permanent", + "reason": "bounce", + "delivery-status": { + "attempt-no": 1, + "message": "No Such User Here", + "code": 550, + "enhanced-code": "", + "session-seconds": 0 + }, + "flags": { + "is-routed": false, + "is-authenticated": true, + "is-system-test": false, + "is-test-mode": false + }, + "envelope": { + "sender": "bob@app.symfony.com", + "transport": "smtp", + "targets": "alice@example.com" + }, + "message": { + "headers": { + "to": "Alice ", + "message-id": "20130503192659.13651.20287@app.symfony.com", + "from": "Bob ", + "subject": "Test permanent_fail webhook" + }, + "attachments": [], + "size": 111 + }, + "recipient": "alice@example.com", + "recipient-domain": "example.com", + "storage": { + "url": "https://se.api.mailgun.net/v3/domains/app.symfony.com/messages/message_key", + "key": "message_key" + }, + "campaigns": [], + "tags": [ + "my_tag_1", + "my_tag_2" + ], + "user-variables": { + "my_var_1": "Mailgun Variable #1", + "my-var-2": "awesome" + } + } +} diff --git a/src/Symfony/Component/Mailer/Bridge/Mailgun/Tests/Webhook/Fixtures/permanent_failure_no_description.php b/src/Symfony/Component/Mailer/Bridge/Mailgun/Tests/Webhook/Fixtures/permanent_failure_no_description.php new file mode 100644 index 0000000000000..8dfffd0362e85 --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/Mailgun/Tests/Webhook/Fixtures/permanent_failure_no_description.php @@ -0,0 +1,12 @@ +setRecipientEmail('alice@example.com'); +$wh->setTags(['my_tag_1', 'my_tag_2']); +$wh->setMetadata(['my_var_1' => 'Mailgun Variable #1', 'my-var-2' => 'awesome']); +$wh->setDate(\DateTimeImmutable::createFromFormat('U.u', '1521233195.375624')); +$wh->setReason('No Such User Here'); + +return $wh; diff --git a/src/Symfony/Component/Mailer/Bridge/Mailgun/Transport/MailgunApiTransport.php b/src/Symfony/Component/Mailer/Bridge/Mailgun/Transport/MailgunApiTransport.php index c66fc525b9627..5e59aa46c46ae 100644 --- a/src/Symfony/Component/Mailer/Bridge/Mailgun/Transport/MailgunApiTransport.php +++ b/src/Symfony/Component/Mailer/Bridge/Mailgun/Transport/MailgunApiTransport.php @@ -37,9 +37,9 @@ public function __construct( #[\SensitiveParameter] private string $key, private string $domain, private ?string $region = null, - HttpClientInterface $client = null, - EventDispatcherInterface $dispatcher = null, - LoggerInterface $logger = null, + ?HttpClientInterface $client = null, + ?EventDispatcherInterface $dispatcher = null, + ?LoggerInterface $logger = null, ) { parent::__construct($client, $dispatcher, $logger); } @@ -85,7 +85,7 @@ protected function doSendApi(SentMessage $sentMessage, Email $email, Envelope $e private function getPayload(Email $email, Envelope $envelope): array { $headers = $email->getHeaders(); - $headers->addHeader('h:Sender', $envelope->getSender()->toString()); + $headers->addMailboxHeader('h:Sender', $envelope->getSender()); $html = $email->getHtmlBody(); if (null !== $html && \is_resource($html)) { if (stream_get_meta_data($html)['seekable'] ?? false) { diff --git a/src/Symfony/Component/Mailer/Bridge/Mailgun/Transport/MailgunHeadersTrait.php b/src/Symfony/Component/Mailer/Bridge/Mailgun/Transport/MailgunHeadersTrait.php index 9d1603960e74e..be27e4b69cc93 100644 --- a/src/Symfony/Component/Mailer/Bridge/Mailgun/Transport/MailgunHeadersTrait.php +++ b/src/Symfony/Component/Mailer/Bridge/Mailgun/Transport/MailgunHeadersTrait.php @@ -23,7 +23,7 @@ */ trait MailgunHeadersTrait { - public function send(RawMessage $message, Envelope $envelope = null): ?SentMessage + public function send(RawMessage $message, ?Envelope $envelope = null): ?SentMessage { if ($message instanceof Message) { $this->addMailgunHeaders($message); diff --git a/src/Symfony/Component/Mailer/Bridge/Mailgun/Transport/MailgunHttpTransport.php b/src/Symfony/Component/Mailer/Bridge/Mailgun/Transport/MailgunHttpTransport.php index 921dd94b8d16d..ca0535b6e37d1 100644 --- a/src/Symfony/Component/Mailer/Bridge/Mailgun/Transport/MailgunHttpTransport.php +++ b/src/Symfony/Component/Mailer/Bridge/Mailgun/Transport/MailgunHttpTransport.php @@ -36,9 +36,9 @@ public function __construct( #[\SensitiveParameter] private string $key, private string $domain, private ?string $region = null, - HttpClientInterface $client = null, - EventDispatcherInterface $dispatcher = null, - LoggerInterface $logger = null, + ?HttpClientInterface $client = null, + ?EventDispatcherInterface $dispatcher = null, + ?LoggerInterface $logger = null, ) { parent::__construct($client, $dispatcher, $logger); } diff --git a/src/Symfony/Component/Mailer/Bridge/Mailgun/Transport/MailgunSmtpTransport.php b/src/Symfony/Component/Mailer/Bridge/Mailgun/Transport/MailgunSmtpTransport.php index 27a38d0bbf3f6..89accebad12bb 100644 --- a/src/Symfony/Component/Mailer/Bridge/Mailgun/Transport/MailgunSmtpTransport.php +++ b/src/Symfony/Component/Mailer/Bridge/Mailgun/Transport/MailgunSmtpTransport.php @@ -22,7 +22,7 @@ class MailgunSmtpTransport extends EsmtpTransport { use MailgunHeadersTrait; - public function __construct(string $username, #[\SensitiveParameter] string $password, string $region = null, EventDispatcherInterface $dispatcher = null, LoggerInterface $logger = null) + public function __construct(string $username, #[\SensitiveParameter] string $password, ?string $region = null, ?EventDispatcherInterface $dispatcher = null, ?LoggerInterface $logger = null) { parent::__construct('us' !== ($region ?: 'us') ? sprintf('smtp.%s.mailgun.org', $region) : 'smtp.mailgun.org', 587, false, $dispatcher, $logger); diff --git a/src/Symfony/Component/Mailer/Bridge/Mailjet/Transport/MailjetApiTransport.php b/src/Symfony/Component/Mailer/Bridge/Mailjet/Transport/MailjetApiTransport.php index dcb4eb1154c22..9dee384135b69 100644 --- a/src/Symfony/Component/Mailer/Bridge/Mailjet/Transport/MailjetApiTransport.php +++ b/src/Symfony/Component/Mailer/Bridge/Mailjet/Transport/MailjetApiTransport.php @@ -54,9 +54,9 @@ class MailjetApiTransport extends AbstractApiTransport public function __construct( private string $publicKey, #[\SensitiveParameter] private string $privateKey, - HttpClientInterface $client = null, - EventDispatcherInterface $dispatcher = null, - LoggerInterface $logger = null, + ?HttpClientInterface $client = null, + ?EventDispatcherInterface $dispatcher = null, + ?LoggerInterface $logger = null, private bool $sandbox = false, ) { parent::__construct($client, $dispatcher, $logger); diff --git a/src/Symfony/Component/Mailer/Bridge/Mailjet/Transport/MailjetSmtpTransport.php b/src/Symfony/Component/Mailer/Bridge/Mailjet/Transport/MailjetSmtpTransport.php index 92fa41106f171..831c51b398b45 100644 --- a/src/Symfony/Component/Mailer/Bridge/Mailjet/Transport/MailjetSmtpTransport.php +++ b/src/Symfony/Component/Mailer/Bridge/Mailjet/Transport/MailjetSmtpTransport.php @@ -17,7 +17,7 @@ class MailjetSmtpTransport extends EsmtpTransport { - public function __construct(string $username, #[\SensitiveParameter] string $password, EventDispatcherInterface $dispatcher = null, LoggerInterface $logger = null) + public function __construct(string $username, #[\SensitiveParameter] string $password, ?EventDispatcherInterface $dispatcher = null, ?LoggerInterface $logger = null) { parent::__construct('in-v3.mailjet.com', 587, false, $dispatcher, $logger); diff --git a/src/Symfony/Component/Mailer/Bridge/Postmark/Transport/PostmarkApiTransport.php b/src/Symfony/Component/Mailer/Bridge/Postmark/Transport/PostmarkApiTransport.php index bcb76922d179e..550f40aac7f54 100644 --- a/src/Symfony/Component/Mailer/Bridge/Postmark/Transport/PostmarkApiTransport.php +++ b/src/Symfony/Component/Mailer/Bridge/Postmark/Transport/PostmarkApiTransport.php @@ -41,9 +41,9 @@ class PostmarkApiTransport extends AbstractApiTransport public function __construct( #[\SensitiveParameter] private string $key, - HttpClientInterface $client = null, - EventDispatcherInterface $dispatcher = null, - LoggerInterface $logger = null, + ?HttpClientInterface $client = null, + ?EventDispatcherInterface $dispatcher = null, + ?LoggerInterface $logger = null, ) { $this->dispatcher = $dispatcher; diff --git a/src/Symfony/Component/Mailer/Bridge/Postmark/Transport/PostmarkSmtpTransport.php b/src/Symfony/Component/Mailer/Bridge/Postmark/Transport/PostmarkSmtpTransport.php index 5896500704375..30ed8c81d89ba 100644 --- a/src/Symfony/Component/Mailer/Bridge/Postmark/Transport/PostmarkSmtpTransport.php +++ b/src/Symfony/Component/Mailer/Bridge/Postmark/Transport/PostmarkSmtpTransport.php @@ -29,7 +29,7 @@ class PostmarkSmtpTransport extends EsmtpTransport { private ?string $messageStream = null; - public function __construct(#[\SensitiveParameter] string $id, EventDispatcherInterface $dispatcher = null, LoggerInterface $logger = null) + public function __construct(#[\SensitiveParameter] string $id, ?EventDispatcherInterface $dispatcher = null, ?LoggerInterface $logger = null) { parent::__construct('smtp.postmarkapp.com', 587, false, $dispatcher, $logger); @@ -37,7 +37,7 @@ public function __construct(#[\SensitiveParameter] string $id, EventDispatcherIn $this->setPassword($id); } - public function send(RawMessage $message, Envelope $envelope = null): ?SentMessage + public function send(RawMessage $message, ?Envelope $envelope = null): ?SentMessage { if ($message instanceof Message) { $this->addPostmarkHeaders($message); diff --git a/src/Symfony/Component/Mailer/Bridge/Resend/.gitattributes b/src/Symfony/Component/Mailer/Bridge/Resend/.gitattributes new file mode 100644 index 0000000000000..84c7add058fb5 --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/Resend/.gitattributes @@ -0,0 +1,4 @@ +/Tests export-ignore +/phpunit.xml.dist export-ignore +/.gitattributes export-ignore +/.gitignore export-ignore diff --git a/src/Symfony/Component/Mailer/Bridge/Resend/.gitignore b/src/Symfony/Component/Mailer/Bridge/Resend/.gitignore new file mode 100644 index 0000000000000..c49a5d8df5c65 --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/Resend/.gitignore @@ -0,0 +1,3 @@ +vendor/ +composer.lock +phpunit.xml diff --git a/src/Symfony/Component/Mailer/Bridge/Resend/CHANGELOG.md b/src/Symfony/Component/Mailer/Bridge/Resend/CHANGELOG.md new file mode 100644 index 0000000000000..5be39cbeeb951 --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/Resend/CHANGELOG.md @@ -0,0 +1,7 @@ +CHANGELOG +========= + +7.1 +--- + + * Add the bridge diff --git a/src/Symfony/Component/Mailer/Bridge/Resend/LICENSE b/src/Symfony/Component/Mailer/Bridge/Resend/LICENSE new file mode 100644 index 0000000000000..e374a5c8339d3 --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/Resend/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2024-present Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/src/Symfony/Component/Mailer/Bridge/Resend/README.md b/src/Symfony/Component/Mailer/Bridge/Resend/README.md new file mode 100644 index 0000000000000..1c45166636412 --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/Resend/README.md @@ -0,0 +1,25 @@ +Resend Bridge +============= + +Provides Resend integration for Symfony Mailer. + +Configuration example: + +```env +# SMTP +MAILER_DSN=resend+smtp://resend:API_KEY@default + +# API +MAILER_DSN=resend+api://API_KEY@default +``` + +where: + - `API_KEY` is your Resend API Key + +Resources +--------- + + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) diff --git a/src/Symfony/Component/Mailer/Bridge/Resend/RemoteEvent/ResendPayloadConverter.php b/src/Symfony/Component/Mailer/Bridge/Resend/RemoteEvent/ResendPayloadConverter.php new file mode 100644 index 0000000000000..3579e7b3d6ec3 --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/Resend/RemoteEvent/ResendPayloadConverter.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\Bridge\Resend\RemoteEvent; + +use Symfony\Component\RemoteEvent\Event\Mailer\AbstractMailerEvent; +use Symfony\Component\RemoteEvent\Event\Mailer\MailerDeliveryEvent; +use Symfony\Component\RemoteEvent\Event\Mailer\MailerEngagementEvent; +use Symfony\Component\RemoteEvent\Exception\ParseException; +use Symfony\Component\RemoteEvent\PayloadConverterInterface; + +final class ResendPayloadConverter implements PayloadConverterInterface +{ + public function convert(array $payload): AbstractMailerEvent + { + if (\in_array($payload['type'], ['email.sent', 'email.delivered', 'email.delivery_delayed', 'email.bounced'], true)) { + $name = match ($payload['type']) { + 'email.sent' => MailerDeliveryEvent::RECEIVED, + 'email.delivered' => MailerDeliveryEvent::DELIVERED, + 'email.delivery_delayed' => MailerDeliveryEvent::DEFERRED, + 'email.bounced' => MailerDeliveryEvent::BOUNCE, + }; + + $event = new MailerDeliveryEvent($name, $payload['data']['email_id'], $payload); + } else { + $name = match ($payload['type']) { + 'email.clicked' => MailerEngagementEvent::CLICK, + 'email.opened' => MailerEngagementEvent::OPEN, + 'email.complained' => MailerEngagementEvent::SPAM, + default => throw new ParseException(sprintf('Unsupported event "%s".', $payload['type'])), + }; + $event = new MailerEngagementEvent($name, $payload['data']['email_id'], $payload); + } + + if (!$date = \DateTimeImmutable::createFromFormat('Y-m-d\TH:i:s.uP', $payload['created_at'])) { + throw new ParseException(sprintf('Invalid date "%s".', $payload['created_at'])); + } + + $event->setDate($date); + $event->setRecipientEmail(implode(', ', $payload['data']['to'])); + $event->setMetadata($payload['data']); + + return $event; + } +} diff --git a/src/Symfony/Component/Mailer/Bridge/Resend/Tests/ResendApiTransportTest.php b/src/Symfony/Component/Mailer/Bridge/Resend/Tests/ResendApiTransportTest.php new file mode 100644 index 0000000000000..1cdadd3df95c9 --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/Resend/Tests/ResendApiTransportTest.php @@ -0,0 +1,179 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\Bridge\Resend\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpClient\MockHttpClient; +use Symfony\Component\HttpClient\Response\JsonMockResponse; +use Symfony\Component\Mailer\Bridge\Resend\Transport\ResendApiTransport; +use Symfony\Component\Mailer\Envelope; +use Symfony\Component\Mailer\Exception\HttpTransportException; +use Symfony\Component\Mailer\Header\MetadataHeader; +use Symfony\Component\Mailer\Header\TagHeader; +use Symfony\Component\Mime\Address; +use Symfony\Component\Mime\Email; +use Symfony\Component\Mime\Part\DataPart; +use Symfony\Contracts\HttpClient\ResponseInterface; + +class ResendApiTransportTest extends TestCase +{ + /** + * @dataProvider getTransportData + */ + public function testToString(ResendApiTransport $transport, string $expected) + { + $this->assertSame($expected, (string) $transport); + } + + public static function getTransportData(): \Generator + { + yield [ + new ResendApiTransport('ACCESS_KEY'), + 'resend+api://api.resend.com', + ]; + + yield [ + (new ResendApiTransport('ACCESS_KEY'))->setHost('example.com'), + 'resend+api://example.com', + ]; + + yield [ + (new ResendApiTransport('ACCESS_KEY'))->setHost('example.com')->setPort(99), + 'resend+api://example.com:99', + ]; + } + + public function testCustomHeader() + { + $params = ['param1' => 'foo', 'param2' => 'bar']; + $json = json_encode(['custom_header_1' => 'custom_value_1']); + + $email = new Email(); + $email->getHeaders() + ->add(new MetadataHeader('custom', $json)) + ->add(new TagHeader('TagInHeaders')) + ->addTextHeader('templateId', 1) + ->addParameterizedHeader('params', 'params', $params) + ->addTextHeader('foo', 'bar'); + $envelope = new Envelope(new Address('alice@system.com', 'Alice'), [new Address('bob@system.com', 'Bob')]); + + $transport = new ResendApiTransport('ACCESS_KEY'); + $method = new \ReflectionMethod(ResendApiTransport::class, 'getPayload'); + $payload = $method->invoke($transport, $email, $envelope); + + $this->assertArrayHasKey('X-Metadata-custom', $payload['headers']); + $this->assertEquals($json, $payload['headers']['X-Metadata-custom']); + $this->assertArrayHasKey('tags', $payload); + $this->assertEquals(['X-Tag' => 'TagInHeaders'], current($payload['tags'])); + $this->assertArrayHasKey('templateId', $payload['headers']); + $this->assertEquals('1', $payload['headers']['templateId']); + $this->assertArrayHasKey('params', $payload['headers']); + $this->assertEquals('params; param1=foo; param2=bar', $payload['headers']['params']); + $this->assertArrayHasKey('foo', $payload['headers']); + $this->assertEquals('bar', $payload['headers']['foo']); + } + + public function testSendThrowsForErrorResponse() + { + $client = new MockHttpClient(function (string $method, string $url, array $options): ResponseInterface { + $this->assertSame('POST', $method); + $this->assertSame('https://api.resend.com:8984/emails', $url); + $this->assertStringContainsString('Accept: */*', $options['headers'][2] ?? $options['request_headers'][1]); + + return new JsonMockResponse(['message' => 'i\'m a teapot'], [ + 'http_code' => 418, + ]); + }); + + $transport = new ResendApiTransport('ACCESS_KEY', $client); + $transport->setPort(8984); + + $mail = new Email(); + $mail->subject('Hello!') + ->to(new Address('tony.stark@marvel.com', 'Tony Stark')) + ->from(new Address('fabpot@symfony.com', 'Fabien')) + ->text('Hello There!'); + + $this->expectException(HttpTransportException::class); + $this->expectExceptionMessage('Unable to send an email: {"message":"i\'m a teapot"} (code 418).'); + $transport->send($mail); + } + + public function testSend() + { + $client = new MockHttpClient(function (string $method, string $url, array $options): ResponseInterface { + $this->assertSame('POST', $method); + $this->assertSame('https://api.resend.com:8984/emails', $url); + $this->assertStringContainsString('Accept: */*', $options['headers'][2] ?? $options['request_headers'][1]); + + return new JsonMockResponse(['id' => 'foobar'], [ + 'http_code' => 200, + ]); + }); + + $transport = new ResendApiTransport('ACCESS_KEY', $client); + $transport->setPort(8984); + + $mail = new Email(); + $mail->subject('Hello!') + ->to(new Address('tony.stark@marvel.com', 'Tony Stark')) + ->from(new Address('fabpot@symfony.com', 'Fabien')) + ->text('Hello here!') + ->html('Hello there!') + ->addCc('foo@bar.fr') + ->addBcc('foo@bar.fr') + ->addReplyTo('foo@bar.fr') + ->addPart(new DataPart('body')); + + $message = $transport->send($mail); + + $this->assertSame('foobar', $message->getMessageId()); + } + + /** + * IDN (internationalized domain names) like kältetechnik-xyz.de need to be transformed to ACE + * (ASCII Compatible Encoding) e.g.xn--kltetechnik-xyz-0kb.de, otherwise resend api answers with 400 http code. + */ + public function testSendForIdnDomains() + { + $client = new MockHttpClient(function (string $method, string $url, array $options): ResponseInterface { + $this->assertSame('POST', $method); + $this->assertSame('https://api.resend.com:8984/emails', $url); + $this->assertStringContainsString('Accept: */*', $options['headers'][2] ?? $options['request_headers'][1]); + + $body = json_decode($options['body'], true); + // to + $this->assertSame('kältetechnik@xn--kltetechnik-xyz-0kb.de', $body['to'][0]); + // sender + $this->assertStringContainsString('info@xn--kltetechnik-xyz-0kb.de', $body['from']); + $this->assertStringContainsString('Kältetechnik Xyz', $body['from']); + + return new JsonMockResponse(['id' => 'foobar'], [ + 'http_code' => 200, + ]); + }); + + $transport = new ResendApiTransport('ACCESS_KEY', $client); + $transport->setPort(8984); + + $mail = new Email(); + $mail->subject('Hello!') + ->to(new Address('kältetechnik@kältetechnik-xyz.de', 'Kältetechnik Xyz')) + ->from(new Address('info@kältetechnik-xyz.de', 'Kältetechnik Xyz')) + ->text('Hello here!') + ->html('Hello there!'); + + $message = $transport->send($mail); + + $this->assertSame('foobar', $message->getMessageId()); + } +} diff --git a/src/Symfony/Component/Mailer/Bridge/Resend/Tests/ResendTransportFactoryTest.php b/src/Symfony/Component/Mailer/Bridge/Resend/Tests/ResendTransportFactoryTest.php new file mode 100644 index 0000000000000..56c7ea3921c7b --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/Resend/Tests/ResendTransportFactoryTest.php @@ -0,0 +1,90 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\Bridge\Resend\Tests; + +use Psr\Log\NullLogger; +use Symfony\Component\HttpClient\MockHttpClient; +use Symfony\Component\Mailer\Bridge\Resend\Transport\ResendApiTransport; +use Symfony\Component\Mailer\Bridge\Resend\Transport\ResendSmtpTransport; +use Symfony\Component\Mailer\Bridge\Resend\Transport\ResendTransportFactory; +use Symfony\Component\Mailer\Test\TransportFactoryTestCase; +use Symfony\Component\Mailer\Transport\Dsn; +use Symfony\Component\Mailer\Transport\TransportFactoryInterface; + +class ResendTransportFactoryTest extends TransportFactoryTestCase +{ + public function getFactory(): TransportFactoryInterface + { + return new ResendTransportFactory(null, new MockHttpClient(), new NullLogger()); + } + + public static function supportsProvider(): iterable + { + yield [ + new Dsn('resend', 'default'), + true, + ]; + + yield [ + new Dsn('resend+smtp', 'default'), + true, + ]; + + yield [ + new Dsn('resend+smtp', 'example.com'), + true, + ]; + + yield [ + new Dsn('resend+api', 'default'), + true, + ]; + } + + public static function createProvider(): iterable + { + yield [ + new Dsn('resend', 'default', self::USER, self::PASSWORD), + new ResendSmtpTransport(self::PASSWORD, null, new NullLogger()), + ]; + + yield [ + new Dsn('resend+smtp', 'default', self::USER, self::PASSWORD), + new ResendSmtpTransport(self::PASSWORD, null, new NullLogger()), + ]; + + yield [ + new Dsn('resend+smtp', 'default', self::USER, self::PASSWORD, 465), + new ResendSmtpTransport(self::PASSWORD, null, new NullLogger()), + ]; + + yield [ + new Dsn('resend+api', 'default', self::USER), + new ResendApiTransport(self::USER, new MockHttpClient(), null, new NullLogger()), + ]; + } + + public static function unsupportedSchemeProvider(): iterable + { + yield [ + new Dsn('resend+foo', 'default', self::USER, self::PASSWORD), + 'The "resend+foo" scheme is not supported; supported schemes for mailer "resend" are: "resend", "resend+smtp", "resend+api".', + ]; + } + + public static function incompleteDsnProvider(): iterable + { + yield [new Dsn('resend+smtp', 'default', self::USER)]; + + yield [new Dsn('resend+api', 'default')]; + } +} diff --git a/src/Symfony/Component/Mailer/Bridge/Resend/Transport/ResendApiTransport.php b/src/Symfony/Component/Mailer/Bridge/Resend/Transport/ResendApiTransport.php new file mode 100644 index 0000000000000..8e9b7a94d08ba --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/Resend/Transport/ResendApiTransport.php @@ -0,0 +1,176 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\Bridge\Resend\Transport; + +use Psr\EventDispatcher\EventDispatcherInterface; +use Psr\Log\LoggerInterface; +use Symfony\Component\Mailer\Envelope; +use Symfony\Component\Mailer\Exception\HttpTransportException; +use Symfony\Component\Mailer\Exception\InvalidArgumentException; +use Symfony\Component\Mailer\Header\TagHeader; +use Symfony\Component\Mailer\SentMessage; +use Symfony\Component\Mailer\Transport\AbstractApiTransport; +use Symfony\Component\Mime\Address; +use Symfony\Component\Mime\Email; +use Symfony\Component\Mime\Header\Headers; +use Symfony\Contracts\HttpClient\Exception\DecodingExceptionInterface; +use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface; +use Symfony\Contracts\HttpClient\HttpClientInterface; +use Symfony\Contracts\HttpClient\ResponseInterface; + +/** + * @author Mathieu Santostefano + */ +final class ResendApiTransport extends AbstractApiTransport +{ + public function __construct( + #[\SensitiveParameter] private readonly string $apiKey, + ?HttpClientInterface $client = null, + ?EventDispatcherInterface $dispatcher = null, + ?LoggerInterface $logger = null, + ) { + parent::__construct($client, $dispatcher, $logger); + } + + public function __toString(): string + { + return sprintf('resend+api://%s', $this->getEndpoint()); + } + + protected function doSendApi(SentMessage $sentMessage, Email $email, Envelope $envelope): ResponseInterface + { + $response = $this->client->request('POST', 'https://'.$this->getEndpoint().'/emails', [ + 'json' => $this->getPayload($email, $envelope), + 'headers' => [ + 'Authorization' => 'Bearer '.$this->apiKey, + ], + ]); + + try { + $statusCode = $response->getStatusCode(); + $result = $response->toArray(false); + } catch (DecodingExceptionInterface) { + throw new HttpTransportException('Unable to send an email: '.$response->getContent(false).sprintf(' (code %d).', $statusCode), $response); + } catch (TransportExceptionInterface $e) { + throw new HttpTransportException('Could not reach the remote Resend server.', $response, 0, $e); + } + + if (200 !== $statusCode) { + throw new HttpTransportException('Unable to send an email: '.$response->getContent(false).sprintf(' (code %d).', $statusCode), $response); + } + + $sentMessage->setMessageId($result['id']); + + return $response; + } + + /** + * @param Address[] $addresses + * + * @return list + */ + private function formatAddresses(array $addresses): array + { + $formattedAddresses = []; + foreach ($addresses as $address) { + $formattedAddresses[] = $address->getEncodedAddress(); + } + + if (\count($formattedAddresses) > 50) { + throw new InvalidArgumentException('Resend API does not support more than 50 recipients.'); + } + + return $formattedAddresses; + } + + private function getPayload(Email $email, Envelope $envelope): array + { + $payload = [ + 'from' => $this->formatAddress($envelope->getSender()), + 'to' => $this->formatAddresses($this->getRecipients($email, $envelope)), + 'subject' => $email->getSubject(), + ]; + if ($attachements = $this->prepareAttachments($email)) { + $payload['attachments'] = $attachements; + } + if ($emails = $email->getReplyTo()) { + $payload['reply_to'] = current($this->formatAddresses($emails)); + } + if ($emails = $email->getCc()) { + $payload['cc'] = $this->formatAddresses($emails); + } + if ($emails = $email->getBcc()) { + $payload['bcc'] = $this->formatAddresses($emails); + } + if ($email->getTextBody()) { + $payload['text'] = $email->getTextBody(); + } + if ($email->getHtmlBody()) { + $payload['html'] = $email->getHtmlBody(); + } + if ($headersAndTags = $this->prepareHeadersAndTags($email->getHeaders())) { + $payload = array_merge($payload, $headersAndTags); + } + + return $payload; + } + + private function prepareAttachments(Email $email): array + { + $attachments = []; + foreach ($email->getAttachments() as $attachment) { + $attachments[] = [ + 'filename' => $attachment->getPreparedHeaders()->getHeaderParameter('Content-Disposition', 'filename'), + 'content' => str_replace("\r\n", '', $attachment->bodyToString()), + ]; + } + + return $attachments; + } + + private function prepareHeadersAndTags(Headers $headers): array + { + $headersAndTags = []; + $headersToBypass = ['from', 'to', 'cc', 'bcc', 'subject', 'reply_to']; + foreach ($headers->all() as $name => $header) { + if (\in_array($name, $headersToBypass, true)) { + continue; + } + + if ($header instanceof TagHeader) { + $headersAndTags['tags'][] = [$header->getName() => $header->getValue()]; + + continue; + } + + $headersAndTags['headers'][$header->getName()] = $header->getBodyAsString(); + } + + return $headersAndTags; + } + + private function formatAddress(Address $address): string + { + $formattedAddress = $address->getEncodedAddress(); + + if ($address->getName()) { + $formattedAddress = $address->getName().' <'.$formattedAddress.'>'; + } + + return $formattedAddress; + } + + private function getEndpoint(): ?string + { + return ($this->host ?: 'api.resend.com').($this->port ? ':'.$this->port : ''); + } +} diff --git a/src/Symfony/Component/Mailer/Bridge/Resend/Transport/ResendSmtpTransport.php b/src/Symfony/Component/Mailer/Bridge/Resend/Transport/ResendSmtpTransport.php new file mode 100644 index 0000000000000..f99dbc1f0b3e2 --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/Resend/Transport/ResendSmtpTransport.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\Mailer\Bridge\Resend\Transport; + +use Psr\EventDispatcher\EventDispatcherInterface; +use Psr\Log\LoggerInterface; +use Symfony\Component\Mailer\Transport\Smtp\EsmtpTransport; + +/** + * @author Mathieu Santostefano + */ +final class ResendSmtpTransport extends EsmtpTransport +{ + public function __construct(#[\SensitiveParameter] string $password, ?EventDispatcherInterface $dispatcher = null, ?LoggerInterface $logger = null) + { + parent::__construct('smtp.resend.com', 465, true, $dispatcher, $logger); + + $this->setUsername('resend'); + $this->setPassword($password); + } +} diff --git a/src/Symfony/Component/Mailer/Bridge/Resend/Transport/ResendTransportFactory.php b/src/Symfony/Component/Mailer/Bridge/Resend/Transport/ResendTransportFactory.php new file mode 100644 index 0000000000000..eba0ba0fa0a76 --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/Resend/Transport/ResendTransportFactory.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\Bridge\Resend\Transport; + +use Symfony\Component\Mailer\Exception\UnsupportedSchemeException; +use Symfony\Component\Mailer\Transport\AbstractTransportFactory; +use Symfony\Component\Mailer\Transport\Dsn; +use Symfony\Component\Mailer\Transport\TransportInterface; + +/** + * @author Mathieu Santostefano + */ +final class ResendTransportFactory extends AbstractTransportFactory +{ + public function create(Dsn $dsn): TransportInterface + { + return match ($dsn->getScheme()) { + 'resend', 'resend+smtp' => new ResendSmtpTransport($this->getPassword($dsn), $this->dispatcher, $this->logger), + 'resend+api' => (new ResendApiTransport($this->getUser($dsn), $this->client, $this->dispatcher, $this->logger)) + ->setHost('default' === $dsn->getHost() ? null : $dsn->getHost()) + ->setPort($dsn->getPort()), + default => throw new UnsupportedSchemeException($dsn, 'resend', $this->getSupportedSchemes()), + }; + } + + protected function getSupportedSchemes(): array + { + return ['resend', 'resend+smtp', 'resend+api']; + } +} diff --git a/src/Symfony/Component/Mailer/Bridge/Resend/Webhook/ResendRequestParser.php b/src/Symfony/Component/Mailer/Bridge/Resend/Webhook/ResendRequestParser.php new file mode 100644 index 0000000000000..b5ed40f1d85cd --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/Resend/Webhook/ResendRequestParser.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\Bridge\Resend\Webhook; + +use Symfony\Component\HttpFoundation\ChainRequestMatcher; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestMatcher\IsJsonRequestMatcher; +use Symfony\Component\HttpFoundation\RequestMatcher\MethodRequestMatcher; +use Symfony\Component\HttpFoundation\RequestMatcher\SchemeRequestMatcher; +use Symfony\Component\HttpFoundation\RequestMatcherInterface; +use Symfony\Component\Mailer\Bridge\Resend\RemoteEvent\ResendPayloadConverter; +use Symfony\Component\RemoteEvent\Event\Mailer\AbstractMailerEvent; +use Symfony\Component\RemoteEvent\Exception\ParseException; +use Symfony\Component\Webhook\Client\AbstractRequestParser; +use Symfony\Component\Webhook\Exception\RejectWebhookException; + +final class ResendRequestParser extends AbstractRequestParser +{ + public function __construct( + private readonly ResendPayloadConverter $converter, + ) { + } + + protected function getRequestMatcher(): RequestMatcherInterface + { + return new ChainRequestMatcher([ + new MethodRequestMatcher('POST'), + new SchemeRequestMatcher('https'), + new IsJsonRequestMatcher(), + ]); + } + + protected function doParse(Request $request, #[\SensitiveParameter] string $secret): ?AbstractMailerEvent + { + $content = $request->toArray(); + if ( + !isset($content['type']) + || !isset($content['created_at']) + || !isset($content['data']) + || !isset($content['data']['created_at']) + || !isset($content['data']['email_id']) + || !isset($content['data']['from']) + || !isset($content['data']['to']) + || !isset($content['data']['subject']) + ) { + throw new RejectWebhookException(406, 'Payload is malformed.'); + } + + try { + return $this->converter->convert($content); + } catch (ParseException $e) { + throw new RejectWebhookException(406, $e->getMessage(), $e); + } + } +} diff --git a/src/Symfony/Component/Mailer/Bridge/Resend/composer.json b/src/Symfony/Component/Mailer/Bridge/Resend/composer.json new file mode 100644 index 0000000000000..4c3d69ae96b6b --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/Resend/composer.json @@ -0,0 +1,33 @@ +{ + "name": "symfony/resend-mailer", + "type": "symfony-mailer-bridge", + "description": "Symfony Resend Mailer Bridge", + "keywords": [], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Mathieu Santostefano", + "homepage": "https://github.com/welcoMattic" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=8.1", + "symfony/mailer": "^6.4|^7.0" + }, + "require-dev": { + "symfony/http-client": "^6.4|^7.0", + "symfony/webhook": "^6.4|^7.0" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\Mailer\\Bridge\\Resend\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev" +} diff --git a/src/Symfony/Component/Mailer/Bridge/Resend/phpunit.xml.dist b/src/Symfony/Component/Mailer/Bridge/Resend/phpunit.xml.dist new file mode 100644 index 0000000000000..bd5f5f35442ec --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/Resend/phpunit.xml.dist @@ -0,0 +1,31 @@ + + + + + + + + + + ./Tests/ + + + + + + ./ + + + ./Resources + ./Tests + ./vendor + + + diff --git a/src/Symfony/Component/Mailer/Bridge/Scaleway/Tests/Transport/ScalewayApiTransportTest.php b/src/Symfony/Component/Mailer/Bridge/Scaleway/Tests/Transport/ScalewayApiTransportTest.php index 74387d234d148..0d91d002cd21c 100644 --- a/src/Symfony/Component/Mailer/Bridge/Scaleway/Tests/Transport/ScalewayApiTransportTest.php +++ b/src/Symfony/Component/Mailer/Bridge/Scaleway/Tests/Transport/ScalewayApiTransportTest.php @@ -18,6 +18,7 @@ use Symfony\Component\Mailer\Exception\HttpTransportException; use Symfony\Component\Mime\Address; use Symfony\Component\Mime\Email; +use Symfony\Component\Mime\Part\DataPart; use Symfony\Contracts\HttpClient\ResponseInterface; class ScalewayApiTransportTest extends TestCase @@ -64,6 +65,10 @@ public function testSend() $this->assertSame(['email' => 'saif.gmati@symfony.com', 'name' => 'Saif Eddin'], $body['to'][0]); $this->assertSame('Hello!', $body['subject']); $this->assertSame('Hello There!', $body['text']); + $this->assertCount(1, $body['attachments']); + $this->assertSame('attachment.txt', $body['attachments'][0]['name']); + $this->assertSame('text/plain', $body['attachments'][0]['type']); + $this->assertSame(base64_encode('some attachment'), $body['attachments'][0]['content']); return new JsonMockResponse(['emails' => [['message_id' => 'foobar']]], [ 'http_code' => 200, @@ -76,7 +81,8 @@ public function testSend() $mail->subject('Hello!') ->to(new Address('saif.gmati@symfony.com', 'Saif Eddin')) ->from(new Address('fabpot@symfony.com', 'Fabien')) - ->text('Hello There!'); + ->text('Hello There!') + ->addPart(new DataPart('some attachment', 'attachment.txt', 'text/plain')); $message = $transport->send($mail); diff --git a/src/Symfony/Component/Mailer/Bridge/Scaleway/Transport/ScalewayApiTransport.php b/src/Symfony/Component/Mailer/Bridge/Scaleway/Transport/ScalewayApiTransport.php index 39e8b97b660ca..e3c64082588ea 100644 --- a/src/Symfony/Component/Mailer/Bridge/Scaleway/Transport/ScalewayApiTransport.php +++ b/src/Symfony/Component/Mailer/Bridge/Scaleway/Transport/ScalewayApiTransport.php @@ -32,9 +32,9 @@ public function __construct( private string $projectId, #[\SensitiveParameter] private string $token, private ?string $region = null, - HttpClientInterface $client = null, - EventDispatcherInterface $dispatcher = null, - LoggerInterface $logger = null, + ?HttpClientInterface $client = null, + ?EventDispatcherInterface $dispatcher = null, + ?LoggerInterface $logger = null, ) { parent::__construct($client, $dispatcher, $logger); } @@ -97,7 +97,7 @@ private function getPayload(Email $email, Envelope $envelope): array $payload['html'] = $email->getHtmlBody(); } if ($attachements = $this->prepareAttachments($email)) { - $payload['attachment'] = $attachements; + $payload['attachments'] = $attachements; } return $payload; @@ -113,7 +113,7 @@ private function prepareAttachments(Email $email): array $attachments[] = [ 'name' => $filename, 'type' => $headers->get('Content-Type')->getBody(), - 'content' => base64_encode($attachment->bodyToString()), + 'content' => str_replace("\r\n", '', $attachment->bodyToString()), ]; } diff --git a/src/Symfony/Component/Mailer/Bridge/Scaleway/Transport/ScalewaySmtpTransport.php b/src/Symfony/Component/Mailer/Bridge/Scaleway/Transport/ScalewaySmtpTransport.php index 441595b20a8f0..729b8a2bd69b8 100644 --- a/src/Symfony/Component/Mailer/Bridge/Scaleway/Transport/ScalewaySmtpTransport.php +++ b/src/Symfony/Component/Mailer/Bridge/Scaleway/Transport/ScalewaySmtpTransport.php @@ -17,7 +17,7 @@ final class ScalewaySmtpTransport extends EsmtpTransport { - public function __construct(string $projetId, #[\SensitiveParameter] string $token, EventDispatcherInterface $dispatcher = null, LoggerInterface $logger = null) + public function __construct(string $projetId, #[\SensitiveParameter] string $token, ?EventDispatcherInterface $dispatcher = null, ?LoggerInterface $logger = null) { parent::__construct('smtp.tem.scw.cloud', 465, true, $dispatcher, $logger); diff --git a/src/Symfony/Component/Mailer/Bridge/Sendgrid/Transport/SendgridApiTransport.php b/src/Symfony/Component/Mailer/Bridge/Sendgrid/Transport/SendgridApiTransport.php index e5869ebde1d06..c1ee972d44b7c 100644 --- a/src/Symfony/Component/Mailer/Bridge/Sendgrid/Transport/SendgridApiTransport.php +++ b/src/Symfony/Component/Mailer/Bridge/Sendgrid/Transport/SendgridApiTransport.php @@ -36,9 +36,9 @@ class SendgridApiTransport extends AbstractApiTransport public function __construct( #[\SensitiveParameter] private string $key, - HttpClientInterface $client = null, - EventDispatcherInterface $dispatcher = null, - LoggerInterface $logger = null, + ?HttpClientInterface $client = null, + ?EventDispatcherInterface $dispatcher = null, + ?LoggerInterface $logger = null, ) { parent::__construct($client, $dispatcher, $logger); } diff --git a/src/Symfony/Component/Mailer/Bridge/Sendgrid/Transport/SendgridSmtpTransport.php b/src/Symfony/Component/Mailer/Bridge/Sendgrid/Transport/SendgridSmtpTransport.php index 5e0ce5a3b982c..647e202fa0073 100644 --- a/src/Symfony/Component/Mailer/Bridge/Sendgrid/Transport/SendgridSmtpTransport.php +++ b/src/Symfony/Component/Mailer/Bridge/Sendgrid/Transport/SendgridSmtpTransport.php @@ -20,7 +20,7 @@ */ class SendgridSmtpTransport extends EsmtpTransport { - public function __construct(#[\SensitiveParameter] string $key, EventDispatcherInterface $dispatcher = null, LoggerInterface $logger = null) + public function __construct(#[\SensitiveParameter] string $key, ?EventDispatcherInterface $dispatcher = null, ?LoggerInterface $logger = null) { parent::__construct('smtp.sendgrid.net', 465, true, $dispatcher, $logger); diff --git a/src/Symfony/Component/Mailer/CHANGELOG.md b/src/Symfony/Component/Mailer/CHANGELOG.md index d58cf3d832353..53b03a0d70fd5 100644 --- a/src/Symfony/Component/Mailer/CHANGELOG.md +++ b/src/Symfony/Component/Mailer/CHANGELOG.md @@ -5,6 +5,7 @@ CHANGELOG --- * Dispatch Postmark's "406 - Inactive recipient" API error code as a `PostmarkDeliveryEvent` instead of throwing an exception + * Add DSN param `auto_tls` to disable automatic STARTTLS 7.0 --- diff --git a/src/Symfony/Component/Mailer/DataCollector/MessageDataCollector.php b/src/Symfony/Component/Mailer/DataCollector/MessageDataCollector.php index 86c1c962dfcea..5c9ac35443075 100644 --- a/src/Symfony/Component/Mailer/DataCollector/MessageDataCollector.php +++ b/src/Symfony/Component/Mailer/DataCollector/MessageDataCollector.php @@ -29,7 +29,7 @@ public function __construct(MessageLoggerListener $logger) $this->events = $logger->getEvents(); } - public function collect(Request $request, Response $response, \Throwable $exception = null): void + public function collect(Request $request, Response $response, ?\Throwable $exception = null): void { $this->data['events'] = $this->events; } diff --git a/src/Symfony/Component/Mailer/Event/MessageEvents.php b/src/Symfony/Component/Mailer/Event/MessageEvents.php index df870f9458a17..2b438e382a4d9 100644 --- a/src/Symfony/Component/Mailer/Event/MessageEvents.php +++ b/src/Symfony/Component/Mailer/Event/MessageEvents.php @@ -42,7 +42,7 @@ public function getTransports(): array /** * @return MessageEvent[] */ - public function getEvents(string $name = null): array + public function getEvents(?string $name = null): array { if (null === $name) { return $this->events; @@ -61,7 +61,7 @@ public function getEvents(string $name = null): array /** * @return RawMessage[] */ - public function getMessages(string $name = null): array + public function getMessages(?string $name = null): array { $events = $this->getEvents($name); $messages = []; diff --git a/src/Symfony/Component/Mailer/EventListener/EnvelopeListener.php b/src/Symfony/Component/Mailer/EventListener/EnvelopeListener.php index d47a688ca9d25..db9dd723adee3 100644 --- a/src/Symfony/Component/Mailer/EventListener/EnvelopeListener.php +++ b/src/Symfony/Component/Mailer/EventListener/EnvelopeListener.php @@ -33,7 +33,7 @@ class EnvelopeListener implements EventSubscriberInterface /** * @param array $recipients */ - public function __construct(Address|string $sender = null, array $recipients = null) + public function __construct(Address|string|null $sender = null, ?array $recipients = null) { if (null !== $sender) { $this->sender = Address::create($sender); diff --git a/src/Symfony/Component/Mailer/EventListener/MessageListener.php b/src/Symfony/Component/Mailer/EventListener/MessageListener.php index 951b5a4d81aba..ec822d9c6d99c 100644 --- a/src/Symfony/Component/Mailer/EventListener/MessageListener.php +++ b/src/Symfony/Component/Mailer/EventListener/MessageListener.php @@ -43,7 +43,7 @@ class MessageListener implements EventSubscriberInterface private array $headerRules = []; private ?BodyRendererInterface $renderer; - public function __construct(Headers $headers = null, BodyRendererInterface $renderer = null, array $headerRules = self::DEFAULT_RULES) + public function __construct(?Headers $headers = null, ?BodyRendererInterface $renderer = null, array $headerRules = self::DEFAULT_RULES) { $this->headers = $headers; $this->renderer = $renderer; diff --git a/src/Symfony/Component/Mailer/Exception/HttpTransportException.php b/src/Symfony/Component/Mailer/Exception/HttpTransportException.php index 4c97211db397c..ad10910f3786c 100644 --- a/src/Symfony/Component/Mailer/Exception/HttpTransportException.php +++ b/src/Symfony/Component/Mailer/Exception/HttpTransportException.php @@ -20,7 +20,7 @@ class HttpTransportException extends TransportException { private ResponseInterface $response; - public function __construct(string $message, ResponseInterface $response, int $code = 0, \Throwable $previous = null) + public function __construct(string $message, ResponseInterface $response, int $code = 0, ?\Throwable $previous = null) { parent::__construct($message, $code, $previous); diff --git a/src/Symfony/Component/Mailer/Exception/UnexpectedResponseException.php b/src/Symfony/Component/Mailer/Exception/UnexpectedResponseException.php new file mode 100644 index 0000000000000..e779df122c9f3 --- /dev/null +++ b/src/Symfony/Component/Mailer/Exception/UnexpectedResponseException.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\Exception; + +class UnexpectedResponseException extends TransportException +{ +} diff --git a/src/Symfony/Component/Mailer/Exception/UnsupportedSchemeException.php b/src/Symfony/Component/Mailer/Exception/UnsupportedSchemeException.php index 0c1cac5d0001e..5ac0d3d730623 100644 --- a/src/Symfony/Component/Mailer/Exception/UnsupportedSchemeException.php +++ b/src/Symfony/Component/Mailer/Exception/UnsupportedSchemeException.php @@ -20,6 +20,10 @@ class UnsupportedSchemeException extends LogicException { private const SCHEME_TO_PACKAGE_MAP = [ + 'azure' => [ + 'class' => Bridge\Azure\Transport\AzureTransportFactory::class, + 'package' => 'symfony/azure-mailer', + ], 'brevo' => [ 'class' => Bridge\Brevo\Transport\BrevoTransportFactory::class, 'package' => 'symfony/brevo-mailer', @@ -56,6 +60,10 @@ class UnsupportedSchemeException extends LogicException 'class' => Bridge\Postmark\Transport\PostmarkTransportFactory::class, 'package' => 'symfony/postmark-mailer', ], + 'resend' => [ + 'class' => Bridge\Resend\Transport\ResendTransportFactory::class, + 'package' => 'symfony/resend-mailer', + ], 'scaleway' => [ 'class' => Bridge\Scaleway\Transport\ScalewayTransportFactory::class, 'package' => 'symfony/scaleway-mailer', @@ -70,7 +78,7 @@ class UnsupportedSchemeException extends LogicException ], ]; - public function __construct(Dsn $dsn, string $name = null, array $supported = []) + public function __construct(Dsn $dsn, ?string $name = null, array $supported = []) { $provider = $dsn->getScheme(); if (false !== $pos = strpos($provider, '+')) { diff --git a/src/Symfony/Component/Mailer/Mailer.php b/src/Symfony/Component/Mailer/Mailer.php index 5319dbef81320..6df99091a2fde 100644 --- a/src/Symfony/Component/Mailer/Mailer.php +++ b/src/Symfony/Component/Mailer/Mailer.php @@ -29,14 +29,14 @@ final class Mailer implements MailerInterface private ?MessageBusInterface $bus; private ?EventDispatcherInterface $dispatcher; - public function __construct(TransportInterface $transport, MessageBusInterface $bus = null, EventDispatcherInterface $dispatcher = null) + public function __construct(TransportInterface $transport, ?MessageBusInterface $bus = null, ?EventDispatcherInterface $dispatcher = null) { $this->transport = $transport; $this->bus = $bus; $this->dispatcher = $dispatcher; } - public function send(RawMessage $message, Envelope $envelope = null): void + public function send(RawMessage $message, ?Envelope $envelope = null): void { if (null === $this->bus) { $this->transport->send($message, $envelope); diff --git a/src/Symfony/Component/Mailer/MailerInterface.php b/src/Symfony/Component/Mailer/MailerInterface.php index eb44cf640c263..8d9540a3e5e3f 100644 --- a/src/Symfony/Component/Mailer/MailerInterface.php +++ b/src/Symfony/Component/Mailer/MailerInterface.php @@ -26,5 +26,5 @@ interface MailerInterface /** * @throws TransportExceptionInterface */ - public function send(RawMessage $message, Envelope $envelope = null): void; + public function send(RawMessage $message, ?Envelope $envelope = null): void; } diff --git a/src/Symfony/Component/Mailer/Messenger/SendEmailMessage.php b/src/Symfony/Component/Mailer/Messenger/SendEmailMessage.php index 1af5c7b3c728b..18bd506201e3c 100644 --- a/src/Symfony/Component/Mailer/Messenger/SendEmailMessage.php +++ b/src/Symfony/Component/Mailer/Messenger/SendEmailMessage.php @@ -22,7 +22,7 @@ class SendEmailMessage private RawMessage $message; private ?Envelope $envelope; - public function __construct(RawMessage $message, Envelope $envelope = null) + public function __construct(RawMessage $message, ?Envelope $envelope = null) { $this->message = $message; $this->envelope = $envelope; diff --git a/src/Symfony/Component/Mailer/Test/Constraint/EmailCount.php b/src/Symfony/Component/Mailer/Test/Constraint/EmailCount.php index 57103ffe33700..2ba80f25025bc 100644 --- a/src/Symfony/Component/Mailer/Test/Constraint/EmailCount.php +++ b/src/Symfony/Component/Mailer/Test/Constraint/EmailCount.php @@ -20,7 +20,7 @@ final class EmailCount extends Constraint private ?string $transport; private bool $queued; - public function __construct(int $expectedValue, string $transport = null, bool $queued = false) + public function __construct(int $expectedValue, ?string $transport = null, bool $queued = false) { $this->expectedValue = $expectedValue; $this->transport = $transport; diff --git a/src/Symfony/Component/Mailer/Test/TransportFactoryTestCase.php b/src/Symfony/Component/Mailer/Test/TransportFactoryTestCase.php index c6bd432ab4361..2ca7db9bc84b1 100644 --- a/src/Symfony/Component/Mailer/Test/TransportFactoryTestCase.php +++ b/src/Symfony/Component/Mailer/Test/TransportFactoryTestCase.php @@ -77,7 +77,7 @@ public function testCreate(Dsn $dsn, TransportInterface $transport) /** * @dataProvider unsupportedSchemeProvider */ - public function testUnsupportedSchemeException(Dsn $dsn, string $message = null) + public function testUnsupportedSchemeException(Dsn $dsn, ?string $message = null) { $factory = $this->getFactory(); diff --git a/src/Symfony/Component/Mailer/Tests/Exception/UnsupportedSchemeExceptionTest.php b/src/Symfony/Component/Mailer/Tests/Exception/UnsupportedSchemeExceptionTest.php index 215eb069c9eb3..273197646d319 100644 --- a/src/Symfony/Component/Mailer/Tests/Exception/UnsupportedSchemeExceptionTest.php +++ b/src/Symfony/Component/Mailer/Tests/Exception/UnsupportedSchemeExceptionTest.php @@ -14,6 +14,7 @@ use PHPUnit\Framework\TestCase; use Symfony\Bridge\PhpUnit\ClassExistsMock; use Symfony\Component\Mailer\Bridge\Amazon\Transport\SesTransportFactory; +use Symfony\Component\Mailer\Bridge\Azure\Transport\AzureTransportFactory; use Symfony\Component\Mailer\Bridge\Brevo\Transport\BrevoTransportFactory; use Symfony\Component\Mailer\Bridge\Google\Transport\GmailTransportFactory; use Symfony\Component\Mailer\Bridge\Infobip\Transport\InfobipTransportFactory; @@ -23,6 +24,7 @@ use Symfony\Component\Mailer\Bridge\Mailjet\Transport\MailjetTransportFactory; use Symfony\Component\Mailer\Bridge\MailPace\Transport\MailPaceTransportFactory; use Symfony\Component\Mailer\Bridge\Postmark\Transport\PostmarkTransportFactory; +use Symfony\Component\Mailer\Bridge\Resend\Transport\ResendTransportFactory; use Symfony\Component\Mailer\Bridge\Scaleway\Transport\ScalewayTransportFactory; use Symfony\Component\Mailer\Bridge\Sendgrid\Transport\SendgridTransportFactory; use Symfony\Component\Mailer\Exception\UnsupportedSchemeException; @@ -37,6 +39,7 @@ public static function setUpBeforeClass(): void { ClassExistsMock::register(__CLASS__); ClassExistsMock::withMockedClasses([ + AzureTransportFactory::class => false, BrevoTransportFactory::class => false, GmailTransportFactory::class => false, InfobipTransportFactory::class => false, @@ -46,6 +49,7 @@ public static function setUpBeforeClass(): void MailjetTransportFactory::class => false, MandrillTransportFactory::class => false, PostmarkTransportFactory::class => false, + ResendTransportFactory::class => false, ScalewayTransportFactory::class => false, SendgridTransportFactory::class => false, SesTransportFactory::class => false, @@ -67,6 +71,7 @@ public function testMessageWhereSchemeIsPartOfSchemeToPackageMap(string $scheme, public static function messageWhereSchemeIsPartOfSchemeToPackageMapProvider(): \Generator { + yield ['azure', 'symfony/azure-mailer']; yield ['brevo', 'symfony/brevo-mailer']; yield ['gmail', 'symfony/google-mailer']; yield ['infobip', 'symfony/infobip-mailer']; @@ -76,6 +81,7 @@ public static function messageWhereSchemeIsPartOfSchemeToPackageMapProvider(): \ yield ['mailpace', 'symfony/mail-pace-mailer']; yield ['mandrill', 'symfony/mailchimp-mailer']; yield ['postmark', 'symfony/postmark-mailer']; + yield ['resend', 'symfony/resend-mailer']; yield ['scaleway', 'symfony/scaleway-mailer']; yield ['sendgrid', 'symfony/sendgrid-mailer']; yield ['ses', 'symfony/amazon-mailer']; diff --git a/src/Symfony/Component/Mailer/Tests/Transport/Smtp/DummyStream.php b/src/Symfony/Component/Mailer/Tests/Transport/Smtp/DummyStream.php index 8f2032e8a54d8..407c90810b78b 100644 --- a/src/Symfony/Component/Mailer/Tests/Transport/Smtp/DummyStream.php +++ b/src/Symfony/Component/Mailer/Tests/Transport/Smtp/DummyStream.php @@ -62,6 +62,8 @@ public function write(string $bytes, $debug = true): void $this->nextResponse = '334 UGFzc3dvcmQ6'; } elseif (str_starts_with($bytes, 'cDRzc3cwcmQ=')) { $this->nextResponse = '535 5.7.139 Authentication unsuccessful'; + } elseif (str_starts_with($bytes, 'dGltZWRvdXQ=')) { + throw new TransportException('Connection to "localhost" timed out.'); } elseif (str_starts_with($bytes, 'AUTH CRAM-MD5')) { $this->nextResponse = '334 PDAxMjM0NTY3ODkuMDEyMzQ1NjdAc3ltZm9ueT4='; } elseif (str_starts_with($bytes, 'dGVzdHVzZXIgNTdlYzg2ODM5OWZhZThjY2M5OWFhZGVjZjhiZTAwNmY=')) { diff --git a/src/Symfony/Component/Mailer/Tests/Transport/Smtp/EsmtpTransportFactoryTest.php b/src/Symfony/Component/Mailer/Tests/Transport/Smtp/EsmtpTransportFactoryTest.php index c2868ccbd8e99..442583a7af179 100644 --- a/src/Symfony/Component/Mailer/Tests/Transport/Smtp/EsmtpTransportFactoryTest.php +++ b/src/Symfony/Component/Mailer/Tests/Transport/Smtp/EsmtpTransportFactoryTest.php @@ -157,5 +157,28 @@ public static function createProvider(): iterable new Dsn('smtps', 'example.com', '', '', 465, ['ping_threshold' => '10']), $transport, ]; + + $transport = new EsmtpTransport('example.com', 25, false, null, $logger); + $transport->setAutoTls(false); + + yield [ + new Dsn('smtp', 'example.com', '', '', 25, ['auto_tls' => false]), + $transport, + ]; + yield [ + new Dsn('smtp', 'example.com', '', '', 0, ['auto_tls' => false]), + $transport, + ]; + yield [ + Dsn::fromString('smtp://:@example.com?auto_tls=false'), + $transport, + ]; + + $transport = new EsmtpTransport('example.com', 465, false, null, $logger); + $transport->setAutoTls(false); + yield [ + Dsn::fromString('smtp://:@example.com:465?auto_tls=false'), + $transport, + ]; } } diff --git a/src/Symfony/Component/Mailer/Tests/Transport/Smtp/EsmtpTransportTest.php b/src/Symfony/Component/Mailer/Tests/Transport/Smtp/EsmtpTransportTest.php index 8e7832258bd47..977d2a05e5981 100644 --- a/src/Symfony/Component/Mailer/Tests/Transport/Smtp/EsmtpTransportTest.php +++ b/src/Symfony/Component/Mailer/Tests/Transport/Smtp/EsmtpTransportTest.php @@ -214,6 +214,41 @@ public function testConstructorWithEmptyAuthenticator() $this->assertEquals(504, $e->getCode()); } } + + public function testSocketTimeout() + { + $stream = new DummyStream(); + $transport = new EsmtpTransport(stream: $stream); + $transport->setUsername('testuser'); + $transport->setPassword('timedout'); + $transport->setAuthenticators([new LoginAuthenticator()]); + + $message = new Email(); + $message->from('sender@example.org'); + $message->addTo('recipient@example.org'); + $message->text('.'); + + try { + $transport->send($message); + $this->fail('Symfony\Component\Mailer\Exception\TransportException to be thrown'); + } catch (TransportException $e) { + $this->assertStringStartsWith('Connection to "localhost" timed out.', $e->getMessage()); + } + + $this->assertEquals( + [ + "EHLO [127.0.0.1]\r\n", + // S: 250 localhost + // S: 250-AUTH PLAIN LOGIN CRAM-MD5 XOAUTH2 + "AUTH LOGIN\r\n", + // S: 334 VXNlcm5hbWU6 + "dGVzdHVzZXI=\r\n", + // S: 334 UGFzc3dvcmQ6 + "dGltZWRvdXQ=\r\n", + ], + $stream->getCommands() + ); + } } class CustomEsmtpTransport extends EsmtpTransport diff --git a/src/Symfony/Component/Mailer/Tests/TransportTest.php b/src/Symfony/Component/Mailer/Tests/TransportTest.php index f0dd6d4be930e..31ed1e40c338e 100644 --- a/src/Symfony/Component/Mailer/Tests/TransportTest.php +++ b/src/Symfony/Component/Mailer/Tests/TransportTest.php @@ -109,7 +109,7 @@ public function __construct(string $host) $this->host = $host; } - public function send(RawMessage $message, Envelope $envelope = null): ?SentMessage + public function send(RawMessage $message, ?Envelope $envelope = null): ?SentMessage { throw new \BadMethodCallException('This method newer should be called.'); } diff --git a/src/Symfony/Component/Mailer/Transport.php b/src/Symfony/Component/Mailer/Transport.php index fae3adf3ca862..7d8a0edcfe90c 100644 --- a/src/Symfony/Component/Mailer/Transport.php +++ b/src/Symfony/Component/Mailer/Transport.php @@ -22,7 +22,9 @@ use Symfony\Component\Mailer\Bridge\MailerSend\Transport\MailerSendTransportFactory; use Symfony\Component\Mailer\Bridge\Mailgun\Transport\MailgunTransportFactory; use Symfony\Component\Mailer\Bridge\Mailjet\Transport\MailjetTransportFactory; +use Symfony\Component\Mailer\Bridge\MailPace\Transport\MailPaceTransportFactory; use Symfony\Component\Mailer\Bridge\Postmark\Transport\PostmarkTransportFactory; +use Symfony\Component\Mailer\Bridge\Resend\Transport\ResendTransportFactory; use Symfony\Component\Mailer\Bridge\Scaleway\Transport\ScalewayTransportFactory; use Symfony\Component\Mailer\Bridge\Sendgrid\Transport\SendgridTransportFactory; use Symfony\Component\Mailer\Exception\InvalidArgumentException; @@ -53,8 +55,10 @@ final class Transport MailerSendTransportFactory::class, MailgunTransportFactory::class, MailjetTransportFactory::class, + MailPaceTransportFactory::class, MandrillTransportFactory::class, PostmarkTransportFactory::class, + ResendTransportFactory::class, ScalewayTransportFactory::class, SendgridTransportFactory::class, SesTransportFactory::class, @@ -62,14 +66,14 @@ final class Transport private iterable $factories; - public static function fromDsn(#[\SensitiveParameter] string $dsn, EventDispatcherInterface $dispatcher = null, HttpClientInterface $client = null, LoggerInterface $logger = null): TransportInterface + public static function fromDsn(#[\SensitiveParameter] string $dsn, ?EventDispatcherInterface $dispatcher = null, ?HttpClientInterface $client = null, ?LoggerInterface $logger = null): TransportInterface { $factory = new self(iterator_to_array(self::getDefaultFactories($dispatcher, $client, $logger))); return $factory->fromString($dsn); } - public static function fromDsns(#[\SensitiveParameter] array $dsns, EventDispatcherInterface $dispatcher = null, HttpClientInterface $client = null, LoggerInterface $logger = null): TransportInterface + public static function fromDsns(#[\SensitiveParameter] array $dsns, ?EventDispatcherInterface $dispatcher = null, ?HttpClientInterface $client = null, ?LoggerInterface $logger = null): TransportInterface { $factory = new self(iterator_to_array(self::getDefaultFactories($dispatcher, $client, $logger))); @@ -165,7 +169,7 @@ public function fromDsnObject(Dsn $dsn): TransportInterface /** * @return \Traversable */ - public static function getDefaultFactories(EventDispatcherInterface $dispatcher = null, HttpClientInterface $client = null, LoggerInterface $logger = null): \Traversable + public static function getDefaultFactories(?EventDispatcherInterface $dispatcher = null, ?HttpClientInterface $client = null, ?LoggerInterface $logger = null): \Traversable { foreach (self::FACTORY_CLASSES as $factoryClass) { if (class_exists($factoryClass)) { diff --git a/src/Symfony/Component/Mailer/Transport/AbstractHttpTransport.php b/src/Symfony/Component/Mailer/Transport/AbstractHttpTransport.php index bd868233cdbdb..1ae1b94419d68 100644 --- a/src/Symfony/Component/Mailer/Transport/AbstractHttpTransport.php +++ b/src/Symfony/Component/Mailer/Transport/AbstractHttpTransport.php @@ -28,7 +28,7 @@ abstract class AbstractHttpTransport extends AbstractTransport protected ?int $port = null; protected ?HttpClientInterface $client; - public function __construct(HttpClientInterface $client = null, EventDispatcherInterface $dispatcher = null, LoggerInterface $logger = null) + public function __construct(?HttpClientInterface $client = null, ?EventDispatcherInterface $dispatcher = null, ?LoggerInterface $logger = null) { $this->client = $client; if (null === $client) { diff --git a/src/Symfony/Component/Mailer/Transport/AbstractTransport.php b/src/Symfony/Component/Mailer/Transport/AbstractTransport.php index ba1f97129f7ef..9a1cd6859953e 100644 --- a/src/Symfony/Component/Mailer/Transport/AbstractTransport.php +++ b/src/Symfony/Component/Mailer/Transport/AbstractTransport.php @@ -35,7 +35,7 @@ abstract class AbstractTransport implements TransportInterface private float $rate = 0; private float $lastSent = 0; - public function __construct(EventDispatcherInterface $dispatcher = null, LoggerInterface $logger = null) + public function __construct(?EventDispatcherInterface $dispatcher = null, ?LoggerInterface $logger = null) { $this->dispatcher = $dispatcher; $this->logger = $logger ?? new NullLogger(); @@ -58,7 +58,7 @@ public function setMaxPerSecond(float $rate): static return $this; } - public function send(RawMessage $message, Envelope $envelope = null): ?SentMessage + public function send(RawMessage $message, ?Envelope $envelope = null): ?SentMessage { $message = clone $message; $envelope = null !== $envelope ? clone $envelope : Envelope::create($message); diff --git a/src/Symfony/Component/Mailer/Transport/AbstractTransportFactory.php b/src/Symfony/Component/Mailer/Transport/AbstractTransportFactory.php index 469841031a474..1e6b794542d8c 100644 --- a/src/Symfony/Component/Mailer/Transport/AbstractTransportFactory.php +++ b/src/Symfony/Component/Mailer/Transport/AbstractTransportFactory.php @@ -25,7 +25,7 @@ abstract class AbstractTransportFactory implements TransportFactoryInterface protected ?HttpClientInterface $client; protected ?LoggerInterface $logger; - public function __construct(EventDispatcherInterface $dispatcher = null, HttpClientInterface $client = null, LoggerInterface $logger = null) + public function __construct(?EventDispatcherInterface $dispatcher = null, ?HttpClientInterface $client = null, ?LoggerInterface $logger = null) { $this->dispatcher = $dispatcher; $this->client = $client; diff --git a/src/Symfony/Component/Mailer/Transport/Dsn.php b/src/Symfony/Component/Mailer/Transport/Dsn.php index 9cb8ac4f4e832..0cff6aad18603 100644 --- a/src/Symfony/Component/Mailer/Transport/Dsn.php +++ b/src/Symfony/Component/Mailer/Transport/Dsn.php @@ -25,7 +25,7 @@ final class Dsn private ?int $port; private array $options; - public function __construct(string $scheme, string $host, string $user = null, #[\SensitiveParameter] string $password = null, int $port = null, array $options = []) + public function __construct(string $scheme, string $host, ?string $user = null, #[\SensitiveParameter] ?string $password = null, ?int $port = null, array $options = []) { $this->scheme = $scheme; $this->host = $host; @@ -77,7 +77,7 @@ public function getPassword(): ?string return $this->password; } - public function getPort(int $default = null): ?int + public function getPort(?int $default = null): ?int { return $this->port ?? $default; } diff --git a/src/Symfony/Component/Mailer/Transport/RoundRobinTransport.php b/src/Symfony/Component/Mailer/Transport/RoundRobinTransport.php index c5587bb2a3585..ac9709bf7b6c4 100644 --- a/src/Symfony/Component/Mailer/Transport/RoundRobinTransport.php +++ b/src/Symfony/Component/Mailer/Transport/RoundRobinTransport.php @@ -46,7 +46,7 @@ public function __construct(array $transports, int $retryPeriod = 60) $this->retryPeriod = $retryPeriod; } - public function send(RawMessage $message, Envelope $envelope = null): ?SentMessage + public function send(RawMessage $message, ?Envelope $envelope = null): ?SentMessage { $exception = null; diff --git a/src/Symfony/Component/Mailer/Transport/SendmailTransport.php b/src/Symfony/Component/Mailer/Transport/SendmailTransport.php index c5296339c8c0f..36bb93802effb 100644 --- a/src/Symfony/Component/Mailer/Transport/SendmailTransport.php +++ b/src/Symfony/Component/Mailer/Transport/SendmailTransport.php @@ -49,7 +49,7 @@ class SendmailTransport extends AbstractTransport * * -f flag will be appended automatically if one is not present. */ - public function __construct(string $command = null, EventDispatcherInterface $dispatcher = null, LoggerInterface $logger = null) + public function __construct(?string $command = null, ?EventDispatcherInterface $dispatcher = null, ?LoggerInterface $logger = null) { parent::__construct($dispatcher, $logger); @@ -68,7 +68,7 @@ public function __construct(string $command = null, EventDispatcherInterface $di } } - public function send(RawMessage $message, Envelope $envelope = null): ?SentMessage + public function send(RawMessage $message, ?Envelope $envelope = null): ?SentMessage { if ($this->transport) { return $this->transport->send($message, $envelope); diff --git a/src/Symfony/Component/Mailer/Transport/Smtp/EsmtpTransport.php b/src/Symfony/Component/Mailer/Transport/Smtp/EsmtpTransport.php index 2683050c9cb22..b341d3411c0ce 100644 --- a/src/Symfony/Component/Mailer/Transport/Smtp/EsmtpTransport.php +++ b/src/Symfony/Component/Mailer/Transport/Smtp/EsmtpTransport.php @@ -15,6 +15,7 @@ use Psr\Log\LoggerInterface; use Symfony\Component\Mailer\Exception\TransportException; use Symfony\Component\Mailer\Exception\TransportExceptionInterface; +use Symfony\Component\Mailer\Exception\UnexpectedResponseException; use Symfony\Component\Mailer\Transport\Smtp\Auth\AuthenticatorInterface; use Symfony\Component\Mailer\Transport\Smtp\Stream\AbstractStream; use Symfony\Component\Mailer\Transport\Smtp\Stream\SocketStream; @@ -31,8 +32,9 @@ class EsmtpTransport extends SmtpTransport private string $username = ''; private string $password = ''; private array $capabilities; + private bool $autoTls = true; - public function __construct(string $host = 'localhost', int $port = 0, bool $tls = null, EventDispatcherInterface $dispatcher = null, LoggerInterface $logger = null, AbstractStream $stream = null, array $authenticators = null) + public function __construct(string $host = 'localhost', int $port = 0, ?bool $tls = null, ?EventDispatcherInterface $dispatcher = null, ?LoggerInterface $logger = null, ?AbstractStream $stream = null, ?array $authenticators = null) { parent::__construct($stream, $dispatcher, $logger); @@ -99,6 +101,21 @@ public function getPassword(): string return $this->password; } + /** + * @return $this + */ + public function setAutoTls(bool $autoTls): static + { + $this->autoTls = $autoTls; + + return $this; + } + + public function isAutoTls(): bool + { + return $this->autoTls; + } + public function setAuthenticators(array $authenticators): void { $this->authenticators = []; @@ -145,7 +162,7 @@ private function doEhloCommand(): string // WARNING: !$stream->isTLS() is right, 100% sure :) // if you think that the ! should be removed, read the code again // if doing so "fixes" your issue then it probably means your SMTP server behaves incorrectly or is wrongly configured - if (!$stream->isTLS() && \defined('OPENSSL_VERSION_NUMBER') && \array_key_exists('STARTTLS', $this->capabilities)) { + if ($this->autoTls && !$stream->isTLS() && \defined('OPENSSL_VERSION_NUMBER') && \array_key_exists('STARTTLS', $this->capabilities)) { $this->executeCommand("STARTTLS\r\n", [220]); if (!$stream->startTLS()) { @@ -199,7 +216,7 @@ private function handleAuth(array $modes): void $authenticator->authenticate($this); return; - } catch (TransportExceptionInterface $e) { + } catch (UnexpectedResponseException $e) { $code = $e->getCode(); try { diff --git a/src/Symfony/Component/Mailer/Transport/Smtp/EsmtpTransportFactory.php b/src/Symfony/Component/Mailer/Transport/Smtp/EsmtpTransportFactory.php index a15d12245d19b..9df0b957451be 100644 --- a/src/Symfony/Component/Mailer/Transport/Smtp/EsmtpTransportFactory.php +++ b/src/Symfony/Component/Mailer/Transport/Smtp/EsmtpTransportFactory.php @@ -23,11 +23,13 @@ final class EsmtpTransportFactory extends AbstractTransportFactory { public function create(Dsn $dsn): TransportInterface { - $tls = 'smtps' === $dsn->getScheme() ? true : null; + $autoTls = '' === $dsn->getOption('auto_tls') || filter_var($dsn->getOption('auto_tls', true), \FILTER_VALIDATE_BOOL); + $tls = 'smtps' === $dsn->getScheme() ? true : ($autoTls ? null : false); $port = $dsn->getPort(0); $host = $dsn->getHost(); $transport = new EsmtpTransport($host, $port, $tls, $this->dispatcher, $this->logger); + $transport->setAutoTls($autoTls); /** @var SocketStream $stream */ $stream = $transport->getStream(); diff --git a/src/Symfony/Component/Mailer/Transport/Smtp/SmtpTransport.php b/src/Symfony/Component/Mailer/Transport/Smtp/SmtpTransport.php index a7fc6e77cc9df..b4a10634e91ca 100644 --- a/src/Symfony/Component/Mailer/Transport/Smtp/SmtpTransport.php +++ b/src/Symfony/Component/Mailer/Transport/Smtp/SmtpTransport.php @@ -17,6 +17,7 @@ use Symfony\Component\Mailer\Exception\LogicException; use Symfony\Component\Mailer\Exception\TransportException; use Symfony\Component\Mailer\Exception\TransportExceptionInterface; +use Symfony\Component\Mailer\Exception\UnexpectedResponseException; use Symfony\Component\Mailer\SentMessage; use Symfony\Component\Mailer\Transport\AbstractTransport; use Symfony\Component\Mailer\Transport\Smtp\Stream\AbstractStream; @@ -41,7 +42,7 @@ class SmtpTransport extends AbstractTransport private string $mtaResult = ''; private string $domain = '[127.0.0.1]'; - public function __construct(AbstractStream $stream = null, EventDispatcherInterface $dispatcher = null, LoggerInterface $logger = null) + public function __construct(?AbstractStream $stream = null, ?EventDispatcherInterface $dispatcher = null, ?LoggerInterface $logger = null) { parent::__construct($dispatcher, $logger); @@ -131,7 +132,7 @@ public function getLocalDomain(): string return $this->domain; } - public function send(RawMessage $message, Envelope $envelope = null): ?SentMessage + public function send(RawMessage $message, ?Envelope $envelope = null): ?SentMessage { try { $message = parent::send($message, $envelope); @@ -330,7 +331,7 @@ private function assertResponseCode(string $response, array $codes): void $codeStr = $code ? sprintf('code "%s"', $code) : 'empty code'; $responseStr = $response ? sprintf(', with message "%s"', trim($response)) : ''; - throw new TransportException(sprintf('Expected response code "%s" but got ', implode('/', $codes)).$codeStr.$responseStr.'.', $code ?: 0); + throw new UnexpectedResponseException(sprintf('Expected response code "%s" but got ', implode('/', $codes)).$codeStr.$responseStr.'.', $code ?: 0); } } diff --git a/src/Symfony/Component/Mailer/Transport/Smtp/Stream/AbstractStream.php b/src/Symfony/Component/Mailer/Transport/Smtp/Stream/AbstractStream.php index db84fb0247a1d..62e008164e5cb 100644 --- a/src/Symfony/Component/Mailer/Transport/Smtp/Stream/AbstractStream.php +++ b/src/Symfony/Component/Mailer/Transport/Smtp/Stream/AbstractStream.php @@ -36,8 +36,9 @@ abstract class AbstractStream public function write(string $bytes, bool $debug = true): void { if ($debug) { + $timestamp = date('c'); foreach (explode("\n", trim($bytes)) as $line) { - $this->debug .= sprintf("> %s\n", $line); + $this->debug .= sprintf("[%s] > %s\n", $timestamp, $line); } } @@ -77,7 +78,7 @@ public function readLine(): string return ''; } - $line = fgets($this->out); + $line = @fgets($this->out); if ('' === $line || false === $line) { $metas = stream_get_meta_data($this->out); if ($metas['timed_out']) { @@ -86,9 +87,12 @@ public function readLine(): string if ($metas['eof']) { throw new TransportException(sprintf('Connection to "%s" has been closed unexpectedly.', $this->getReadConnectionDescription())); } + if (false === $line) { + throw new TransportException(sprintf('Unable to read from connection to "%s": ', $this->getReadConnectionDescription()).error_get_last()['message']); + } } - $this->debug .= sprintf('< %s', $line); + $this->debug .= sprintf('[%s] < %s', date('c'), $line); return $line; } diff --git a/src/Symfony/Component/Mailer/Transport/Smtp/Stream/SocketStream.php b/src/Symfony/Component/Mailer/Transport/Smtp/Stream/SocketStream.php index 0f7620763f950..49e6ea4995ee8 100644 --- a/src/Symfony/Component/Mailer/Transport/Smtp/Stream/SocketStream.php +++ b/src/Symfony/Component/Mailer/Transport/Smtp/Stream/SocketStream.php @@ -160,7 +160,7 @@ public function initialize(): void } stream_set_blocking($this->stream, true); - stream_set_timeout($this->stream, $timeout); + stream_set_timeout($this->stream, (int) $timeout, (int) (($timeout - (int) $timeout) * 1000000)); $this->in = &$this->stream; $this->out = &$this->stream; } diff --git a/src/Symfony/Component/Mailer/Transport/TransportInterface.php b/src/Symfony/Component/Mailer/Transport/TransportInterface.php index e9cba03ab12f3..01570cab47183 100644 --- a/src/Symfony/Component/Mailer/Transport/TransportInterface.php +++ b/src/Symfony/Component/Mailer/Transport/TransportInterface.php @@ -29,5 +29,5 @@ interface TransportInterface extends \Stringable /** * @throws TransportExceptionInterface */ - public function send(RawMessage $message, Envelope $envelope = null): ?SentMessage; + public function send(RawMessage $message, ?Envelope $envelope = null): ?SentMessage; } diff --git a/src/Symfony/Component/Mailer/Transport/Transports.php b/src/Symfony/Component/Mailer/Transport/Transports.php index 1beaa883e49ec..f02b9bc9bf856 100644 --- a/src/Symfony/Component/Mailer/Transport/Transports.php +++ b/src/Symfony/Component/Mailer/Transport/Transports.php @@ -44,7 +44,7 @@ public function __construct(iterable $transports) } } - public function send(RawMessage $message, Envelope $envelope = null): ?SentMessage + public function send(RawMessage $message, ?Envelope $envelope = null): ?SentMessage { /** @var Message $message */ if (RawMessage::class === $message::class || !$message->getHeaders()->has('X-Transport')) { diff --git a/src/Symfony/Component/Messenger/Bridge/AmazonSqs/Tests/Transport/AmazonSqsTransportTest.php b/src/Symfony/Component/Messenger/Bridge/AmazonSqs/Tests/Transport/AmazonSqsTransportTest.php index feb547e3f6709..fbe49e2952de1 100644 --- a/src/Symfony/Component/Messenger/Bridge/AmazonSqs/Tests/Transport/AmazonSqsTransportTest.php +++ b/src/Symfony/Component/Messenger/Bridge/AmazonSqs/Tests/Transport/AmazonSqsTransportTest.php @@ -151,7 +151,7 @@ public function testItConvertsHttpExceptionDuringResetIntoTransportException() $this->transport->reset(); } - private function getTransport(SerializerInterface $serializer = null, Connection $connection = null) + private function getTransport(?SerializerInterface $serializer = null, ?Connection $connection = null) { $serializer ??= $this->createMock(SerializerInterface::class); $connection ??= $this->createMock(Connection::class); @@ -162,7 +162,7 @@ private function getTransport(SerializerInterface $serializer = null, Connection private function createHttpException(): HttpException { $response = $this->createMock(ResponseInterface::class); - $response->method('getInfo')->willReturnCallback(static function (string $type = null) { + $response->method('getInfo')->willReturnCallback(static function (?string $type = null) { $info = [ 'http_code' => 500, 'url' => 'https://symfony.com', diff --git a/src/Symfony/Component/Messenger/Bridge/AmazonSqs/Tests/Transport/ConnectionTest.php b/src/Symfony/Component/Messenger/Bridge/AmazonSqs/Tests/Transport/ConnectionTest.php index 4a2446569372f..691b3fdd06861 100644 --- a/src/Symfony/Component/Messenger/Bridge/AmazonSqs/Tests/Transport/ConnectionTest.php +++ b/src/Symfony/Component/Messenger/Bridge/AmazonSqs/Tests/Transport/ConnectionTest.php @@ -17,6 +17,7 @@ use AsyncAws\Sqs\Result\ReceiveMessageResult; use AsyncAws\Sqs\SqsClient; use AsyncAws\Sqs\ValueObject\Message; +use Composer\InstalledVersions; use PHPUnit\Framework\TestCase; use Psr\Log\NullLogger; use Symfony\Component\HttpClient\MockHttpClient; @@ -358,6 +359,16 @@ public function testLoggerWithDebugOption() private function getMockedQueueUrlResponse(): MockResponse { + if ($this->isAsyncAwsSqsVersion2Installed()) { + return new MockResponse( + << @@ -373,6 +384,28 @@ private function getMockedQueueUrlResponse(): MockResponse private function getMockedReceiveMessageResponse(): MockResponse { + if ($this->isAsyncAwsSqsVersion2Installed()) { + return new MockResponse(<<=8.2", "async-aws/core": "^1.7", - "async-aws/sqs": "^1.0", + "async-aws/sqs": "^1.0|^2.0", "symfony/messenger": "^6.4|^7.0", "symfony/service-contracts": "^2.5|^3", "psr/log": "^1|^2|^3" diff --git a/src/Symfony/Component/Messenger/Bridge/Amqp/Tests/Transport/AmqpTransportTest.php b/src/Symfony/Component/Messenger/Bridge/Amqp/Tests/Transport/AmqpTransportTest.php index 433aeebdfe5d5..21857d42b94d2 100644 --- a/src/Symfony/Component/Messenger/Bridge/Amqp/Tests/Transport/AmqpTransportTest.php +++ b/src/Symfony/Component/Messenger/Bridge/Amqp/Tests/Transport/AmqpTransportTest.php @@ -52,7 +52,7 @@ public function testReceivesMessages() $this->assertSame($decodedMessage, $envelopes[0]->getMessage()); } - private function getTransport(SerializerInterface $serializer = null, Connection $connection = null): AmqpTransport + private function getTransport(?SerializerInterface $serializer = null, ?Connection $connection = null): AmqpTransport { $serializer ??= $this->createMock(SerializerInterface::class); $connection ??= $this->createMock(Connection::class); diff --git a/src/Symfony/Component/Messenger/Bridge/Amqp/Transport/AmqpReceiver.php b/src/Symfony/Component/Messenger/Bridge/Amqp/Transport/AmqpReceiver.php index fecf39b29afc3..631f79da9dcb9 100644 --- a/src/Symfony/Component/Messenger/Bridge/Amqp/Transport/AmqpReceiver.php +++ b/src/Symfony/Component/Messenger/Bridge/Amqp/Transport/AmqpReceiver.php @@ -30,7 +30,7 @@ class AmqpReceiver implements QueueReceiverInterface, MessageCountAwareInterface private SerializerInterface $serializer; private Connection $connection; - public function __construct(Connection $connection, SerializerInterface $serializer = null) + public function __construct(Connection $connection, ?SerializerInterface $serializer = null) { $this->connection = $connection; $this->serializer = $serializer ?? new PhpSerializer(); diff --git a/src/Symfony/Component/Messenger/Bridge/Amqp/Transport/AmqpSender.php b/src/Symfony/Component/Messenger/Bridge/Amqp/Transport/AmqpSender.php index cbcecffa7ef57..4f7caaa71635f 100644 --- a/src/Symfony/Component/Messenger/Bridge/Amqp/Transport/AmqpSender.php +++ b/src/Symfony/Component/Messenger/Bridge/Amqp/Transport/AmqpSender.php @@ -29,7 +29,7 @@ class AmqpSender implements SenderInterface private SerializerInterface $serializer; private Connection $connection; - public function __construct(Connection $connection, SerializerInterface $serializer = null) + public function __construct(Connection $connection, ?SerializerInterface $serializer = null) { $this->connection = $connection; $this->serializer = $serializer ?? new PhpSerializer(); diff --git a/src/Symfony/Component/Messenger/Bridge/Amqp/Transport/AmqpStamp.php b/src/Symfony/Component/Messenger/Bridge/Amqp/Transport/AmqpStamp.php index 127593d38b66d..a91467f87bc93 100644 --- a/src/Symfony/Component/Messenger/Bridge/Amqp/Transport/AmqpStamp.php +++ b/src/Symfony/Component/Messenger/Bridge/Amqp/Transport/AmqpStamp.php @@ -24,7 +24,7 @@ final class AmqpStamp implements NonSendableStampInterface private array $attributes; private bool $isRetryAttempt = false; - public function __construct(string $routingKey = null, int $flags = \AMQP_NOPARAM, array $attributes = []) + public function __construct(?string $routingKey = null, int $flags = \AMQP_NOPARAM, array $attributes = []) { $this->routingKey = $routingKey; $this->flags = $flags; @@ -46,7 +46,7 @@ public function getAttributes(): array return $this->attributes; } - public static function createFromAmqpEnvelope(\AMQPEnvelope $amqpEnvelope, self $previousStamp = null, string $retryRoutingKey = null): self + public static function createFromAmqpEnvelope(\AMQPEnvelope $amqpEnvelope, ?self $previousStamp = null, ?string $retryRoutingKey = null): self { $attr = $previousStamp->attributes ?? []; @@ -79,7 +79,7 @@ public function isRetryAttempt(): bool return $this->isRetryAttempt; } - public static function createWithAttributes(array $attributes, self $previousStamp = null): self + public static function createWithAttributes(array $attributes, ?self $previousStamp = null): self { return new self( $previousStamp->routingKey ?? null, diff --git a/src/Symfony/Component/Messenger/Bridge/Amqp/Transport/AmqpTransport.php b/src/Symfony/Component/Messenger/Bridge/Amqp/Transport/AmqpTransport.php index 227653609d8e8..edb09614c6597 100644 --- a/src/Symfony/Component/Messenger/Bridge/Amqp/Transport/AmqpTransport.php +++ b/src/Symfony/Component/Messenger/Bridge/Amqp/Transport/AmqpTransport.php @@ -29,7 +29,7 @@ class AmqpTransport implements QueueReceiverInterface, TransportInterface, Setup private AmqpReceiver $receiver; private AmqpSender $sender; - public function __construct(Connection $connection, SerializerInterface $serializer = null) + public function __construct(Connection $connection, ?SerializerInterface $serializer = null) { $this->connection = $connection; $this->serializer = $serializer ?? new PhpSerializer(); diff --git a/src/Symfony/Component/Messenger/Bridge/Amqp/Transport/Connection.php b/src/Symfony/Component/Messenger/Bridge/Amqp/Transport/Connection.php index 28150946c8b8c..f24c8b74c04f5 100644 --- a/src/Symfony/Component/Messenger/Bridge/Amqp/Transport/Connection.php +++ b/src/Symfony/Component/Messenger/Bridge/Amqp/Transport/Connection.php @@ -13,6 +13,7 @@ use Symfony\Component\Messenger\Exception\InvalidArgumentException; use Symfony\Component\Messenger\Exception\LogicException; +use Symfony\Component\Messenger\Exception\TransportException; /** * An AMQP connection. @@ -96,7 +97,7 @@ class Connection private \AMQPExchange $amqpDelayExchange; private int $lastActivityTime = 0; - public function __construct(#[\SensitiveParameter] array $connectionOptions, array $exchangeOptions, array $queuesOptions, AmqpFactory $amqpFactory = null) + public function __construct(#[\SensitiveParameter] array $connectionOptions, array $exchangeOptions, array $queuesOptions, ?AmqpFactory $amqpFactory = null) { if (!\extension_loaded('amqp')) { throw new LogicException(sprintf('You cannot use the "%s" as the "amqp" extension is not installed.', __CLASS__)); @@ -160,7 +161,7 @@ public function __construct(#[\SensitiveParameter] array $connectionOptions, arr * * verify: Enable or disable peer verification. If peer verification is enabled then the common name in the * server certificate must match the server name. Peer verification is enabled by default. */ - public static function fromDsn(#[\SensitiveParameter] string $dsn, array $options = [], AmqpFactory $amqpFactory = null): self + public static function fromDsn(#[\SensitiveParameter] string $dsn, array $options = [], ?AmqpFactory $amqpFactory = null): self { if (false === $params = parse_url($dsn)) { // this is a valid URI that parse_url cannot handle when you want to pass all parameters as options @@ -278,7 +279,7 @@ private static function hasCaCertConfigured(array $amqpOptions): bool /** * @throws \AMQPException */ - public function publish(string $body, array $headers = [], int $delayInMs = 0, AmqpStamp $amqpStamp = null): void + public function publish(string $body, array $headers = [], int $delayInMs = 0, ?AmqpStamp $amqpStamp = null): void { $this->clearWhenDisconnected(); @@ -312,7 +313,7 @@ public function countMessagesInQueues(): int /** * @throws \AMQPException */ - private function publishWithDelay(string $body, array $headers, int $delay, AmqpStamp $amqpStamp = null): void + private function publishWithDelay(string $body, array $headers, int $delay, ?AmqpStamp $amqpStamp = null): void { $routingKey = $this->getRoutingKeyForMessage($amqpStamp); $isRetryAttempt = $amqpStamp ? $amqpStamp->isRetryAttempt() : false; @@ -328,7 +329,7 @@ private function publishWithDelay(string $body, array $headers, int $delay, Amqp ); } - private function publishOnExchange(\AMQPExchange $exchange, string $body, string $routingKey = null, array $headers = [], AmqpStamp $amqpStamp = null): void + private function publishOnExchange(\AMQPExchange $exchange, string $body, ?string $routingKey = null, array $headers = [], ?AmqpStamp $amqpStamp = null): void { $attributes = $amqpStamp ? $amqpStamp->getAttributes() : []; $attributes['headers'] = array_merge($attributes['headers'] ?? [], $headers); @@ -493,7 +494,7 @@ public function channel(): \AMQPChannel $this->amqpChannel->confirmSelect(); $this->amqpChannel->setConfirmCallback( static fn (): bool => false, - static fn (): bool => false + static fn () => throw new TransportException('Message publication failed due to a negative acknowledgment (nack) from the broker.'), ); } @@ -509,6 +510,9 @@ public function channel(): \AMQPChannel public function queue(string $queueName): \AMQPQueue { if (!isset($this->amqpQueues[$queueName])) { + if (!\array_key_exists($queueName, $this->queuesOptions)) { + throw new InvalidArgumentException(sprintf('Exchange "%s" does not have a queue named "%s", known queues are "%s".', $this->exchangeOptions['name'], $queueName, implode('", "', array_keys($this->queuesOptions)))); + } $queueConfig = $this->queuesOptions[$queueName]; $amqpQueue = $this->amqpFactory->createQueue($this->channel()); diff --git a/src/Symfony/Component/Messenger/Bridge/Beanstalkd/Tests/Transport/BeanstalkdTransportTest.php b/src/Symfony/Component/Messenger/Bridge/Beanstalkd/Tests/Transport/BeanstalkdTransportTest.php index 6ba15f30d4fa7..cdbd80e980ce0 100644 --- a/src/Symfony/Component/Messenger/Bridge/Beanstalkd/Tests/Transport/BeanstalkdTransportTest.php +++ b/src/Symfony/Component/Messenger/Bridge/Beanstalkd/Tests/Transport/BeanstalkdTransportTest.php @@ -50,7 +50,7 @@ public function testReceivesMessages() $this->assertSame($decodedMessage, $envelopes[0]->getMessage()); } - private function getTransport(SerializerInterface $serializer = null, Connection $connection = null): BeanstalkdTransport + private function getTransport(?SerializerInterface $serializer = null, ?Connection $connection = null): BeanstalkdTransport { $serializer ??= $this->createMock(SerializerInterface::class); $connection ??= $this->createMock(Connection::class); diff --git a/src/Symfony/Component/Messenger/Bridge/Beanstalkd/Transport/BeanstalkdReceiver.php b/src/Symfony/Component/Messenger/Bridge/Beanstalkd/Transport/BeanstalkdReceiver.php index 734aa8296b587..c89a75a2c8735 100644 --- a/src/Symfony/Component/Messenger/Bridge/Beanstalkd/Transport/BeanstalkdReceiver.php +++ b/src/Symfony/Component/Messenger/Bridge/Beanstalkd/Transport/BeanstalkdReceiver.php @@ -27,7 +27,7 @@ class BeanstalkdReceiver implements ReceiverInterface, MessageCountAwareInterfac private Connection $connection; private SerializerInterface $serializer; - public function __construct(Connection $connection, SerializerInterface $serializer = null) + public function __construct(Connection $connection, ?SerializerInterface $serializer = null) { $this->connection = $connection; $this->serializer = $serializer ?? new PhpSerializer(); diff --git a/src/Symfony/Component/Messenger/Bridge/Beanstalkd/Transport/BeanstalkdSender.php b/src/Symfony/Component/Messenger/Bridge/Beanstalkd/Transport/BeanstalkdSender.php index ff1cac38220e2..fc3f87780ebe9 100644 --- a/src/Symfony/Component/Messenger/Bridge/Beanstalkd/Transport/BeanstalkdSender.php +++ b/src/Symfony/Component/Messenger/Bridge/Beanstalkd/Transport/BeanstalkdSender.php @@ -25,7 +25,7 @@ class BeanstalkdSender implements SenderInterface private Connection $connection; private SerializerInterface $serializer; - public function __construct(Connection $connection, SerializerInterface $serializer = null) + public function __construct(Connection $connection, ?SerializerInterface $serializer = null) { $this->connection = $connection; $this->serializer = $serializer ?? new PhpSerializer(); diff --git a/src/Symfony/Component/Messenger/Bridge/Beanstalkd/Transport/BeanstalkdTransport.php b/src/Symfony/Component/Messenger/Bridge/Beanstalkd/Transport/BeanstalkdTransport.php index d6698a3fae484..d1ecbb7851dde 100644 --- a/src/Symfony/Component/Messenger/Bridge/Beanstalkd/Transport/BeanstalkdTransport.php +++ b/src/Symfony/Component/Messenger/Bridge/Beanstalkd/Transport/BeanstalkdTransport.php @@ -27,7 +27,7 @@ class BeanstalkdTransport implements TransportInterface, MessageCountAwareInterf private BeanstalkdReceiver $receiver; private BeanstalkdSender $sender; - public function __construct(Connection $connection, SerializerInterface $serializer = null) + public function __construct(Connection $connection, ?SerializerInterface $serializer = null) { $this->connection = $connection; $this->serializer = $serializer ?? new PhpSerializer(); diff --git a/src/Symfony/Component/Messenger/Bridge/Doctrine/Tests/Transport/DoctrineTransportTest.php b/src/Symfony/Component/Messenger/Bridge/Doctrine/Tests/Transport/DoctrineTransportTest.php index fea497d14409c..40285e2a1158d 100644 --- a/src/Symfony/Component/Messenger/Bridge/Doctrine/Tests/Transport/DoctrineTransportTest.php +++ b/src/Symfony/Component/Messenger/Bridge/Doctrine/Tests/Transport/DoctrineTransportTest.php @@ -69,7 +69,7 @@ public function testConfigureSchema() $transport->configureSchema($schema, $dbalConnection, static fn () => true); } - private function getTransport(SerializerInterface $serializer = null, Connection $connection = null): DoctrineTransport + private function getTransport(?SerializerInterface $serializer = null, ?Connection $connection = null): DoctrineTransport { $serializer ??= $this->createMock(SerializerInterface::class); $connection ??= $this->createMock(Connection::class); diff --git a/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/Connection.php b/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/Connection.php index 8e2c1f7312597..cead3359a7304 100644 --- a/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/Connection.php +++ b/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/Connection.php @@ -290,7 +290,7 @@ public function getMessageCount(): int return $this->executeQuery($queryBuilder->getSQL(), $queryBuilder->getParameters(), $queryBuilder->getParameterTypes())->fetchOne(); } - public function findAll(int $limit = null): array + public function findAll(?int $limit = null): array { $queryBuilder = $this->createAvailableMessagesQueryBuilder(); diff --git a/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/DoctrineReceiver.php b/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/DoctrineReceiver.php index 79310f35a9a9c..2f6e4a5a823ad 100644 --- a/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/DoctrineReceiver.php +++ b/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/DoctrineReceiver.php @@ -33,7 +33,7 @@ class DoctrineReceiver implements ListableReceiverInterface, MessageCountAwareIn private Connection $connection; private SerializerInterface $serializer; - public function __construct(Connection $connection, SerializerInterface $serializer = null) + public function __construct(Connection $connection, ?SerializerInterface $serializer = null) { $this->connection = $connection; $this->serializer = $serializer ?? new PhpSerializer(); @@ -92,7 +92,7 @@ public function getMessageCount(): int } } - public function all(int $limit = null): iterable + public function all(?int $limit = null): iterable { try { $doctrineEnvelopes = $this->connection->findAll($limit); diff --git a/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/DoctrineSender.php b/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/DoctrineSender.php index cccce15e104d8..6a73ddcae0366 100644 --- a/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/DoctrineSender.php +++ b/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/DoctrineSender.php @@ -28,7 +28,7 @@ class DoctrineSender implements SenderInterface private Connection $connection; private SerializerInterface $serializer; - public function __construct(Connection $connection, SerializerInterface $serializer = null) + public function __construct(Connection $connection, ?SerializerInterface $serializer = null) { $this->connection = $connection; $this->serializer = $serializer ?? new PhpSerializer(); diff --git a/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/DoctrineTransport.php b/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/DoctrineTransport.php index ad8db5c0c0453..d629008c96d58 100644 --- a/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/DoctrineTransport.php +++ b/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/DoctrineTransport.php @@ -57,7 +57,7 @@ public function getMessageCount(): int return $this->getReceiver()->getMessageCount(); } - public function all(int $limit = null): iterable + public function all(?int $limit = null): iterable { return $this->getReceiver()->all($limit); } diff --git a/src/Symfony/Component/Messenger/Bridge/Redis/Tests/Transport/RedisTransportTest.php b/src/Symfony/Component/Messenger/Bridge/Redis/Tests/Transport/RedisTransportTest.php index 199edb8944ae0..1c6b6b2b2a59b 100644 --- a/src/Symfony/Component/Messenger/Bridge/Redis/Tests/Transport/RedisTransportTest.php +++ b/src/Symfony/Component/Messenger/Bridge/Redis/Tests/Transport/RedisTransportTest.php @@ -54,7 +54,7 @@ public function testReceivesMessages() $this->assertSame($decodedMessage, $envelopes[0]->getMessage()); } - private function getTransport(SerializerInterface $serializer = null, Connection $connection = null): RedisTransport + private function getTransport(?SerializerInterface $serializer = null, ?Connection $connection = null): RedisTransport { $serializer ??= $this->createMock(SerializerInterface::class); $connection ??= $this->createMock(Connection::class); diff --git a/src/Symfony/Component/Messenger/Bridge/Redis/Transport/Connection.php b/src/Symfony/Component/Messenger/Bridge/Redis/Transport/Connection.php index f2b31961ad453..3d79838cbc0b7 100644 --- a/src/Symfony/Component/Messenger/Bridge/Redis/Transport/Connection.php +++ b/src/Symfony/Component/Messenger/Bridge/Redis/Transport/Connection.php @@ -69,7 +69,7 @@ class Connection private bool $deleteAfterReject; private bool $couldHavePendingMessages = true; - public function __construct(array $options, \Redis|Relay|\RedisCluster $redis = null) + public function __construct(array $options, \Redis|Relay|\RedisCluster|null $redis = null) { if (version_compare(phpversion('redis'), '4.3.0', '<')) { throw new LogicException('The redis transport requires php-redis 4.3.0 or higher.'); @@ -203,7 +203,7 @@ private static function initializeRedisCluster(?\RedisCluster $redis, array $hos return $redis; } - public static function fromDsn(#[\SensitiveParameter] string $dsn, array $options = [], \Redis|Relay|\RedisCluster $redis = null): self + public static function fromDsn(#[\SensitiveParameter] string $dsn, array $options = [], \Redis|Relay|\RedisCluster|null $redis = null): self { if (!str_contains($dsn, ',')) { $params = self::parseDsn($dsn, $options); diff --git a/src/Symfony/Component/Messenger/Bridge/Redis/Transport/RedisReceiver.php b/src/Symfony/Component/Messenger/Bridge/Redis/Transport/RedisReceiver.php index 990a09ed8f9c2..cf6be2660a5ba 100644 --- a/src/Symfony/Component/Messenger/Bridge/Redis/Transport/RedisReceiver.php +++ b/src/Symfony/Component/Messenger/Bridge/Redis/Transport/RedisReceiver.php @@ -29,7 +29,7 @@ class RedisReceiver implements ReceiverInterface, MessageCountAwareInterface private Connection $connection; private SerializerInterface $serializer; - public function __construct(Connection $connection, SerializerInterface $serializer = null) + public function __construct(Connection $connection, ?SerializerInterface $serializer = null) { $this->connection = $connection; $this->serializer = $serializer ?? new PhpSerializer(); diff --git a/src/Symfony/Component/Messenger/Bridge/Redis/Transport/RedisTransport.php b/src/Symfony/Component/Messenger/Bridge/Redis/Transport/RedisTransport.php index 8ba7ff4408403..e425f24def473 100644 --- a/src/Symfony/Component/Messenger/Bridge/Redis/Transport/RedisTransport.php +++ b/src/Symfony/Component/Messenger/Bridge/Redis/Transport/RedisTransport.php @@ -29,7 +29,7 @@ class RedisTransport implements TransportInterface, SetupableTransportInterface, private RedisReceiver $receiver; private RedisSender $sender; - public function __construct(Connection $connection, SerializerInterface $serializer = null) + public function __construct(Connection $connection, ?SerializerInterface $serializer = null) { $this->connection = $connection; $this->serializer = $serializer ?? new PhpSerializer(); diff --git a/src/Symfony/Component/Messenger/Command/AbstractFailedMessagesCommand.php b/src/Symfony/Component/Messenger/Command/AbstractFailedMessagesCommand.php index 928443c97bd70..e5f6ae925de60 100644 --- a/src/Symfony/Component/Messenger/Command/AbstractFailedMessagesCommand.php +++ b/src/Symfony/Component/Messenger/Command/AbstractFailedMessagesCommand.php @@ -50,7 +50,7 @@ abstract class AbstractFailedMessagesCommand extends Command private ?string $globalFailureReceiverName; - public function __construct(?string $globalFailureReceiverName, ServiceProviderInterface $failureTransports, PhpSerializer $phpSerializer = null) + public function __construct(?string $globalFailureReceiverName, ServiceProviderInterface $failureTransports, ?PhpSerializer $phpSerializer = null) { $this->failureTransports = $failureTransports; $this->globalFailureReceiverName = $globalFailureReceiverName; @@ -159,7 +159,7 @@ protected function printPendingMessagesMessage(ReceiverInterface $receiver, Symf } } - protected function getReceiver(string $name = null): ReceiverInterface + protected function getReceiver(?string $name = null): ReceiverInterface { if (null === $name ??= $this->globalFailureReceiverName) { throw new InvalidArgumentException(sprintf('No default failure transport is defined. Available transports are: "%s".', implode('", "', array_keys($this->failureTransports->getProvidedServices())))); diff --git a/src/Symfony/Component/Messenger/Command/ConsumeMessagesCommand.php b/src/Symfony/Component/Messenger/Command/ConsumeMessagesCommand.php index 129995de7b30b..bfbcc5d09b0c0 100644 --- a/src/Symfony/Component/Messenger/Command/ConsumeMessagesCommand.php +++ b/src/Symfony/Component/Messenger/Command/ConsumeMessagesCommand.php @@ -54,7 +54,7 @@ class ConsumeMessagesCommand extends Command implements SignalableCommandInterfa private ?array $signals; private ?Worker $worker = null; - public function __construct(RoutableMessageBus $routableBus, ContainerInterface $receiverLocator, EventDispatcherInterface $eventDispatcher, LoggerInterface $logger = null, array $receiverNames = [], ResetServicesListener $resetServicesListener = null, array $busIds = [], ContainerInterface $rateLimiterLocator = null, array $signals = null) + public function __construct(RoutableMessageBus $routableBus, ContainerInterface $receiverLocator, EventDispatcherInterface $eventDispatcher, ?LoggerInterface $logger = null, array $receiverNames = [], ?ResetServicesListener $resetServicesListener = null, array $busIds = [], ?ContainerInterface $rateLimiterLocator = null, ?array $signals = null) { $this->routableBus = $routableBus; $this->receiverLocator = $receiverLocator; diff --git a/src/Symfony/Component/Messenger/Command/FailedMessagesRetryCommand.php b/src/Symfony/Component/Messenger/Command/FailedMessagesRetryCommand.php index 53e70bb99534c..677743c99e446 100644 --- a/src/Symfony/Component/Messenger/Command/FailedMessagesRetryCommand.php +++ b/src/Symfony/Component/Messenger/Command/FailedMessagesRetryCommand.php @@ -47,7 +47,7 @@ class FailedMessagesRetryCommand extends AbstractFailedMessagesCommand implement private bool $forceExit = false; private ?Worker $worker = null; - public function __construct(?string $globalReceiverName, ServiceProviderInterface $failureTransports, MessageBusInterface $messageBus, EventDispatcherInterface $eventDispatcher, LoggerInterface $logger = null, PhpSerializer $phpSerializer = null, array $signals = null) + public function __construct(?string $globalReceiverName, ServiceProviderInterface $failureTransports, MessageBusInterface $messageBus, EventDispatcherInterface $eventDispatcher, ?LoggerInterface $logger = null, ?PhpSerializer $phpSerializer = null, ?array $signals = null) { $this->eventDispatcher = $eventDispatcher; $this->messageBus = $messageBus; diff --git a/src/Symfony/Component/Messenger/Command/FailedMessagesShowCommand.php b/src/Symfony/Component/Messenger/Command/FailedMessagesShowCommand.php index d4648e2747a99..25fb9a8de0fbf 100644 --- a/src/Symfony/Component/Messenger/Command/FailedMessagesShowCommand.php +++ b/src/Symfony/Component/Messenger/Command/FailedMessagesShowCommand.php @@ -84,7 +84,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int return 0; } - private function listMessages(?string $failedTransportName, SymfonyStyle $io, int $max, string $classFilter = null): void + private function listMessages(?string $failedTransportName, SymfonyStyle $io, int $max, ?string $classFilter = null): void { /** @var ListableReceiverInterface $receiver */ $receiver = $this->getReceiver($failedTransportName); diff --git a/src/Symfony/Component/Messenger/DataCollector/MessengerDataCollector.php b/src/Symfony/Component/Messenger/DataCollector/MessengerDataCollector.php index 3322511369965..01b78e8e2d91a 100644 --- a/src/Symfony/Component/Messenger/DataCollector/MessengerDataCollector.php +++ b/src/Symfony/Component/Messenger/DataCollector/MessengerDataCollector.php @@ -32,7 +32,7 @@ public function registerBus(string $name, TraceableMessageBus $bus): void $this->traceableBuses[$name] = $bus; } - public function collect(Request $request, Response $response, \Throwable $exception = null): void + public function collect(Request $request, Response $response, ?\Throwable $exception = null): void { // Noop. Everything is collected live by the traceable buses & cloned as late as possible. } @@ -106,7 +106,7 @@ private function collectMessage(string $busName, array $tracedMessage): array return $debugRepresentation; } - public function getExceptionsCount(string $bus = null): int + public function getExceptionsCount(?string $bus = null): int { $count = 0; foreach ($this->getMessages($bus) as $message) { @@ -116,7 +116,7 @@ public function getExceptionsCount(string $bus = null): int return $count; } - public function getMessages(string $bus = null): array + public function getMessages(?string $bus = null): array { if (null === $bus) { return $this->data['messages']; diff --git a/src/Symfony/Component/Messenger/Envelope.php b/src/Symfony/Component/Messenger/Envelope.php index a9ea4e0b48f5f..03fb4c8ea9e12 100644 --- a/src/Symfony/Component/Messenger/Envelope.php +++ b/src/Symfony/Component/Messenger/Envelope.php @@ -114,7 +114,7 @@ public function last(string $stampFqcn): ?StampInterface * * @psalm-return ($stampFqcn is string : array, list> ? list) */ - public function all(string $stampFqcn = null): array + public function all(?string $stampFqcn = null): array { if (null !== $stampFqcn) { return $this->stamps[$stampFqcn] ?? []; diff --git a/src/Symfony/Component/Messenger/Event/WorkerMessageReceivedEvent.php b/src/Symfony/Component/Messenger/Event/WorkerMessageReceivedEvent.php index ba11e59793a48..71210a5f237d6 100644 --- a/src/Symfony/Component/Messenger/Event/WorkerMessageReceivedEvent.php +++ b/src/Symfony/Component/Messenger/Event/WorkerMessageReceivedEvent.php @@ -20,7 +20,7 @@ final class WorkerMessageReceivedEvent extends AbstractWorkerMessageEvent { private bool $shouldHandle = true; - public function shouldHandle(bool $shouldHandle = null): bool + public function shouldHandle(?bool $shouldHandle = null): bool { if (null !== $shouldHandle) { $this->shouldHandle = $shouldHandle; diff --git a/src/Symfony/Component/Messenger/EventListener/SendFailedMessageForRetryListener.php b/src/Symfony/Component/Messenger/EventListener/SendFailedMessageForRetryListener.php index 9417d8b762d49..506206712e046 100644 --- a/src/Symfony/Component/Messenger/EventListener/SendFailedMessageForRetryListener.php +++ b/src/Symfony/Component/Messenger/EventListener/SendFailedMessageForRetryListener.php @@ -39,7 +39,7 @@ class SendFailedMessageForRetryListener implements EventSubscriberInterface private ?EventDispatcherInterface $eventDispatcher; private int $historySize; - public function __construct(ContainerInterface $sendersLocator, ContainerInterface $retryStrategyLocator, LoggerInterface $logger = null, EventDispatcherInterface $eventDispatcher = null, int $historySize = 10) + public function __construct(ContainerInterface $sendersLocator, ContainerInterface $retryStrategyLocator, ?LoggerInterface $logger = null, ?EventDispatcherInterface $eventDispatcher = null, int $historySize = 10) { $this->sendersLocator = $sendersLocator; $this->retryStrategyLocator = $retryStrategyLocator; diff --git a/src/Symfony/Component/Messenger/EventListener/SendFailedMessageToFailureTransportListener.php b/src/Symfony/Component/Messenger/EventListener/SendFailedMessageToFailureTransportListener.php index cfc908ce1bb7b..dda978439b793 100644 --- a/src/Symfony/Component/Messenger/EventListener/SendFailedMessageToFailureTransportListener.php +++ b/src/Symfony/Component/Messenger/EventListener/SendFailedMessageToFailureTransportListener.php @@ -29,7 +29,7 @@ class SendFailedMessageToFailureTransportListener implements EventSubscriberInte private ContainerInterface $failureSenders; private ?LoggerInterface $logger; - public function __construct(ContainerInterface $failureSenders, LoggerInterface $logger = null) + public function __construct(ContainerInterface $failureSenders, ?LoggerInterface $logger = null) { $this->failureSenders = $failureSenders; $this->logger = $logger; diff --git a/src/Symfony/Component/Messenger/EventListener/StopWorkerOnFailureLimitListener.php b/src/Symfony/Component/Messenger/EventListener/StopWorkerOnFailureLimitListener.php index 36f514be887dd..0dcdda14f72da 100644 --- a/src/Symfony/Component/Messenger/EventListener/StopWorkerOnFailureLimitListener.php +++ b/src/Symfony/Component/Messenger/EventListener/StopWorkerOnFailureLimitListener.php @@ -26,7 +26,7 @@ class StopWorkerOnFailureLimitListener implements EventSubscriberInterface private ?LoggerInterface $logger; private int $failedMessages = 0; - public function __construct(int $maximumNumberOfFailures, LoggerInterface $logger = null) + public function __construct(int $maximumNumberOfFailures, ?LoggerInterface $logger = null) { $this->maximumNumberOfFailures = $maximumNumberOfFailures; $this->logger = $logger; diff --git a/src/Symfony/Component/Messenger/EventListener/StopWorkerOnMemoryLimitListener.php b/src/Symfony/Component/Messenger/EventListener/StopWorkerOnMemoryLimitListener.php index e5f65f86bf1d5..5e422c08406cf 100644 --- a/src/Symfony/Component/Messenger/EventListener/StopWorkerOnMemoryLimitListener.php +++ b/src/Symfony/Component/Messenger/EventListener/StopWorkerOnMemoryLimitListener.php @@ -25,7 +25,7 @@ class StopWorkerOnMemoryLimitListener implements EventSubscriberInterface private ?LoggerInterface $logger; private \Closure $memoryResolver; - public function __construct(int $memoryLimit, LoggerInterface $logger = null, callable $memoryResolver = null) + public function __construct(int $memoryLimit, ?LoggerInterface $logger = null, ?callable $memoryResolver = null) { $this->memoryLimit = $memoryLimit; $this->logger = $logger; diff --git a/src/Symfony/Component/Messenger/EventListener/StopWorkerOnMessageLimitListener.php b/src/Symfony/Component/Messenger/EventListener/StopWorkerOnMessageLimitListener.php index 0f2b2a3f0b05b..83ea24c7c9d10 100644 --- a/src/Symfony/Component/Messenger/EventListener/StopWorkerOnMessageLimitListener.php +++ b/src/Symfony/Component/Messenger/EventListener/StopWorkerOnMessageLimitListener.php @@ -26,7 +26,7 @@ class StopWorkerOnMessageLimitListener implements EventSubscriberInterface private ?LoggerInterface $logger; private int $receivedMessages = 0; - public function __construct(int $maximumNumberOfMessages, LoggerInterface $logger = null) + public function __construct(int $maximumNumberOfMessages, ?LoggerInterface $logger = null) { $this->maximumNumberOfMessages = $maximumNumberOfMessages; $this->logger = $logger; diff --git a/src/Symfony/Component/Messenger/EventListener/StopWorkerOnRestartSignalListener.php b/src/Symfony/Component/Messenger/EventListener/StopWorkerOnRestartSignalListener.php index ac9963b5367cb..256dcc01b4c2e 100644 --- a/src/Symfony/Component/Messenger/EventListener/StopWorkerOnRestartSignalListener.php +++ b/src/Symfony/Component/Messenger/EventListener/StopWorkerOnRestartSignalListener.php @@ -28,7 +28,7 @@ class StopWorkerOnRestartSignalListener implements EventSubscriberInterface private ?LoggerInterface $logger; private float $workerStartedAt = 0; - public function __construct(CacheItemPoolInterface $cachePool, LoggerInterface $logger = null) + public function __construct(CacheItemPoolInterface $cachePool, ?LoggerInterface $logger = null) { $this->cachePool = $cachePool; $this->logger = $logger; diff --git a/src/Symfony/Component/Messenger/EventListener/StopWorkerOnTimeLimitListener.php b/src/Symfony/Component/Messenger/EventListener/StopWorkerOnTimeLimitListener.php index 47ac5c411c969..6e5b879ba982d 100644 --- a/src/Symfony/Component/Messenger/EventListener/StopWorkerOnTimeLimitListener.php +++ b/src/Symfony/Component/Messenger/EventListener/StopWorkerOnTimeLimitListener.php @@ -27,7 +27,7 @@ class StopWorkerOnTimeLimitListener implements EventSubscriberInterface private ?LoggerInterface $logger; private float $endTime = 0; - public function __construct(int $timeLimitInSeconds, LoggerInterface $logger = null) + public function __construct(int $timeLimitInSeconds, ?LoggerInterface $logger = null) { $this->timeLimitInSeconds = $timeLimitInSeconds; $this->logger = $logger; diff --git a/src/Symfony/Component/Messenger/Exception/DelayedMessageHandlingException.php b/src/Symfony/Component/Messenger/Exception/DelayedMessageHandlingException.php index f3500242475ce..6a109e472369f 100644 --- a/src/Symfony/Component/Messenger/Exception/DelayedMessageHandlingException.php +++ b/src/Symfony/Component/Messenger/Exception/DelayedMessageHandlingException.php @@ -26,7 +26,7 @@ class DelayedMessageHandlingException extends RuntimeException implements Wrappe private array $exceptions; private ?Envelope $envelope; - public function __construct(array $exceptions, Envelope $envelope = null) + public function __construct(array $exceptions, ?Envelope $envelope = null) { $this->envelope = $envelope; diff --git a/src/Symfony/Component/Messenger/Exception/StopWorkerException.php b/src/Symfony/Component/Messenger/Exception/StopWorkerException.php index e53bd32b7c489..c2100c28d8fb4 100644 --- a/src/Symfony/Component/Messenger/Exception/StopWorkerException.php +++ b/src/Symfony/Component/Messenger/Exception/StopWorkerException.php @@ -16,7 +16,7 @@ */ class StopWorkerException extends RuntimeException implements StopWorkerExceptionInterface { - public function __construct(string $message = 'Worker should stop.', \Throwable $previous = null) + public function __construct(string $message = 'Worker should stop.', ?\Throwable $previous = null) { parent::__construct($message, 0, $previous); } diff --git a/src/Symfony/Component/Messenger/Exception/WrappedExceptionsInterface.php b/src/Symfony/Component/Messenger/Exception/WrappedExceptionsInterface.php index 845439763f38f..8cd644c31ecf7 100644 --- a/src/Symfony/Component/Messenger/Exception/WrappedExceptionsInterface.php +++ b/src/Symfony/Component/Messenger/Exception/WrappedExceptionsInterface.php @@ -21,5 +21,5 @@ interface WrappedExceptionsInterface /** * @return \Throwable[] */ - public function getWrappedExceptions(string $class = null, bool $recursive = false): array; + public function getWrappedExceptions(?string $class = null, bool $recursive = false): array; } diff --git a/src/Symfony/Component/Messenger/Exception/WrappedExceptionsTrait.php b/src/Symfony/Component/Messenger/Exception/WrappedExceptionsTrait.php index bede05bec51db..121f9e998101c 100644 --- a/src/Symfony/Component/Messenger/Exception/WrappedExceptionsTrait.php +++ b/src/Symfony/Component/Messenger/Exception/WrappedExceptionsTrait.php @@ -23,7 +23,7 @@ trait WrappedExceptionsTrait /** * @return \Throwable[] */ - public function getWrappedExceptions(string $class = null, bool $recursive = false): array + public function getWrappedExceptions(?string $class = null, bool $recursive = false): array { return $this->getWrappedExceptionsRecursively($class, $recursive, $this->exceptions); } diff --git a/src/Symfony/Component/Messenger/Handler/Acknowledger.php b/src/Symfony/Component/Messenger/Handler/Acknowledger.php index 3e6b15813f951..3fa7a8fc95db9 100644 --- a/src/Symfony/Component/Messenger/Handler/Acknowledger.php +++ b/src/Symfony/Component/Messenger/Handler/Acknowledger.php @@ -26,7 +26,7 @@ class Acknowledger /** * @param \Closure(\Throwable|null, mixed):void|null $ack */ - public function __construct(string $handlerClass, \Closure $ack = null) + public function __construct(string $handlerClass, ?\Closure $ack = null) { $this->handlerClass = $handlerClass; $this->ack = $ack ?? static function () {}; @@ -67,7 +67,7 @@ public function __destruct() } } - private function doAck(\Throwable $e = null, mixed $result = null): void + private function doAck(?\Throwable $e = null, mixed $result = null): void { if (!$ack = $this->ack) { throw new LogicException(sprintf('The acknowledger cannot be called twice by the "%s" batch handler.', $this->handlerClass)); diff --git a/src/Symfony/Component/Messenger/Middleware/HandleMessageMiddleware.php b/src/Symfony/Component/Messenger/Middleware/HandleMessageMiddleware.php index c4e4a2d02b676..f8e6f3171d81f 100644 --- a/src/Symfony/Component/Messenger/Middleware/HandleMessageMiddleware.php +++ b/src/Symfony/Component/Messenger/Middleware/HandleMessageMiddleware.php @@ -64,7 +64,7 @@ public function handle(Envelope $envelope, StackInterface $stack): Envelope /** @var AckStamp $ackStamp */ if ($batchHandler && $ackStamp = $envelope->last(AckStamp::class)) { - $ack = new Acknowledger(get_debug_type($batchHandler), static function (\Throwable $e = null, $result = null) use ($envelope, $ackStamp, $handlerDescriptor) { + $ack = new Acknowledger(get_debug_type($batchHandler), static function (?\Throwable $e = null, $result = null) use ($envelope, $ackStamp, $handlerDescriptor) { if (null !== $e) { $e = new HandlerFailedException($envelope, [$handlerDescriptor->getName() => $e]); } else { diff --git a/src/Symfony/Component/Messenger/Middleware/StackMiddleware.php b/src/Symfony/Component/Messenger/Middleware/StackMiddleware.php index 30195de65e935..70db0eb80ba3b 100644 --- a/src/Symfony/Component/Messenger/Middleware/StackMiddleware.php +++ b/src/Symfony/Component/Messenger/Middleware/StackMiddleware.php @@ -24,7 +24,7 @@ class StackMiddleware implements MiddlewareInterface, StackInterface /** * @param iterable|MiddlewareInterface|null $middlewareIterator */ - public function __construct(iterable|MiddlewareInterface $middlewareIterator = null) + public function __construct(iterable|MiddlewareInterface|null $middlewareIterator = null) { $this->stack = new MiddlewareStack(); diff --git a/src/Symfony/Component/Messenger/Retry/MultiplierRetryStrategy.php b/src/Symfony/Component/Messenger/Retry/MultiplierRetryStrategy.php index 129c61f06245e..b61135882414a 100644 --- a/src/Symfony/Component/Messenger/Retry/MultiplierRetryStrategy.php +++ b/src/Symfony/Component/Messenger/Retry/MultiplierRetryStrategy.php @@ -73,7 +73,7 @@ public function __construct(int $maxRetries = 3, int $delayMilliseconds = 1000, /** * @param \Throwable|null $throwable The cause of the failed handling */ - public function isRetryable(Envelope $message, \Throwable $throwable = null): bool + public function isRetryable(Envelope $message, ?\Throwable $throwable = null): bool { $retries = RedeliveryStamp::getRetryCountFromEnvelope($message); @@ -83,7 +83,7 @@ public function isRetryable(Envelope $message, \Throwable $throwable = null): bo /** * @param \Throwable|null $throwable The cause of the failed handling */ - public function getWaitingTime(Envelope $message, \Throwable $throwable = null): int + public function getWaitingTime(Envelope $message, ?\Throwable $throwable = null): int { $retries = RedeliveryStamp::getRetryCountFromEnvelope($message); diff --git a/src/Symfony/Component/Messenger/Retry/RetryStrategyInterface.php b/src/Symfony/Component/Messenger/Retry/RetryStrategyInterface.php index 368ab3196f7b6..0a8223e5ac118 100644 --- a/src/Symfony/Component/Messenger/Retry/RetryStrategyInterface.php +++ b/src/Symfony/Component/Messenger/Retry/RetryStrategyInterface.php @@ -23,12 +23,12 @@ interface RetryStrategyInterface /** * @param \Throwable|null $throwable The cause of the failed handling */ - public function isRetryable(Envelope $message, \Throwable $throwable = null): bool; + public function isRetryable(Envelope $message, ?\Throwable $throwable = null): bool; /** * @param \Throwable|null $throwable The cause of the failed handling * * @return int The time to delay/wait in milliseconds */ - public function getWaitingTime(Envelope $message, \Throwable $throwable = null): int; + public function getWaitingTime(Envelope $message, ?\Throwable $throwable = null): int; } diff --git a/src/Symfony/Component/Messenger/RoutableMessageBus.php b/src/Symfony/Component/Messenger/RoutableMessageBus.php index 08f0d7882a820..a33be18559ddb 100644 --- a/src/Symfony/Component/Messenger/RoutableMessageBus.php +++ b/src/Symfony/Component/Messenger/RoutableMessageBus.php @@ -28,7 +28,7 @@ class RoutableMessageBus implements MessageBusInterface private ContainerInterface $busLocator; private ?MessageBusInterface $fallbackBus; - public function __construct(ContainerInterface $busLocator, MessageBusInterface $fallbackBus = null) + public function __construct(ContainerInterface $busLocator, ?MessageBusInterface $fallbackBus = null) { $this->busLocator = $busLocator; $this->fallbackBus = $fallbackBus; diff --git a/src/Symfony/Component/Messenger/Stamp/AckStamp.php b/src/Symfony/Component/Messenger/Stamp/AckStamp.php index 6f5743e5d117b..945d78803398d 100644 --- a/src/Symfony/Component/Messenger/Stamp/AckStamp.php +++ b/src/Symfony/Component/Messenger/Stamp/AckStamp.php @@ -26,7 +26,7 @@ public function __construct( ) { } - public function ack(Envelope $envelope, \Throwable $e = null): void + public function ack(Envelope $envelope, ?\Throwable $e = null): void { ($this->ack)($envelope, $e); } diff --git a/src/Symfony/Component/Messenger/Stamp/ErrorDetailsStamp.php b/src/Symfony/Component/Messenger/Stamp/ErrorDetailsStamp.php index 1bf4f9d30d542..9d0458f976816 100644 --- a/src/Symfony/Component/Messenger/Stamp/ErrorDetailsStamp.php +++ b/src/Symfony/Component/Messenger/Stamp/ErrorDetailsStamp.php @@ -24,7 +24,7 @@ final class ErrorDetailsStamp implements StampInterface private string $exceptionMessage; private ?FlattenException $flattenException; - public function __construct(string $exceptionClass, int|string $exceptionCode, string $exceptionMessage, FlattenException $flattenException = null) + public function __construct(string $exceptionClass, int|string $exceptionCode, string $exceptionMessage, ?FlattenException $flattenException = null) { $this->exceptionClass = $exceptionClass; $this->exceptionCode = $exceptionCode; diff --git a/src/Symfony/Component/Messenger/Stamp/RedeliveryStamp.php b/src/Symfony/Component/Messenger/Stamp/RedeliveryStamp.php index 5df028cf7c984..00e48fd9ce4e7 100644 --- a/src/Symfony/Component/Messenger/Stamp/RedeliveryStamp.php +++ b/src/Symfony/Component/Messenger/Stamp/RedeliveryStamp.php @@ -21,7 +21,7 @@ final class RedeliveryStamp implements StampInterface private int $retryCount; private \DateTimeInterface $redeliveredAt; - public function __construct(int $retryCount, \DateTimeInterface $redeliveredAt = null) + public function __construct(int $retryCount, ?\DateTimeInterface $redeliveredAt = null) { $this->retryCount = $retryCount; $this->redeliveredAt = $redeliveredAt ?? new \DateTimeImmutable(); diff --git a/src/Symfony/Component/Messenger/Stamp/SentStamp.php b/src/Symfony/Component/Messenger/Stamp/SentStamp.php index d1092db90e99b..753b2cc91b008 100644 --- a/src/Symfony/Component/Messenger/Stamp/SentStamp.php +++ b/src/Symfony/Component/Messenger/Stamp/SentStamp.php @@ -23,7 +23,7 @@ final class SentStamp implements NonSendableStampInterface private string $senderClass; private ?string $senderAlias; - public function __construct(string $senderClass, string $senderAlias = null) + public function __construct(string $senderClass, ?string $senderAlias = null) { $this->senderAlias = $senderAlias; $this->senderClass = $senderClass; diff --git a/src/Symfony/Component/Messenger/Test/Middleware/MiddlewareTestCase.php b/src/Symfony/Component/Messenger/Test/Middleware/MiddlewareTestCase.php index 5563537c29910..0231bb4b934e3 100644 --- a/src/Symfony/Component/Messenger/Test/Middleware/MiddlewareTestCase.php +++ b/src/Symfony/Component/Messenger/Test/Middleware/MiddlewareTestCase.php @@ -44,7 +44,7 @@ protected function getStackMock(bool $nextIsCalled = true) return new StackMiddleware($nextMiddleware); } - protected function getThrowingStackMock(\Throwable $throwable = null) + protected function getThrowingStackMock(?\Throwable $throwable = null) { $nextMiddleware = $this->createMock(MiddlewareInterface::class); $nextMiddleware diff --git a/src/Symfony/Component/Messenger/Tests/Command/ConsumeMessagesCommandTest.php b/src/Symfony/Component/Messenger/Tests/Command/ConsumeMessagesCommandTest.php index 6a05113d12d76..7790e074ad609 100644 --- a/src/Symfony/Component/Messenger/Tests/Command/ConsumeMessagesCommandTest.php +++ b/src/Symfony/Component/Messenger/Tests/Command/ConsumeMessagesCommandTest.php @@ -16,7 +16,7 @@ use Symfony\Component\Console\Exception\InvalidOptionException; use Symfony\Component\Console\Tester\CommandCompletionTester; use Symfony\Component\Console\Tester\CommandTester; -use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\DependencyInjection\Container; use Symfony\Component\DependencyInjection\ServiceLocator; use Symfony\Component\EventDispatcher\EventDispatcher; use Symfony\Component\EventDispatcher\EventDispatcherInterface; @@ -47,16 +47,14 @@ public function testBasicRun() $receiver = $this->createMock(ReceiverInterface::class); $receiver->expects($this->once())->method('get')->willReturn([$envelope]); - $receiverLocator = $this->createMock(ContainerInterface::class); - $receiverLocator->expects($this->once())->method('has')->with('dummy-receiver')->willReturn(true); - $receiverLocator->expects($this->once())->method('get')->with('dummy-receiver')->willReturn($receiver); + $receiverLocator = new Container(); + $receiverLocator->set('dummy-receiver', $receiver); $bus = $this->createMock(MessageBusInterface::class); $bus->expects($this->once())->method('dispatch'); - $busLocator = $this->createMock(ContainerInterface::class); - $busLocator->expects($this->once())->method('has')->with('dummy-bus')->willReturn(true); - $busLocator->expects($this->once())->method('get')->with('dummy-bus')->willReturn($bus); + $busLocator = new Container(); + $busLocator->set('dummy-bus', $bus); $command = new ConsumeMessagesCommand(new RoutableMessageBus($busLocator), $receiverLocator, new EventDispatcher()); @@ -79,16 +77,14 @@ public function testRunWithBusOption() $receiver = $this->createMock(ReceiverInterface::class); $receiver->expects($this->once())->method('get')->willReturn([$envelope]); - $receiverLocator = $this->createMock(ContainerInterface::class); - $receiverLocator->expects($this->once())->method('has')->with('dummy-receiver')->willReturn(true); - $receiverLocator->expects($this->once())->method('get')->with('dummy-receiver')->willReturn($receiver); + $receiverLocator = new Container(); + $receiverLocator->set('dummy-receiver', $receiver); $bus = $this->createMock(MessageBusInterface::class); $bus->expects($this->once())->method('dispatch'); - $busLocator = $this->createMock(ContainerInterface::class); - $busLocator->expects($this->once())->method('has')->with('dummy-bus')->willReturn(true); - $busLocator->expects($this->once())->method('get')->with('dummy-bus')->willReturn($bus); + $busLocator = new Container(); + $busLocator->set('dummy-bus', $bus); $command = new ConsumeMessagesCommand(new RoutableMessageBus($busLocator), $receiverLocator, new EventDispatcher()); @@ -125,9 +121,8 @@ public function testRunWithResetServicesOption(bool $shouldReset) ]); $msgCount = 3; - $receiverLocator = $this->createMock(ContainerInterface::class); - $receiverLocator->expects($this->once())->method('has')->with('dummy-receiver')->willReturn(true); - $receiverLocator->expects($this->once())->method('get')->with('dummy-receiver')->willReturn($receiver); + $receiverLocator = new Container(); + $receiverLocator->set('dummy-receiver', $receiver); $bus = $this->createMock(RoutableMessageBus::class); $bus->expects($this->exactly($msgCount))->method('dispatch'); @@ -155,12 +150,10 @@ public function testRunWithResetServicesOption(bool $shouldReset) */ public function testRunWithInvalidOption(string $option, string $value, string $expectedMessage) { - $receiverLocator = $this->createMock(ContainerInterface::class); - $receiverLocator->expects($this->once())->method('has')->with('dummy-receiver')->willReturn(true); + $receiverLocator = new Container(); + $receiverLocator->set('dummy-receiver', new \stdClass()); - $busLocator = $this->createMock(ContainerInterface::class); - - $command = new ConsumeMessagesCommand(new RoutableMessageBus($busLocator), $receiverLocator, new EventDispatcher()); + $command = new ConsumeMessagesCommand(new RoutableMessageBus(new Container()), $receiverLocator, new EventDispatcher()); $application = new Application(); $application->add($command); @@ -190,15 +183,13 @@ public function testRunWithTimeLimit() $receiver = $this->createMock(ReceiverInterface::class); $receiver->method('get')->willReturn([$envelope]); - $receiverLocator = $this->createMock(ContainerInterface::class); - $receiverLocator->method('has')->with('dummy-receiver')->willReturn(true); - $receiverLocator->method('get')->with('dummy-receiver')->willReturn($receiver); + $receiverLocator = new Container(); + $receiverLocator->set('dummy-receiver', $receiver); $bus = $this->createMock(MessageBusInterface::class); - $busLocator = $this->createMock(ContainerInterface::class); - $busLocator->method('has')->with('dummy-bus')->willReturn(true); - $busLocator->method('get')->with('dummy-bus')->willReturn($bus); + $busLocator = new Container(); + $busLocator->set('dummy-bus', $bus); $command = new ConsumeMessagesCommand(new RoutableMessageBus($busLocator), $receiverLocator, new EventDispatcher()); @@ -224,21 +215,15 @@ public function testRunWithAllOption() $receiver2 = $this->createMock(ReceiverInterface::class); $receiver2->method('get')->willReturn([$envelope2]); - $receiverLocator = $this->createMock(ContainerInterface::class); - $receiverLocator->expects($this->exactly(2)) - ->method('has') - ->willReturnCallback(static fn (string $id): bool => \in_array($id, ['dummy-receiver1', 'dummy-receiver2'], true)); - - $receiverLocator->expects($this->exactly(2)) - ->method('get') - ->willReturnCallback(static fn (string $id): ReceiverInterface => 'dummy-receiver1' === $id ? $receiver1 : $receiver2); + $receiverLocator = new Container(); + $receiverLocator->set('dummy-receiver1', $receiver1); + $receiverLocator->set('dummy-receiver2', $receiver2); $bus = $this->createMock(MessageBusInterface::class); $bus->expects($this->exactly(2))->method('dispatch'); - $busLocator = $this->createMock(ContainerInterface::class); - $busLocator->expects($this->exactly(2))->method('has')->with('dummy-bus')->willReturn(true); - $busLocator->expects($this->exactly(2))->method('get')->with('dummy-bus')->willReturn($bus); + $busLocator = new Container(); + $busLocator->set('dummy-bus', $bus); $command = new ConsumeMessagesCommand( new RoutableMessageBus($busLocator), @@ -264,8 +249,7 @@ public function testRunWithAllOption() public function testComplete(array $input, array $expectedSuggestions) { $bus = $this->createMock(RoutableMessageBus::class); - $receiverLocator = $this->createMock(ContainerInterface::class); - $command = new ConsumeMessagesCommand($bus, $receiverLocator, new EventDispatcher(), null, ['async', 'async_high', 'failed'], null, ['messenger.bus.default']); + $command = new ConsumeMessagesCommand($bus, new Container(), new EventDispatcher(), null, ['async', 'async_high', 'failed'], null, ['messenger.bus.default']); $tester = new CommandCompletionTester($command); $suggestions = $tester->complete($input); $this->assertSame($expectedSuggestions, $suggestions); diff --git a/src/Symfony/Component/Messenger/Tests/EventListener/SendFailedMessageForRetryListenerTest.php b/src/Symfony/Component/Messenger/Tests/EventListener/SendFailedMessageForRetryListenerTest.php index fccdae9bfa215..6edf81dee4865 100644 --- a/src/Symfony/Component/Messenger/Tests/EventListener/SendFailedMessageForRetryListenerTest.php +++ b/src/Symfony/Component/Messenger/Tests/EventListener/SendFailedMessageForRetryListenerTest.php @@ -12,8 +12,8 @@ namespace Symfony\Component\Messenger\Tests\EventListener; use PHPUnit\Framework\TestCase; -use Psr\Container\ContainerInterface; use Psr\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\DependencyInjection\Container; use Symfony\Component\Messenger\Envelope; use Symfony\Component\Messenger\Event\WorkerMessageFailedEvent; use Symfony\Component\Messenger\EventListener\SendFailedMessageForRetryListener; @@ -27,13 +27,11 @@ class SendFailedMessageForRetryListenerTest extends TestCase { public function testNoRetryStrategyCausesNoRetry() { - $senderLocator = $this->createMock(ContainerInterface::class); - $senderLocator->expects($this->never())->method('has'); - $senderLocator->expects($this->never())->method('get'); - $retryStrategyLocator = $this->createMock(ContainerInterface::class); - $retryStrategyLocator->expects($this->once())->method('has')->willReturn(false); - - $listener = new SendFailedMessageForRetryListener($senderLocator, $retryStrategyLocator); + $sender = $this->createMock(SenderInterface::class); + $sender->expects($this->never())->method('send'); + $sendersLocator = new Container(); + $sendersLocator->set('my_receiver', $sender); + $listener = new SendFailedMessageForRetryListener($sendersLocator, new Container()); $exception = new \Exception('no!'); $envelope = new Envelope(new \stdClass()); @@ -59,15 +57,13 @@ public function testRecoverableStrategyCausesRetry() return $envelope; }); - $senderLocator = $this->createMock(ContainerInterface::class); - $senderLocator->expects($this->once())->method('has')->willReturn(true); - $senderLocator->expects($this->once())->method('get')->willReturn($sender); - $retryStategy = $this->createMock(RetryStrategyInterface::class); - $retryStategy->expects($this->never())->method('isRetryable'); - $retryStategy->expects($this->once())->method('getWaitingTime')->willReturn(1000); - $retryStrategyLocator = $this->createMock(ContainerInterface::class); - $retryStrategyLocator->expects($this->once())->method('has')->willReturn(true); - $retryStrategyLocator->expects($this->once())->method('get')->willReturn($retryStategy); + $senderLocator = new Container(); + $senderLocator->set('my_receiver', $sender); + $retryStrategy = $this->createMock(RetryStrategyInterface::class); + $retryStrategy->expects($this->never())->method('isRetryable'); + $retryStrategy->expects($this->once())->method('getWaitingTime')->willReturn(1000); + $retryStrategyLocator = new Container(); + $retryStrategyLocator->set('my_receiver', $retryStrategy); $listener = new SendFailedMessageForRetryListener($senderLocator, $retryStrategyLocator); @@ -98,15 +94,13 @@ public function testEnvelopeIsSentToTransportOnRetry() return $envelope; }); - $senderLocator = $this->createMock(ContainerInterface::class); - $senderLocator->expects($this->once())->method('has')->willReturn(true); - $senderLocator->expects($this->once())->method('get')->willReturn($sender); - $retryStategy = $this->createMock(RetryStrategyInterface::class); - $retryStategy->expects($this->once())->method('isRetryable')->willReturn(true); - $retryStategy->expects($this->once())->method('getWaitingTime')->willReturn(1000); - $retryStrategyLocator = $this->createMock(ContainerInterface::class); - $retryStrategyLocator->expects($this->once())->method('has')->willReturn(true); - $retryStrategyLocator->expects($this->once())->method('get')->willReturn($retryStategy); + $senderLocator = new Container(); + $senderLocator->set('my_receiver', $sender); + $retryStrategy = $this->createMock(RetryStrategyInterface::class); + $retryStrategy->expects($this->once())->method('isRetryable')->willReturn(true); + $retryStrategy->expects($this->once())->method('getWaitingTime')->willReturn(1000); + $retryStrategyLocator = new Container(); + $retryStrategyLocator->set('my_receiver', $retryStrategy); $eventDispatcher = $this->createMock(EventDispatcherInterface::class); $eventDispatcher->expects($this->once())->method('dispatch'); @@ -138,15 +132,13 @@ public function testEnvelopeIsSentToTransportOnRetryWithExceptionPassedToRetrySt return $envelope; }); - $senderLocator = $this->createMock(ContainerInterface::class); - $senderLocator->expects($this->once())->method('has')->willReturn(true); - $senderLocator->expects($this->once())->method('get')->willReturn($sender); - $retryStategy = $this->createMock(RetryStrategyInterface::class); - $retryStategy->expects($this->once())->method('isRetryable')->with($envelope, $exception)->willReturn(true); - $retryStategy->expects($this->once())->method('getWaitingTime')->with($envelope, $exception)->willReturn(1000); - $retryStrategyLocator = $this->createMock(ContainerInterface::class); - $retryStrategyLocator->expects($this->once())->method('has')->willReturn(true); - $retryStrategyLocator->expects($this->once())->method('get')->willReturn($retryStategy); + $senderLocator = new Container(); + $senderLocator->set('my_receiver', $sender); + $retryStrategy = $this->createMock(RetryStrategyInterface::class); + $retryStrategy->expects($this->once())->method('isRetryable')->with($envelope, $exception)->willReturn(true); + $retryStrategy->expects($this->once())->method('getWaitingTime')->with($envelope, $exception)->willReturn(1000); + $retryStrategyLocator = new Container(); + $retryStrategyLocator->set('my_receiver', $retryStrategy); $listener = new SendFailedMessageForRetryListener($senderLocator, $retryStrategyLocator); @@ -174,15 +166,13 @@ public function testEnvelopeKeepOnlyTheLast10Stamps() return $envelope; }); - $senderLocator = $this->createMock(ContainerInterface::class); - $senderLocator->expects($this->once())->method('has')->willReturn(true); - $senderLocator->expects($this->once())->method('get')->willReturn($sender); + $senderLocator = new Container(); + $senderLocator->set('my_receiver', $sender); $retryStrategy = $this->createMock(RetryStrategyInterface::class); $retryStrategy->expects($this->once())->method('isRetryable')->willReturn(true); $retryStrategy->expects($this->once())->method('getWaitingTime')->willReturn(1000); - $retryStrategyLocator = $this->createMock(ContainerInterface::class); - $retryStrategyLocator->expects($this->once())->method('has')->willReturn(true); - $retryStrategyLocator->expects($this->once())->method('get')->willReturn($retryStrategy); + $retryStrategyLocator = new Container(); + $retryStrategyLocator->set('my_receiver', $retryStrategy); $listener = new SendFailedMessageForRetryListener($senderLocator, $retryStrategyLocator); diff --git a/src/Symfony/Component/Messenger/Tests/FailureIntegrationTest.php b/src/Symfony/Component/Messenger/Tests/FailureIntegrationTest.php index 654cae4b93ae1..41367f65ee96a 100644 --- a/src/Symfony/Component/Messenger/Tests/FailureIntegrationTest.php +++ b/src/Symfony/Component/Messenger/Tests/FailureIntegrationTest.php @@ -12,8 +12,8 @@ namespace Symfony\Component\Messenger\Tests; use PHPUnit\Framework\TestCase; -use Psr\Container\ContainerInterface; use Psr\Log\NullLogger; +use Symfony\Component\DependencyInjection\Container; use Symfony\Component\DependencyInjection\ServiceLocator; use Symfony\Component\EventDispatcher\EventDispatcher; use Symfony\Component\Messenger\Envelope; @@ -61,25 +61,20 @@ public function testRequeueMechanism() 'the_failure_transport' => $failureTransport, ]; - $locator = $this->createMock(ContainerInterface::class); - $locator->expects($this->any()) - ->method('has') - ->willReturn(true); - $locator->expects($this->any()) - ->method('get') - ->willReturnCallback(fn ($transportName) => $transports[$transportName]); + $locator = new Container(); + + foreach ($transports as $transportName => $transport) { + $locator->set($transportName, $transport); + } + $senderLocator = new SendersLocator( [DummyMessage::class => ['transport1', 'transport2']], $locator ); - $retryStrategyLocator = $this->createMock(ContainerInterface::class); - $retryStrategyLocator->expects($this->any()) - ->method('has') - ->willReturn(true); - $retryStrategyLocator->expects($this->any()) - ->method('get') - ->willReturn(new MultiplierRetryStrategy(1)); + $retryStrategyLocator = new Container(); + $retryStrategyLocator->set('the_failure_transport', new MultiplierRetryStrategy(1)); + $retryStrategyLocator->set('transport1', new MultiplierRetryStrategy(1)); // using to so we can lazily get the bus later and avoid circular problem $transport1HandlerThatFails = new DummyTestHandler(true); @@ -247,27 +242,17 @@ public function testMultipleFailedTransportsWithoutGlobalFailureTransport() 'the_failure_transport2' => $failureTransport2, ]; - $locator = $this->createMock(ContainerInterface::class); - $locator->expects($this->any()) - ->method('has') - ->willReturn(true); - $locator->expects($this->any()) - ->method('get') - ->willReturnCallback(fn ($transportName) => $transports[$transportName]); + $locator = new Container(); + + foreach ($transports as $transportName => $transport) { + $locator->set($transportName, $transport); + } + $senderLocator = new SendersLocator( [DummyMessage::class => ['transport1', 'transport2']], $locator ); - // retry strategy with zero retries so it goes to the failed transport after failure - $retryStrategyLocator = $this->createMock(ContainerInterface::class); - $retryStrategyLocator->expects($this->any()) - ->method('has') - ->willReturn(true); - $retryStrategyLocator->expects($this->any()) - ->method('get') - ->willReturn(new MultiplierRetryStrategy(0)); - // using to so we can lazily get the bus later and avoid circular problem $transport1HandlerThatFails = new DummyTestHandler(true); $transport2HandlerThatFails = new DummyTestHandler(true); @@ -289,7 +274,7 @@ public function testMultipleFailedTransportsWithoutGlobalFailureTransport() new HandleMessageMiddleware($handlerLocator), ]); - $dispatcher->addSubscriber(new SendFailedMessageForRetryListener($locator, $retryStrategyLocator)); + $dispatcher->addSubscriber(new SendFailedMessageForRetryListener($locator, new Container())); $dispatcher->addSubscriber(new SendFailedMessageToFailureTransportListener( $sendersLocatorFailureTransport, new NullLogger() @@ -368,22 +353,16 @@ public function testStampsAddedByMiddlewaresDontDisappearWhenDelayedMessageFails 'transport1' => $transport1, ]; - $locator = $this->createMock(ContainerInterface::class); - $locator->expects($this->any()) - ->method('has') - ->willReturn(true); - $locator->expects($this->any()) - ->method('get') - ->willReturnCallback(fn ($transportName) => $transports[$transportName]); + $locator = new Container(); + + foreach ($transports as $transportName => $transport) { + $locator->set($transportName, $transport); + } + $senderLocator = new SendersLocator([], $locator); - $retryStrategyLocator = $this->createMock(ContainerInterface::class); - $retryStrategyLocator->expects($this->any()) - ->method('has') - ->willReturn(true); - $retryStrategyLocator->expects($this->any()) - ->method('get') - ->willReturn(new MultiplierRetryStrategy(1)); + $retryStrategyLocator = new Container(); + $retryStrategyLocator->set('transport1', new MultiplierRetryStrategy(1)); $syncHandlerThatFails = new DummyTestHandler(true); diff --git a/src/Symfony/Component/Messenger/Tests/Middleware/HandleMessageMiddlewareTest.php b/src/Symfony/Component/Messenger/Tests/Middleware/HandleMessageMiddlewareTest.php index 13b0bb856de19..2b31b8c1fbf9b 100644 --- a/src/Symfony/Component/Messenger/Tests/Middleware/HandleMessageMiddlewareTest.php +++ b/src/Symfony/Component/Messenger/Tests/Middleware/HandleMessageMiddlewareTest.php @@ -204,7 +204,7 @@ public function testBatchHandler() use BatchHandlerTrait; - public function __invoke(DummyMessage $message, Acknowledger $ack = null) + public function __invoke(DummyMessage $message, ?Acknowledger $ack = null) { return $this->handle($message, $ack); } @@ -229,7 +229,7 @@ private function process(array $jobs): void ])); $ackedMessages = []; - $ack = static function (Envelope $envelope, \Throwable $e = null) use (&$ackedMessages) { + $ack = static function (Envelope $envelope, ?\Throwable $e = null) use (&$ackedMessages) { if (null !== $e) { throw $e; } @@ -258,7 +258,7 @@ public function testBatchHandlerNoAck() $handler = new class() implements BatchHandlerInterface { use BatchHandlerTrait; - public function __invoke(DummyMessage $message, Acknowledger $ack = null) + public function __invoke(DummyMessage $message, ?Acknowledger $ack = null) { return $this->handle($message, $ack); } @@ -278,7 +278,7 @@ private function process(array $jobs): void ])); $error = null; - $ack = static function (Envelope $envelope, \Throwable $e = null) use (&$error) { + $ack = static function (Envelope $envelope, ?\Throwable $e = null) use (&$error) { $error = $e; }; @@ -295,7 +295,7 @@ public function testBatchHandlerNoBatch() use BatchHandlerTrait; - public function __invoke(DummyMessage $message, Acknowledger $ack = null) + public function __invoke(DummyMessage $message, ?Acknowledger $ack = null) { return $this->handle($message, $ack); } diff --git a/src/Symfony/Component/Messenger/Tests/Middleware/SendMessageMiddlewareTest.php b/src/Symfony/Component/Messenger/Tests/Middleware/SendMessageMiddlewareTest.php index df402764c1e98..59027f2045b00 100644 --- a/src/Symfony/Component/Messenger/Tests/Middleware/SendMessageMiddlewareTest.php +++ b/src/Symfony/Component/Messenger/Tests/Middleware/SendMessageMiddlewareTest.php @@ -11,8 +11,8 @@ namespace Symfony\Component\Messenger\Tests\Middleware; -use Psr\Container\ContainerInterface; use Psr\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\DependencyInjection\Container; use Symfony\Component\Messenger\Envelope; use Symfony\Component\Messenger\Event\SendMessageToTransportsEvent; use Symfony\Component\Messenger\Exception\NoSenderForMessageException; @@ -234,13 +234,11 @@ public function testAllowNoRouting() private function createSendersLocator(array $sendersMap, array $senders): SendersLocator { - $container = $this->createMock(ContainerInterface::class); - $container->expects($this->any()) - ->method('has') - ->willReturnCallback(fn ($id) => isset($senders[$id])); - $container->expects($this->any()) - ->method('get') - ->willReturnCallback(fn ($id) => $senders[$id]); + $container = new Container(); + + foreach ($senders as $id => $sender) { + $container->set($id, $sender); + } return new SendersLocator($sendersMap, $container); } diff --git a/src/Symfony/Component/Messenger/Tests/Middleware/TraceableMiddlewareTest.php b/src/Symfony/Component/Messenger/Tests/Middleware/TraceableMiddlewareTest.php index a2b2514374673..7af32bbcef1f0 100644 --- a/src/Symfony/Component/Messenger/Tests/Middleware/TraceableMiddlewareTest.php +++ b/src/Symfony/Component/Messenger/Tests/Middleware/TraceableMiddlewareTest.php @@ -53,7 +53,7 @@ public function handle(Envelope $envelope, StackInterface $stack): Envelope $stopwatch->expects($this->exactly(2)) ->method('start') - ->willReturnCallback(function (string $name, string $category = null) use (&$series) { + ->willReturnCallback(function (string $name, ?string $category = null) use (&$series) { [$constraint, $expectedCategory] = array_shift($series); $constraint->evaluate($name); @@ -195,7 +195,7 @@ class_exists(TraceableMiddleware::class); ]; $stopwatch->expects($this->exactly(4)) ->method('start') - ->willReturnCallback(function (string $name, string $category = null) use (&$startSeries) { + ->willReturnCallback(function (string $name, ?string $category = null) use (&$startSeries) { [$constraint, $expectedCategory] = array_shift($startSeries); $constraint->evaluate($name); diff --git a/src/Symfony/Component/Messenger/Tests/RoutableMessageBusTest.php b/src/Symfony/Component/Messenger/Tests/RoutableMessageBusTest.php index a41373523dad6..6a67a54dac961 100644 --- a/src/Symfony/Component/Messenger/Tests/RoutableMessageBusTest.php +++ b/src/Symfony/Component/Messenger/Tests/RoutableMessageBusTest.php @@ -12,7 +12,7 @@ namespace Symfony\Component\Messenger\Tests; use PHPUnit\Framework\TestCase; -use Psr\Container\ContainerInterface; +use Symfony\Component\DependencyInjection\Container; use Symfony\Component\Messenger\Envelope; use Symfony\Component\Messenger\Exception\InvalidArgumentException; use Symfony\Component\Messenger\MessageBusInterface; @@ -29,9 +29,8 @@ public function testItRoutesToTheCorrectBus() $bus1 = $this->createMock(MessageBusInterface::class); $bus2 = $this->createMock(MessageBusInterface::class); - $container = $this->createMock(ContainerInterface::class); - $container->expects($this->once())->method('has')->with('foo_bus')->willReturn(true); - $container->expects($this->once())->method('get')->willReturn($bus2); + $container = new Container(); + $container->set('foo_bus', $bus2); $stamp = new DelayStamp(5); $bus1->expects($this->never())->method('dispatch'); @@ -49,9 +48,7 @@ public function testItRoutesToDefaultBus() $defaultBus->expects($this->once())->method('dispatch')->with($envelope, [$stamp]) ->willReturn($envelope); - $container = $this->createMock(ContainerInterface::class); - - $routableBus = new RoutableMessageBus($container, $defaultBus); + $routableBus = new RoutableMessageBus(new Container(), $defaultBus); $this->assertSame($envelope, $routableBus->dispatch($envelope, [$stamp])); } @@ -65,8 +62,7 @@ public function testItExceptionOnBusNotFound() new BusNameStamp('my_cool_bus'), ]); - $container = $this->createMock(ContainerInterface::class); - $routableBus = new RoutableMessageBus($container); + $routableBus = new RoutableMessageBus(new Container()); $routableBus->dispatch($envelope); } @@ -77,8 +73,7 @@ public function testItExceptionOnDefaultBusNotFound() $envelope = new Envelope(new \stdClass()); - $container = $this->createMock(ContainerInterface::class); - $routableBus = new RoutableMessageBus($container); + $routableBus = new RoutableMessageBus(new Container()); $routableBus->dispatch($envelope); } } diff --git a/src/Symfony/Component/Messenger/Tests/Transport/Sender/SendersLocatorTest.php b/src/Symfony/Component/Messenger/Tests/Transport/Sender/SendersLocatorTest.php index 6b005cfd86e27..3aafa82f73d73 100644 --- a/src/Symfony/Component/Messenger/Tests/Transport/Sender/SendersLocatorTest.php +++ b/src/Symfony/Component/Messenger/Tests/Transport/Sender/SendersLocatorTest.php @@ -13,6 +13,7 @@ use PHPUnit\Framework\TestCase; use Psr\Container\ContainerInterface; +use Symfony\Component\DependencyInjection\Container; use Symfony\Component\Messenger\Envelope; use Symfony\Component\Messenger\Stamp\TransportNamesStamp; use Symfony\Component\Messenger\Tests\Fixtures\DummyMessage; @@ -73,13 +74,11 @@ public function testSendersMapWithFallback() private function createContainer(array $senders): ContainerInterface { - $container = $this->createMock(ContainerInterface::class); - $container->expects($this->any()) - ->method('has') - ->willReturnCallback(fn ($id) => isset($senders[$id])); - $container->expects($this->any()) - ->method('get') - ->willReturnCallback(fn ($id) => $senders[$id]); + $container = new Container(); + + foreach ($senders as $id => $sender) { + $container->set($id, $sender); + } return $container; } diff --git a/src/Symfony/Component/Messenger/Tests/Transport/Serialization/PhpSerializerTest.php b/src/Symfony/Component/Messenger/Tests/Transport/Serialization/PhpSerializerTest.php index c83606a59fdb3..7ff0a00c03831 100644 --- a/src/Symfony/Component/Messenger/Tests/Transport/Serialization/PhpSerializerTest.php +++ b/src/Symfony/Component/Messenger/Tests/Transport/Serialization/PhpSerializerTest.php @@ -33,21 +33,21 @@ public function testEncodedIsDecodable() public function testDecodingFailsWithMissingBodyKey() { + $serializer = $this->createPhpSerializer(); + $this->expectException(MessageDecodingFailedException::class); $this->expectExceptionMessage('Encoded envelope should have at least a "body", or maybe you should implement your own serializer'); - $serializer = $this->createPhpSerializer(); - $serializer->decode([]); } public function testDecodingFailsWithBadFormat() { + $serializer = $this->createPhpSerializer(); + $this->expectException(MessageDecodingFailedException::class); $this->expectExceptionMessageMatches('/Could not decode/'); - $serializer = $this->createPhpSerializer(); - $serializer->decode([ 'body' => '{"message": "bar"}', ]); @@ -55,11 +55,11 @@ public function testDecodingFailsWithBadFormat() public function testDecodingFailsWithBadBase64Body() { + $serializer = $this->createPhpSerializer(); + $this->expectException(MessageDecodingFailedException::class); $this->expectExceptionMessageMatches('/Could not decode/'); - $serializer = $this->createPhpSerializer(); - $serializer->decode([ 'body' => 'x', ]); @@ -67,16 +67,29 @@ public function testDecodingFailsWithBadBase64Body() public function testDecodingFailsWithBadClass() { + $serializer = $this->createPhpSerializer(); + $this->expectException(MessageDecodingFailedException::class); $this->expectExceptionMessageMatches('/class "ReceivedSt0mp" not found/'); - $serializer = $this->createPhpSerializer(); - $serializer->decode([ 'body' => 'O:13:"ReceivedSt0mp":0:{}', ]); } + public function testDecodingFailsForPropertyTypeMismatch() + { + $serializer = $this->createPhpSerializer(); + $encodedEnvelope = $serializer->encode(new Envelope(new DummyMessage('true'))); + // Simulate a change of property type in the code base + $encodedEnvelope['body'] = str_replace('s:4:\"true\"', 'b:1', $encodedEnvelope['body']); + + $this->expectException(MessageDecodingFailedException::class); + $this->expectExceptionMessageMatches('/Could not decode/'); + + $serializer->decode($encodedEnvelope); + } + public function testEncodedSkipsNonEncodeableStamps() { $serializer = $this->createPhpSerializer(); diff --git a/src/Symfony/Component/Messenger/Tests/WorkerTest.php b/src/Symfony/Component/Messenger/Tests/WorkerTest.php index c9e1f2f7e87ee..55c28357d1f48 100644 --- a/src/Symfony/Component/Messenger/Tests/WorkerTest.php +++ b/src/Symfony/Component/Messenger/Tests/WorkerTest.php @@ -615,7 +615,7 @@ class DummyBatchHandler implements BatchHandlerInterface public array $processedMessages; - public function __invoke(DummyMessage $message, Acknowledger $ack = null) + public function __invoke(DummyMessage $message, ?Acknowledger $ack = null) { return $this->handle($message, $ack); } diff --git a/src/Symfony/Component/Messenger/Transport/Receiver/ListableReceiverInterface.php b/src/Symfony/Component/Messenger/Transport/Receiver/ListableReceiverInterface.php index 3b5b98b500ea1..32f769ed17709 100644 --- a/src/Symfony/Component/Messenger/Transport/Receiver/ListableReceiverInterface.php +++ b/src/Symfony/Component/Messenger/Transport/Receiver/ListableReceiverInterface.php @@ -29,7 +29,7 @@ interface ListableReceiverInterface extends ReceiverInterface * * @return Envelope[]|iterable */ - public function all(int $limit = null): iterable; + public function all(?int $limit = null): iterable; /** * Returns the Envelope by id or none. diff --git a/src/Symfony/Component/Messenger/Transport/Serialization/Normalizer/FlattenExceptionNormalizer.php b/src/Symfony/Component/Messenger/Transport/Serialization/Normalizer/FlattenExceptionNormalizer.php index 93044e2319418..3f670dc6a31f5 100644 --- a/src/Symfony/Component/Messenger/Transport/Serialization/Normalizer/FlattenExceptionNormalizer.php +++ b/src/Symfony/Component/Messenger/Transport/Serialization/Normalizer/FlattenExceptionNormalizer.php @@ -26,7 +26,7 @@ final class FlattenExceptionNormalizer implements DenormalizerInterface, Normali { use NormalizerAwareTrait; - public function normalize(mixed $object, string $format = null, array $context = []): array + public function normalize(mixed $object, ?string $format = null, array $context = []): array { $normalized = [ 'message' => $object->getMessage(), @@ -52,12 +52,12 @@ public function getSupportedTypes(?string $format): array ]; } - public function supportsNormalization(mixed $data, string $format = null, array $context = []): bool + public function supportsNormalization(mixed $data, ?string $format = null, array $context = []): bool { return $data instanceof FlattenException && ($context[Serializer::MESSENGER_SERIALIZATION_CONTEXT] ?? false); } - public function denormalize(mixed $data, string $type, string $format = null, array $context = []): FlattenException + public function denormalize(mixed $data, string $type, ?string $format = null, array $context = []): FlattenException { $object = new FlattenException(); @@ -83,7 +83,7 @@ public function denormalize(mixed $data, string $type, string $format = null, ar return $object; } - public function supportsDenormalization(mixed $data, string $type, string $format = null, array $context = []): bool + public function supportsDenormalization(mixed $data, string $type, ?string $format = null, array $context = []): bool { return FlattenException::class === $type && ($context[Serializer::MESSENGER_SERIALIZATION_CONTEXT] ?? false); } diff --git a/src/Symfony/Component/Messenger/Transport/Serialization/PhpSerializer.php b/src/Symfony/Component/Messenger/Transport/Serialization/PhpSerializer.php index b1cb42d7e3dba..f9cd992cd4e10 100644 --- a/src/Symfony/Component/Messenger/Transport/Serialization/PhpSerializer.php +++ b/src/Symfony/Component/Messenger/Transport/Serialization/PhpSerializer.php @@ -75,16 +75,14 @@ private function safelyUnserialize(string $contents): Envelope throw new MessageDecodingFailedException('Could not decode an empty message using PHP serialization.'); } - $signalingException = new MessageDecodingFailedException(sprintf('Could not decode message using PHP serialization: %s.', $contents)); - if ($this->acceptPhpIncompleteClass) { $prevUnserializeHandler = ini_set('unserialize_callback_func', null); } else { $prevUnserializeHandler = ini_set('unserialize_callback_func', self::class.'::handleUnserializeCallback'); } - $prevErrorHandler = set_error_handler(function ($type, $msg, $file, $line, $context = []) use (&$prevErrorHandler, $signalingException) { + $prevErrorHandler = set_error_handler(function ($type, $msg, $file, $line, $context = []) use (&$prevErrorHandler) { if (__FILE__ === $file) { - throw $signalingException; + throw new \ErrorException($msg, 0, $type, $file, $line); } return $prevErrorHandler ? $prevErrorHandler($type, $msg, $file, $line, $context) : false; @@ -93,13 +91,19 @@ private function safelyUnserialize(string $contents): Envelope try { /** @var Envelope */ $envelope = unserialize($contents); + } catch (\Throwable $e) { + if ($e instanceof MessageDecodingFailedException) { + throw $e; + } + + throw new MessageDecodingFailedException('Could not decode Envelope: '.$e->getMessage(), 0, $e); } finally { restore_error_handler(); ini_set('unserialize_callback_func', $prevUnserializeHandler); } if (!$envelope instanceof Envelope) { - throw $signalingException; + throw new MessageDecodingFailedException('Could not decode message into an Envelope.'); } if ($envelope->getMessage() instanceof \__PHP_Incomplete_Class) { diff --git a/src/Symfony/Component/Messenger/Transport/Serialization/Serializer.php b/src/Symfony/Component/Messenger/Transport/Serialization/Serializer.php index 443da0a67c9e7..b7f8ea9ed4b38 100644 --- a/src/Symfony/Component/Messenger/Transport/Serialization/Serializer.php +++ b/src/Symfony/Component/Messenger/Transport/Serialization/Serializer.php @@ -39,7 +39,7 @@ class Serializer implements SerializerInterface private string $format; private array $context; - public function __construct(SymfonySerializerInterface $serializer = null, string $format = 'json', array $context = []) + public function __construct(?SymfonySerializerInterface $serializer = null, string $format = 'json', array $context = []) { $this->serializer = $serializer ?? self::create()->serializer; $this->format = $format; diff --git a/src/Symfony/Component/Messenger/Worker.php b/src/Symfony/Component/Messenger/Worker.php index eb0af634769b9..7e25f7de1b54f 100644 --- a/src/Symfony/Component/Messenger/Worker.php +++ b/src/Symfony/Component/Messenger/Worker.php @@ -152,7 +152,7 @@ private function handleMessage(Envelope $envelope, string $transportName): void } $acked = false; - $ack = function (Envelope $envelope, \Throwable $e = null) use ($transportName, &$acked) { + $ack = function (Envelope $envelope, ?\Throwable $e = null) use ($transportName, &$acked) { $acked = true; $this->acks[] = [$transportName, $envelope, $e]; }; diff --git a/src/Symfony/Component/Mime/Crypto/SMimeEncrypter.php b/src/Symfony/Component/Mime/Crypto/SMimeEncrypter.php index 7d5dfecd219a7..c7c05452cbe18 100644 --- a/src/Symfony/Component/Mime/Crypto/SMimeEncrypter.php +++ b/src/Symfony/Component/Mime/Crypto/SMimeEncrypter.php @@ -26,7 +26,7 @@ final class SMimeEncrypter extends SMime * @param string|string[] $certificate The path (or array of paths) of the file(s) containing the X.509 certificate(s) * @param int|null $cipher A set of algorithms used to encrypt the message. Must be one of these PHP constants: https://www.php.net/manual/en/openssl.ciphers.php */ - public function __construct(string|array $certificate, int $cipher = null) + public function __construct(string|array $certificate, ?int $cipher = null) { if (!\extension_loaded('openssl')) { throw new \LogicException('PHP extension "openssl" is required to use SMime.'); diff --git a/src/Symfony/Component/Mime/Crypto/SMimeSigner.php b/src/Symfony/Component/Mime/Crypto/SMimeSigner.php index 79a61a2772e47..eaa423d8c0eab 100644 --- a/src/Symfony/Component/Mime/Crypto/SMimeSigner.php +++ b/src/Symfony/Component/Mime/Crypto/SMimeSigner.php @@ -31,7 +31,7 @@ final class SMimeSigner extends SMime * @param string|null $extraCerts The path of the file containing intermediate certificates (in PEM format) needed by the signing certificate * @param int|null $signOptions Bitwise operator options for openssl_pkcs7_sign() (@see https://secure.php.net/manual/en/openssl.pkcs7.flags.php) */ - public function __construct(string $certificate, string $privateKey, string $privateKeyPassphrase = null, string $extraCerts = null, int $signOptions = null) + public function __construct(string $certificate, string $privateKey, ?string $privateKeyPassphrase = null, ?string $extraCerts = null, ?int $signOptions = null) { if (!\extension_loaded('openssl')) { throw new \LogicException('PHP extension "openssl" is required to use SMime.'); diff --git a/src/Symfony/Component/Mime/DraftEmail.php b/src/Symfony/Component/Mime/DraftEmail.php index a60fea17360a8..82ce76369e4a3 100644 --- a/src/Symfony/Component/Mime/DraftEmail.php +++ b/src/Symfony/Component/Mime/DraftEmail.php @@ -19,7 +19,7 @@ */ class DraftEmail extends Email { - public function __construct(Headers $headers = null, AbstractPart $body = null) + public function __construct(?Headers $headers = null, ?AbstractPart $body = null) { parent::__construct($headers, $body); diff --git a/src/Symfony/Component/Mime/Email.php b/src/Symfony/Component/Mime/Email.php index 100d9bccbe2db..233afc075a7bc 100644 --- a/src/Symfony/Component/Mime/Email.php +++ b/src/Symfony/Component/Mime/Email.php @@ -120,6 +120,10 @@ public function addFrom(Address|string ...$addresses): static */ public function from(Address|string ...$addresses): static { + if (!$addresses) { + throw new LogicException('"from()" must be called with at least one address.'); + } + return $this->setListAddressHeaderBody('From', $addresses); } @@ -325,7 +329,7 @@ public function getHtmlCharset(): ?string * * @return $this */ - public function attach($body, string $name = null, string $contentType = null): static + public function attach($body, ?string $name = null, ?string $contentType = null): static { return $this->addPart(new DataPart($body, $name, $contentType)); } @@ -333,7 +337,7 @@ public function attach($body, string $name = null, string $contentType = null): /** * @return $this */ - public function attachFromPath(string $path, string $name = null, string $contentType = null): static + public function attachFromPath(string $path, ?string $name = null, ?string $contentType = null): static { return $this->addPart(new DataPart(new File($path), $name, $contentType)); } @@ -343,7 +347,7 @@ public function attachFromPath(string $path, string $name = null, string $conten * * @return $this */ - public function embed($body, string $name = null, string $contentType = null): static + public function embed($body, ?string $name = null, ?string $contentType = null): static { return $this->addPart((new DataPart($body, $name, $contentType))->asInline()); } @@ -351,7 +355,7 @@ public function embed($body, string $name = null, string $contentType = null): s /** * @return $this */ - public function embedFromPath(string $path, string $name = null, string $contentType = null): static + public function embedFromPath(string $path, ?string $name = null, ?string $contentType = null): static { return $this->addPart((new DataPart(new File($path), $name, $contentType))->asInline()); } diff --git a/src/Symfony/Component/Mime/FileinfoMimeTypeGuesser.php b/src/Symfony/Component/Mime/FileinfoMimeTypeGuesser.php index aff5901159ad3..776124f8ccf04 100644 --- a/src/Symfony/Component/Mime/FileinfoMimeTypeGuesser.php +++ b/src/Symfony/Component/Mime/FileinfoMimeTypeGuesser.php @@ -28,7 +28,7 @@ class FileinfoMimeTypeGuesser implements MimeTypeGuesserInterface * * @see http://www.php.net/manual/en/function.finfo-open.php */ - public function __construct(string $magicFile = null) + public function __construct(?string $magicFile = null) { $this->magicFile = $magicFile; } diff --git a/src/Symfony/Component/Mime/Header/AbstractHeader.php b/src/Symfony/Component/Mime/Header/AbstractHeader.php index 389f366226720..e6cb604b63f36 100644 --- a/src/Symfony/Component/Mime/Header/AbstractHeader.php +++ b/src/Symfony/Component/Mime/Header/AbstractHeader.php @@ -229,7 +229,7 @@ protected function generateTokenLines(string $token): array /** * Generate a list of all tokens in the final header. */ - protected function toTokens(string $string = null): array + protected function toTokens(?string $string = null): array { $string ??= $this->getBodyAsString(); diff --git a/src/Symfony/Component/Mime/Header/Headers.php b/src/Symfony/Component/Mime/Header/Headers.php index 48c73e0ccf82a..164f4ac339eae 100644 --- a/src/Symfony/Component/Mime/Header/Headers.php +++ b/src/Symfony/Component/Mime/Header/Headers.php @@ -190,7 +190,7 @@ public function get(string $name): ?HeaderInterface return array_shift($values); } - public function all(string $name = null): iterable + public function all(?string $name = null): iterable { if (null === $name) { foreach ($this->headers as $name => $collection) { diff --git a/src/Symfony/Component/Mime/Header/ParameterizedHeader.php b/src/Symfony/Component/Mime/Header/ParameterizedHeader.php index 0618e82bddcae..5ef4f2120fbd8 100644 --- a/src/Symfony/Component/Mime/Header/ParameterizedHeader.php +++ b/src/Symfony/Component/Mime/Header/ParameterizedHeader.php @@ -85,7 +85,7 @@ public function getBodyAsString(): string * This doesn't need to be overridden in theory, but it is for implementation * reasons to prevent potential breakage of attributes. */ - protected function toTokens(string $string = null): array + protected function toTokens(?string $string = null): array { $tokens = parent::toTokens(parent::getBodyAsString()); diff --git a/src/Symfony/Component/Mime/Message.php b/src/Symfony/Component/Mime/Message.php index 438ec319aeb96..355cbd8c984b3 100644 --- a/src/Symfony/Component/Mime/Message.php +++ b/src/Symfony/Component/Mime/Message.php @@ -24,7 +24,7 @@ class Message extends RawMessage private Headers $headers; private ?AbstractPart $body; - public function __construct(Headers $headers = null, AbstractPart $body = null) + public function __construct(?Headers $headers = null, ?AbstractPart $body = null) { $this->headers = $headers ? clone $headers : new Headers(); $this->body = $body; @@ -140,7 +140,10 @@ public function generateMessageId(): string if ($this->headers->has('Sender')) { $sender = $this->headers->get('Sender')->getAddress(); } elseif ($this->headers->has('From')) { - $sender = $this->headers->get('From')->getAddresses()[0]; + if (!$froms = $this->headers->get('From')->getAddresses()) { + throw new LogicException('A "From" header must have at least one email address.'); + } + $sender = $froms[0]; } else { throw new LogicException('An email must have a "From" or a "Sender" header.'); } diff --git a/src/Symfony/Component/Mime/Part/DataPart.php b/src/Symfony/Component/Mime/Part/DataPart.php index f1a5eba0dd783..b1c581e6f64ed 100644 --- a/src/Symfony/Component/Mime/Part/DataPart.php +++ b/src/Symfony/Component/Mime/Part/DataPart.php @@ -29,7 +29,7 @@ class DataPart extends TextPart /** * @param resource|string|File $body Use a File instance to defer loading the file until rendering */ - public function __construct($body, string $filename = null, string $contentType = null, string $encoding = null) + public function __construct($body, ?string $filename = null, ?string $contentType = null, ?string $encoding = null) { if ($body instanceof File && !$filename) { $filename = $body->getFilename(); @@ -47,7 +47,7 @@ public function __construct($body, string $filename = null, string $contentType $this->setDisposition('attachment'); } - public static function fromPath(string $path, string $name = null, string $contentType = null): self + public static function fromPath(string $path, ?string $name = null, ?string $contentType = null): self { return new self(new File($path), $name, $contentType); } diff --git a/src/Symfony/Component/Mime/Part/TextPart.php b/src/Symfony/Component/Mime/Part/TextPart.php index 4e8bcba3c3a4c..a13300955e36c 100644 --- a/src/Symfony/Component/Mime/Part/TextPart.php +++ b/src/Symfony/Component/Mime/Part/TextPart.php @@ -40,7 +40,7 @@ class TextPart extends AbstractPart /** * @param resource|string|File $body Use a File instance to defer loading the file until rendering */ - public function __construct($body, ?string $charset = 'utf-8', string $subtype = 'plain', string $encoding = null) + public function __construct($body, ?string $charset = 'utf-8', string $subtype = 'plain', ?string $encoding = null) { parent::__construct(); diff --git a/src/Symfony/Component/Mime/RawMessage.php b/src/Symfony/Component/Mime/RawMessage.php index 5ee1f6101fd54..4ffa1870c0d8e 100644 --- a/src/Symfony/Component/Mime/RawMessage.php +++ b/src/Symfony/Component/Mime/RawMessage.php @@ -18,7 +18,7 @@ */ class RawMessage { - private iterable|string|null $message = null; + private iterable|string $message; private bool $isGeneratorClosed; public function __construct(iterable|string $message) diff --git a/src/Symfony/Component/Mime/Test/Constraint/EmailAttachmentCount.php b/src/Symfony/Component/Mime/Test/Constraint/EmailAttachmentCount.php index d5c8ae981189c..2f5a59492fa4c 100644 --- a/src/Symfony/Component/Mime/Test/Constraint/EmailAttachmentCount.php +++ b/src/Symfony/Component/Mime/Test/Constraint/EmailAttachmentCount.php @@ -20,7 +20,7 @@ final class EmailAttachmentCount extends Constraint private int $expectedValue; private ?string $transport; - public function __construct(int $expectedValue, string $transport = null) + public function __construct(int $expectedValue, ?string $transport = null) { $this->expectedValue = $expectedValue; $this->transport = $transport; diff --git a/src/Symfony/Component/Mime/Tests/EmailTest.php b/src/Symfony/Component/Mime/Tests/EmailTest.php index 543491c072815..ae61f26f605b4 100644 --- a/src/Symfony/Component/Mime/Tests/EmailTest.php +++ b/src/Symfony/Component/Mime/Tests/EmailTest.php @@ -15,6 +15,7 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\Mime\Address; use Symfony\Component\Mime\Email; +use Symfony\Component\Mime\Exception\LogicException; use Symfony\Component\Mime\Part\DataPart; use Symfony\Component\Mime\Part\File; use Symfony\Component\Mime\Part\Multipart\AlternativePart; @@ -63,6 +64,13 @@ public function testSender() $this->assertSame($fabien, $e->getSender()); } + public function testFromWithNoAddress() + { + $e = new Email(); + $this->expectException(LogicException::class); + $e->from(); + } + public function testFrom() { $e = new Email(); @@ -556,8 +564,7 @@ public function testSymfonySerialize() } ] }, - "body": null, - "message": null + "body": null } EOF; diff --git a/src/Symfony/Component/Mime/Tests/MessageTest.php b/src/Symfony/Component/Mime/Tests/MessageTest.php index 4ca1900625695..4f7c2a67ce396 100644 --- a/src/Symfony/Component/Mime/Tests/MessageTest.php +++ b/src/Symfony/Component/Mime/Tests/MessageTest.php @@ -13,6 +13,7 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\Mime\Address; +use Symfony\Component\Mime\Exception\LogicException; use Symfony\Component\Mime\Header\Headers; use Symfony\Component\Mime\Header\MailboxListHeader; use Symfony\Component\Mime\Header\UnstructuredHeader; @@ -125,6 +126,14 @@ public function testGetPreparedHeadersHasSenderWhenNeeded() $this->assertEquals('thomas@symfony.com', $message->getPreparedHeaders()->get('Sender')->getAddress()->getAddress()); } + public function testGenerateMessageIdThrowsWhenHasFromButNoAddresses() + { + $message = new Message(); + $message->getHeaders()->addMailboxListHeader('From', []); + $this->expectException(LogicException::class); + $message->generateMessageId(); + } + public function testToString() { $message = new Message(); @@ -245,8 +254,7 @@ public function testSymfonySerialize() ] }, "class": "Symfony\\\\Component\\\\Mime\\\\Part\\\\Multipart\\\\MixedPart" - }, - "message": null + } } EOF; diff --git a/src/Symfony/Component/Notifier/Bridge/AllMySms/AllMySmsTransport.php b/src/Symfony/Component/Notifier/Bridge/AllMySms/AllMySmsTransport.php index eead100ffdbbc..445c2420096f7 100644 --- a/src/Symfony/Component/Notifier/Bridge/AllMySms/AllMySmsTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/AllMySms/AllMySmsTransport.php @@ -32,8 +32,8 @@ public function __construct( private string $login, #[\SensitiveParameter] private string $apiKey, private ?string $from = null, - HttpClientInterface $client = null, - EventDispatcherInterface $dispatcher = null, + ?HttpClientInterface $client = null, + ?EventDispatcherInterface $dispatcher = null, ) { parent::__construct($client, $dispatcher); } diff --git a/src/Symfony/Component/Notifier/Bridge/AllMySms/Tests/AllMySmsTransportTest.php b/src/Symfony/Component/Notifier/Bridge/AllMySms/Tests/AllMySmsTransportTest.php index 3ca13b792e900..5a01c72f65eca 100644 --- a/src/Symfony/Component/Notifier/Bridge/AllMySms/Tests/AllMySmsTransportTest.php +++ b/src/Symfony/Component/Notifier/Bridge/AllMySms/Tests/AllMySmsTransportTest.php @@ -22,7 +22,7 @@ final class AllMySmsTransportTest extends TransportTestCase { - public static function createTransport(HttpClientInterface $client = null, string $from = null): AllMySmsTransport + public static function createTransport(?HttpClientInterface $client = null, ?string $from = null): AllMySmsTransport { return new AllMySmsTransport('login', 'apiKey', $from, $client ?? new MockHttpClient()); } diff --git a/src/Symfony/Component/Notifier/Bridge/AmazonSns/AmazonSnsTransport.php b/src/Symfony/Component/Notifier/Bridge/AmazonSns/AmazonSnsTransport.php index 234cfb8ffecab..365b6be73cfbc 100644 --- a/src/Symfony/Component/Notifier/Bridge/AmazonSns/AmazonSnsTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/AmazonSns/AmazonSnsTransport.php @@ -30,8 +30,8 @@ final class AmazonSnsTransport extends AbstractTransport { public function __construct( private SnsClient $snsClient, - HttpClientInterface $client = null, - EventDispatcherInterface $dispatcher = null, + ?HttpClientInterface $client = null, + ?EventDispatcherInterface $dispatcher = null, ) { parent::__construct($client, $dispatcher); } diff --git a/src/Symfony/Component/Notifier/Bridge/AmazonSns/Tests/AmazonSnsTransportTest.php b/src/Symfony/Component/Notifier/Bridge/AmazonSns/Tests/AmazonSnsTransportTest.php index bcc6840114974..7b302aecc0ea7 100644 --- a/src/Symfony/Component/Notifier/Bridge/AmazonSns/Tests/AmazonSnsTransportTest.php +++ b/src/Symfony/Component/Notifier/Bridge/AmazonSns/Tests/AmazonSnsTransportTest.php @@ -26,7 +26,7 @@ class AmazonSnsTransportTest extends TransportTestCase { - public static function createTransport(HttpClientInterface $client = null): AmazonSnsTransport + public static function createTransport(?HttpClientInterface $client = null): AmazonSnsTransport { return (new AmazonSnsTransport(new SnsClient(['region' => 'eu-west-3']), $client ?? new MockHttpClient()))->setHost('host.test'); } diff --git a/src/Symfony/Component/Notifier/Bridge/Bandwidth/BandwidthTransport.php b/src/Symfony/Component/Notifier/Bridge/Bandwidth/BandwidthTransport.php index f096d5ffa5179..a7fc3c26dae6e 100644 --- a/src/Symfony/Component/Notifier/Bridge/Bandwidth/BandwidthTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/Bandwidth/BandwidthTransport.php @@ -36,8 +36,8 @@ public function __construct( private readonly string $accountId, private readonly string $applicationId, private readonly ?string $priority, - HttpClientInterface $client = null, - EventDispatcherInterface $dispatcher = null, + ?HttpClientInterface $client = null, + ?EventDispatcherInterface $dispatcher = null, ) { parent::__construct($client, $dispatcher); } diff --git a/src/Symfony/Component/Notifier/Bridge/Bandwidth/Tests/BandwidthTransportTest.php b/src/Symfony/Component/Notifier/Bridge/Bandwidth/Tests/BandwidthTransportTest.php index 6d8419331f7e1..f14ce0b836e08 100644 --- a/src/Symfony/Component/Notifier/Bridge/Bandwidth/Tests/BandwidthTransportTest.php +++ b/src/Symfony/Component/Notifier/Bridge/Bandwidth/Tests/BandwidthTransportTest.php @@ -24,7 +24,7 @@ final class BandwidthTransportTest extends TransportTestCase { - public static function createTransport(HttpClientInterface $client = null, string $from = 'from'): BandwidthTransport + public static function createTransport(?HttpClientInterface $client = null, string $from = 'from'): BandwidthTransport { return new BandwidthTransport('username', 'password', $from, 'account_id', 'application_id', 'priority', $client ?? new MockHttpClient()); } diff --git a/src/Symfony/Component/Notifier/Bridge/Bluesky/BlueskyTransport.php b/src/Symfony/Component/Notifier/Bridge/Bluesky/BlueskyTransport.php index 65ab4c559ea2e..2a3552e83402e 100644 --- a/src/Symfony/Component/Notifier/Bridge/Bluesky/BlueskyTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/Bluesky/BlueskyTransport.php @@ -36,8 +36,8 @@ public function __construct( #[\SensitiveParameter] private string $user, #[\SensitiveParameter] private string $password, private LoggerInterface $logger, - HttpClientInterface $client = null, - EventDispatcherInterface $dispatcher = null, + ?HttpClientInterface $client = null, + ?EventDispatcherInterface $dispatcher = null, ) { parent::__construct($client, $dispatcher); } diff --git a/src/Symfony/Component/Notifier/Bridge/Bluesky/BlueskyTransportFactory.php b/src/Symfony/Component/Notifier/Bridge/Bluesky/BlueskyTransportFactory.php index 2a37ab74fc11b..d3c37112a89ef 100644 --- a/src/Symfony/Component/Notifier/Bridge/Bluesky/BlueskyTransportFactory.php +++ b/src/Symfony/Component/Notifier/Bridge/Bluesky/BlueskyTransportFactory.php @@ -25,8 +25,8 @@ final class BlueskyTransportFactory extends AbstractTransportFactory { public function __construct( - EventDispatcherInterface $dispatcher = null, - HttpClientInterface $client = null, + ?EventDispatcherInterface $dispatcher = null, + ?HttpClientInterface $client = null, private ?LoggerInterface $logger = null, ) { parent::__construct($dispatcher, $client); diff --git a/src/Symfony/Component/Notifier/Bridge/Bluesky/Tests/BlueskyTransportTest.php b/src/Symfony/Component/Notifier/Bridge/Bluesky/Tests/BlueskyTransportTest.php index bcf0d04fa2e1d..f6c5005c666cf 100644 --- a/src/Symfony/Component/Notifier/Bridge/Bluesky/Tests/BlueskyTransportTest.php +++ b/src/Symfony/Component/Notifier/Bridge/Bluesky/Tests/BlueskyTransportTest.php @@ -25,7 +25,7 @@ final class BlueskyTransportTest extends TransportTestCase { - public static function createTransport(HttpClientInterface $client = null): BlueskyTransport + public static function createTransport(?HttpClientInterface $client = null): BlueskyTransport { $blueskyTransport = new BlueskyTransport('username', 'password', new NullLogger(), $client ?? new MockHttpClient()); $blueskyTransport->setHost('bsky.social'); @@ -267,7 +267,7 @@ public function testParseFacetsUrlWithTrickyRegex() /** * A small helper function to test BlueskyTransport::parseFacets(). */ - private function parseFacets(string $input, HttpClientInterface $httpClient = null): array + private function parseFacets(string $input, ?HttpClientInterface $httpClient = null): array { $class = new \ReflectionClass(BlueskyTransport::class); $method = $class->getMethod('parseFacets'); diff --git a/src/Symfony/Component/Notifier/Bridge/Brevo/BrevoTransport.php b/src/Symfony/Component/Notifier/Bridge/Brevo/BrevoTransport.php index 4c166192294e6..2a348ab549402 100644 --- a/src/Symfony/Component/Notifier/Bridge/Brevo/BrevoTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/Brevo/BrevoTransport.php @@ -31,8 +31,8 @@ final class BrevoTransport extends AbstractTransport public function __construct( #[\SensitiveParameter] private readonly string $apiKey, private readonly string $sender, - HttpClientInterface $client = null, - EventDispatcherInterface $dispatcher = null, + ?HttpClientInterface $client = null, + ?EventDispatcherInterface $dispatcher = null, ) { parent::__construct($client, $dispatcher); } diff --git a/src/Symfony/Component/Notifier/Bridge/Brevo/Tests/BrevoTransportTest.php b/src/Symfony/Component/Notifier/Bridge/Brevo/Tests/BrevoTransportTest.php index cfdad9f6207e4..f37b987f5bfdf 100644 --- a/src/Symfony/Component/Notifier/Bridge/Brevo/Tests/BrevoTransportTest.php +++ b/src/Symfony/Component/Notifier/Bridge/Brevo/Tests/BrevoTransportTest.php @@ -23,7 +23,7 @@ final class BrevoTransportTest extends TransportTestCase { - public static function createTransport(HttpClientInterface $client = null): BrevoTransport + public static function createTransport(?HttpClientInterface $client = null): BrevoTransport { return (new BrevoTransport('api-key', '0611223344', $client ?? new MockHttpClient()))->setHost('host.test'); } diff --git a/src/Symfony/Component/Notifier/Bridge/Chatwork/ChatworkTransport.php b/src/Symfony/Component/Notifier/Bridge/Chatwork/ChatworkTransport.php index 9efd2ff3bd3f7..22ea5000e4205 100644 --- a/src/Symfony/Component/Notifier/Bridge/Chatwork/ChatworkTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/Chatwork/ChatworkTransport.php @@ -31,8 +31,8 @@ class ChatworkTransport extends AbstractTransport public function __construct( #[\SensitiveParameter] private string $apiToken, private string $roomId, - HttpClientInterface $client = null, - EventDispatcherInterface $dispatcher = null, + ?HttpClientInterface $client = null, + ?EventDispatcherInterface $dispatcher = null, ) { parent::__construct($client, $dispatcher); } diff --git a/src/Symfony/Component/Notifier/Bridge/Chatwork/Tests/ChatworkTransportTest.php b/src/Symfony/Component/Notifier/Bridge/Chatwork/Tests/ChatworkTransportTest.php index cf05c3a7b2a02..bb9da39ef627a 100644 --- a/src/Symfony/Component/Notifier/Bridge/Chatwork/Tests/ChatworkTransportTest.php +++ b/src/Symfony/Component/Notifier/Bridge/Chatwork/Tests/ChatworkTransportTest.php @@ -24,7 +24,7 @@ class ChatworkTransportTest extends TransportTestCase { - public static function createTransport(HttpClientInterface $client = null): TransportInterface + public static function createTransport(?HttpClientInterface $client = null): TransportInterface { return (new ChatworkTransport('testToken', 'testRoomId', $client ?? new MockHttpClient()))->setHost('host.test'); } diff --git a/src/Symfony/Component/Notifier/Bridge/ClickSend/ClickSendTransport.php b/src/Symfony/Component/Notifier/Bridge/ClickSend/ClickSendTransport.php index 652f219071633..76abd4b95f761 100644 --- a/src/Symfony/Component/Notifier/Bridge/ClickSend/ClickSendTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/ClickSend/ClickSendTransport.php @@ -36,8 +36,8 @@ public function __construct( private readonly ?string $source = null, private readonly ?string $listId = null, private readonly ?string $fromEmail = null, - HttpClientInterface $client = null, - EventDispatcherInterface $dispatcher = null, + ?HttpClientInterface $client = null, + ?EventDispatcherInterface $dispatcher = null, ) { parent::__construct($client, $dispatcher); } diff --git a/src/Symfony/Component/Notifier/Bridge/ClickSend/Tests/ClickSendTransportTest.php b/src/Symfony/Component/Notifier/Bridge/ClickSend/Tests/ClickSendTransportTest.php index cf8eabab0d2ec..166bb10d8a8dc 100644 --- a/src/Symfony/Component/Notifier/Bridge/ClickSend/Tests/ClickSendTransportTest.php +++ b/src/Symfony/Component/Notifier/Bridge/ClickSend/Tests/ClickSendTransportTest.php @@ -24,7 +24,7 @@ final class ClickSendTransportTest extends TransportTestCase { - public static function createTransport(HttpClientInterface $client = null, string $from = 'test_from', string $source = 'test_source', int $listId = 99, string $fromEmail = 'foo@bar.com'): ClickSendTransport + public static function createTransport(?HttpClientInterface $client = null, string $from = 'test_from', string $source = 'test_source', int $listId = 99, string $fromEmail = 'foo@bar.com'): ClickSendTransport { return new ClickSendTransport('test_username', 'test_key', $from, $source, $listId, $fromEmail, $client ?? new MockHttpClient()); } diff --git a/src/Symfony/Component/Notifier/Bridge/Clickatell/ClickatellTransport.php b/src/Symfony/Component/Notifier/Bridge/Clickatell/ClickatellTransport.php index 5d9dd017fe918..dd162a3a80e22 100644 --- a/src/Symfony/Component/Notifier/Bridge/Clickatell/ClickatellTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/Clickatell/ClickatellTransport.php @@ -31,8 +31,8 @@ final class ClickatellTransport extends AbstractTransport public function __construct( #[\SensitiveParameter] private string $authToken, private ?string $from = null, - HttpClientInterface $client = null, - EventDispatcherInterface $dispatcher = null, + ?HttpClientInterface $client = null, + ?EventDispatcherInterface $dispatcher = null, ) { parent::__construct($client, $dispatcher); } diff --git a/src/Symfony/Component/Notifier/Bridge/Clickatell/Tests/ClickatellTransportTest.php b/src/Symfony/Component/Notifier/Bridge/Clickatell/Tests/ClickatellTransportTest.php index bf3de264e73ad..e76a04ed75a7e 100644 --- a/src/Symfony/Component/Notifier/Bridge/Clickatell/Tests/ClickatellTransportTest.php +++ b/src/Symfony/Component/Notifier/Bridge/Clickatell/Tests/ClickatellTransportTest.php @@ -25,7 +25,7 @@ final class ClickatellTransportTest extends TransportTestCase { - public static function createTransport(HttpClientInterface $client = null, string $from = null): ClickatellTransport + public static function createTransport(?HttpClientInterface $client = null, ?string $from = null): ClickatellTransport { return new ClickatellTransport('authToken', $from, $client ?? new MockHttpClient()); } diff --git a/src/Symfony/Component/Notifier/Bridge/ContactEveryone/ContactEveryoneTransport.php b/src/Symfony/Component/Notifier/Bridge/ContactEveryone/ContactEveryoneTransport.php index 2b40e4be4cfab..3ef7624aa1ed6 100644 --- a/src/Symfony/Component/Notifier/Bridge/ContactEveryone/ContactEveryoneTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/ContactEveryone/ContactEveryoneTransport.php @@ -33,8 +33,8 @@ public function __construct( #[\SensitiveParameter] private string $token, private ?string $diffusionName, private ?string $category, - HttpClientInterface $client = null, - EventDispatcherInterface $dispatcher = null, + ?HttpClientInterface $client = null, + ?EventDispatcherInterface $dispatcher = null, ) { parent::__construct($client, $dispatcher); } diff --git a/src/Symfony/Component/Notifier/Bridge/ContactEveryone/Tests/ContactEveryoneTransportTest.php b/src/Symfony/Component/Notifier/Bridge/ContactEveryone/Tests/ContactEveryoneTransportTest.php index 643b3c7b4743b..a765bda1aad2d 100644 --- a/src/Symfony/Component/Notifier/Bridge/ContactEveryone/Tests/ContactEveryoneTransportTest.php +++ b/src/Symfony/Component/Notifier/Bridge/ContactEveryone/Tests/ContactEveryoneTransportTest.php @@ -24,7 +24,7 @@ final class ContactEveryoneTransportTest extends TransportTestCase { - public static function createTransport(HttpClientInterface $client = null): ContactEveryoneTransport + public static function createTransport(?HttpClientInterface $client = null): ContactEveryoneTransport { return new ContactEveryoneTransport('API_TOKEN', 'Symfony', 'Foo', $client ?? new MockHttpClient()); } diff --git a/src/Symfony/Component/Notifier/Bridge/Discord/DiscordTransport.php b/src/Symfony/Component/Notifier/Bridge/Discord/DiscordTransport.php index 8611a8a041660..49798a172305c 100644 --- a/src/Symfony/Component/Notifier/Bridge/Discord/DiscordTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/Discord/DiscordTransport.php @@ -34,8 +34,8 @@ final class DiscordTransport extends AbstractTransport public function __construct( #[\SensitiveParameter] private string $token, private string $webhookId, - HttpClientInterface $client = null, - EventDispatcherInterface $dispatcher = null, + ?HttpClientInterface $client = null, + ?EventDispatcherInterface $dispatcher = null, ) { parent::__construct($client, $dispatcher); } diff --git a/src/Symfony/Component/Notifier/Bridge/Discord/Tests/DiscordTransportTest.php b/src/Symfony/Component/Notifier/Bridge/Discord/Tests/DiscordTransportTest.php index 7db81ebd7f77e..6d4a830c1e702 100644 --- a/src/Symfony/Component/Notifier/Bridge/Discord/Tests/DiscordTransportTest.php +++ b/src/Symfony/Component/Notifier/Bridge/Discord/Tests/DiscordTransportTest.php @@ -24,7 +24,7 @@ final class DiscordTransportTest extends TransportTestCase { - public static function createTransport(HttpClientInterface $client = null): DiscordTransport + public static function createTransport(?HttpClientInterface $client = null): DiscordTransport { return (new DiscordTransport('testToken', 'testWebhookId', $client ?? new MockHttpClient()))->setHost('host.test'); } diff --git a/src/Symfony/Component/Notifier/Bridge/Engagespot/EngagespotTransport.php b/src/Symfony/Component/Notifier/Bridge/Engagespot/EngagespotTransport.php index e6fc2aeb23c21..00a5d96d83d25 100644 --- a/src/Symfony/Component/Notifier/Bridge/Engagespot/EngagespotTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/Engagespot/EngagespotTransport.php @@ -32,8 +32,8 @@ final class EngagespotTransport extends AbstractTransport public function __construct( #[\SensitiveParameter] private string $apiKey, private string $campaignName, - HttpClientInterface $client = null, - EventDispatcherInterface $dispatcher = null, + ?HttpClientInterface $client = null, + ?EventDispatcherInterface $dispatcher = null, ) { parent::__construct($client, $dispatcher); } diff --git a/src/Symfony/Component/Notifier/Bridge/Engagespot/Tests/EngagespotTransportTest.php b/src/Symfony/Component/Notifier/Bridge/Engagespot/Tests/EngagespotTransportTest.php index c996a841af26b..9686f5fe57c2c 100644 --- a/src/Symfony/Component/Notifier/Bridge/Engagespot/Tests/EngagespotTransportTest.php +++ b/src/Symfony/Component/Notifier/Bridge/Engagespot/Tests/EngagespotTransportTest.php @@ -24,7 +24,7 @@ */ final class EngagespotTransportTest extends TransportTestCase { - public static function createTransport(HttpClientInterface $client = null): EngagespotTransport + public static function createTransport(?HttpClientInterface $client = null): EngagespotTransport { return new EngagespotTransport('apiKey', 'TEST', $client ?? new MockHttpClient()); } diff --git a/src/Symfony/Component/Notifier/Bridge/Esendex/EsendexTransport.php b/src/Symfony/Component/Notifier/Bridge/Esendex/EsendexTransport.php index 981c00e488858..4b0eb3ed6551d 100644 --- a/src/Symfony/Component/Notifier/Bridge/Esendex/EsendexTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/Esendex/EsendexTransport.php @@ -31,8 +31,8 @@ public function __construct( #[\SensitiveParameter] private string $password, private string $accountReference, private string $from, - HttpClientInterface $client = null, - EventDispatcherInterface $dispatcher = null, + ?HttpClientInterface $client = null, + ?EventDispatcherInterface $dispatcher = null, ) { parent::__construct($client, $dispatcher); } diff --git a/src/Symfony/Component/Notifier/Bridge/Esendex/Tests/EsendexTransportTest.php b/src/Symfony/Component/Notifier/Bridge/Esendex/Tests/EsendexTransportTest.php index d61eb20da253a..c854b12df9298 100644 --- a/src/Symfony/Component/Notifier/Bridge/Esendex/Tests/EsendexTransportTest.php +++ b/src/Symfony/Component/Notifier/Bridge/Esendex/Tests/EsendexTransportTest.php @@ -24,7 +24,7 @@ final class EsendexTransportTest extends TransportTestCase { - public static function createTransport(HttpClientInterface $client = null): EsendexTransport + public static function createTransport(?HttpClientInterface $client = null): EsendexTransport { return (new EsendexTransport('email', 'password', 'testAccountReference', 'testFrom', $client ?? new MockHttpClient()))->setHost('host.test'); } diff --git a/src/Symfony/Component/Notifier/Bridge/Expo/ExpoTransport.php b/src/Symfony/Component/Notifier/Bridge/Expo/ExpoTransport.php index 36aaf26a766ce..7aa6c1843c208 100644 --- a/src/Symfony/Component/Notifier/Bridge/Expo/ExpoTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/Expo/ExpoTransport.php @@ -31,8 +31,8 @@ final class ExpoTransport extends AbstractTransport public function __construct( #[\SensitiveParameter] private ?string $token = null, - HttpClientInterface $client = null, - EventDispatcherInterface $dispatcher = null, + ?HttpClientInterface $client = null, + ?EventDispatcherInterface $dispatcher = null, ) { parent::__construct($client, $dispatcher); } diff --git a/src/Symfony/Component/Notifier/Bridge/Expo/Tests/ExpoTransportTest.php b/src/Symfony/Component/Notifier/Bridge/Expo/Tests/ExpoTransportTest.php index 38ccd4aa055b5..80dc21893e2e4 100644 --- a/src/Symfony/Component/Notifier/Bridge/Expo/Tests/ExpoTransportTest.php +++ b/src/Symfony/Component/Notifier/Bridge/Expo/Tests/ExpoTransportTest.php @@ -24,7 +24,7 @@ */ final class ExpoTransportTest extends TransportTestCase { - public static function createTransport(HttpClientInterface $client = null): ExpoTransport + public static function createTransport(?HttpClientInterface $client = null): ExpoTransport { return new ExpoTransport('token', $client ?? new MockHttpClient()); } diff --git a/src/Symfony/Component/Notifier/Bridge/FakeChat/FakeChatEmailTransport.php b/src/Symfony/Component/Notifier/Bridge/FakeChat/FakeChatEmailTransport.php index 6728716b1adf2..ba54938980c86 100644 --- a/src/Symfony/Component/Notifier/Bridge/FakeChat/FakeChatEmailTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/FakeChat/FakeChatEmailTransport.php @@ -33,7 +33,7 @@ final class FakeChatEmailTransport extends AbstractTransport private string $to; private string $from; - public function __construct(MailerInterface $mailer, string $to, string $from, HttpClientInterface $client = null, EventDispatcherInterface $dispatcher = null) + public function __construct(MailerInterface $mailer, string $to, string $from, ?HttpClientInterface $client = null, ?EventDispatcherInterface $dispatcher = null) { $this->mailer = $mailer; $this->to = $to; diff --git a/src/Symfony/Component/Notifier/Bridge/FakeChat/FakeChatLoggerTransport.php b/src/Symfony/Component/Notifier/Bridge/FakeChat/FakeChatLoggerTransport.php index 9992d54d117f6..7aba4e3b3980d 100644 --- a/src/Symfony/Component/Notifier/Bridge/FakeChat/FakeChatLoggerTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/FakeChat/FakeChatLoggerTransport.php @@ -29,8 +29,8 @@ final class FakeChatLoggerTransport extends AbstractTransport public function __construct( private LoggerInterface $logger, - HttpClientInterface $client = null, - EventDispatcherInterface $dispatcher = null, + ?HttpClientInterface $client = null, + ?EventDispatcherInterface $dispatcher = null, ) { parent::__construct($client, $dispatcher); } diff --git a/src/Symfony/Component/Notifier/Bridge/FakeChat/FakeChatTransportFactory.php b/src/Symfony/Component/Notifier/Bridge/FakeChat/FakeChatTransportFactory.php index 38a9109b55293..e2f2347618832 100644 --- a/src/Symfony/Component/Notifier/Bridge/FakeChat/FakeChatTransportFactory.php +++ b/src/Symfony/Component/Notifier/Bridge/FakeChat/FakeChatTransportFactory.php @@ -29,7 +29,7 @@ final class FakeChatTransportFactory extends AbstractTransportFactory private ?MailerInterface $mailer; private ?LoggerInterface $logger; - public function __construct(MailerInterface $mailer = null, LoggerInterface $logger = null, EventDispatcherInterface $dispatcher = null, HttpClientInterface $client = null) + public function __construct(?MailerInterface $mailer = null, ?LoggerInterface $logger = null, ?EventDispatcherInterface $dispatcher = null, ?HttpClientInterface $client = null) { parent::__construct($dispatcher, $client); diff --git a/src/Symfony/Component/Notifier/Bridge/FakeChat/Tests/FakeChatEmailTransportTest.php b/src/Symfony/Component/Notifier/Bridge/FakeChat/Tests/FakeChatEmailTransportTest.php index 0a3502679acf4..26dcf3ca11e28 100644 --- a/src/Symfony/Component/Notifier/Bridge/FakeChat/Tests/FakeChatEmailTransportTest.php +++ b/src/Symfony/Component/Notifier/Bridge/FakeChat/Tests/FakeChatEmailTransportTest.php @@ -24,7 +24,7 @@ final class FakeChatEmailTransportTest extends TransportTestCase { - public static function createTransport(HttpClientInterface $client = null, string $transportName = null): FakeChatEmailTransport + public static function createTransport(?HttpClientInterface $client = null, ?string $transportName = null): FakeChatEmailTransport { $transport = (new FakeChatEmailTransport(new DummyMailer(), 'recipient@email.net', 'sender@email.net', $client ?? new MockHttpClient())); diff --git a/src/Symfony/Component/Notifier/Bridge/FakeChat/Tests/FakeChatLoggerTransportTest.php b/src/Symfony/Component/Notifier/Bridge/FakeChat/Tests/FakeChatLoggerTransportTest.php index 05f2d45a638d0..6587954edad1a 100644 --- a/src/Symfony/Component/Notifier/Bridge/FakeChat/Tests/FakeChatLoggerTransportTest.php +++ b/src/Symfony/Component/Notifier/Bridge/FakeChat/Tests/FakeChatLoggerTransportTest.php @@ -24,7 +24,7 @@ final class FakeChatLoggerTransportTest extends TransportTestCase { - public static function createTransport(HttpClientInterface $client = null, LoggerInterface $logger = null): FakeChatLoggerTransport + public static function createTransport(?HttpClientInterface $client = null, ?LoggerInterface $logger = null): FakeChatLoggerTransport { return new FakeChatLoggerTransport($logger ?? new NullLogger(), $client ?? new MockHttpClient()); } diff --git a/src/Symfony/Component/Notifier/Bridge/FakeSms/FakeSmsEmailTransport.php b/src/Symfony/Component/Notifier/Bridge/FakeSms/FakeSmsEmailTransport.php index a9efb7acf6343..e83e57a006011 100644 --- a/src/Symfony/Component/Notifier/Bridge/FakeSms/FakeSmsEmailTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/FakeSms/FakeSmsEmailTransport.php @@ -34,7 +34,7 @@ final class FakeSmsEmailTransport extends AbstractTransport private string $to; private string $from; - public function __construct(MailerInterface $mailer, string $to, string $from, HttpClientInterface $client = null, EventDispatcherInterface $dispatcher = null) + public function __construct(MailerInterface $mailer, string $to, string $from, ?HttpClientInterface $client = null, ?EventDispatcherInterface $dispatcher = null) { $this->mailer = $mailer; $this->to = $to; diff --git a/src/Symfony/Component/Notifier/Bridge/FakeSms/FakeSmsLoggerTransport.php b/src/Symfony/Component/Notifier/Bridge/FakeSms/FakeSmsLoggerTransport.php index babdf57009e22..a14d33ae25d95 100644 --- a/src/Symfony/Component/Notifier/Bridge/FakeSms/FakeSmsLoggerTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/FakeSms/FakeSmsLoggerTransport.php @@ -29,8 +29,8 @@ final class FakeSmsLoggerTransport extends AbstractTransport public function __construct( private LoggerInterface $logger, - HttpClientInterface $client = null, - EventDispatcherInterface $dispatcher = null, + ?HttpClientInterface $client = null, + ?EventDispatcherInterface $dispatcher = null, ) { parent::__construct($client, $dispatcher); } diff --git a/src/Symfony/Component/Notifier/Bridge/FakeSms/FakeSmsTransportFactory.php b/src/Symfony/Component/Notifier/Bridge/FakeSms/FakeSmsTransportFactory.php index 6afd9f3a8e8d4..8d0410e86899c 100644 --- a/src/Symfony/Component/Notifier/Bridge/FakeSms/FakeSmsTransportFactory.php +++ b/src/Symfony/Component/Notifier/Bridge/FakeSms/FakeSmsTransportFactory.php @@ -30,7 +30,7 @@ final class FakeSmsTransportFactory extends AbstractTransportFactory private ?MailerInterface $mailer; private ?LoggerInterface $logger; - public function __construct(MailerInterface $mailer = null, LoggerInterface $logger = null, EventDispatcherInterface $dispatcher = null, HttpClientInterface $client = null) + public function __construct(?MailerInterface $mailer = null, ?LoggerInterface $logger = null, ?EventDispatcherInterface $dispatcher = null, ?HttpClientInterface $client = null) { parent::__construct($dispatcher, $client); diff --git a/src/Symfony/Component/Notifier/Bridge/FakeSms/Tests/FakeSmsEmailTransportTest.php b/src/Symfony/Component/Notifier/Bridge/FakeSms/Tests/FakeSmsEmailTransportTest.php index 61a2e8b2f1ba2..de3af39c95219 100644 --- a/src/Symfony/Component/Notifier/Bridge/FakeSms/Tests/FakeSmsEmailTransportTest.php +++ b/src/Symfony/Component/Notifier/Bridge/FakeSms/Tests/FakeSmsEmailTransportTest.php @@ -23,7 +23,7 @@ final class FakeSmsEmailTransportTest extends TransportTestCase { - public static function createTransport(HttpClientInterface $client = null, string $transportName = null): FakeSmsEmailTransport + public static function createTransport(?HttpClientInterface $client = null, ?string $transportName = null): FakeSmsEmailTransport { $transport = (new FakeSmsEmailTransport(new DummyMailer(), 'recipient@email.net', 'sender@email.net', $client ?? new MockHttpClient())); diff --git a/src/Symfony/Component/Notifier/Bridge/FakeSms/Tests/FakeSmsLoggerTransportTest.php b/src/Symfony/Component/Notifier/Bridge/FakeSms/Tests/FakeSmsLoggerTransportTest.php index bf61bb851ee1f..4c8ac00ebdc71 100644 --- a/src/Symfony/Component/Notifier/Bridge/FakeSms/Tests/FakeSmsLoggerTransportTest.php +++ b/src/Symfony/Component/Notifier/Bridge/FakeSms/Tests/FakeSmsLoggerTransportTest.php @@ -23,7 +23,7 @@ final class FakeSmsLoggerTransportTest extends TransportTestCase { - public static function createTransport(HttpClientInterface $client = null, LoggerInterface $logger = null): FakeSmsLoggerTransport + public static function createTransport(?HttpClientInterface $client = null, ?LoggerInterface $logger = null): FakeSmsLoggerTransport { $transport = (new FakeSmsLoggerTransport($logger ?? new NullLogger(), $client ?? new MockHttpClient())); diff --git a/src/Symfony/Component/Notifier/Bridge/Firebase/FirebaseTransport.php b/src/Symfony/Component/Notifier/Bridge/Firebase/FirebaseTransport.php index 2adaf4653ef08..3299a862277df 100644 --- a/src/Symfony/Component/Notifier/Bridge/Firebase/FirebaseTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/Firebase/FirebaseTransport.php @@ -31,8 +31,8 @@ final class FirebaseTransport extends AbstractTransport public function __construct( #[\SensitiveParameter] private string $token, - HttpClientInterface $client = null, - EventDispatcherInterface $dispatcher = null, + ?HttpClientInterface $client = null, + ?EventDispatcherInterface $dispatcher = null, ) { parent::__construct($client, $dispatcher); } diff --git a/src/Symfony/Component/Notifier/Bridge/Firebase/Tests/FirebaseTransportTest.php b/src/Symfony/Component/Notifier/Bridge/Firebase/Tests/FirebaseTransportTest.php index e0124ba35f931..704e9b9212ee4 100644 --- a/src/Symfony/Component/Notifier/Bridge/Firebase/Tests/FirebaseTransportTest.php +++ b/src/Symfony/Component/Notifier/Bridge/Firebase/Tests/FirebaseTransportTest.php @@ -28,7 +28,7 @@ */ final class FirebaseTransportTest extends TransportTestCase { - public static function createTransport(HttpClientInterface $client = null): FirebaseTransport + public static function createTransport(?HttpClientInterface $client = null): FirebaseTransport { return new FirebaseTransport('username:password', $client ?? new MockHttpClient()); } diff --git a/src/Symfony/Component/Notifier/Bridge/FortySixElks/FortySixElksTransport.php b/src/Symfony/Component/Notifier/Bridge/FortySixElks/FortySixElksTransport.php index 8b28dbba7294a..cf78b9adb25b1 100644 --- a/src/Symfony/Component/Notifier/Bridge/FortySixElks/FortySixElksTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/FortySixElks/FortySixElksTransport.php @@ -32,8 +32,8 @@ public function __construct( private string $apiUsername, #[\SensitiveParameter] private string $apiPassword, private string $from, - HttpClientInterface $client = null, - EventDispatcherInterface $dispatcher = null, + ?HttpClientInterface $client = null, + ?EventDispatcherInterface $dispatcher = null, ) { parent::__construct($client, $dispatcher); } diff --git a/src/Symfony/Component/Notifier/Bridge/FortySixElks/Tests/FortySixElksTransportTest.php b/src/Symfony/Component/Notifier/Bridge/FortySixElks/Tests/FortySixElksTransportTest.php index cf9fcd9fa3340..4a757a8568a89 100644 --- a/src/Symfony/Component/Notifier/Bridge/FortySixElks/Tests/FortySixElksTransportTest.php +++ b/src/Symfony/Component/Notifier/Bridge/FortySixElks/Tests/FortySixElksTransportTest.php @@ -24,7 +24,7 @@ class FortySixElksTransportTest extends TransportTestCase { - public static function createTransport(HttpClientInterface $client = null): FortySixElksTransport + public static function createTransport(?HttpClientInterface $client = null): FortySixElksTransport { return new FortySixElksTransport('api_username', 'api_password', 'Symfony', $client ?? new MockHttpClient()); } diff --git a/src/Symfony/Component/Notifier/Bridge/FreeMobile/FreeMobileTransport.php b/src/Symfony/Component/Notifier/Bridge/FreeMobile/FreeMobileTransport.php index 08f057dd417ae..ae008936b6d5a 100644 --- a/src/Symfony/Component/Notifier/Bridge/FreeMobile/FreeMobileTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/FreeMobile/FreeMobileTransport.php @@ -33,8 +33,8 @@ public function __construct( private string $login, #[\SensitiveParameter] private string $password, private string $phone, - HttpClientInterface $client = null, - EventDispatcherInterface $dispatcher = null, + ?HttpClientInterface $client = null, + ?EventDispatcherInterface $dispatcher = null, ) { $this->phone = str_replace('+33', '0', $phone); diff --git a/src/Symfony/Component/Notifier/Bridge/FreeMobile/Tests/FreeMobileTransportTest.php b/src/Symfony/Component/Notifier/Bridge/FreeMobile/Tests/FreeMobileTransportTest.php index f61ae14c83771..4010b86caff66 100644 --- a/src/Symfony/Component/Notifier/Bridge/FreeMobile/Tests/FreeMobileTransportTest.php +++ b/src/Symfony/Component/Notifier/Bridge/FreeMobile/Tests/FreeMobileTransportTest.php @@ -22,7 +22,7 @@ final class FreeMobileTransportTest extends TransportTestCase { - public static function createTransport(HttpClientInterface $client = null): FreeMobileTransport + public static function createTransport(?HttpClientInterface $client = null): FreeMobileTransport { return new FreeMobileTransport('login', 'pass', '0611223344', $client ?? new MockHttpClient()); } diff --git a/src/Symfony/Component/Notifier/Bridge/GatewayApi/GatewayApiTransport.php b/src/Symfony/Component/Notifier/Bridge/GatewayApi/GatewayApiTransport.php index 254f2bf2e0987..7fcd58c16a642 100644 --- a/src/Symfony/Component/Notifier/Bridge/GatewayApi/GatewayApiTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/GatewayApi/GatewayApiTransport.php @@ -31,8 +31,8 @@ final class GatewayApiTransport extends AbstractTransport public function __construct( #[\SensitiveParameter] private string $authToken, private string $from, - HttpClientInterface $client = null, - EventDispatcherInterface $dispatcher = null, + ?HttpClientInterface $client = null, + ?EventDispatcherInterface $dispatcher = null, ) { parent::__construct($client, $dispatcher); } diff --git a/src/Symfony/Component/Notifier/Bridge/GatewayApi/Tests/GatewayApiTransportTest.php b/src/Symfony/Component/Notifier/Bridge/GatewayApi/Tests/GatewayApiTransportTest.php index 0d62a98e92c32..ce0c9f1717869 100644 --- a/src/Symfony/Component/Notifier/Bridge/GatewayApi/Tests/GatewayApiTransportTest.php +++ b/src/Symfony/Component/Notifier/Bridge/GatewayApi/Tests/GatewayApiTransportTest.php @@ -28,7 +28,7 @@ */ final class GatewayApiTransportTest extends TransportTestCase { - public static function createTransport(HttpClientInterface $client = null): GatewayApiTransport + public static function createTransport(?HttpClientInterface $client = null): GatewayApiTransport { return new GatewayApiTransport('authtoken', 'Symfony', $client ?? new MockHttpClient()); } diff --git a/src/Symfony/Component/Notifier/Bridge/Gitter/GitterTransport.php b/src/Symfony/Component/Notifier/Bridge/Gitter/GitterTransport.php index 30d2e21ad5f2a..e2062446937c0 100644 --- a/src/Symfony/Component/Notifier/Bridge/Gitter/GitterTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/Gitter/GitterTransport.php @@ -31,8 +31,8 @@ final class GitterTransport extends AbstractTransport public function __construct( #[\SensitiveParameter] private string $token, private string $roomId, - HttpClientInterface $client = null, - EventDispatcherInterface $dispatcher = null, + ?HttpClientInterface $client = null, + ?EventDispatcherInterface $dispatcher = null, ) { parent::__construct($client, $dispatcher); } diff --git a/src/Symfony/Component/Notifier/Bridge/Gitter/Tests/GitterTransportTest.php b/src/Symfony/Component/Notifier/Bridge/Gitter/Tests/GitterTransportTest.php index 2afe49649abb9..daf8036a4e7a1 100644 --- a/src/Symfony/Component/Notifier/Bridge/Gitter/Tests/GitterTransportTest.php +++ b/src/Symfony/Component/Notifier/Bridge/Gitter/Tests/GitterTransportTest.php @@ -24,7 +24,7 @@ */ final class GitterTransportTest extends TransportTestCase { - public static function createTransport(HttpClientInterface $client = null): GitterTransport + public static function createTransport(?HttpClientInterface $client = null): GitterTransport { return (new GitterTransport('token', '5539a3ee5etest0d3255bfef', $client ?? new MockHttpClient()))->setHost('api.gitter.im'); } diff --git a/src/Symfony/Component/Notifier/Bridge/GoIp/GoIpTransport.php b/src/Symfony/Component/Notifier/Bridge/GoIp/GoIpTransport.php index 42687f9cec627..02059ce3b045c 100644 --- a/src/Symfony/Component/Notifier/Bridge/GoIp/GoIpTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/GoIp/GoIpTransport.php @@ -34,8 +34,8 @@ public function __construct( private readonly string $username, #[\SensitiveParameter] private readonly string $password, private readonly int $simSlot, - HttpClientInterface $client = null, - EventDispatcherInterface $dispatcher = null, + ?HttpClientInterface $client = null, + ?EventDispatcherInterface $dispatcher = null, ) { parent::__construct($client, $dispatcher); } diff --git a/src/Symfony/Component/Notifier/Bridge/GoIp/Tests/GoIpTransportTest.php b/src/Symfony/Component/Notifier/Bridge/GoIp/Tests/GoIpTransportTest.php index 3d9992c07e5ca..4ba5cdbfa2f08 100644 --- a/src/Symfony/Component/Notifier/Bridge/GoIp/Tests/GoIpTransportTest.php +++ b/src/Symfony/Component/Notifier/Bridge/GoIp/Tests/GoIpTransportTest.php @@ -33,7 +33,7 @@ public static function toStringProvider(): iterable yield ['goip://host.test:4000?sim_slot=4', self::createTransport()]; } - public static function createTransport(HttpClientInterface $client = null): GoIpTransport + public static function createTransport(?HttpClientInterface $client = null): GoIpTransport { return (new GoIpTransport('user', 'pass', 4, $client ?? new MockHttpClient())) ->setHost('host.test') diff --git a/src/Symfony/Component/Notifier/Bridge/GoogleChat/GoogleChatTransport.php b/src/Symfony/Component/Notifier/Bridge/GoogleChat/GoogleChatTransport.php index 5d1a1018a0ec8..496128971798d 100644 --- a/src/Symfony/Component/Notifier/Bridge/GoogleChat/GoogleChatTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/GoogleChat/GoogleChatTransport.php @@ -44,8 +44,8 @@ public function __construct( #[\SensitiveParameter] private string $accessKey, #[\SensitiveParameter] private string $accessToken, private ?string $threadKey = null, - HttpClientInterface $client = null, - EventDispatcherInterface $dispatcher = null, + ?HttpClientInterface $client = null, + ?EventDispatcherInterface $dispatcher = null, ) { parent::__construct($client, $dispatcher); } diff --git a/src/Symfony/Component/Notifier/Bridge/GoogleChat/Tests/GoogleChatTransportTest.php b/src/Symfony/Component/Notifier/Bridge/GoogleChat/Tests/GoogleChatTransportTest.php index a474b94f985e1..4f423290c3608 100644 --- a/src/Symfony/Component/Notifier/Bridge/GoogleChat/Tests/GoogleChatTransportTest.php +++ b/src/Symfony/Component/Notifier/Bridge/GoogleChat/Tests/GoogleChatTransportTest.php @@ -27,7 +27,7 @@ final class GoogleChatTransportTest extends TransportTestCase { - public static function createTransport(HttpClientInterface $client = null, string $threadKey = null): GoogleChatTransport + public static function createTransport(?HttpClientInterface $client = null, ?string $threadKey = null): GoogleChatTransport { return new GoogleChatTransport('My-Space', 'theAccessKey', 'theAccessToken=', $threadKey, $client ?? new MockHttpClient()); } diff --git a/src/Symfony/Component/Notifier/Bridge/Infobip/InfobipTransport.php b/src/Symfony/Component/Notifier/Bridge/Infobip/InfobipTransport.php index e1c2cbcc20d25..742434d843f9b 100644 --- a/src/Symfony/Component/Notifier/Bridge/Infobip/InfobipTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/Infobip/InfobipTransport.php @@ -30,8 +30,8 @@ final class InfobipTransport extends AbstractTransport public function __construct( #[\SensitiveParameter] private string $authToken, private string $from, - HttpClientInterface $client = null, - EventDispatcherInterface $dispatcher = null, + ?HttpClientInterface $client = null, + ?EventDispatcherInterface $dispatcher = null, ) { parent::__construct($client, $dispatcher); } diff --git a/src/Symfony/Component/Notifier/Bridge/Infobip/Tests/InfobipTransportTest.php b/src/Symfony/Component/Notifier/Bridge/Infobip/Tests/InfobipTransportTest.php index 760f63dd45fd9..99ad3f1142773 100644 --- a/src/Symfony/Component/Notifier/Bridge/Infobip/Tests/InfobipTransportTest.php +++ b/src/Symfony/Component/Notifier/Bridge/Infobip/Tests/InfobipTransportTest.php @@ -21,7 +21,7 @@ final class InfobipTransportTest extends TransportTestCase { - public static function createTransport(HttpClientInterface $client = null): InfobipTransport + public static function createTransport(?HttpClientInterface $client = null): InfobipTransport { return (new InfobipTransport('authtoken', '0611223344', $client ?? new MockHttpClient()))->setHost('host.test'); } diff --git a/src/Symfony/Component/Notifier/Bridge/Iqsms/IqsmsTransport.php b/src/Symfony/Component/Notifier/Bridge/Iqsms/IqsmsTransport.php index 95d3b4845834b..9790097470889 100644 --- a/src/Symfony/Component/Notifier/Bridge/Iqsms/IqsmsTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/Iqsms/IqsmsTransport.php @@ -32,8 +32,8 @@ public function __construct( private string $login, #[\SensitiveParameter] private string $password, private string $from, - HttpClientInterface $client = null, - EventDispatcherInterface $dispatcher = null, + ?HttpClientInterface $client = null, + ?EventDispatcherInterface $dispatcher = null, ) { parent::__construct($client, $dispatcher); } diff --git a/src/Symfony/Component/Notifier/Bridge/Iqsms/Tests/IqsmsTransportTest.php b/src/Symfony/Component/Notifier/Bridge/Iqsms/Tests/IqsmsTransportTest.php index 54d884210f759..74d0ad414a200 100644 --- a/src/Symfony/Component/Notifier/Bridge/Iqsms/Tests/IqsmsTransportTest.php +++ b/src/Symfony/Component/Notifier/Bridge/Iqsms/Tests/IqsmsTransportTest.php @@ -21,7 +21,7 @@ final class IqsmsTransportTest extends TransportTestCase { - public static function createTransport(HttpClientInterface $client = null): IqsmsTransport + public static function createTransport(?HttpClientInterface $client = null): IqsmsTransport { return new IqsmsTransport('login', 'password', 'sender', $client ?? new MockHttpClient()); } diff --git a/src/Symfony/Component/Notifier/Bridge/Isendpro/IsendproTransport.php b/src/Symfony/Component/Notifier/Bridge/Isendpro/IsendproTransport.php index 0d6e0efbe1724..e24aa573c3b45 100644 --- a/src/Symfony/Component/Notifier/Bridge/Isendpro/IsendproTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/Isendpro/IsendproTransport.php @@ -31,8 +31,8 @@ public function __construct( private ?string $from = null, private bool $noStop = false, private bool $sandbox = false, - HttpClientInterface $client = null, - EventDispatcherInterface $dispatcher = null, + ?HttpClientInterface $client = null, + ?EventDispatcherInterface $dispatcher = null, ) { parent::__construct($client, $dispatcher); } diff --git a/src/Symfony/Component/Notifier/Bridge/Isendpro/Tests/IsendproTransportFactoryTest.php b/src/Symfony/Component/Notifier/Bridge/Isendpro/Tests/IsendproTransportFactoryTest.php index dc2f7a9d1914f..482e4f411ab0c 100644 --- a/src/Symfony/Component/Notifier/Bridge/Isendpro/Tests/IsendproTransportFactoryTest.php +++ b/src/Symfony/Component/Notifier/Bridge/Isendpro/Tests/IsendproTransportFactoryTest.php @@ -78,7 +78,7 @@ public static function unsupportedSchemeProvider(): iterable /** * @dataProvider missingRequiredOptionProvider */ - public function testMissingRequiredOptionException(string $dsn, string $message = null) + public function testMissingRequiredOptionException(string $dsn, ?string $message = null) { $this->markTestIncomplete('The only required option is account key id, matched by incompleteDsnProvider'); } diff --git a/src/Symfony/Component/Notifier/Bridge/Isendpro/Tests/IsendproTransportTest.php b/src/Symfony/Component/Notifier/Bridge/Isendpro/Tests/IsendproTransportTest.php index 63f5663b032c8..4581a67b74fb0 100644 --- a/src/Symfony/Component/Notifier/Bridge/Isendpro/Tests/IsendproTransportTest.php +++ b/src/Symfony/Component/Notifier/Bridge/Isendpro/Tests/IsendproTransportTest.php @@ -23,7 +23,7 @@ final class IsendproTransportTest extends TransportTestCase { - public static function createTransport(HttpClientInterface $client = null): IsendproTransport + public static function createTransport(?HttpClientInterface $client = null): IsendproTransport { return (new IsendproTransport('accound_key_id', null, false, false, $client ?? new MockHttpClient()))->setHost('host.test'); } diff --git a/src/Symfony/Component/Notifier/Bridge/KazInfoTeh/KazInfoTehTransport.php b/src/Symfony/Component/Notifier/Bridge/KazInfoTeh/KazInfoTehTransport.php index 29d8bc71b4e3c..7762c978722e6 100644 --- a/src/Symfony/Component/Notifier/Bridge/KazInfoTeh/KazInfoTehTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/KazInfoTeh/KazInfoTehTransport.php @@ -32,8 +32,8 @@ public function __construct( private string $username, #[\SensitiveParameter] private string $password, private string $sender, - HttpClientInterface $client = null, - EventDispatcherInterface $dispatcher = null, + ?HttpClientInterface $client = null, + ?EventDispatcherInterface $dispatcher = null, ) { parent::__construct($client, $dispatcher); } diff --git a/src/Symfony/Component/Notifier/Bridge/KazInfoTeh/Tests/KazInfoTehTransportTest.php b/src/Symfony/Component/Notifier/Bridge/KazInfoTeh/Tests/KazInfoTehTransportTest.php index a0b66f5a03a2f..6aad9d0fd4611 100644 --- a/src/Symfony/Component/Notifier/Bridge/KazInfoTeh/Tests/KazInfoTehTransportTest.php +++ b/src/Symfony/Component/Notifier/Bridge/KazInfoTeh/Tests/KazInfoTehTransportTest.php @@ -26,7 +26,7 @@ */ final class KazInfoTehTransportTest extends TransportTestCase { - public static function createTransport(HttpClientInterface $client = null): TransportInterface + public static function createTransport(?HttpClientInterface $client = null): TransportInterface { return (new KazInfoTehTransport('username', 'password', 'sender', $client ?? new MockHttpClient()))->setHost('test.host'); } diff --git a/src/Symfony/Component/Notifier/Bridge/LightSms/LightSmsTransport.php b/src/Symfony/Component/Notifier/Bridge/LightSms/LightSmsTransport.php index 1ea6550723c6b..d9aa65d5a7177 100644 --- a/src/Symfony/Component/Notifier/Bridge/LightSms/LightSmsTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/LightSms/LightSmsTransport.php @@ -75,8 +75,8 @@ public function __construct( private string $login, #[\SensitiveParameter] private string $password, private string $from, - HttpClientInterface $client = null, - EventDispatcherInterface $dispatcher = null, + ?HttpClientInterface $client = null, + ?EventDispatcherInterface $dispatcher = null, ) { parent::__construct($client, $dispatcher); } diff --git a/src/Symfony/Component/Notifier/Bridge/LightSms/Tests/LightSmsTransportTest.php b/src/Symfony/Component/Notifier/Bridge/LightSms/Tests/LightSmsTransportTest.php index 5c687477af097..27f6a479a39ac 100644 --- a/src/Symfony/Component/Notifier/Bridge/LightSms/Tests/LightSmsTransportTest.php +++ b/src/Symfony/Component/Notifier/Bridge/LightSms/Tests/LightSmsTransportTest.php @@ -21,7 +21,7 @@ final class LightSmsTransportTest extends TransportTestCase { - public static function createTransport(HttpClientInterface $client = null): LightSmsTransport + public static function createTransport(?HttpClientInterface $client = null): LightSmsTransport { return new LightSmsTransport('accountSid', 'authToken', 'from', $client ?? new MockHttpClient()); } diff --git a/src/Symfony/Component/Notifier/Bridge/LineNotify/LineNotifyTransport.php b/src/Symfony/Component/Notifier/Bridge/LineNotify/LineNotifyTransport.php index f15b23da97606..255ec00e7db02 100644 --- a/src/Symfony/Component/Notifier/Bridge/LineNotify/LineNotifyTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/LineNotify/LineNotifyTransport.php @@ -29,8 +29,8 @@ final class LineNotifyTransport extends AbstractTransport public function __construct( #[\SensitiveParameter] private string $token, - HttpClientInterface $client = null, - EventDispatcherInterface $dispatcher = null, + ?HttpClientInterface $client = null, + ?EventDispatcherInterface $dispatcher = null, ) { parent::__construct($client, $dispatcher); } diff --git a/src/Symfony/Component/Notifier/Bridge/LineNotify/Tests/LineNotifyTransportTest.php b/src/Symfony/Component/Notifier/Bridge/LineNotify/Tests/LineNotifyTransportTest.php index b48d41e944fb4..35b6e3165e426 100644 --- a/src/Symfony/Component/Notifier/Bridge/LineNotify/Tests/LineNotifyTransportTest.php +++ b/src/Symfony/Component/Notifier/Bridge/LineNotify/Tests/LineNotifyTransportTest.php @@ -26,7 +26,7 @@ */ final class LineNotifyTransportTest extends TransportTestCase { - public static function createTransport(HttpClientInterface $client = null): LineNotifyTransport + public static function createTransport(?HttpClientInterface $client = null): LineNotifyTransport { return (new LineNotifyTransport('testToken', $client ?? new MockHttpClient()))->setHost('host.test'); } diff --git a/src/Symfony/Component/Notifier/Bridge/LinkedIn/LinkedInTransport.php b/src/Symfony/Component/Notifier/Bridge/LinkedIn/LinkedInTransport.php index 03843e0b94b98..8adc7b48bd952 100644 --- a/src/Symfony/Component/Notifier/Bridge/LinkedIn/LinkedInTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/LinkedIn/LinkedInTransport.php @@ -35,8 +35,8 @@ final class LinkedInTransport extends AbstractTransport public function __construct( #[\SensitiveParameter] private string $authToken, private string $accountId, - HttpClientInterface $client = null, - EventDispatcherInterface $dispatcher = null, + ?HttpClientInterface $client = null, + ?EventDispatcherInterface $dispatcher = null, ) { parent::__construct($client, $dispatcher); } diff --git a/src/Symfony/Component/Notifier/Bridge/LinkedIn/Share/ShareContentShare.php b/src/Symfony/Component/Notifier/Bridge/LinkedIn/Share/ShareContentShare.php index ee0bc9e0d65ac..f12f1e5f4f10d 100644 --- a/src/Symfony/Component/Notifier/Bridge/LinkedIn/Share/ShareContentShare.php +++ b/src/Symfony/Component/Notifier/Bridge/LinkedIn/Share/ShareContentShare.php @@ -52,7 +52,7 @@ final class ShareContentShare extends AbstractLinkedInShare self::LIVE_VIDEO, ]; - public function __construct(string $text, array $attributes = [], string $inferredLocale = null, ShareMediaShare $media = null, string $primaryLandingPageUrl = null, string $shareMediaCategory = self::NONE) + public function __construct(string $text, array $attributes = [], ?string $inferredLocale = null, ?ShareMediaShare $media = null, ?string $primaryLandingPageUrl = null, string $shareMediaCategory = self::NONE) { $this->options['shareCommentary'] = [ 'attributes' => $attributes, diff --git a/src/Symfony/Component/Notifier/Bridge/LinkedIn/Share/ShareMediaShare.php b/src/Symfony/Component/Notifier/Bridge/LinkedIn/Share/ShareMediaShare.php index 82dd3bcaf70e2..b252fa103a747 100644 --- a/src/Symfony/Component/Notifier/Bridge/LinkedIn/Share/ShareMediaShare.php +++ b/src/Symfony/Component/Notifier/Bridge/LinkedIn/Share/ShareMediaShare.php @@ -38,7 +38,7 @@ class ShareMediaShare extends AbstractLinkedInShare self::REGISTER, ]; - public function __construct(string $text, array $attributes = [], string $inferredLocale = null, bool $landingPage = false, string $landingPageTitle = null, string $landingPageUrl = null) + public function __construct(string $text, array $attributes = [], ?string $inferredLocale = null, bool $landingPage = false, ?string $landingPageTitle = null, ?string $landingPageUrl = null) { $this->options['description'] = [ 'text' => $text, diff --git a/src/Symfony/Component/Notifier/Bridge/LinkedIn/Tests/LinkedInTransportTest.php b/src/Symfony/Component/Notifier/Bridge/LinkedIn/Tests/LinkedInTransportTest.php index 23261e66d5064..599cd63d0bb68 100644 --- a/src/Symfony/Component/Notifier/Bridge/LinkedIn/Tests/LinkedInTransportTest.php +++ b/src/Symfony/Component/Notifier/Bridge/LinkedIn/Tests/LinkedInTransportTest.php @@ -26,7 +26,7 @@ final class LinkedInTransportTest extends TransportTestCase { - public static function createTransport(HttpClientInterface $client = null): LinkedInTransport + public static function createTransport(?HttpClientInterface $client = null): LinkedInTransport { return (new LinkedInTransport('AuthToken', 'AccountId', $client ?? new MockHttpClient()))->setHost('host.test'); } diff --git a/src/Symfony/Component/Notifier/Bridge/Mailjet/MailjetTransport.php b/src/Symfony/Component/Notifier/Bridge/Mailjet/MailjetTransport.php index c6f4791878b31..b16d5b3400df0 100644 --- a/src/Symfony/Component/Notifier/Bridge/Mailjet/MailjetTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/Mailjet/MailjetTransport.php @@ -31,8 +31,8 @@ final class MailjetTransport extends AbstractTransport public function __construct( #[\SensitiveParameter] private string $authToken, private string $from, - HttpClientInterface $client = null, - EventDispatcherInterface $dispatcher = null, + ?HttpClientInterface $client = null, + ?EventDispatcherInterface $dispatcher = null, ) { parent::__construct($client, $dispatcher); } diff --git a/src/Symfony/Component/Notifier/Bridge/Mailjet/Tests/MailjetTransportTest.php b/src/Symfony/Component/Notifier/Bridge/Mailjet/Tests/MailjetTransportTest.php index 077a4ec495e29..acb2842acd188 100644 --- a/src/Symfony/Component/Notifier/Bridge/Mailjet/Tests/MailjetTransportTest.php +++ b/src/Symfony/Component/Notifier/Bridge/Mailjet/Tests/MailjetTransportTest.php @@ -21,7 +21,7 @@ final class MailjetTransportTest extends TransportTestCase { - public static function createTransport(HttpClientInterface $client = null): MailjetTransport + public static function createTransport(?HttpClientInterface $client = null): MailjetTransport { return (new MailjetTransport('authtoken', 'Mailjet', $client ?? new MockHttpClient()))->setHost('host.test'); } diff --git a/src/Symfony/Component/Notifier/Bridge/Mastodon/MastodonOptions.php b/src/Symfony/Component/Notifier/Bridge/Mastodon/MastodonOptions.php index 2d6c316b03699..dff4169f21746 100644 --- a/src/Symfony/Component/Notifier/Bridge/Mastodon/MastodonOptions.php +++ b/src/Symfony/Component/Notifier/Bridge/Mastodon/MastodonOptions.php @@ -49,7 +49,7 @@ public function poll(array $choices, int $expiresIn): static /** * @return $this */ - public function attachMedia(File $file, File $thumbnail = null, string $description = null, string $focus = null): static + public function attachMedia(File $file, ?File $thumbnail = null, ?string $description = null, ?string $focus = null): static { $this->options['attach'][] = [ 'file' => $file, diff --git a/src/Symfony/Component/Notifier/Bridge/Mastodon/MastodonTransport.php b/src/Symfony/Component/Notifier/Bridge/Mastodon/MastodonTransport.php index 5daf19c9fcd44..088e75ebd4ae0 100644 --- a/src/Symfony/Component/Notifier/Bridge/Mastodon/MastodonTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/Mastodon/MastodonTransport.php @@ -35,8 +35,8 @@ final class MastodonTransport extends AbstractTransport { public function __construct( #[\SensitiveParameter] private readonly string $accessToken, - HttpClientInterface $client = null, - EventDispatcherInterface $dispatcher = null, + ?HttpClientInterface $client = null, + ?EventDispatcherInterface $dispatcher = null, ) { parent::__construct($client, $dispatcher); } diff --git a/src/Symfony/Component/Notifier/Bridge/Mastodon/Tests/MastodonTransportTest.php b/src/Symfony/Component/Notifier/Bridge/Mastodon/Tests/MastodonTransportTest.php index 836dfff11f7bf..a1b35d90aeeaf 100644 --- a/src/Symfony/Component/Notifier/Bridge/Mastodon/Tests/MastodonTransportTest.php +++ b/src/Symfony/Component/Notifier/Bridge/Mastodon/Tests/MastodonTransportTest.php @@ -27,7 +27,7 @@ */ class MastodonTransportTest extends TransportTestCase { - public static function createTransport(HttpClientInterface $client = null): MastodonTransport + public static function createTransport(?HttpClientInterface $client = null): MastodonTransport { return (new MastodonTransport('testAccessToken', $client ?? new MockHttpClient()))->setHost('host.test'); } diff --git a/src/Symfony/Component/Notifier/Bridge/Mattermost/MattermostTransport.php b/src/Symfony/Component/Notifier/Bridge/Mattermost/MattermostTransport.php index 3053166daa9e2..f22f5013c7b9d 100644 --- a/src/Symfony/Component/Notifier/Bridge/Mattermost/MattermostTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/Mattermost/MattermostTransport.php @@ -30,8 +30,8 @@ public function __construct( #[\SensitiveParameter] private string $token, private string $channel, private ?string $path = null, - HttpClientInterface $client = null, - EventDispatcherInterface $dispatcher = null, + ?HttpClientInterface $client = null, + ?EventDispatcherInterface $dispatcher = null, ) { parent::__construct($client, $dispatcher); diff --git a/src/Symfony/Component/Notifier/Bridge/Mattermost/Tests/MattermostTransportTest.php b/src/Symfony/Component/Notifier/Bridge/Mattermost/Tests/MattermostTransportTest.php index a34f60da5fd4c..d631bd7da41e5 100644 --- a/src/Symfony/Component/Notifier/Bridge/Mattermost/Tests/MattermostTransportTest.php +++ b/src/Symfony/Component/Notifier/Bridge/Mattermost/Tests/MattermostTransportTest.php @@ -24,7 +24,7 @@ */ final class MattermostTransportTest extends TransportTestCase { - public static function createTransport(HttpClientInterface $client = null): MattermostTransport + public static function createTransport(?HttpClientInterface $client = null): MattermostTransport { return (new MattermostTransport('testAccessToken', 'testChannel', null, $client ?? new MockHttpClient()))->setHost('host.test'); } diff --git a/src/Symfony/Component/Notifier/Bridge/Mercure/MercureOptions.php b/src/Symfony/Component/Notifier/Bridge/Mercure/MercureOptions.php index 2901f2025833c..da3b2e8797b34 100644 --- a/src/Symfony/Component/Notifier/Bridge/Mercure/MercureOptions.php +++ b/src/Symfony/Component/Notifier/Bridge/Mercure/MercureOptions.php @@ -27,7 +27,7 @@ final class MercureOptions implements MessageOptionsInterface /** * @param string|string[]|null $topics */ - public function __construct(string|array $topics = null, bool $private = false, string $id = null, string $type = null, int $retry = null) + public function __construct(string|array|null $topics = null, bool $private = false, ?string $id = null, ?string $type = null, ?int $retry = null) { $this->topics = null !== $topics ? (array) $topics : null; $this->private = $private; diff --git a/src/Symfony/Component/Notifier/Bridge/Mercure/MercureTransport.php b/src/Symfony/Component/Notifier/Bridge/Mercure/MercureTransport.php index 04b1c54008300..72559b85fa85f 100644 --- a/src/Symfony/Component/Notifier/Bridge/Mercure/MercureTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/Mercure/MercureTransport.php @@ -38,9 +38,9 @@ final class MercureTransport extends AbstractTransport public function __construct( private HubInterface $hub, private string $hubId, - string|array $topics = null, - HttpClientInterface $client = null, - EventDispatcherInterface $dispatcher = null, + string|array|null $topics = null, + ?HttpClientInterface $client = null, + ?EventDispatcherInterface $dispatcher = null, ) { $this->topics = $topics ?? 'https://symfony.com/notifier'; diff --git a/src/Symfony/Component/Notifier/Bridge/Mercure/MercureTransportFactory.php b/src/Symfony/Component/Notifier/Bridge/Mercure/MercureTransportFactory.php index f235beb651441..2e3646b289978 100644 --- a/src/Symfony/Component/Notifier/Bridge/Mercure/MercureTransportFactory.php +++ b/src/Symfony/Component/Notifier/Bridge/Mercure/MercureTransportFactory.php @@ -27,7 +27,7 @@ final class MercureTransportFactory extends AbstractTransportFactory { private HubRegistry $registry; - public function __construct(HubRegistry $registry, EventDispatcherInterface $dispatcher = null, HttpClientInterface $client = null) + public function __construct(HubRegistry $registry, ?EventDispatcherInterface $dispatcher = null, ?HttpClientInterface $client = null) { parent::__construct($dispatcher, $client); diff --git a/src/Symfony/Component/Notifier/Bridge/Mercure/Tests/MercureTransportTest.php b/src/Symfony/Component/Notifier/Bridge/Mercure/Tests/MercureTransportTest.php index 758bdb7229af7..bfe9190a8e592 100644 --- a/src/Symfony/Component/Notifier/Bridge/Mercure/Tests/MercureTransportTest.php +++ b/src/Symfony/Component/Notifier/Bridge/Mercure/Tests/MercureTransportTest.php @@ -34,7 +34,7 @@ */ final class MercureTransportTest extends TransportTestCase { - public static function createTransport(HttpClientInterface $client = null, HubInterface $hub = null, string $hubId = 'hubId', $topics = null): MercureTransport + public static function createTransport(?HttpClientInterface $client = null, ?HubInterface $hub = null, string $hubId = 'hubId', $topics = null): MercureTransport { $hub ??= new DummyHub(); diff --git a/src/Symfony/Component/Notifier/Bridge/MessageBird/MessageBirdTransport.php b/src/Symfony/Component/Notifier/Bridge/MessageBird/MessageBirdTransport.php index 574245ae83a29..3954267e7f3c5 100644 --- a/src/Symfony/Component/Notifier/Bridge/MessageBird/MessageBirdTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/MessageBird/MessageBirdTransport.php @@ -31,8 +31,8 @@ final class MessageBirdTransport extends AbstractTransport public function __construct( #[\SensitiveParameter] private string $token, private string $from, - HttpClientInterface $client = null, - EventDispatcherInterface $dispatcher = null, + ?HttpClientInterface $client = null, + ?EventDispatcherInterface $dispatcher = null, ) { parent::__construct($client, $dispatcher); diff --git a/src/Symfony/Component/Notifier/Bridge/MessageBird/Tests/MessageBirdTransportTest.php b/src/Symfony/Component/Notifier/Bridge/MessageBird/Tests/MessageBirdTransportTest.php index 8aad64c83d4a0..5984ebbedad08 100644 --- a/src/Symfony/Component/Notifier/Bridge/MessageBird/Tests/MessageBirdTransportTest.php +++ b/src/Symfony/Component/Notifier/Bridge/MessageBird/Tests/MessageBirdTransportTest.php @@ -22,7 +22,7 @@ final class MessageBirdTransportTest extends TransportTestCase { - public static function createTransport(HttpClientInterface $client = null): MessageBirdTransport + public static function createTransport(?HttpClientInterface $client = null): MessageBirdTransport { return new MessageBirdTransport('token', 'from', $client ?? new MockHttpClient()); } diff --git a/src/Symfony/Component/Notifier/Bridge/MessageMedia/MessageMediaTransport.php b/src/Symfony/Component/Notifier/Bridge/MessageMedia/MessageMediaTransport.php index 9fccadcac7e6a..8b8a17c0ddcb5 100644 --- a/src/Symfony/Component/Notifier/Bridge/MessageMedia/MessageMediaTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/MessageMedia/MessageMediaTransport.php @@ -33,8 +33,8 @@ public function __construct( #[\SensitiveParameter] private string $apiKey, #[\SensitiveParameter] private string $apiSecret, private ?string $from = null, - HttpClientInterface $client = null, - EventDispatcherInterface $dispatcher = null, + ?HttpClientInterface $client = null, + ?EventDispatcherInterface $dispatcher = null, ) { parent::__construct($client, $dispatcher); } diff --git a/src/Symfony/Component/Notifier/Bridge/MessageMedia/Tests/MessageMediaTransportTest.php b/src/Symfony/Component/Notifier/Bridge/MessageMedia/Tests/MessageMediaTransportTest.php index 984c6a1b79a48..3dfdaf24a456c 100644 --- a/src/Symfony/Component/Notifier/Bridge/MessageMedia/Tests/MessageMediaTransportTest.php +++ b/src/Symfony/Component/Notifier/Bridge/MessageMedia/Tests/MessageMediaTransportTest.php @@ -25,7 +25,7 @@ final class MessageMediaTransportTest extends TransportTestCase { - public static function createTransport(HttpClientInterface $client = null, string $from = null): MessageMediaTransport + public static function createTransport(?HttpClientInterface $client = null, ?string $from = null): MessageMediaTransport { return new MessageMediaTransport('apiKey', 'apiSecret', $from, $client ?? new MockHttpClient()); } diff --git a/src/Symfony/Component/Notifier/Bridge/MicrosoftTeams/MicrosoftTeamsTransport.php b/src/Symfony/Component/Notifier/Bridge/MicrosoftTeams/MicrosoftTeamsTransport.php index 423d24aa0e844..6b9c132a537fe 100644 --- a/src/Symfony/Component/Notifier/Bridge/MicrosoftTeams/MicrosoftTeamsTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/MicrosoftTeams/MicrosoftTeamsTransport.php @@ -31,8 +31,8 @@ final class MicrosoftTeamsTransport extends AbstractTransport public function __construct( private string $path, - HttpClientInterface $client = null, - EventDispatcherInterface $dispatcher = null, + ?HttpClientInterface $client = null, + ?EventDispatcherInterface $dispatcher = null, ) { parent::__construct($client, $dispatcher); diff --git a/src/Symfony/Component/Notifier/Bridge/MicrosoftTeams/Tests/MicrosoftTeamsTransportTest.php b/src/Symfony/Component/Notifier/Bridge/MicrosoftTeams/Tests/MicrosoftTeamsTransportTest.php index be856e590ce01..e1a9fdae186d2 100644 --- a/src/Symfony/Component/Notifier/Bridge/MicrosoftTeams/Tests/MicrosoftTeamsTransportTest.php +++ b/src/Symfony/Component/Notifier/Bridge/MicrosoftTeams/Tests/MicrosoftTeamsTransportTest.php @@ -26,7 +26,7 @@ final class MicrosoftTeamsTransportTest extends TransportTestCase { - public static function createTransport(HttpClientInterface $client = null): MicrosoftTeamsTransport + public static function createTransport(?HttpClientInterface $client = null): MicrosoftTeamsTransport { return (new MicrosoftTeamsTransport('/testPath', $client ?? new MockHttpClient()))->setHost('host.test'); } diff --git a/src/Symfony/Component/Notifier/Bridge/Mobyt/MobytTransport.php b/src/Symfony/Component/Notifier/Bridge/Mobyt/MobytTransport.php index a682768b988f8..9a900e162f264 100644 --- a/src/Symfony/Component/Notifier/Bridge/Mobyt/MobytTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/Mobyt/MobytTransport.php @@ -34,9 +34,9 @@ public function __construct( private string $accountSid, #[\SensitiveParameter] private string $authToken, private string $from, - string $typeQuality = null, - HttpClientInterface $client = null, - EventDispatcherInterface $dispatcher = null, + ?string $typeQuality = null, + ?HttpClientInterface $client = null, + ?EventDispatcherInterface $dispatcher = null, ) { $typeQuality ??= MobytOptions::MESSAGE_TYPE_QUALITY_LOW; MobytOptions::validateMessageType($typeQuality); diff --git a/src/Symfony/Component/Notifier/Bridge/Mobyt/Tests/MobytTransportTest.php b/src/Symfony/Component/Notifier/Bridge/Mobyt/Tests/MobytTransportTest.php index 9b187fc76fb15..ac24a2864edc6 100644 --- a/src/Symfony/Component/Notifier/Bridge/Mobyt/Tests/MobytTransportTest.php +++ b/src/Symfony/Component/Notifier/Bridge/Mobyt/Tests/MobytTransportTest.php @@ -25,7 +25,7 @@ */ final class MobytTransportTest extends TransportTestCase { - public static function createTransport(HttpClientInterface $client = null, string $messageType = MobytOptions::MESSAGE_TYPE_QUALITY_LOW): MobytTransport + public static function createTransport(?HttpClientInterface $client = null, string $messageType = MobytOptions::MESSAGE_TYPE_QUALITY_LOW): MobytTransport { return (new MobytTransport('accountSid', 'authToken', 'from', $messageType, $client ?? new MockHttpClient()))->setHost('host.test'); } diff --git a/src/Symfony/Component/Notifier/Bridge/Novu/NovuTransport.php b/src/Symfony/Component/Notifier/Bridge/Novu/NovuTransport.php index 40e6d294861f2..fff678eca0cd0 100644 --- a/src/Symfony/Component/Notifier/Bridge/Novu/NovuTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/Novu/NovuTransport.php @@ -30,8 +30,8 @@ class NovuTransport extends AbstractTransport public function __construct( #[\SensitiveParameter] protected string $apiKey, - HttpClientInterface $client = null, - EventDispatcherInterface $dispatcher = null, + ?HttpClientInterface $client = null, + ?EventDispatcherInterface $dispatcher = null, ) { parent::__construct($client, $dispatcher); } diff --git a/src/Symfony/Component/Notifier/Bridge/Novu/Tests/NovuTransportTest.php b/src/Symfony/Component/Notifier/Bridge/Novu/Tests/NovuTransportTest.php index a386ea62d70e6..7bb3f3c9a2b95 100644 --- a/src/Symfony/Component/Notifier/Bridge/Novu/Tests/NovuTransportTest.php +++ b/src/Symfony/Component/Notifier/Bridge/Novu/Tests/NovuTransportTest.php @@ -25,7 +25,7 @@ class NovuTransportTest extends TransportTestCase { - public static function createTransport(HttpClientInterface $client = null): TransportInterface + public static function createTransport(?HttpClientInterface $client = null): TransportInterface { return (new NovuTransport('9c9ced75881ddc65c033273f466b42d1', $client ?? new MockHttpClient()))->setHost('host.test'); } diff --git a/src/Symfony/Component/Notifier/Bridge/Ntfy/NtfyTransport.php b/src/Symfony/Component/Notifier/Bridge/Ntfy/NtfyTransport.php index 48ab57ee9692a..6e193d595c478 100644 --- a/src/Symfony/Component/Notifier/Bridge/Ntfy/NtfyTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/Ntfy/NtfyTransport.php @@ -34,8 +34,8 @@ final class NtfyTransport extends AbstractTransport public function __construct( private string $topic, private bool $secureHttp = true, - HttpClientInterface $client = null, - EventDispatcherInterface $dispatcher = null, + ?HttpClientInterface $client = null, + ?EventDispatcherInterface $dispatcher = null, ) { parent::__construct($client, $dispatcher); } @@ -88,6 +88,8 @@ protected function doSend(MessageInterface $message): SentMessage if (null !== $this->user && null !== $this->password) { $headers['Authorization'] = 'Basic '.rtrim(base64_encode($this->user.':'.$this->password), '='); + } elseif (null !== $this->password) { + $headers['Authorization'] = 'Bearer '.$this->password; } $response = $this->client->request('POST', ($this->secureHttp ? 'https' : 'http').'://'.$this->getEndpoint(), [ diff --git a/src/Symfony/Component/Notifier/Bridge/Ntfy/NtfyTransportFactory.php b/src/Symfony/Component/Notifier/Bridge/Ntfy/NtfyTransportFactory.php index b0f534e75a003..cede6795da4b9 100644 --- a/src/Symfony/Component/Notifier/Bridge/Ntfy/NtfyTransportFactory.php +++ b/src/Symfony/Component/Notifier/Bridge/Ntfy/NtfyTransportFactory.php @@ -41,8 +41,11 @@ public function create(Dsn $dsn): TransportInterface $transport->setPort($port); } - if (!empty($user = $dsn->getUser()) && !empty($password = $dsn->getPassword())) { + if (!empty($user = $dsn->getUser())) { $transport->setUser($user); + } + + if (!empty($password = $dsn->getPassword())) { $transport->setPassword($password); } diff --git a/src/Symfony/Component/Notifier/Bridge/Ntfy/Tests/NtfyTransportFactoryTest.php b/src/Symfony/Component/Notifier/Bridge/Ntfy/Tests/NtfyTransportFactoryTest.php index d54fdd122f880..35a77a54b379a 100644 --- a/src/Symfony/Component/Notifier/Bridge/Ntfy/Tests/NtfyTransportFactoryTest.php +++ b/src/Symfony/Component/Notifier/Bridge/Ntfy/Tests/NtfyTransportFactoryTest.php @@ -31,6 +31,10 @@ public static function createProvider(): iterable 'ntfy://ntfy.sh/test', 'ntfy://user:password@default/test', ]; + yield [ + 'ntfy://ntfy.sh/test', + 'ntfy://:password@default/test', + ]; yield [ 'ntfy://ntfy.sh:8888/test', 'ntfy://user:password@default:8888/test?secureHttp=off', diff --git a/src/Symfony/Component/Notifier/Bridge/Ntfy/Tests/NtfyTransportTest.php b/src/Symfony/Component/Notifier/Bridge/Ntfy/Tests/NtfyTransportTest.php index 5d2782bb23187..d1868710efdf8 100644 --- a/src/Symfony/Component/Notifier/Bridge/Ntfy/Tests/NtfyTransportTest.php +++ b/src/Symfony/Component/Notifier/Bridge/Ntfy/Tests/NtfyTransportTest.php @@ -25,7 +25,7 @@ */ final class NtfyTransportTest extends TransportTestCase { - public static function createTransport(HttpClientInterface $client = null): NtfyTransport + public static function createTransport(?HttpClientInterface $client = null): NtfyTransport { return new NtfyTransport('test', true, $client ?? new MockHttpClient()); } @@ -85,6 +85,32 @@ public function testSend() $this->assertSame('2BYIwRmvBKcv', $sentMessage->getMessageId()); } + public function testSendWithPassword() + { + $response = $this->createMock(ResponseInterface::class); + $response->expects($this->exactly(2)) + ->method('getStatusCode') + ->willReturn(200); + $response->expects($this->once()) + ->method('getContent') + ->willReturn(json_encode(['id' => '2BYIwRmvBKcv', 'event' => 'message'])); + + $client = new MockHttpClient(function (string $method, string $url, array $options = []) use ($response): ResponseInterface { + $expectedBody = json_encode(['topic' => 'test', 'title' => 'Hello', 'message' => 'World']); + $expectedAuthorization = 'Authorization: Bearer testtokentesttoken'; + $this->assertJsonStringEqualsJsonString($expectedBody, $options['body']); + $this->assertTrue(\in_array($expectedAuthorization, $options['headers'], true)); + + return $response; + }); + + $transport = $this->createTransport($client)->setPassword('testtokentesttoken'); + + $sentMessage = $transport->send(new PushMessage('Hello', 'World')); + + $this->assertSame('2BYIwRmvBKcv', $sentMessage->getMessageId()); + } + public function testSendWithUserAndPassword() { $response = $this->createMock(ResponseInterface::class); diff --git a/src/Symfony/Component/Notifier/Bridge/Octopush/OctopushTransport.php b/src/Symfony/Component/Notifier/Bridge/Octopush/OctopushTransport.php index 837e9a2f123f3..c789a98e1485c 100644 --- a/src/Symfony/Component/Notifier/Bridge/Octopush/OctopushTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/Octopush/OctopushTransport.php @@ -33,8 +33,8 @@ public function __construct( #[\SensitiveParameter] private string $apiKey, private string $from, private string $type, - HttpClientInterface $client = null, - EventDispatcherInterface $dispatcher = null, + ?HttpClientInterface $client = null, + ?EventDispatcherInterface $dispatcher = null, ) { parent::__construct($client, $dispatcher); diff --git a/src/Symfony/Component/Notifier/Bridge/Octopush/Tests/OctopushTransportTest.php b/src/Symfony/Component/Notifier/Bridge/Octopush/Tests/OctopushTransportTest.php index 42776fecc5b1c..43c15bb7dfc96 100644 --- a/src/Symfony/Component/Notifier/Bridge/Octopush/Tests/OctopushTransportTest.php +++ b/src/Symfony/Component/Notifier/Bridge/Octopush/Tests/OctopushTransportTest.php @@ -21,7 +21,7 @@ final class OctopushTransportTest extends TransportTestCase { - public static function createTransport(HttpClientInterface $client = null): OctopushTransport + public static function createTransport(?HttpClientInterface $client = null): OctopushTransport { return new OctopushTransport('userLogin', 'apiKey', 'from', 'type', $client ?? new MockHttpClient()); } diff --git a/src/Symfony/Component/Notifier/Bridge/OneSignal/OneSignalTransport.php b/src/Symfony/Component/Notifier/Bridge/OneSignal/OneSignalTransport.php index 3498d733fa91a..28a093fd9bd6a 100644 --- a/src/Symfony/Component/Notifier/Bridge/OneSignal/OneSignalTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/OneSignal/OneSignalTransport.php @@ -33,8 +33,8 @@ public function __construct( private string $appId, #[\SensitiveParameter] private string $apiKey, private ?string $defaultRecipientId = null, - HttpClientInterface $client = null, - EventDispatcherInterface $dispatcher = null, + ?HttpClientInterface $client = null, + ?EventDispatcherInterface $dispatcher = null, ) { parent::__construct($client, $dispatcher); diff --git a/src/Symfony/Component/Notifier/Bridge/OneSignal/Tests/OneSignalTransportTest.php b/src/Symfony/Component/Notifier/Bridge/OneSignal/Tests/OneSignalTransportTest.php index bcdb18e027ba2..66e1618bedcb4 100644 --- a/src/Symfony/Component/Notifier/Bridge/OneSignal/Tests/OneSignalTransportTest.php +++ b/src/Symfony/Component/Notifier/Bridge/OneSignal/Tests/OneSignalTransportTest.php @@ -30,7 +30,7 @@ */ final class OneSignalTransportTest extends TransportTestCase { - public static function createTransport(HttpClientInterface $client = null, string $recipientId = null): OneSignalTransport + public static function createTransport(?HttpClientInterface $client = null, ?string $recipientId = null): OneSignalTransport { return new OneSignalTransport('9fb175f0-0b32-4e99-ae97-bd228b9eb246', 'api_key', $recipientId, $client ?? new MockHttpClient()); } diff --git a/src/Symfony/Component/Notifier/Bridge/OrangeSms/OrangeSmsTransport.php b/src/Symfony/Component/Notifier/Bridge/OrangeSms/OrangeSmsTransport.php index 5f27ec1e353a0..bad0383e4c0a1 100644 --- a/src/Symfony/Component/Notifier/Bridge/OrangeSms/OrangeSmsTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/OrangeSms/OrangeSmsTransport.php @@ -29,8 +29,8 @@ public function __construct( #[\SensitiveParameter] private string $clientSecret, private string $from, private ?string $senderName = null, - HttpClientInterface $client = null, - EventDispatcherInterface $dispatcher = null, + ?HttpClientInterface $client = null, + ?EventDispatcherInterface $dispatcher = null, ) { parent::__construct($client, $dispatcher); diff --git a/src/Symfony/Component/Notifier/Bridge/OrangeSms/Tests/OrangeSmsTransportTest.php b/src/Symfony/Component/Notifier/Bridge/OrangeSms/Tests/OrangeSmsTransportTest.php index 62d6a9f247dd2..c1d61ec14c634 100644 --- a/src/Symfony/Component/Notifier/Bridge/OrangeSms/Tests/OrangeSmsTransportTest.php +++ b/src/Symfony/Component/Notifier/Bridge/OrangeSms/Tests/OrangeSmsTransportTest.php @@ -21,7 +21,7 @@ final class OrangeSmsTransportTest extends TransportTestCase { - public static function createTransport(HttpClientInterface $client = null): OrangeSmsTransport + public static function createTransport(?HttpClientInterface $client = null): OrangeSmsTransport { return (new OrangeSmsTransport('CLIENT_ID', 'CLIENT_SECRET', 'FROM', 'SENDER_NAME', $client ?? new MockHttpClient()))->setHost('host.test'); } diff --git a/src/Symfony/Component/Notifier/Bridge/OvhCloud/OvhCloudTransport.php b/src/Symfony/Component/Notifier/Bridge/OvhCloud/OvhCloudTransport.php index f5c6f97f5380e..575e72224e3ad 100644 --- a/src/Symfony/Component/Notifier/Bridge/OvhCloud/OvhCloudTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/OvhCloud/OvhCloudTransport.php @@ -36,8 +36,8 @@ public function __construct( #[\SensitiveParameter] private string $applicationSecret, #[\SensitiveParameter] private string $consumerKey, private string $serviceName, - HttpClientInterface $client = null, - EventDispatcherInterface $dispatcher = null, + ?HttpClientInterface $client = null, + ?EventDispatcherInterface $dispatcher = null, ) { parent::__construct($client, $dispatcher); diff --git a/src/Symfony/Component/Notifier/Bridge/OvhCloud/Tests/OvhCloudTransportTest.php b/src/Symfony/Component/Notifier/Bridge/OvhCloud/Tests/OvhCloudTransportTest.php index cc099581c949e..2d904e5c4379e 100644 --- a/src/Symfony/Component/Notifier/Bridge/OvhCloud/Tests/OvhCloudTransportTest.php +++ b/src/Symfony/Component/Notifier/Bridge/OvhCloud/Tests/OvhCloudTransportTest.php @@ -23,7 +23,7 @@ final class OvhCloudTransportTest extends TransportTestCase { - public static function createTransport(HttpClientInterface $client = null, string $sender = null, bool $noStopClause = false): OvhCloudTransport + public static function createTransport(?HttpClientInterface $client = null, ?string $sender = null, bool $noStopClause = false): OvhCloudTransport { return (new OvhCloudTransport('applicationKey', 'applicationSecret', 'consumerKey', 'serviceName', $client ?? new MockHttpClient()))->setSender($sender)->setNoStopClause($noStopClause); } diff --git a/src/Symfony/Component/Notifier/Bridge/PagerDuty/PagerDutyTransport.php b/src/Symfony/Component/Notifier/Bridge/PagerDuty/PagerDutyTransport.php index 4aaeb6621b6b2..01a39133fc567 100644 --- a/src/Symfony/Component/Notifier/Bridge/PagerDuty/PagerDutyTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/PagerDuty/PagerDutyTransport.php @@ -30,8 +30,8 @@ final class PagerDutyTransport extends AbstractTransport public function __construct( #[\SensitiveParameter] private readonly string $token, - HttpClientInterface $client = null, - EventDispatcherInterface $dispatcher = null, + ?HttpClientInterface $client = null, + ?EventDispatcherInterface $dispatcher = null, ) { parent::__construct($client, $dispatcher); @@ -47,7 +47,7 @@ public function supports(MessageInterface $message): bool return $message instanceof PushMessage && (null === $message->getOptions() || $message->getOptions() instanceof PagerDutyOptions); } - protected function doSend(MessageInterface $message = null): SentMessage + protected function doSend(?MessageInterface $message = null): SentMessage { if (!$message instanceof PushMessage) { throw new UnsupportedMessageTypeException(__CLASS__, PushMessage::class, $message); diff --git a/src/Symfony/Component/Notifier/Bridge/PagerDuty/Tests/PagerDutyTransportTest.php b/src/Symfony/Component/Notifier/Bridge/PagerDuty/Tests/PagerDutyTransportTest.php index da3b55d5df0a7..808882a65a445 100644 --- a/src/Symfony/Component/Notifier/Bridge/PagerDuty/Tests/PagerDutyTransportTest.php +++ b/src/Symfony/Component/Notifier/Bridge/PagerDuty/Tests/PagerDutyTransportTest.php @@ -23,7 +23,7 @@ final class PagerDutyTransportTest extends TransportTestCase { - public static function createTransport(HttpClientInterface $client = null): PagerDutyTransport + public static function createTransport(?HttpClientInterface $client = null): PagerDutyTransport { return (new PagerDutyTransport('testToken', $client ?? new MockHttpClient()))->setHost('test.pagerduty.com'); } diff --git a/src/Symfony/Component/Notifier/Bridge/Plivo/PlivoTransport.php b/src/Symfony/Component/Notifier/Bridge/Plivo/PlivoTransport.php index f8f6adb09415f..d1995c2d471a7 100644 --- a/src/Symfony/Component/Notifier/Bridge/Plivo/PlivoTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/Plivo/PlivoTransport.php @@ -34,8 +34,8 @@ public function __construct( private readonly string $authId, #[\SensitiveParameter] private readonly string $authToken, private readonly string $from, - HttpClientInterface $client = null, - EventDispatcherInterface $dispatcher = null, + ?HttpClientInterface $client = null, + ?EventDispatcherInterface $dispatcher = null, ) { parent::__construct($client, $dispatcher); } diff --git a/src/Symfony/Component/Notifier/Bridge/Plivo/Tests/PlivoTransportTest.php b/src/Symfony/Component/Notifier/Bridge/Plivo/Tests/PlivoTransportTest.php index 1174e8df83ba5..07b7c41bb0b14 100644 --- a/src/Symfony/Component/Notifier/Bridge/Plivo/Tests/PlivoTransportTest.php +++ b/src/Symfony/Component/Notifier/Bridge/Plivo/Tests/PlivoTransportTest.php @@ -24,7 +24,7 @@ final class PlivoTransportTest extends TransportTestCase { - public static function createTransport(HttpClientInterface $client = null, string $from = 'from'): PlivoTransport + public static function createTransport(?HttpClientInterface $client = null, string $from = 'from'): PlivoTransport { return new PlivoTransport('authId', 'authToken', $from, $client ?? new MockHttpClient()); } diff --git a/src/Symfony/Component/Notifier/Bridge/Pushover/PushoverTransport.php b/src/Symfony/Component/Notifier/Bridge/Pushover/PushoverTransport.php index 900dcf84e4248..5fee06d40810a 100644 --- a/src/Symfony/Component/Notifier/Bridge/Pushover/PushoverTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/Pushover/PushoverTransport.php @@ -31,8 +31,8 @@ final class PushoverTransport extends AbstractTransport public function __construct( #[\SensitiveParameter] private readonly string $userKey, #[\SensitiveParameter] private readonly string $appToken, - HttpClientInterface $client = null, - EventDispatcherInterface $dispatcher = null, + ?HttpClientInterface $client = null, + ?EventDispatcherInterface $dispatcher = null, ) { parent::__construct($client, $dispatcher); } diff --git a/src/Symfony/Component/Notifier/Bridge/Pushover/Tests/PushoverTransportTest.php b/src/Symfony/Component/Notifier/Bridge/Pushover/Tests/PushoverTransportTest.php index dbe24dfaffbf9..0e99ea3592f30 100644 --- a/src/Symfony/Component/Notifier/Bridge/Pushover/Tests/PushoverTransportTest.php +++ b/src/Symfony/Component/Notifier/Bridge/Pushover/Tests/PushoverTransportTest.php @@ -23,7 +23,7 @@ final class PushoverTransportTest extends TransportTestCase { - public static function createTransport(HttpClientInterface $client = null): PushoverTransport + public static function createTransport(?HttpClientInterface $client = null): PushoverTransport { return new PushoverTransport('userKey', 'appToken', $client ?? new MockHttpClient()); } diff --git a/src/Symfony/Component/Notifier/Bridge/Redlink/RedlinkTransport.php b/src/Symfony/Component/Notifier/Bridge/Redlink/RedlinkTransport.php index 77e892cf71748..a7146c907a873 100644 --- a/src/Symfony/Component/Notifier/Bridge/Redlink/RedlinkTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/Redlink/RedlinkTransport.php @@ -33,8 +33,8 @@ public function __construct( #[\SensitiveParameter] private readonly string $appToken, private readonly ?string $from, private readonly ?string $version, - HttpClientInterface $client = null, - EventDispatcherInterface $dispatcher = null, + ?HttpClientInterface $client = null, + ?EventDispatcherInterface $dispatcher = null, ) { parent::__construct($client, $dispatcher); } diff --git a/src/Symfony/Component/Notifier/Bridge/Redlink/Tests/RedlinkTransportTest.php b/src/Symfony/Component/Notifier/Bridge/Redlink/Tests/RedlinkTransportTest.php index 353e39c8bf533..84d225832e801 100644 --- a/src/Symfony/Component/Notifier/Bridge/Redlink/Tests/RedlinkTransportTest.php +++ b/src/Symfony/Component/Notifier/Bridge/Redlink/Tests/RedlinkTransportTest.php @@ -23,7 +23,7 @@ final class RedlinkTransportTest extends TransportTestCase { - public static function createTransport(HttpClientInterface $client = null): RedlinkTransport + public static function createTransport(?HttpClientInterface $client = null): RedlinkTransport { return (new RedlinkTransport( 'testApiToken', diff --git a/src/Symfony/Component/Notifier/Bridge/RingCentral/RingCentralOptions.php b/src/Symfony/Component/Notifier/Bridge/RingCentral/RingCentralOptions.php index c3d3c6fff9476..8897f0af697e0 100644 --- a/src/Symfony/Component/Notifier/Bridge/RingCentral/RingCentralOptions.php +++ b/src/Symfony/Component/Notifier/Bridge/RingCentral/RingCentralOptions.php @@ -33,7 +33,7 @@ public function getRecipientId(): ?string /** * @return $this */ - public function country(string $id, string $isoCode = null, string $name = null, string $uri = null, string $callingCode = null): static + public function country(string $id, ?string $isoCode = null, ?string $name = null, ?string $uri = null, ?string $callingCode = null): static { $this->options['country'] = [ 'id' => $id, diff --git a/src/Symfony/Component/Notifier/Bridge/RingCentral/RingCentralTransport.php b/src/Symfony/Component/Notifier/Bridge/RingCentral/RingCentralTransport.php index eb12c487cf62b..7868aa41b4e03 100644 --- a/src/Symfony/Component/Notifier/Bridge/RingCentral/RingCentralTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/RingCentral/RingCentralTransport.php @@ -32,8 +32,8 @@ final class RingCentralTransport extends AbstractTransport public function __construct( #[\SensitiveParameter] private readonly string $apiToken, private readonly string $from, - HttpClientInterface $client = null, - EventDispatcherInterface $dispatcher = null, + ?HttpClientInterface $client = null, + ?EventDispatcherInterface $dispatcher = null, ) { parent::__construct($client, $dispatcher); } diff --git a/src/Symfony/Component/Notifier/Bridge/RingCentral/Tests/RingCentralTransportTest.php b/src/Symfony/Component/Notifier/Bridge/RingCentral/Tests/RingCentralTransportTest.php index b82581191a2d3..76c4d6f544eee 100644 --- a/src/Symfony/Component/Notifier/Bridge/RingCentral/Tests/RingCentralTransportTest.php +++ b/src/Symfony/Component/Notifier/Bridge/RingCentral/Tests/RingCentralTransportTest.php @@ -24,7 +24,7 @@ final class RingCentralTransportTest extends TransportTestCase { - public static function createTransport(HttpClientInterface $client = null, string $from = 'from'): RingCentralTransport + public static function createTransport(?HttpClientInterface $client = null, string $from = 'from'): RingCentralTransport { return new RingCentralTransport('apiToken', $from, $client ?? new MockHttpClient()); } diff --git a/src/Symfony/Component/Notifier/Bridge/RocketChat/RocketChatTransport.php b/src/Symfony/Component/Notifier/Bridge/RocketChat/RocketChatTransport.php index 65ed42b510f83..a283a861f4091 100644 --- a/src/Symfony/Component/Notifier/Bridge/RocketChat/RocketChatTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/RocketChat/RocketChatTransport.php @@ -31,8 +31,8 @@ final class RocketChatTransport extends AbstractTransport public function __construct( #[\SensitiveParameter] private string $accessToken, private ?string $chatChannel = null, - HttpClientInterface $client = null, - EventDispatcherInterface $dispatcher = null, + ?HttpClientInterface $client = null, + ?EventDispatcherInterface $dispatcher = null, ) { parent::__construct($client, $dispatcher); diff --git a/src/Symfony/Component/Notifier/Bridge/RocketChat/Tests/RocketChatTransportTest.php b/src/Symfony/Component/Notifier/Bridge/RocketChat/Tests/RocketChatTransportTest.php index da14e546e60a9..99d9213eb6952 100644 --- a/src/Symfony/Component/Notifier/Bridge/RocketChat/Tests/RocketChatTransportTest.php +++ b/src/Symfony/Component/Notifier/Bridge/RocketChat/Tests/RocketChatTransportTest.php @@ -24,7 +24,7 @@ */ final class RocketChatTransportTest extends TransportTestCase { - public static function createTransport(HttpClientInterface $client = null, string $channel = null): RocketChatTransport + public static function createTransport(?HttpClientInterface $client = null, ?string $channel = null): RocketChatTransport { return new RocketChatTransport('testAccessToken', $channel, $client ?? new MockHttpClient()); } diff --git a/src/Symfony/Component/Notifier/Bridge/Sendberry/SendberryTransport.php b/src/Symfony/Component/Notifier/Bridge/Sendberry/SendberryTransport.php index 08b6c52fd8cff..a96826aeb56d6 100644 --- a/src/Symfony/Component/Notifier/Bridge/Sendberry/SendberryTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/Sendberry/SendberryTransport.php @@ -34,8 +34,8 @@ public function __construct( #[\SensitiveParameter] private string $password, private string $authKey, private string $from, - HttpClientInterface $client = null, - EventDispatcherInterface $dispatcher = null, + ?HttpClientInterface $client = null, + ?EventDispatcherInterface $dispatcher = null, ) { parent::__construct($client, $dispatcher); diff --git a/src/Symfony/Component/Notifier/Bridge/Sendberry/Tests/SendberryTransportTest.php b/src/Symfony/Component/Notifier/Bridge/Sendberry/Tests/SendberryTransportTest.php index c196a454ec485..53db393b21516 100644 --- a/src/Symfony/Component/Notifier/Bridge/Sendberry/Tests/SendberryTransportTest.php +++ b/src/Symfony/Component/Notifier/Bridge/Sendberry/Tests/SendberryTransportTest.php @@ -21,7 +21,7 @@ final class SendberryTransportTest extends TransportTestCase { - public static function createTransport(HttpClientInterface $client = null): SendberryTransport + public static function createTransport(?HttpClientInterface $client = null): SendberryTransport { return new SendberryTransport('username', 'password', 'auth_key', 'from', $client ?? new MockHttpClient()); } diff --git a/src/Symfony/Component/Notifier/Bridge/Sevenio/SevenIoTransport.php b/src/Symfony/Component/Notifier/Bridge/Sevenio/SevenIoTransport.php index 5ca093bcd4966..97737c5f8af31 100644 --- a/src/Symfony/Component/Notifier/Bridge/Sevenio/SevenIoTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/Sevenio/SevenIoTransport.php @@ -31,8 +31,8 @@ final class SevenIoTransport extends AbstractTransport public function __construct( #[\SensitiveParameter] private string $apiKey, private ?string $from = null, - HttpClientInterface $client = null, - EventDispatcherInterface $dispatcher = null, + ?HttpClientInterface $client = null, + ?EventDispatcherInterface $dispatcher = null, ) { parent::__construct($client, $dispatcher); } diff --git a/src/Symfony/Component/Notifier/Bridge/Sevenio/Tests/SevenIoTransportTest.php b/src/Symfony/Component/Notifier/Bridge/Sevenio/Tests/SevenIoTransportTest.php index 444471c09a47a..1527cbce72d55 100644 --- a/src/Symfony/Component/Notifier/Bridge/Sevenio/Tests/SevenIoTransportTest.php +++ b/src/Symfony/Component/Notifier/Bridge/Sevenio/Tests/SevenIoTransportTest.php @@ -21,7 +21,7 @@ final class SevenIoTransportTest extends TransportTestCase { - public static function createTransport(HttpClientInterface $client = null, string $from = null): SevenIoTransport + public static function createTransport(?HttpClientInterface $client = null, ?string $from = null): SevenIoTransport { return new SevenIoTransport('apiKey', $from, $client ?? new MockHttpClient()); } diff --git a/src/Symfony/Component/Notifier/Bridge/SimpleTextin/SimpleTextinTransport.php b/src/Symfony/Component/Notifier/Bridge/SimpleTextin/SimpleTextinTransport.php index ab138f5c64064..1a7f5fe9bc8eb 100644 --- a/src/Symfony/Component/Notifier/Bridge/SimpleTextin/SimpleTextinTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/SimpleTextin/SimpleTextinTransport.php @@ -32,8 +32,8 @@ final class SimpleTextinTransport extends AbstractTransport public function __construct( #[\SensitiveParameter] private readonly string $apiKey, private readonly ?string $from = null, - HttpClientInterface $client = null, - EventDispatcherInterface $dispatcher = null, + ?HttpClientInterface $client = null, + ?EventDispatcherInterface $dispatcher = null, ) { parent::__construct($client, $dispatcher); } diff --git a/src/Symfony/Component/Notifier/Bridge/SimpleTextin/Tests/SimpleTextinTransportTest.php b/src/Symfony/Component/Notifier/Bridge/SimpleTextin/Tests/SimpleTextinTransportTest.php index 642231c90260d..4daf0884c42b7 100644 --- a/src/Symfony/Component/Notifier/Bridge/SimpleTextin/Tests/SimpleTextinTransportTest.php +++ b/src/Symfony/Component/Notifier/Bridge/SimpleTextin/Tests/SimpleTextinTransportTest.php @@ -23,7 +23,7 @@ final class SimpleTextinTransportTest extends TransportTestCase { - public static function createTransport(HttpClientInterface $client = null, string $from = 'test_from'): SimpleTextinTransport + public static function createTransport(?HttpClientInterface $client = null, string $from = 'test_from'): SimpleTextinTransport { return new SimpleTextinTransport('test_api_key', $from, $client ?? new MockHttpClient()); } diff --git a/src/Symfony/Component/Notifier/Bridge/Sinch/SinchTransport.php b/src/Symfony/Component/Notifier/Bridge/Sinch/SinchTransport.php index 271eb0800954c..b24e606c6a010 100644 --- a/src/Symfony/Component/Notifier/Bridge/Sinch/SinchTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/Sinch/SinchTransport.php @@ -32,8 +32,8 @@ public function __construct( private string $accountSid, #[\SensitiveParameter] private string $authToken, private string $from, - HttpClientInterface $client = null, - EventDispatcherInterface $dispatcher = null, + ?HttpClientInterface $client = null, + ?EventDispatcherInterface $dispatcher = null, ) { parent::__construct($client, $dispatcher); diff --git a/src/Symfony/Component/Notifier/Bridge/Sinch/Tests/SinchTransportTest.php b/src/Symfony/Component/Notifier/Bridge/Sinch/Tests/SinchTransportTest.php index 595e66f0d88a1..9c17cc6525314 100644 --- a/src/Symfony/Component/Notifier/Bridge/Sinch/Tests/SinchTransportTest.php +++ b/src/Symfony/Component/Notifier/Bridge/Sinch/Tests/SinchTransportTest.php @@ -21,7 +21,7 @@ final class SinchTransportTest extends TransportTestCase { - public static function createTransport(HttpClientInterface $client = null): SinchTransport + public static function createTransport(?HttpClientInterface $client = null): SinchTransport { return new SinchTransport('accountSid', 'authToken', 'sender', $client ?? new MockHttpClient()); } diff --git a/src/Symfony/Component/Notifier/Bridge/Slack/Block/SlackActionsBlock.php b/src/Symfony/Component/Notifier/Bridge/Slack/Block/SlackActionsBlock.php index 7e2f9b8616136..ea74e5a1bb227 100644 --- a/src/Symfony/Component/Notifier/Bridge/Slack/Block/SlackActionsBlock.php +++ b/src/Symfony/Component/Notifier/Bridge/Slack/Block/SlackActionsBlock.php @@ -24,7 +24,7 @@ public function __construct() /** * @return $this */ - public function button(string $text, string $url, string $style = null): static + public function button(string $text, string $url, ?string $style = null): static { if (25 === \count($this->options['elements'] ?? [])) { throw new \LogicException('Maximum number of buttons should not exceed 25.'); diff --git a/src/Symfony/Component/Notifier/Bridge/Slack/SlackTransport.php b/src/Symfony/Component/Notifier/Bridge/Slack/SlackTransport.php index e93fead503a91..37457fd759eca 100644 --- a/src/Symfony/Component/Notifier/Bridge/Slack/SlackTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/Slack/SlackTransport.php @@ -31,8 +31,8 @@ final class SlackTransport extends AbstractTransport public function __construct( #[\SensitiveParameter] private string $accessToken, private ?string $channel = null, - HttpClientInterface $client = null, - EventDispatcherInterface $dispatcher = null, + ?HttpClientInterface $client = null, + ?EventDispatcherInterface $dispatcher = null, ) { if (!preg_match('/^xox(b-|p-|a-2)/', $accessToken)) { diff --git a/src/Symfony/Component/Notifier/Bridge/Slack/Tests/SlackOptionsTest.php b/src/Symfony/Component/Notifier/Bridge/Slack/Tests/SlackOptionsTest.php index fed91e975de8a..748b7f2ebf7bc 100644 --- a/src/Symfony/Component/Notifier/Bridge/Slack/Tests/SlackOptionsTest.php +++ b/src/Symfony/Component/Notifier/Bridge/Slack/Tests/SlackOptionsTest.php @@ -27,7 +27,7 @@ final class SlackOptionsTest extends TestCase * @dataProvider toArrayProvider * @dataProvider toArraySimpleOptionsProvider */ - public function testToArray(array $options, array $expected = null) + public function testToArray(array $options, ?array $expected = null) { $this->assertSame($expected ?? $options, (new SlackOptions($options))->toArray()); } diff --git a/src/Symfony/Component/Notifier/Bridge/Slack/Tests/SlackTransportTest.php b/src/Symfony/Component/Notifier/Bridge/Slack/Tests/SlackTransportTest.php index 8619b85ff3e30..0b9a17fc08493 100644 --- a/src/Symfony/Component/Notifier/Bridge/Slack/Tests/SlackTransportTest.php +++ b/src/Symfony/Component/Notifier/Bridge/Slack/Tests/SlackTransportTest.php @@ -27,7 +27,7 @@ final class SlackTransportTest extends TransportTestCase { - public static function createTransport(HttpClientInterface $client = null, string $channel = null): SlackTransport + public static function createTransport(?HttpClientInterface $client = null, ?string $channel = null): SlackTransport { return new SlackTransport('xoxb-TestToken', $channel, $client ?? new MockHttpClient()); } diff --git a/src/Symfony/Component/Notifier/Bridge/Sms77/Sms77Transport.php b/src/Symfony/Component/Notifier/Bridge/Sms77/Sms77Transport.php index e7f2c2fa88af5..c86e5b5f7eaa3 100644 --- a/src/Symfony/Component/Notifier/Bridge/Sms77/Sms77Transport.php +++ b/src/Symfony/Component/Notifier/Bridge/Sms77/Sms77Transport.php @@ -31,8 +31,8 @@ final class Sms77Transport extends AbstractTransport public function __construct( #[\SensitiveParameter] private string $apiKey, private ?string $from = null, - HttpClientInterface $client = null, - EventDispatcherInterface $dispatcher = null, + ?HttpClientInterface $client = null, + ?EventDispatcherInterface $dispatcher = null, ) { parent::__construct($client, $dispatcher); diff --git a/src/Symfony/Component/Notifier/Bridge/Sms77/Tests/Sms77TransportTest.php b/src/Symfony/Component/Notifier/Bridge/Sms77/Tests/Sms77TransportTest.php index 3c46483ec8d63..0a1ad1f4a4d06 100644 --- a/src/Symfony/Component/Notifier/Bridge/Sms77/Tests/Sms77TransportTest.php +++ b/src/Symfony/Component/Notifier/Bridge/Sms77/Tests/Sms77TransportTest.php @@ -21,7 +21,7 @@ final class Sms77TransportTest extends TransportTestCase { - public static function createTransport(HttpClientInterface $client = null, string $from = null): Sms77Transport + public static function createTransport(?HttpClientInterface $client = null, ?string $from = null): Sms77Transport { return new Sms77Transport('apiKey', $from, $client ?? new MockHttpClient()); } diff --git a/src/Symfony/Component/Notifier/Bridge/SmsBiuras/SmsBiurasTransport.php b/src/Symfony/Component/Notifier/Bridge/SmsBiuras/SmsBiurasTransport.php index a0bcd59ffac86..7663735327f12 100644 --- a/src/Symfony/Component/Notifier/Bridge/SmsBiuras/SmsBiurasTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/SmsBiuras/SmsBiurasTransport.php @@ -47,8 +47,8 @@ public function __construct( #[\SensitiveParameter] private string $apiKey, private string $from, private bool $testMode, - HttpClientInterface $client = null, - EventDispatcherInterface $dispatcher = null, + ?HttpClientInterface $client = null, + ?EventDispatcherInterface $dispatcher = null, ) { parent::__construct($client, $dispatcher); diff --git a/src/Symfony/Component/Notifier/Bridge/SmsBiuras/Tests/SmsBiurasTransportTest.php b/src/Symfony/Component/Notifier/Bridge/SmsBiuras/Tests/SmsBiurasTransportTest.php index e92a5a19d7c94..d622ca8b342bc 100644 --- a/src/Symfony/Component/Notifier/Bridge/SmsBiuras/Tests/SmsBiurasTransportTest.php +++ b/src/Symfony/Component/Notifier/Bridge/SmsBiuras/Tests/SmsBiurasTransportTest.php @@ -22,7 +22,7 @@ final class SmsBiurasTransportTest extends TransportTestCase { - public static function createTransport(HttpClientInterface $client = null): SmsBiurasTransport + public static function createTransport(?HttpClientInterface $client = null): SmsBiurasTransport { return new SmsBiurasTransport('uid', 'api_key', 'from', true, $client ?? new MockHttpClient()); } diff --git a/src/Symfony/Component/Notifier/Bridge/SmsFactor/SmsFactorTransport.php b/src/Symfony/Component/Notifier/Bridge/SmsFactor/SmsFactorTransport.php index 9f938981dde87..4efe419f9411e 100644 --- a/src/Symfony/Component/Notifier/Bridge/SmsFactor/SmsFactorTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/SmsFactor/SmsFactorTransport.php @@ -32,8 +32,8 @@ public function __construct( #[\SensitiveParameter] private string $tokenApi, private ?string $sender, private ?SmsFactorPushType $pushType, - HttpClientInterface $client = null, - EventDispatcherInterface $dispatcher = null, + ?HttpClientInterface $client = null, + ?EventDispatcherInterface $dispatcher = null, ) { parent::__construct($client, $dispatcher); diff --git a/src/Symfony/Component/Notifier/Bridge/SmsFactor/Tests/SmsFactorTransportTest.php b/src/Symfony/Component/Notifier/Bridge/SmsFactor/Tests/SmsFactorTransportTest.php index 08aa014c7128f..94678dfc48e05 100644 --- a/src/Symfony/Component/Notifier/Bridge/SmsFactor/Tests/SmsFactorTransportTest.php +++ b/src/Symfony/Component/Notifier/Bridge/SmsFactor/Tests/SmsFactorTransportTest.php @@ -22,7 +22,7 @@ final class SmsFactorTransportTest extends TransportTestCase { - public static function createTransport(HttpClientInterface $client = null): SmsFactorTransport + public static function createTransport(?HttpClientInterface $client = null): SmsFactorTransport { return (new SmsFactorTransport('TOKEN', 'MY_COMPANY', SmsFactorPushType::Alert, $client ?? new MockHttpClient()))->setHost('host.test'); } diff --git a/src/Symfony/Component/Notifier/Bridge/SmsSluzba/SmsSluzbaTransport.php b/src/Symfony/Component/Notifier/Bridge/SmsSluzba/SmsSluzbaTransport.php index 63b71950ad97c..7919554a5cf42 100644 --- a/src/Symfony/Component/Notifier/Bridge/SmsSluzba/SmsSluzbaTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/SmsSluzba/SmsSluzbaTransport.php @@ -32,8 +32,8 @@ final class SmsSluzbaTransport extends AbstractTransport public function __construct( #[\SensitiveParameter] private string $username, #[\SensitiveParameter] private string $password, - HttpClientInterface $client = null, - EventDispatcherInterface $dispatcher = null, + ?HttpClientInterface $client = null, + ?EventDispatcherInterface $dispatcher = null, ) { parent::__construct($client, $dispatcher); } diff --git a/src/Symfony/Component/Notifier/Bridge/SmsSluzba/Tests/SmsSluzbaTransportTest.php b/src/Symfony/Component/Notifier/Bridge/SmsSluzba/Tests/SmsSluzbaTransportTest.php index 2b87384e004dc..627813bba022a 100644 --- a/src/Symfony/Component/Notifier/Bridge/SmsSluzba/Tests/SmsSluzbaTransportTest.php +++ b/src/Symfony/Component/Notifier/Bridge/SmsSluzba/Tests/SmsSluzbaTransportTest.php @@ -20,7 +20,7 @@ final class SmsSluzbaTransportTest extends TransportTestCase { - public static function createTransport(HttpClientInterface $client = null, string $from = null): SmsSluzbaTransport + public static function createTransport(?HttpClientInterface $client = null, ?string $from = null): SmsSluzbaTransport { return new SmsSluzbaTransport('username', 'password'); } diff --git a/src/Symfony/Component/Notifier/Bridge/Smsapi/README.md b/src/Symfony/Component/Notifier/Bridge/Smsapi/README.md index 5c562fdff4004..c75a50575d038 100644 --- a/src/Symfony/Component/Notifier/Bridge/Smsapi/README.md +++ b/src/Symfony/Component/Notifier/Bridge/Smsapi/README.md @@ -1,7 +1,8 @@ SMSAPI Notifier =============== -Provides [Smsapi](https://ssl.smsapi.pl) integration for Symfony Notifier. +Provides [Smsapi](https://smsapi.pl) integration for Symfony Notifier. +This bridge can also be used with https://smsapi.com. DSN example ----------- @@ -10,13 +11,18 @@ DSN example SMSAPI_DSN=smsapi://TOKEN@default?from=FROM&fast=FAST&test=TEST ``` +// for https://smsapi.com set the correct endpoint: +``` +SMSAPI_DSN=smsapi://TOKEN@api.smsapi.com?from=FROM +``` + where: - `TOKEN` is your API Token (OAuth) - `FROM` is the sender name (default ""), skip this field to use the cheapest "eco" shipping method. - `FAST` setting this parameter to "1" (default "0") will result in sending message with the highest priority which ensures the quickest possible time of delivery. Attention! Fast messages cost more than normal messages. - `TEST` setting this parameter to "1" (default "0") will result in sending message in test mode (message is validated, but not sent). -See your account info at https://ssl.smsapi.pl/ +See your account info at https://smsapi.pl or https://smsapi.com Resources --------- diff --git a/src/Symfony/Component/Notifier/Bridge/Smsapi/SmsapiTransport.php b/src/Symfony/Component/Notifier/Bridge/Smsapi/SmsapiTransport.php index 2afad41943421..7475b4a8cc108 100644 --- a/src/Symfony/Component/Notifier/Bridge/Smsapi/SmsapiTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/Smsapi/SmsapiTransport.php @@ -35,8 +35,8 @@ final class SmsapiTransport extends AbstractTransport public function __construct( #[\SensitiveParameter] private string $authToken, private string $from = '', - HttpClientInterface $client = null, - EventDispatcherInterface $dispatcher = null, + ?HttpClientInterface $client = null, + ?EventDispatcherInterface $dispatcher = null, ) { @@ -121,6 +121,9 @@ protected function doSend(MessageInterface $message): SentMessage throw new TransportException(sprintf('Unable to send the SMS: "%s".', $content['message'] ?? 'unknown error'), $response); } - return new SentMessage($message, (string) $this); + $sentMessage = new SentMessage($message, (string) $this); + $sentMessage->setMessageId($content['list'][0]['id'] ?? ''); + + return $sentMessage; } } diff --git a/src/Symfony/Component/Notifier/Bridge/Smsapi/Tests/SmsapiTransportTest.php b/src/Symfony/Component/Notifier/Bridge/Smsapi/Tests/SmsapiTransportTest.php index d309c1528bb33..c7cd0e7d87fda 100644 --- a/src/Symfony/Component/Notifier/Bridge/Smsapi/Tests/SmsapiTransportTest.php +++ b/src/Symfony/Component/Notifier/Bridge/Smsapi/Tests/SmsapiTransportTest.php @@ -23,7 +23,7 @@ final class SmsapiTransportTest extends TransportTestCase { - public static function createTransport(HttpClientInterface $client = null, string $from = '', bool $fast = false, bool $test = false): SmsapiTransport + public static function createTransport(?HttpClientInterface $client = null, string $from = '', bool $fast = false, bool $test = false): SmsapiTransport { return (new SmsapiTransport('testToken', $from, $client ?? new MockHttpClient()))->setHost('test.host')->setFast($fast)->setTest($test); } diff --git a/src/Symfony/Component/Notifier/Bridge/Smsbox/SmsboxTransport.php b/src/Symfony/Component/Notifier/Bridge/Smsbox/SmsboxTransport.php index 862a6e058a889..f9edbe63e00b4 100644 --- a/src/Symfony/Component/Notifier/Bridge/Smsbox/SmsboxTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/Smsbox/SmsboxTransport.php @@ -37,8 +37,8 @@ public function __construct( private Mode $mode, private Strategy $strategy, private ?string $sender, - HttpClientInterface $client = null, - EventDispatcherInterface $dispatcher = null, + ?HttpClientInterface $client = null, + ?EventDispatcherInterface $dispatcher = null, ) { parent::__construct($client, $dispatcher); } diff --git a/src/Symfony/Component/Notifier/Bridge/Smsbox/Tests/SmsboxTransportTest.php b/src/Symfony/Component/Notifier/Bridge/Smsbox/Tests/SmsboxTransportTest.php index a85ca6a8ed647..746636494aaaf 100644 --- a/src/Symfony/Component/Notifier/Bridge/Smsbox/Tests/SmsboxTransportTest.php +++ b/src/Symfony/Component/Notifier/Bridge/Smsbox/Tests/SmsboxTransportTest.php @@ -28,7 +28,7 @@ final class SmsboxTransportTest extends TransportTestCase { - public static function createTransport(HttpClientInterface $client = null): SmsboxTransport + public static function createTransport(?HttpClientInterface $client = null): SmsboxTransport { return new SmsboxTransport('apikey', Mode::Standard, Strategy::Marketing, null, $client ?? new MockHttpClient()); } diff --git a/src/Symfony/Component/Notifier/Bridge/Smsc/SmscTransport.php b/src/Symfony/Component/Notifier/Bridge/Smsc/SmscTransport.php index 98fc4323fea80..3d65cd06d2cc4 100644 --- a/src/Symfony/Component/Notifier/Bridge/Smsc/SmscTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/Smsc/SmscTransport.php @@ -34,8 +34,8 @@ public function __construct( private string $login, #[\SensitiveParameter] private string $password, private string $from, - HttpClientInterface $client = null, - EventDispatcherInterface $dispatcher = null, + ?HttpClientInterface $client = null, + ?EventDispatcherInterface $dispatcher = null, ) { parent::__construct($client, $dispatcher); diff --git a/src/Symfony/Component/Notifier/Bridge/Smsc/Tests/SmscTransportTest.php b/src/Symfony/Component/Notifier/Bridge/Smsc/Tests/SmscTransportTest.php index 34df70a4ed69e..4037f06870fb7 100644 --- a/src/Symfony/Component/Notifier/Bridge/Smsc/Tests/SmscTransportTest.php +++ b/src/Symfony/Component/Notifier/Bridge/Smsc/Tests/SmscTransportTest.php @@ -21,7 +21,7 @@ final class SmscTransportTest extends TransportTestCase { - public static function createTransport(HttpClientInterface $client = null): SmscTransport + public static function createTransport(?HttpClientInterface $client = null): SmscTransport { return new SmscTransport('login', 'password', 'MyApp', $client ?? new MockHttpClient()); } diff --git a/src/Symfony/Component/Notifier/Bridge/Smsmode/SmsmodeTransport.php b/src/Symfony/Component/Notifier/Bridge/Smsmode/SmsmodeTransport.php index bf38938f4b0f2..d81a3621e9173 100644 --- a/src/Symfony/Component/Notifier/Bridge/Smsmode/SmsmodeTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/Smsmode/SmsmodeTransport.php @@ -32,8 +32,8 @@ final class SmsmodeTransport extends AbstractTransport public function __construct( #[\SensitiveParameter] private readonly string $apiKey, private readonly ?string $from = null, - HttpClientInterface $client = null, - EventDispatcherInterface $dispatcher = null, + ?HttpClientInterface $client = null, + ?EventDispatcherInterface $dispatcher = null, ) { parent::__construct($client, $dispatcher); } diff --git a/src/Symfony/Component/Notifier/Bridge/Smsmode/Tests/SmsmodeTransportTest.php b/src/Symfony/Component/Notifier/Bridge/Smsmode/Tests/SmsmodeTransportTest.php index f94902ce8d1d9..1ef1d1b5fca81 100644 --- a/src/Symfony/Component/Notifier/Bridge/Smsmode/Tests/SmsmodeTransportTest.php +++ b/src/Symfony/Component/Notifier/Bridge/Smsmode/Tests/SmsmodeTransportTest.php @@ -24,7 +24,7 @@ final class SmsmodeTransportTest extends TransportTestCase { - public static function createTransport(HttpClientInterface $client = null, string $from = 'test_from'): SmsmodeTransport + public static function createTransport(?HttpClientInterface $client = null, string $from = 'test_from'): SmsmodeTransport { return new SmsmodeTransport('test_api_key', $from, $client ?? new MockHttpClient()); } diff --git a/src/Symfony/Component/Notifier/Bridge/SpotHit/SpotHitTransport.php b/src/Symfony/Component/Notifier/Bridge/SpotHit/SpotHitTransport.php index 48cd3ded1d936..1cf0d98642569 100644 --- a/src/Symfony/Component/Notifier/Bridge/SpotHit/SpotHitTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/SpotHit/SpotHitTransport.php @@ -39,8 +39,8 @@ final class SpotHitTransport extends AbstractTransport public function __construct( #[\SensitiveParameter] private string $token, private ?string $from = null, - HttpClientInterface $client = null, - EventDispatcherInterface $dispatcher = null, + ?HttpClientInterface $client = null, + ?EventDispatcherInterface $dispatcher = null, ) { parent::__construct($client, $dispatcher); diff --git a/src/Symfony/Component/Notifier/Bridge/SpotHit/Tests/SpotHitTransportTest.php b/src/Symfony/Component/Notifier/Bridge/SpotHit/Tests/SpotHitTransportTest.php index f8bfc9fcb9082..21e8f5d2d5b66 100644 --- a/src/Symfony/Component/Notifier/Bridge/SpotHit/Tests/SpotHitTransportTest.php +++ b/src/Symfony/Component/Notifier/Bridge/SpotHit/Tests/SpotHitTransportTest.php @@ -22,7 +22,7 @@ final class SpotHitTransportTest extends TransportTestCase { - public static function createTransport(HttpClientInterface $client = null): SpotHitTransport + public static function createTransport(?HttpClientInterface $client = null): SpotHitTransport { return (new SpotHitTransport('api_token', 'MyCompany', $client ?? new MockHttpClient()))->setHost('host.test'); } diff --git a/src/Symfony/Component/Notifier/Bridge/Telegram/TelegramOptions.php b/src/Symfony/Component/Notifier/Bridge/Telegram/TelegramOptions.php index 86601172ef946..53b85bc2e6fe0 100644 --- a/src/Symfony/Component/Notifier/Bridge/Telegram/TelegramOptions.php +++ b/src/Symfony/Component/Notifier/Bridge/Telegram/TelegramOptions.php @@ -275,7 +275,7 @@ public function uploadAnimation(string $path): static /** * @return $this */ - public function sticker(string $url, string $emoji = null): static + public function sticker(string $url, ?string $emoji = null): static { $this->options['sticker'] = $url; $this->options['emoji'] = $emoji; @@ -286,7 +286,7 @@ public function sticker(string $url, string $emoji = null): static /** * @return $this */ - public function uploadSticker(string $path, string $emoji = null): static + public function uploadSticker(string $path, ?string $emoji = null): static { $this->options['upload']['sticker'] = $path; $this->options['emoji'] = $emoji; @@ -297,7 +297,7 @@ public function uploadSticker(string $path, string $emoji = null): static /** * @return $this */ - public function contact(string $phoneNumber, string $firstName, string $lastName = null, string $vCard = null): static + public function contact(string $phoneNumber, string $firstName, ?string $lastName = null, ?string $vCard = null): static { $this->options['contact'] = [ 'phone_number' => $phoneNumber, diff --git a/src/Symfony/Component/Notifier/Bridge/Telegram/TelegramTransport.php b/src/Symfony/Component/Notifier/Bridge/Telegram/TelegramTransport.php index fd214724f3551..c2036cc6e7916 100644 --- a/src/Symfony/Component/Notifier/Bridge/Telegram/TelegramTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/Telegram/TelegramTransport.php @@ -50,8 +50,8 @@ final class TelegramTransport extends AbstractTransport public function __construct( #[\SensitiveParameter] private string $token, private ?string $chatChannel = null, - HttpClientInterface $client = null, - EventDispatcherInterface $dispatcher = null, + ?HttpClientInterface $client = null, + ?EventDispatcherInterface $dispatcher = null, ) { parent::__construct($client, $dispatcher); diff --git a/src/Symfony/Component/Notifier/Bridge/Telegram/Tests/TelegramTransportTest.php b/src/Symfony/Component/Notifier/Bridge/Telegram/Tests/TelegramTransportTest.php index 47d21c6631721..e041d0e88348b 100644 --- a/src/Symfony/Component/Notifier/Bridge/Telegram/Tests/TelegramTransportTest.php +++ b/src/Symfony/Component/Notifier/Bridge/Telegram/Tests/TelegramTransportTest.php @@ -28,7 +28,7 @@ final class TelegramTransportTest extends TransportTestCase { private const FIXTURE_FILE = __DIR__.'/Fixtures/image.png'; - public static function createTransport(HttpClientInterface $client = null, string $channel = null): TelegramTransport + public static function createTransport(?HttpClientInterface $client = null, ?string $channel = null): TelegramTransport { return new TelegramTransport('token', $channel, $client ?? new MockHttpClient()); } diff --git a/src/Symfony/Component/Notifier/Bridge/Telnyx/TelnyxTransport.php b/src/Symfony/Component/Notifier/Bridge/Telnyx/TelnyxTransport.php index 20193c7207095..e0f50172d193b 100644 --- a/src/Symfony/Component/Notifier/Bridge/Telnyx/TelnyxTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/Telnyx/TelnyxTransport.php @@ -33,8 +33,8 @@ public function __construct( #[\SensitiveParameter] private string $apiKey, private string $from, private ?string $messagingProfileId, - HttpClientInterface $client = null, - EventDispatcherInterface $dispatcher = null, + ?HttpClientInterface $client = null, + ?EventDispatcherInterface $dispatcher = null, ) { parent::__construct($client, $dispatcher); diff --git a/src/Symfony/Component/Notifier/Bridge/Telnyx/Tests/TelnyxTransportTest.php b/src/Symfony/Component/Notifier/Bridge/Telnyx/Tests/TelnyxTransportTest.php index 15da69c2909b9..2ce5e95ef7f0a 100644 --- a/src/Symfony/Component/Notifier/Bridge/Telnyx/Tests/TelnyxTransportTest.php +++ b/src/Symfony/Component/Notifier/Bridge/Telnyx/Tests/TelnyxTransportTest.php @@ -21,7 +21,7 @@ final class TelnyxTransportTest extends TransportTestCase { - public static function createTransport(HttpClientInterface $client = null): TelnyxTransport + public static function createTransport(?HttpClientInterface $client = null): TelnyxTransport { return new TelnyxTransport('api_key', 'from', 'messaging_profile_id', $client ?? new MockHttpClient()); } diff --git a/src/Symfony/Component/Notifier/Bridge/Termii/TermiiTransport.php b/src/Symfony/Component/Notifier/Bridge/Termii/TermiiTransport.php index 396b67d4a0f36..97b6398fdfbe6 100644 --- a/src/Symfony/Component/Notifier/Bridge/Termii/TermiiTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/Termii/TermiiTransport.php @@ -34,8 +34,8 @@ public function __construct( #[\SensitiveParameter] private readonly string $apiKey, private readonly string $from, private readonly string $channel, - HttpClientInterface $client = null, - EventDispatcherInterface $dispatcher = null, + ?HttpClientInterface $client = null, + ?EventDispatcherInterface $dispatcher = null, ) { parent::__construct($client, $dispatcher); } diff --git a/src/Symfony/Component/Notifier/Bridge/Termii/Tests/TermiiTransportTest.php b/src/Symfony/Component/Notifier/Bridge/Termii/Tests/TermiiTransportTest.php index 3c22b2e9dfa2f..5d199f5f1759d 100644 --- a/src/Symfony/Component/Notifier/Bridge/Termii/Tests/TermiiTransportTest.php +++ b/src/Symfony/Component/Notifier/Bridge/Termii/Tests/TermiiTransportTest.php @@ -24,7 +24,7 @@ final class TermiiTransportTest extends TransportTestCase { - public static function createTransport(HttpClientInterface $client = null, string $from = 'from'): TermiiTransport + public static function createTransport(?HttpClientInterface $client = null, string $from = 'from'): TermiiTransport { return new TermiiTransport('apiKey', $from, 'generic', $client ?? new MockHttpClient()); } diff --git a/src/Symfony/Component/Notifier/Bridge/TurboSms/Tests/TurboSmsTransportTest.php b/src/Symfony/Component/Notifier/Bridge/TurboSms/Tests/TurboSmsTransportTest.php index f26abac4b8ccb..f9a3f1e646b14 100644 --- a/src/Symfony/Component/Notifier/Bridge/TurboSms/Tests/TurboSmsTransportTest.php +++ b/src/Symfony/Component/Notifier/Bridge/TurboSms/Tests/TurboSmsTransportTest.php @@ -25,7 +25,7 @@ final class TurboSmsTransportTest extends TransportTestCase { - public static function createTransport(HttpClientInterface $client = null): TurboSmsTransport + public static function createTransport(?HttpClientInterface $client = null): TurboSmsTransport { return new TurboSmsTransport('authToken', 'sender', $client ?? new MockHttpClient()); } diff --git a/src/Symfony/Component/Notifier/Bridge/TurboSms/TurboSmsTransport.php b/src/Symfony/Component/Notifier/Bridge/TurboSms/TurboSmsTransport.php index 8b5b887fe7cb7..5bbf3c7114ba1 100644 --- a/src/Symfony/Component/Notifier/Bridge/TurboSms/TurboSmsTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/TurboSms/TurboSmsTransport.php @@ -37,8 +37,8 @@ final class TurboSmsTransport extends AbstractTransport public function __construct( #[\SensitiveParameter] private string $authToken, private string $from, - HttpClientInterface $client = null, - EventDispatcherInterface $dispatcher = null, + ?HttpClientInterface $client = null, + ?EventDispatcherInterface $dispatcher = null, ) { $this->assertValidFrom($from); diff --git a/src/Symfony/Component/Notifier/Bridge/Twilio/Tests/TwilioTransportTest.php b/src/Symfony/Component/Notifier/Bridge/Twilio/Tests/TwilioTransportTest.php index 88e2351224735..da412d4ebbff8 100644 --- a/src/Symfony/Component/Notifier/Bridge/Twilio/Tests/TwilioTransportTest.php +++ b/src/Symfony/Component/Notifier/Bridge/Twilio/Tests/TwilioTransportTest.php @@ -23,7 +23,7 @@ final class TwilioTransportTest extends TransportTestCase { - public static function createTransport(HttpClientInterface $client = null, string $from = 'from'): TwilioTransport + public static function createTransport(?HttpClientInterface $client = null, string $from = 'from'): TwilioTransport { return new TwilioTransport('accountSid', 'authToken', $from, $client ?? new MockHttpClient()); } diff --git a/src/Symfony/Component/Notifier/Bridge/Twilio/TwilioTransport.php b/src/Symfony/Component/Notifier/Bridge/Twilio/TwilioTransport.php index 81bc33a53119b..468ec17386708 100644 --- a/src/Symfony/Component/Notifier/Bridge/Twilio/TwilioTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/Twilio/TwilioTransport.php @@ -33,8 +33,8 @@ public function __construct( private string $accountSid, #[\SensitiveParameter] private string $authToken, private string $from, - HttpClientInterface $client = null, - EventDispatcherInterface $dispatcher = null, + ?HttpClientInterface $client = null, + ?EventDispatcherInterface $dispatcher = null, ) { parent::__construct($client, $dispatcher); diff --git a/src/Symfony/Component/Notifier/Bridge/Twitter/Tests/TwitterTransportTest.php b/src/Symfony/Component/Notifier/Bridge/Twitter/Tests/TwitterTransportTest.php index 4cbc22bdc20ed..4b34b2257becb 100644 --- a/src/Symfony/Component/Notifier/Bridge/Twitter/Tests/TwitterTransportTest.php +++ b/src/Symfony/Component/Notifier/Bridge/Twitter/Tests/TwitterTransportTest.php @@ -24,7 +24,7 @@ class TwitterTransportTest extends TransportTestCase { - public static function createTransport(HttpClientInterface $client = null): TwitterTransport + public static function createTransport(?HttpClientInterface $client = null): TwitterTransport { return new TwitterTransport('APIK', 'APIS', 'TOKEN', 'SECRET', $client ?? new MockHttpClient()); } diff --git a/src/Symfony/Component/Notifier/Bridge/Twitter/TwitterOptions.php b/src/Symfony/Component/Notifier/Bridge/Twitter/TwitterOptions.php index 2fc331e1c8491..4ae6f35a31ae3 100644 --- a/src/Symfony/Component/Notifier/Bridge/Twitter/TwitterOptions.php +++ b/src/Symfony/Component/Notifier/Bridge/Twitter/TwitterOptions.php @@ -117,7 +117,7 @@ public function attachGif(File $file, string $alt = '', array $extraOwners = []) * * @return $this */ - public function attachVideo(File $file, string $alt = '', File $subtitles = null, bool $amplify = false, array $extraOwners = []): static + public function attachVideo(File $file, string $alt = '', ?File $subtitles = null, bool $amplify = false, array $extraOwners = []): static { $this->options['attach'][] = [ 'file' => $file, diff --git a/src/Symfony/Component/Notifier/Bridge/Twitter/TwitterTransport.php b/src/Symfony/Component/Notifier/Bridge/Twitter/TwitterTransport.php index fd26ca993c148..ecd8418ca8bbc 100644 --- a/src/Symfony/Component/Notifier/Bridge/Twitter/TwitterTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/Twitter/TwitterTransport.php @@ -41,8 +41,8 @@ public function __construct( #[\SensitiveParameter] private string $apiSecret, #[\SensitiveParameter] private string $accessToken, #[\SensitiveParameter] private string $accessSecret, - HttpClientInterface $client = null, - EventDispatcherInterface $dispatcher = null, + ?HttpClientInterface $client = null, + ?EventDispatcherInterface $dispatcher = null, ) { parent::__construct($client, $dispatcher); diff --git a/src/Symfony/Component/Notifier/Bridge/Unifonic/Tests/UnifonicTransportTest.php b/src/Symfony/Component/Notifier/Bridge/Unifonic/Tests/UnifonicTransportTest.php index 454c5d108beaf..fa1fcb87a65d6 100644 --- a/src/Symfony/Component/Notifier/Bridge/Unifonic/Tests/UnifonicTransportTest.php +++ b/src/Symfony/Component/Notifier/Bridge/Unifonic/Tests/UnifonicTransportTest.php @@ -24,7 +24,7 @@ final class UnifonicTransportTest extends TransportTestCase { - public static function createTransport(HttpClientInterface $client = null, string $host = null): UnifonicTransport + public static function createTransport(?HttpClientInterface $client = null, ?string $host = null): UnifonicTransport { return (new UnifonicTransport('S3cr3t', 'Sender', $client ?? new MockHttpClient()))->setHost($host); } diff --git a/src/Symfony/Component/Notifier/Bridge/Unifonic/UnifonicTransport.php b/src/Symfony/Component/Notifier/Bridge/Unifonic/UnifonicTransport.php index 27e473660cc79..730e83a468c5c 100644 --- a/src/Symfony/Component/Notifier/Bridge/Unifonic/UnifonicTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/Unifonic/UnifonicTransport.php @@ -31,8 +31,8 @@ final class UnifonicTransport extends AbstractTransport public function __construct( #[\SensitiveParameter] private readonly string $appSid, private readonly ?string $from = null, - HttpClientInterface $client = null, - EventDispatcherInterface $dispatcher = null, + ?HttpClientInterface $client = null, + ?EventDispatcherInterface $dispatcher = null, ) { parent::__construct($client, $dispatcher); } diff --git a/src/Symfony/Component/Notifier/Bridge/Vonage/Tests/VonageTransportTest.php b/src/Symfony/Component/Notifier/Bridge/Vonage/Tests/VonageTransportTest.php index a89e9bcc6164d..fe602748b5506 100644 --- a/src/Symfony/Component/Notifier/Bridge/Vonage/Tests/VonageTransportTest.php +++ b/src/Symfony/Component/Notifier/Bridge/Vonage/Tests/VonageTransportTest.php @@ -21,7 +21,7 @@ final class VonageTransportTest extends TransportTestCase { - public static function createTransport(HttpClientInterface $client = null): VonageTransport + public static function createTransport(?HttpClientInterface $client = null): VonageTransport { return new VonageTransport('apiKey', 'apiSecret', 'sender', $client ?? new MockHttpClient()); } diff --git a/src/Symfony/Component/Notifier/Bridge/Vonage/VonageTransport.php b/src/Symfony/Component/Notifier/Bridge/Vonage/VonageTransport.php index c21e22b4fb48b..cc2eb53e3c506 100644 --- a/src/Symfony/Component/Notifier/Bridge/Vonage/VonageTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/Vonage/VonageTransport.php @@ -33,8 +33,8 @@ public function __construct( #[\SensitiveParameter] private string $apiKey, #[\SensitiveParameter] private string $apiSecret, private string $from, - HttpClientInterface $client = null, - EventDispatcherInterface $dispatcher = null, + ?HttpClientInterface $client = null, + ?EventDispatcherInterface $dispatcher = null, ) { parent::__construct($client, $dispatcher); diff --git a/src/Symfony/Component/Notifier/Bridge/Yunpian/Tests/YunpianTransportTest.php b/src/Symfony/Component/Notifier/Bridge/Yunpian/Tests/YunpianTransportTest.php index 673ebc34dfbea..1cce15e7c50de 100644 --- a/src/Symfony/Component/Notifier/Bridge/Yunpian/Tests/YunpianTransportTest.php +++ b/src/Symfony/Component/Notifier/Bridge/Yunpian/Tests/YunpianTransportTest.php @@ -22,7 +22,7 @@ final class YunpianTransportTest extends TransportTestCase { - public static function createTransport(HttpClientInterface $client = null): YunpianTransport + public static function createTransport(?HttpClientInterface $client = null): YunpianTransport { return new YunpianTransport('api_key', $client ?? new MockHttpClient()); } diff --git a/src/Symfony/Component/Notifier/Bridge/Yunpian/YunpianTransport.php b/src/Symfony/Component/Notifier/Bridge/Yunpian/YunpianTransport.php index e4b3a721da1cd..92a5bd4190c86 100644 --- a/src/Symfony/Component/Notifier/Bridge/Yunpian/YunpianTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/Yunpian/YunpianTransport.php @@ -31,8 +31,8 @@ class YunpianTransport extends AbstractTransport public function __construct( #[\SensitiveParameter] private string $apiKey, - HttpClientInterface $client = null, - EventDispatcherInterface $dispatcher = null, + ?HttpClientInterface $client = null, + ?EventDispatcherInterface $dispatcher = null, ) { parent::__construct($client, $dispatcher); diff --git a/src/Symfony/Component/Notifier/Bridge/Zendesk/Tests/ZendeskTransportTest.php b/src/Symfony/Component/Notifier/Bridge/Zendesk/Tests/ZendeskTransportTest.php index 162c3ddc65d80..dce2cd75e40db 100644 --- a/src/Symfony/Component/Notifier/Bridge/Zendesk/Tests/ZendeskTransportTest.php +++ b/src/Symfony/Component/Notifier/Bridge/Zendesk/Tests/ZendeskTransportTest.php @@ -21,7 +21,7 @@ final class ZendeskTransportTest extends TransportTestCase { - public static function createTransport(HttpClientInterface $client = null): ZendeskTransport + public static function createTransport(?HttpClientInterface $client = null): ZendeskTransport { return (new ZendeskTransport('testEmail', 'testToken', $client ?? new MockHttpClient()))->setHost('test.zendesk.com'); } diff --git a/src/Symfony/Component/Notifier/Bridge/Zendesk/ZendeskOptions.php b/src/Symfony/Component/Notifier/Bridge/Zendesk/ZendeskOptions.php index 2b84d437f5c00..ec951d463a5b1 100644 --- a/src/Symfony/Component/Notifier/Bridge/Zendesk/ZendeskOptions.php +++ b/src/Symfony/Component/Notifier/Bridge/Zendesk/ZendeskOptions.php @@ -20,7 +20,7 @@ final class ZendeskOptions implements MessageOptionsInterface { private ?string $priority; - public function __construct(string $priority = null) + public function __construct(?string $priority = null) { $this->priority = $priority; } diff --git a/src/Symfony/Component/Notifier/Bridge/Zendesk/ZendeskTransport.php b/src/Symfony/Component/Notifier/Bridge/Zendesk/ZendeskTransport.php index abe7f9f430dfa..b633e30fac837 100644 --- a/src/Symfony/Component/Notifier/Bridge/Zendesk/ZendeskTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/Zendesk/ZendeskTransport.php @@ -29,8 +29,8 @@ final class ZendeskTransport extends AbstractTransport public function __construct( private string $email, #[\SensitiveParameter] private string $token, - HttpClientInterface $client = null, - EventDispatcherInterface $dispatcher = null, + ?HttpClientInterface $client = null, + ?EventDispatcherInterface $dispatcher = null, ) { parent::__construct($client, $dispatcher); @@ -46,7 +46,7 @@ public function supports(MessageInterface $message): bool return $message instanceof ChatMessage && (null === $message->getOptions() || $message->getOptions() instanceof ZendeskOptions); } - protected function doSend(MessageInterface $message = null): SentMessage + protected function doSend(?MessageInterface $message = null): SentMessage { if (!$message instanceof ChatMessage) { throw new UnsupportedMessageTypeException(__CLASS__, ChatMessage::class, $message); diff --git a/src/Symfony/Component/Notifier/Bridge/Zulip/Tests/ZulipTransportTest.php b/src/Symfony/Component/Notifier/Bridge/Zulip/Tests/ZulipTransportTest.php index 87ed672196695..1c505f1b5dd44 100644 --- a/src/Symfony/Component/Notifier/Bridge/Zulip/Tests/ZulipTransportTest.php +++ b/src/Symfony/Component/Notifier/Bridge/Zulip/Tests/ZulipTransportTest.php @@ -21,7 +21,7 @@ final class ZulipTransportTest extends TransportTestCase { - public static function createTransport(HttpClientInterface $client = null): ZulipTransport + public static function createTransport(?HttpClientInterface $client = null): ZulipTransport { return (new ZulipTransport('testEmail', 'testToken', 'testChannel', $client ?? new MockHttpClient()))->setHost('test.host'); } diff --git a/src/Symfony/Component/Notifier/Bridge/Zulip/ZulipOptions.php b/src/Symfony/Component/Notifier/Bridge/Zulip/ZulipOptions.php index 479f7519f0854..695c1f71b96f1 100644 --- a/src/Symfony/Component/Notifier/Bridge/Zulip/ZulipOptions.php +++ b/src/Symfony/Component/Notifier/Bridge/Zulip/ZulipOptions.php @@ -21,7 +21,7 @@ final class ZulipOptions implements MessageOptionsInterface private ?string $topic; private ?string $recipient; - public function __construct(string $topic = null, string $recipient = null) + public function __construct(?string $topic = null, ?string $recipient = null) { $this->topic = $topic; $this->recipient = $recipient; diff --git a/src/Symfony/Component/Notifier/Bridge/Zulip/ZulipTransport.php b/src/Symfony/Component/Notifier/Bridge/Zulip/ZulipTransport.php index 286dc6addb709..0ba551b552f9a 100644 --- a/src/Symfony/Component/Notifier/Bridge/Zulip/ZulipTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/Zulip/ZulipTransport.php @@ -31,8 +31,8 @@ public function __construct( private string $email, #[\SensitiveParameter] private string $token, private string $channel, - HttpClientInterface $client = null, - EventDispatcherInterface $dispatcher = null, + ?HttpClientInterface $client = null, + ?EventDispatcherInterface $dispatcher = null, ) { diff --git a/src/Symfony/Component/Notifier/Channel/AbstractChannel.php b/src/Symfony/Component/Notifier/Channel/AbstractChannel.php index 7b8f342ee3cdb..4acf3cf358762 100644 --- a/src/Symfony/Component/Notifier/Channel/AbstractChannel.php +++ b/src/Symfony/Component/Notifier/Channel/AbstractChannel.php @@ -23,7 +23,7 @@ abstract class AbstractChannel implements ChannelInterface protected ?TransportInterface $transport; protected ?MessageBusInterface $bus; - public function __construct(TransportInterface $transport = null, MessageBusInterface $bus = null) + public function __construct(?TransportInterface $transport = null, ?MessageBusInterface $bus = null) { if (null === $transport && null === $bus) { throw new LogicException(sprintf('"%s" needs a Transport or a Bus but both cannot be "null".', static::class)); diff --git a/src/Symfony/Component/Notifier/Channel/BrowserChannel.php b/src/Symfony/Component/Notifier/Channel/BrowserChannel.php index 077888fef2c68..5a9df12bed867 100644 --- a/src/Symfony/Component/Notifier/Channel/BrowserChannel.php +++ b/src/Symfony/Component/Notifier/Channel/BrowserChannel.php @@ -28,7 +28,7 @@ public function __construct( ) { } - public function notify(Notification $notification, RecipientInterface $recipient, string $transportName = null): void + public function notify(Notification $notification, RecipientInterface $recipient, ?string $transportName = null): void { if (null === $request = $this->stack->getCurrentRequest()) { return; diff --git a/src/Symfony/Component/Notifier/Channel/ChannelInterface.php b/src/Symfony/Component/Notifier/Channel/ChannelInterface.php index ab3115230d79d..ae072aa99cb3f 100644 --- a/src/Symfony/Component/Notifier/Channel/ChannelInterface.php +++ b/src/Symfony/Component/Notifier/Channel/ChannelInterface.php @@ -19,7 +19,7 @@ */ interface ChannelInterface { - public function notify(Notification $notification, RecipientInterface $recipient, string $transportName = null): void; + public function notify(Notification $notification, RecipientInterface $recipient, ?string $transportName = null): void; public function supports(Notification $notification, RecipientInterface $recipient): bool; } diff --git a/src/Symfony/Component/Notifier/Channel/ChatChannel.php b/src/Symfony/Component/Notifier/Channel/ChatChannel.php index 792b4c03ed070..ce621e8460d73 100644 --- a/src/Symfony/Component/Notifier/Channel/ChatChannel.php +++ b/src/Symfony/Component/Notifier/Channel/ChatChannel.php @@ -21,7 +21,7 @@ */ class ChatChannel extends AbstractChannel { - public function notify(Notification $notification, RecipientInterface $recipient, string $transportName = null): void + public function notify(Notification $notification, RecipientInterface $recipient, ?string $transportName = null): void { $message = null; if ($notification instanceof ChatNotificationInterface) { diff --git a/src/Symfony/Component/Notifier/Channel/EmailChannel.php b/src/Symfony/Component/Notifier/Channel/EmailChannel.php index f517c57911c8e..af2a75831f861 100644 --- a/src/Symfony/Component/Notifier/Channel/EmailChannel.php +++ b/src/Symfony/Component/Notifier/Channel/EmailChannel.php @@ -34,7 +34,7 @@ class EmailChannel implements ChannelInterface public function __construct( private ?TransportInterface $transport = null, private ?MessageBusInterface $bus = null, - string $from = null, + ?string $from = null, private ?Envelope $envelope = null, ) { if (null === $transport && null === $bus) { @@ -47,7 +47,7 @@ public function __construct( /** * @param EmailRecipientInterface $recipient */ - public function notify(Notification $notification, RecipientInterface $recipient, string $transportName = null): void + public function notify(Notification $notification, RecipientInterface $recipient, ?string $transportName = null): void { $message = null; if ($notification instanceof EmailNotificationInterface) { diff --git a/src/Symfony/Component/Notifier/Channel/PushChannel.php b/src/Symfony/Component/Notifier/Channel/PushChannel.php index f2f79adc973d3..244c25fbcce02 100644 --- a/src/Symfony/Component/Notifier/Channel/PushChannel.php +++ b/src/Symfony/Component/Notifier/Channel/PushChannel.php @@ -21,7 +21,7 @@ */ class PushChannel extends AbstractChannel { - public function notify(Notification $notification, RecipientInterface $recipient, string $transportName = null): void + public function notify(Notification $notification, RecipientInterface $recipient, ?string $transportName = null): void { $message = null; if ($notification instanceof PushNotificationInterface) { diff --git a/src/Symfony/Component/Notifier/Channel/SmsChannel.php b/src/Symfony/Component/Notifier/Channel/SmsChannel.php index e9a486d0605d4..87186b9b51b9f 100644 --- a/src/Symfony/Component/Notifier/Channel/SmsChannel.php +++ b/src/Symfony/Component/Notifier/Channel/SmsChannel.php @@ -25,7 +25,7 @@ class SmsChannel extends AbstractChannel /** * @param SmsRecipientInterface $recipient */ - public function notify(Notification $notification, RecipientInterface $recipient, string $transportName = null): void + public function notify(Notification $notification, RecipientInterface $recipient, ?string $transportName = null): void { $message = null; if ($notification instanceof SmsNotificationInterface) { diff --git a/src/Symfony/Component/Notifier/DataCollector/NotificationDataCollector.php b/src/Symfony/Component/Notifier/DataCollector/NotificationDataCollector.php index 555a72b352600..ad98830da5250 100644 --- a/src/Symfony/Component/Notifier/DataCollector/NotificationDataCollector.php +++ b/src/Symfony/Component/Notifier/DataCollector/NotificationDataCollector.php @@ -27,7 +27,7 @@ public function __construct( ) { } - public function collect(Request $request, Response $response, \Throwable $exception = null): void + public function collect(Request $request, Response $response, ?\Throwable $exception = null): void { $this->data['events'] = $this->logger->getEvents(); } diff --git a/src/Symfony/Component/Notifier/Event/NotificationEvents.php b/src/Symfony/Component/Notifier/Event/NotificationEvents.php index 024af2b94d88c..57d574388b899 100644 --- a/src/Symfony/Component/Notifier/Event/NotificationEvents.php +++ b/src/Symfony/Component/Notifier/Event/NotificationEvents.php @@ -35,7 +35,7 @@ public function getTransports(): array /** * @return MessageEvent[] */ - public function getEvents(string $name = null): array + public function getEvents(?string $name = null): array { if (null === $name) { return $this->events; @@ -54,7 +54,7 @@ public function getEvents(string $name = null): array /** * @return MessageInterface[] */ - public function getMessages(string $name = null): array + public function getMessages(?string $name = null): array { $events = $this->getEvents($name); $messages = []; diff --git a/src/Symfony/Component/Notifier/Exception/IncompleteDsnException.php b/src/Symfony/Component/Notifier/Exception/IncompleteDsnException.php index 4b57f63b52f62..0c84f7d372bc3 100644 --- a/src/Symfony/Component/Notifier/Exception/IncompleteDsnException.php +++ b/src/Symfony/Component/Notifier/Exception/IncompleteDsnException.php @@ -19,7 +19,7 @@ class IncompleteDsnException extends InvalidArgumentException public function __construct( string $message, private ?string $dsn = null, - \Throwable $previous = null, + ?\Throwable $previous = null, ) { if ($dsn) { $message = sprintf('Invalid "%s" notifier DSN: %s', $dsn, $message); diff --git a/src/Symfony/Component/Notifier/Exception/MissingRequiredOptionException.php b/src/Symfony/Component/Notifier/Exception/MissingRequiredOptionException.php index 12062ddfe1a03..a799498482090 100644 --- a/src/Symfony/Component/Notifier/Exception/MissingRequiredOptionException.php +++ b/src/Symfony/Component/Notifier/Exception/MissingRequiredOptionException.php @@ -16,7 +16,7 @@ */ class MissingRequiredOptionException extends IncompleteDsnException { - public function __construct(string $option, string $dsn = null, \Throwable $previous = null) + public function __construct(string $option, ?string $dsn = null, ?\Throwable $previous = null) { $message = sprintf('The option "%s" is required but missing.', $option); diff --git a/src/Symfony/Component/Notifier/Exception/MultipleExclusiveOptionsUsedException.php b/src/Symfony/Component/Notifier/Exception/MultipleExclusiveOptionsUsedException.php index 418b32dabe8b3..6f72232dd8d6c 100644 --- a/src/Symfony/Component/Notifier/Exception/MultipleExclusiveOptionsUsedException.php +++ b/src/Symfony/Component/Notifier/Exception/MultipleExclusiveOptionsUsedException.php @@ -20,7 +20,7 @@ class MultipleExclusiveOptionsUsedException extends InvalidArgumentException * @param string[] $usedExclusiveOptions * @param string[] $exclusiveOptions */ - public function __construct(array $usedExclusiveOptions, array $exclusiveOptions, \Throwable $previous = null) + public function __construct(array $usedExclusiveOptions, array $exclusiveOptions, ?\Throwable $previous = null) { $message = sprintf( 'Multiple exclusive options have been used "%s". Only one of "%s" can be used.', diff --git a/src/Symfony/Component/Notifier/Exception/TransportException.php b/src/Symfony/Component/Notifier/Exception/TransportException.php index 1c2b584ef0899..70935141b0ca6 100644 --- a/src/Symfony/Component/Notifier/Exception/TransportException.php +++ b/src/Symfony/Component/Notifier/Exception/TransportException.php @@ -24,7 +24,7 @@ public function __construct( string $message, private ResponseInterface $response, int $code = 0, - \Throwable $previous = null, + ?\Throwable $previous = null, ) { $this->debug .= $response->getInfo('debug') ?? ''; diff --git a/src/Symfony/Component/Notifier/Exception/UnsupportedSchemeException.php b/src/Symfony/Component/Notifier/Exception/UnsupportedSchemeException.php index dedd12612d2bf..0d7fa06e65768 100644 --- a/src/Symfony/Component/Notifier/Exception/UnsupportedSchemeException.php +++ b/src/Symfony/Component/Notifier/Exception/UnsupportedSchemeException.php @@ -321,7 +321,7 @@ class UnsupportedSchemeException extends LogicException /** * @param string[] $supported */ - public function __construct(Dsn $dsn, string $name = null, array $supported = [], \Throwable $previous = null) + public function __construct(Dsn $dsn, ?string $name = null, array $supported = [], ?\Throwable $previous = null) { $provider = $dsn->getScheme(); if (false !== $pos = strpos($provider, '+')) { diff --git a/src/Symfony/Component/Notifier/Notification/ChatNotificationInterface.php b/src/Symfony/Component/Notifier/Notification/ChatNotificationInterface.php index 7b42fac5fe8c1..396c455859ac9 100644 --- a/src/Symfony/Component/Notifier/Notification/ChatNotificationInterface.php +++ b/src/Symfony/Component/Notifier/Notification/ChatNotificationInterface.php @@ -19,5 +19,5 @@ */ interface ChatNotificationInterface { - public function asChatMessage(RecipientInterface $recipient, string $transport = null): ?ChatMessage; + public function asChatMessage(RecipientInterface $recipient, ?string $transport = null): ?ChatMessage; } diff --git a/src/Symfony/Component/Notifier/Notification/EmailNotificationInterface.php b/src/Symfony/Component/Notifier/Notification/EmailNotificationInterface.php index 55660427ab01e..d2aac4775d05e 100644 --- a/src/Symfony/Component/Notifier/Notification/EmailNotificationInterface.php +++ b/src/Symfony/Component/Notifier/Notification/EmailNotificationInterface.php @@ -19,5 +19,5 @@ */ interface EmailNotificationInterface { - public function asEmailMessage(EmailRecipientInterface $recipient, string $transport = null): ?EmailMessage; + public function asEmailMessage(EmailRecipientInterface $recipient, ?string $transport = null): ?EmailMessage; } diff --git a/src/Symfony/Component/Notifier/Notification/PushNotificationInterface.php b/src/Symfony/Component/Notifier/Notification/PushNotificationInterface.php index 949dc4893bb77..fa416a90f8cf7 100644 --- a/src/Symfony/Component/Notifier/Notification/PushNotificationInterface.php +++ b/src/Symfony/Component/Notifier/Notification/PushNotificationInterface.php @@ -16,5 +16,5 @@ interface PushNotificationInterface { - public function asPushMessage(RecipientInterface $recipient, string $transport = null): ?PushMessage; + public function asPushMessage(RecipientInterface $recipient, ?string $transport = null): ?PushMessage; } diff --git a/src/Symfony/Component/Notifier/Notification/SmsNotificationInterface.php b/src/Symfony/Component/Notifier/Notification/SmsNotificationInterface.php index 02db67ac2a6f3..a9ba53a48628c 100644 --- a/src/Symfony/Component/Notifier/Notification/SmsNotificationInterface.php +++ b/src/Symfony/Component/Notifier/Notification/SmsNotificationInterface.php @@ -19,5 +19,5 @@ */ interface SmsNotificationInterface { - public function asSmsMessage(SmsRecipientInterface $recipient, string $transport = null): ?SmsMessage; + public function asSmsMessage(SmsRecipientInterface $recipient, ?string $transport = null): ?SmsMessage; } diff --git a/src/Symfony/Component/Notifier/Test/TransportFactoryTestCase.php b/src/Symfony/Component/Notifier/Test/TransportFactoryTestCase.php index 706cdea506656..7b1eaf21a6d32 100644 --- a/src/Symfony/Component/Notifier/Test/TransportFactoryTestCase.php +++ b/src/Symfony/Component/Notifier/Test/TransportFactoryTestCase.php @@ -86,7 +86,7 @@ public function testCreate(string $expected, string $dsn) /** * @dataProvider unsupportedSchemeProvider */ - public function testUnsupportedSchemeException(string $dsn, string $message = null) + public function testUnsupportedSchemeException(string $dsn, ?string $message = null) { $factory = $this->createFactory(); @@ -103,7 +103,7 @@ public function testUnsupportedSchemeException(string $dsn, string $message = nu /** * @dataProvider incompleteDsnProvider */ - public function testIncompleteDsnException(string $dsn, string $message = null) + public function testIncompleteDsnException(string $dsn, ?string $message = null) { $factory = $this->createFactory(); @@ -120,7 +120,7 @@ public function testIncompleteDsnException(string $dsn, string $message = null) /** * @dataProvider missingRequiredOptionProvider */ - public function testMissingRequiredOptionException(string $dsn, string $message = null) + public function testMissingRequiredOptionException(string $dsn, ?string $message = null) { $factory = $this->createFactory(); diff --git a/src/Symfony/Component/Notifier/Test/TransportTestCase.php b/src/Symfony/Component/Notifier/Test/TransportTestCase.php index 1eb37e9987d41..aa3aa69bdc743 100644 --- a/src/Symfony/Component/Notifier/Test/TransportTestCase.php +++ b/src/Symfony/Component/Notifier/Test/TransportTestCase.php @@ -27,7 +27,7 @@ abstract class TransportTestCase extends TestCase protected const CUSTOM_HOST = 'host.test'; protected const CUSTOM_PORT = 42; - abstract public static function createTransport(HttpClientInterface $client = null): TransportInterface; + abstract public static function createTransport(?HttpClientInterface $client = null): TransportInterface; /** * @return iterable @@ -55,7 +55,7 @@ public function testToString(string $expected, TransportInterface $transport) /** * @dataProvider supportedMessagesProvider */ - public function testSupportedMessages(MessageInterface $message, TransportInterface $transport = null) + public function testSupportedMessages(MessageInterface $message, ?TransportInterface $transport = null) { $transport ??= $this->createTransport(); @@ -65,7 +65,7 @@ public function testSupportedMessages(MessageInterface $message, TransportInterf /** * @dataProvider unsupportedMessagesProvider */ - public function testUnsupportedMessages(MessageInterface $message, TransportInterface $transport = null) + public function testUnsupportedMessages(MessageInterface $message, ?TransportInterface $transport = null) { $transport ??= $this->createTransport(); @@ -75,7 +75,7 @@ public function testUnsupportedMessages(MessageInterface $message, TransportInte /** * @dataProvider unsupportedMessagesProvider */ - public function testUnsupportedMessagesTrowUnsupportedMessageTypeExceptionWhenSend(MessageInterface $message, TransportInterface $transport = null) + public function testUnsupportedMessagesTrowUnsupportedMessageTypeExceptionWhenSend(MessageInterface $message, ?TransportInterface $transport = null) { $transport ??= $this->createTransport(); diff --git a/src/Symfony/Component/Notifier/Tests/Channel/AbstractChannelTest.php b/src/Symfony/Component/Notifier/Tests/Channel/AbstractChannelTest.php index f704bb0401efd..ae93ba2732d85 100644 --- a/src/Symfony/Component/Notifier/Tests/Channel/AbstractChannelTest.php +++ b/src/Symfony/Component/Notifier/Tests/Channel/AbstractChannelTest.php @@ -32,7 +32,7 @@ public function testChannelCannotBeConstructedWithoutTransportAndBus() class DummyChannel extends AbstractChannel { - public function notify(Notification $notification, RecipientInterface $recipient, string $transportName = null): void + public function notify(Notification $notification, RecipientInterface $recipient, ?string $transportName = null): void { return; } diff --git a/src/Symfony/Component/Notifier/Tests/Event/FailedMessageEventTest.php b/src/Symfony/Component/Notifier/Tests/Event/FailedMessageEventTest.php index 33f266e1a26ca..a2446a1aaa686 100644 --- a/src/Symfony/Component/Notifier/Tests/Event/FailedMessageEventTest.php +++ b/src/Symfony/Component/Notifier/Tests/Event/FailedMessageEventTest.php @@ -58,7 +58,7 @@ public function testFailedMessageEventIsDisptachIfError() $transport = new class($clientMock, $eventDispatcherMock) extends AbstractTransport { public NullTransportException $exception; - public function __construct($client, EventDispatcherInterface $dispatcher = null) + public function __construct($client, ?EventDispatcherInterface $dispatcher = null) { $this->exception = new NullTransportException(); parent::__construct($client, $dispatcher); diff --git a/src/Symfony/Component/Notifier/Tests/Mailer/DummyMailer.php b/src/Symfony/Component/Notifier/Tests/Mailer/DummyMailer.php index c1e57fd7281d4..6b0daef02d1bc 100644 --- a/src/Symfony/Component/Notifier/Tests/Mailer/DummyMailer.php +++ b/src/Symfony/Component/Notifier/Tests/Mailer/DummyMailer.php @@ -22,7 +22,7 @@ class DummyMailer implements MailerInterface { private RawMessage $sentMessage; - public function send(RawMessage $message, Envelope $envelope = null): void + public function send(RawMessage $message, ?Envelope $envelope = null): void { $this->sentMessage = $message; } diff --git a/src/Symfony/Component/Notifier/Tests/Transport/DsnTest.php b/src/Symfony/Component/Notifier/Tests/Transport/DsnTest.php index a75f1608d8090..5b9a183ae21e3 100644 --- a/src/Symfony/Component/Notifier/Tests/Transport/DsnTest.php +++ b/src/Symfony/Component/Notifier/Tests/Transport/DsnTest.php @@ -21,7 +21,7 @@ final class DsnTest extends TestCase /** * @dataProvider constructProvider */ - public function testConstruct(string $dsnString, string $scheme, string $host, string $user = null, string $password = null, int $port = null, array $options = [], string $path = null) + public function testConstruct(string $dsnString, string $scheme, string $host, ?string $user = null, ?string $password = null, ?int $port = null, array $options = [], ?string $path = null) { $dsn = new Dsn($dsnString); $this->assertSame($dsnString, $dsn->getOriginalDsn()); @@ -172,7 +172,7 @@ public static function invalidDsnProvider(): iterable /** * @dataProvider getOptionProvider */ - public function testGetOption($expected, string $dsnString, string $option, string $default = null) + public function testGetOption($expected, string $dsnString, string $option, ?string $default = null) { $dsn = new Dsn($dsnString); diff --git a/src/Symfony/Component/Notifier/Transport.php b/src/Symfony/Component/Notifier/Transport.php index 3e1ee1683c796..5b80040bbfafe 100644 --- a/src/Symfony/Component/Notifier/Transport.php +++ b/src/Symfony/Component/Notifier/Transport.php @@ -31,6 +31,7 @@ final class Transport Bridge\AllMySms\AllMySmsTransportFactory::class, Bridge\AmazonSns\AmazonSnsTransportFactory::class, Bridge\Bandwidth\BandwidthTransportFactory::class, + Bridge\Bluesky\BlueskyTransportFactory::class, Bridge\Brevo\BrevoTransportFactory::class, Bridge\Chatwork\ChatworkTransportFactory::class, Bridge\Clickatell\ClickatellTransportFactory::class, @@ -88,6 +89,7 @@ final class Transport Bridge\Smsc\SmscTransportFactory::class, Bridge\SmsFactor\SmsFactorTransportFactory::class, Bridge\Smsmode\SmsmodeTransportFactory::class, + Bridge\SmsSluzba\SmsSluzbaTransportFactory::class, Bridge\SpotHit\SpotHitTransportFactory::class, Bridge\Telegram\TelegramTransportFactory::class, Bridge\Telnyx\TelnyxTransportFactory::class, @@ -95,20 +97,21 @@ final class Transport Bridge\TurboSms\TurboSmsTransportFactory::class, Bridge\Twilio\TwilioTransportFactory::class, Bridge\Twitter\TwitterTransportFactory::class, + Bridge\Unifonic\UnifonicTransportFactory::class, Bridge\Vonage\VonageTransportFactory::class, Bridge\Yunpian\YunpianTransportFactory::class, Bridge\Zendesk\ZendeskTransportFactory::class, Bridge\Zulip\ZulipTransportFactory::class, ]; - public static function fromDsn(#[\SensitiveParameter] string $dsn, EventDispatcherInterface $dispatcher = null, HttpClientInterface $client = null): TransportInterface + public static function fromDsn(#[\SensitiveParameter] string $dsn, ?EventDispatcherInterface $dispatcher = null, ?HttpClientInterface $client = null): TransportInterface { $factory = new self(self::getDefaultFactories($dispatcher, $client)); return $factory->fromString($dsn); } - public static function fromDsns(#[\SensitiveParameter] array $dsns, EventDispatcherInterface $dispatcher = null, HttpClientInterface $client = null): TransportInterface + public static function fromDsns(#[\SensitiveParameter] array $dsns, ?EventDispatcherInterface $dispatcher = null, ?HttpClientInterface $client = null): TransportInterface { $factory = new self(iterator_to_array(self::getDefaultFactories($dispatcher, $client))); @@ -175,7 +178,7 @@ private function createFromDsns(#[\SensitiveParameter] array $dsns): array /** * @return TransportFactoryInterface[] */ - private static function getDefaultFactories(EventDispatcherInterface $dispatcher = null, HttpClientInterface $client = null): iterable + private static function getDefaultFactories(?EventDispatcherInterface $dispatcher = null, ?HttpClientInterface $client = null): iterable { foreach (self::FACTORY_CLASSES as $factoryClass) { if (class_exists($factoryClass)) { diff --git a/src/Symfony/Component/Notifier/Transport/AbstractTransport.php b/src/Symfony/Component/Notifier/Transport/AbstractTransport.php index 1338be370abdd..809f15fb5a874 100644 --- a/src/Symfony/Component/Notifier/Transport/AbstractTransport.php +++ b/src/Symfony/Component/Notifier/Transport/AbstractTransport.php @@ -34,7 +34,7 @@ abstract class AbstractTransport implements TransportInterface protected ?string $host = null; protected ?int $port = null; - public function __construct(HttpClientInterface $client = null, EventDispatcherInterface $dispatcher = null) + public function __construct(?HttpClientInterface $client = null, ?EventDispatcherInterface $dispatcher = null) { $this->client = $client; if (null === $client) { diff --git a/src/Symfony/Component/Notifier/Transport/AbstractTransportFactory.php b/src/Symfony/Component/Notifier/Transport/AbstractTransportFactory.php index acb703c6e07a3..706d3e38a4ff1 100644 --- a/src/Symfony/Component/Notifier/Transport/AbstractTransportFactory.php +++ b/src/Symfony/Component/Notifier/Transport/AbstractTransportFactory.php @@ -24,7 +24,7 @@ abstract class AbstractTransportFactory implements TransportFactoryInterface protected ?EventDispatcherInterface $dispatcher; protected ?HttpClientInterface $client; - public function __construct(EventDispatcherInterface $dispatcher = null, HttpClientInterface $client = null) + public function __construct(?EventDispatcherInterface $dispatcher = null, ?HttpClientInterface $client = null) { $this->dispatcher = $dispatcher; $this->client = $client; diff --git a/src/Symfony/Component/Notifier/Transport/Dsn.php b/src/Symfony/Component/Notifier/Transport/Dsn.php index 8767bf4a0deca..2afe28cefdac9 100644 --- a/src/Symfony/Component/Notifier/Transport/Dsn.php +++ b/src/Symfony/Component/Notifier/Transport/Dsn.php @@ -74,7 +74,7 @@ public function getPassword(): ?string return $this->password; } - public function getPort(int $default = null): ?int + public function getPort(?int $default = null): ?int { return $this->port ?? $default; } diff --git a/src/Symfony/Component/Notifier/Transport/NullTransport.php b/src/Symfony/Component/Notifier/Transport/NullTransport.php index f1cf481d18974..fbd3021c7efbc 100644 --- a/src/Symfony/Component/Notifier/Transport/NullTransport.php +++ b/src/Symfony/Component/Notifier/Transport/NullTransport.php @@ -25,7 +25,7 @@ class NullTransport implements TransportInterface { private ?EventDispatcherInterface $dispatcher; - public function __construct(EventDispatcherInterface $dispatcher = null) + public function __construct(?EventDispatcherInterface $dispatcher = null) { $this->dispatcher = $dispatcher; } diff --git a/src/Symfony/Component/PasswordHasher/Exception/InvalidPasswordException.php b/src/Symfony/Component/PasswordHasher/Exception/InvalidPasswordException.php index 30b09d8c35319..c70a4d5561531 100644 --- a/src/Symfony/Component/PasswordHasher/Exception/InvalidPasswordException.php +++ b/src/Symfony/Component/PasswordHasher/Exception/InvalidPasswordException.php @@ -16,7 +16,7 @@ */ class InvalidPasswordException extends \RuntimeException implements ExceptionInterface { - public function __construct(string $message = 'Invalid password.', int $code = 0, \Throwable $previous = null) + public function __construct(string $message = 'Invalid password.', int $code = 0, ?\Throwable $previous = null) { parent::__construct($message, $code, $previous); } diff --git a/src/Symfony/Component/PasswordHasher/Hasher/MessageDigestPasswordHasher.php b/src/Symfony/Component/PasswordHasher/Hasher/MessageDigestPasswordHasher.php index 7d7278c0913a7..0e7d7383919eb 100644 --- a/src/Symfony/Component/PasswordHasher/Hasher/MessageDigestPasswordHasher.php +++ b/src/Symfony/Component/PasswordHasher/Hasher/MessageDigestPasswordHasher.php @@ -48,7 +48,7 @@ public function __construct(string $algorithm = 'sha512', bool $encodeHashAsBase $this->iterations = $iterations; } - public function hash(#[\SensitiveParameter] string $plainPassword, string $salt = null): string + public function hash(#[\SensitiveParameter] string $plainPassword, ?string $salt = null): string { if ($this->isPasswordTooLong($plainPassword)) { throw new InvalidPasswordException(); @@ -69,7 +69,7 @@ public function hash(#[\SensitiveParameter] string $plainPassword, string $salt return $this->encodeHashAsBase64 ? base64_encode($digest) : bin2hex($digest); } - public function verify(string $hashedPassword, #[\SensitiveParameter] string $plainPassword, string $salt = null): bool + public function verify(string $hashedPassword, #[\SensitiveParameter] string $plainPassword, ?string $salt = null): bool { if (\strlen($hashedPassword) !== $this->hashLength || str_contains($hashedPassword, '$')) { return false; diff --git a/src/Symfony/Component/PasswordHasher/Hasher/MigratingPasswordHasher.php b/src/Symfony/Component/PasswordHasher/Hasher/MigratingPasswordHasher.php index cc3a9aa2f7415..639ffc8eec6a1 100644 --- a/src/Symfony/Component/PasswordHasher/Hasher/MigratingPasswordHasher.php +++ b/src/Symfony/Component/PasswordHasher/Hasher/MigratingPasswordHasher.php @@ -33,12 +33,12 @@ public function __construct(PasswordHasherInterface $bestHasher, PasswordHasherI $this->extraHashers = $extraHashers; } - public function hash(#[\SensitiveParameter] string $plainPassword, string $salt = null): string + public function hash(#[\SensitiveParameter] string $plainPassword, ?string $salt = null): string { return $this->bestHasher->hash($plainPassword, $salt); } - public function verify(string $hashedPassword, #[\SensitiveParameter] string $plainPassword, string $salt = null): bool + public function verify(string $hashedPassword, #[\SensitiveParameter] string $plainPassword, ?string $salt = null): bool { if ($this->bestHasher->verify($hashedPassword, $plainPassword, $salt)) { return true; diff --git a/src/Symfony/Component/PasswordHasher/Hasher/NativePasswordHasher.php b/src/Symfony/Component/PasswordHasher/Hasher/NativePasswordHasher.php index 2259152071080..413bcecf32eae 100644 --- a/src/Symfony/Component/PasswordHasher/Hasher/NativePasswordHasher.php +++ b/src/Symfony/Component/PasswordHasher/Hasher/NativePasswordHasher.php @@ -31,7 +31,7 @@ final class NativePasswordHasher implements PasswordHasherInterface /** * @param string|null $algorithm An algorithm supported by password_hash() or null to use the best available algorithm */ - public function __construct(int $opsLimit = null, int $memLimit = null, int $cost = null, string $algorithm = null) + public function __construct(?int $opsLimit = null, ?int $memLimit = null, ?int $cost = null, ?string $algorithm = null) { $cost ??= 13; $opsLimit ??= max(4, \defined('SODIUM_CRYPTO_PWHASH_OPSLIMIT_INTERACTIVE') ? \SODIUM_CRYPTO_PWHASH_OPSLIMIT_INTERACTIVE : 4); diff --git a/src/Symfony/Component/PasswordHasher/Hasher/PasswordHasherFactory.php b/src/Symfony/Component/PasswordHasher/Hasher/PasswordHasherFactory.php index 7c0391714ac4d..ebfe70e00721c 100644 --- a/src/Symfony/Component/PasswordHasher/Hasher/PasswordHasherFactory.php +++ b/src/Symfony/Component/PasswordHasher/Hasher/PasswordHasherFactory.php @@ -200,7 +200,7 @@ private function getHasherConfigFromAlgorithm(array $config): array $config['algorithm'] = 'native'; $config['native_algorithm'] = \PASSWORD_ARGON2ID; } else { - throw new LogicException(sprintf('Algorithm "argon2id" is not available. Either use "%s", upgrade to PHP 7.3+ or use libsodium 1.0.15+ instead.', \defined('PASSWORD_ARGON2I') || $hasSodium ? 'argon2i", "auto' : 'auto')); + throw new LogicException(sprintf('Algorithm "argon2id" is not available; use "%s" or libsodium 1.0.15+ instead.', \defined('PASSWORD_ARGON2I') || $hasSodium ? 'argon2i", "auto' : 'auto')); } return $this->getHasherConfigFromAlgorithm($config); diff --git a/src/Symfony/Component/PasswordHasher/Hasher/Pbkdf2PasswordHasher.php b/src/Symfony/Component/PasswordHasher/Hasher/Pbkdf2PasswordHasher.php index 0c314d1ae2e1c..45d390ce4bc09 100644 --- a/src/Symfony/Component/PasswordHasher/Hasher/Pbkdf2PasswordHasher.php +++ b/src/Symfony/Component/PasswordHasher/Hasher/Pbkdf2PasswordHasher.php @@ -59,7 +59,7 @@ public function __construct(string $algorithm = 'sha512', bool $encodeHashAsBase $this->iterations = $iterations; } - public function hash(#[\SensitiveParameter] string $plainPassword, string $salt = null): string + public function hash(#[\SensitiveParameter] string $plainPassword, ?string $salt = null): string { if ($this->isPasswordTooLong($plainPassword)) { throw new InvalidPasswordException(); @@ -74,7 +74,7 @@ public function hash(#[\SensitiveParameter] string $plainPassword, string $salt return $this->encodeHashAsBase64 ? base64_encode($digest) : bin2hex($digest); } - public function verify(string $hashedPassword, #[\SensitiveParameter] string $plainPassword, string $salt = null): bool + public function verify(string $hashedPassword, #[\SensitiveParameter] string $plainPassword, ?string $salt = null): bool { if (\strlen($hashedPassword) !== $this->encodedLength || str_contains($hashedPassword, '$')) { return false; diff --git a/src/Symfony/Component/PasswordHasher/Hasher/PlaintextPasswordHasher.php b/src/Symfony/Component/PasswordHasher/Hasher/PlaintextPasswordHasher.php index 537075bba0c6e..cffb6340a03d5 100644 --- a/src/Symfony/Component/PasswordHasher/Hasher/PlaintextPasswordHasher.php +++ b/src/Symfony/Component/PasswordHasher/Hasher/PlaintextPasswordHasher.php @@ -35,7 +35,7 @@ public function __construct(bool $ignorePasswordCase = false) $this->ignorePasswordCase = $ignorePasswordCase; } - public function hash(#[\SensitiveParameter] string $plainPassword, string $salt = null): string + public function hash(#[\SensitiveParameter] string $plainPassword, ?string $salt = null): string { if ($this->isPasswordTooLong($plainPassword)) { throw new InvalidPasswordException(); @@ -44,7 +44,7 @@ public function hash(#[\SensitiveParameter] string $plainPassword, string $salt return $this->mergePasswordAndSalt($plainPassword, $salt); } - public function verify(string $hashedPassword, #[\SensitiveParameter] string $plainPassword, string $salt = null): bool + public function verify(string $hashedPassword, #[\SensitiveParameter] string $plainPassword, ?string $salt = null): bool { if ($this->isPasswordTooLong($plainPassword)) { return false; diff --git a/src/Symfony/Component/PasswordHasher/Hasher/SodiumPasswordHasher.php b/src/Symfony/Component/PasswordHasher/Hasher/SodiumPasswordHasher.php index 37ca7d9718621..ae6c03fdb6679 100644 --- a/src/Symfony/Component/PasswordHasher/Hasher/SodiumPasswordHasher.php +++ b/src/Symfony/Component/PasswordHasher/Hasher/SodiumPasswordHasher.php @@ -29,7 +29,7 @@ final class SodiumPasswordHasher implements PasswordHasherInterface private int $opsLimit; private int $memLimit; - public function __construct(int $opsLimit = null, int $memLimit = null) + public function __construct(?int $opsLimit = null, ?int $memLimit = null) { if (!self::isSupported()) { throw new LogicException('Libsodium is not available. You should either install the sodium extension or use a different password hasher.'); diff --git a/src/Symfony/Component/PasswordHasher/LegacyPasswordHasherInterface.php b/src/Symfony/Component/PasswordHasher/LegacyPasswordHasherInterface.php index b8606ae05ec9e..8efef376b154f 100644 --- a/src/Symfony/Component/PasswordHasher/LegacyPasswordHasherInterface.php +++ b/src/Symfony/Component/PasswordHasher/LegacyPasswordHasherInterface.php @@ -27,10 +27,10 @@ interface LegacyPasswordHasherInterface extends PasswordHasherInterface * * @throws InvalidPasswordException If the plain password is invalid, e.g. excessively long */ - public function hash(#[\SensitiveParameter] string $plainPassword, string $salt = null): string; + public function hash(#[\SensitiveParameter] string $plainPassword, ?string $salt = null): string; /** * Checks that a plain password and a salt match a password hash. */ - public function verify(string $hashedPassword, #[\SensitiveParameter] string $plainPassword, string $salt = null): bool; + public function verify(string $hashedPassword, #[\SensitiveParameter] string $plainPassword, ?string $salt = null): bool; } diff --git a/src/Symfony/Component/PasswordHasher/Tests/Hasher/MessageDigestPasswordHasherTest.php b/src/Symfony/Component/PasswordHasher/Tests/Hasher/MessageDigestPasswordHasherTest.php index 2e7a192bf8ad2..1b29774a05907 100644 --- a/src/Symfony/Component/PasswordHasher/Tests/Hasher/MessageDigestPasswordHasherTest.php +++ b/src/Symfony/Component/PasswordHasher/Tests/Hasher/MessageDigestPasswordHasherTest.php @@ -60,4 +60,12 @@ public function testCheckPasswordLength() $this->assertFalse($hasher->verify('encoded', str_repeat('a', 5000), 'salt')); } + + public function testUsingBracketInSaltThrows() + { + $hasher = new MessageDigestPasswordHasher('sha256', false, 1); + + $this->expectException(\LogicException::class); + $hasher->hash('password', '{'); + } } diff --git a/src/Symfony/Component/PasswordHasher/Tests/Hasher/NativePasswordHasherTest.php b/src/Symfony/Component/PasswordHasher/Tests/Hasher/NativePasswordHasherTest.php index 5dc301916eed3..b87f4e988c6b5 100644 --- a/src/Symfony/Component/PasswordHasher/Tests/Hasher/NativePasswordHasherTest.php +++ b/src/Symfony/Component/PasswordHasher/Tests/Hasher/NativePasswordHasherTest.php @@ -12,6 +12,7 @@ namespace Symfony\Component\PasswordHasher\Tests\Hasher; use PHPUnit\Framework\TestCase; +use Symfony\Component\PasswordHasher\Exception\InvalidPasswordException; use Symfony\Component\PasswordHasher\Hasher\NativePasswordHasher; /** @@ -119,4 +120,40 @@ public function testNeedsRehash() $hasher = new NativePasswordHasher(5, 11000, 5); $this->assertTrue($hasher->needsRehash($hash)); } + + public function testLowOpsLimitsThrows() + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessageMatches('/\$opsLimit must be (\d+) or greater\./'); + + new NativePasswordHasher(2); + } + + public function testLowMemLimitThrows() + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessageMatches('/\$memLimit must be (\d+)(.?) or greater\./'); + + new NativePasswordHasher(3, 9999); + } + + /** + * @testWith [1] + * [40] + */ + public function testInvalidCostThrows(int $cost) + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessageMatches('/\$cost must be in the range of (\d+)-(\d+)\./'); + + new NativePasswordHasher(4, 11000, $cost); + } + + public function testHashTooLongPasswordThrows() + { + $this->expectException(InvalidPasswordException::class); + + $hasher = new NativePasswordHasher(4, 11000, 4); + $hasher->hash(str_repeat('a', 5000)); + } } diff --git a/src/Symfony/Component/PasswordHasher/Tests/Hasher/PasswordHasherFactoryTest.php b/src/Symfony/Component/PasswordHasher/Tests/Hasher/PasswordHasherFactoryTest.php index e2c2b8305fc22..33ac3e5226fbb 100644 --- a/src/Symfony/Component/PasswordHasher/Tests/Hasher/PasswordHasherFactoryTest.php +++ b/src/Symfony/Component/PasswordHasher/Tests/Hasher/PasswordHasherFactoryTest.php @@ -175,6 +175,22 @@ public function testMigrateFrom() $this->assertStringStartsWith(\SODIUM_CRYPTO_PWHASH_STRPREFIX, $hasher->hash('foo', null)); } + public function testMissingClass() + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('"class" must be set in {"arguments":[]}'); + + (new PasswordHasherFactory([SomeUser::class => ['arguments' => []]]))->getPasswordHasher(SomeUser::class); + } + + public function testMissingArguments() + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('"arguments" must be set in {"class":"stdClass"}'); + + (new PasswordHasherFactory([SomeUser::class => ['class' => \stdClass::class]]))->getPasswordHasher(SomeUser::class); + } + public function testDefaultMigratingHashers() { $this->assertInstanceOf( diff --git a/src/Symfony/Component/PasswordHasher/Tests/Hasher/PlaintextPasswordHasherTest.php b/src/Symfony/Component/PasswordHasher/Tests/Hasher/PlaintextPasswordHasherTest.php index dc24db632ab16..dad518db0fd7f 100644 --- a/src/Symfony/Component/PasswordHasher/Tests/Hasher/PlaintextPasswordHasherTest.php +++ b/src/Symfony/Component/PasswordHasher/Tests/Hasher/PlaintextPasswordHasherTest.php @@ -53,4 +53,12 @@ public function testCheckPasswordLength() $this->assertFalse($hasher->verify('encoded', str_repeat('a', 5000), 'salt')); } + + public function testUsingBracketInSaltThrows() + { + $hasher = new PlaintextPasswordHasher(); + + $this->expectException(\LogicException::class); + $hasher->hash('password', '{'); + } } diff --git a/src/Symfony/Component/Process/ExecutableFinder.php b/src/Symfony/Component/Process/ExecutableFinder.php index 2f0f68c12cec4..a47477078b1a0 100644 --- a/src/Symfony/Component/Process/ExecutableFinder.php +++ b/src/Symfony/Component/Process/ExecutableFinder.php @@ -44,7 +44,7 @@ public function addSuffix(string $suffix): void * @param string|null $default The default to return if no executable is found * @param array $extraDirs Additional dirs to check into */ - public function find(string $name, string $default = null, array $extraDirs = []): ?string + public function find(string $name, ?string $default = null, array $extraDirs = []): ?string { $dirs = array_merge( explode(\PATH_SEPARATOR, getenv('PATH') ?: getenv('Path')), diff --git a/src/Symfony/Component/Process/InputStream.php b/src/Symfony/Component/Process/InputStream.php index d0517de22b0be..cd91029e8c443 100644 --- a/src/Symfony/Component/Process/InputStream.php +++ b/src/Symfony/Component/Process/InputStream.php @@ -29,7 +29,7 @@ class InputStream implements \IteratorAggregate /** * Sets a callback that is called when the write buffer becomes empty. */ - public function onEmpty(callable $onEmpty = null): void + public function onEmpty(?callable $onEmpty = null): void { $this->onEmpty = null !== $onEmpty ? $onEmpty(...) : null; } diff --git a/src/Symfony/Component/Process/PhpExecutableFinder.php b/src/Symfony/Component/Process/PhpExecutableFinder.php index ae053641e93b0..4a882e0f25d37 100644 --- a/src/Symfony/Component/Process/PhpExecutableFinder.php +++ b/src/Symfony/Component/Process/PhpExecutableFinder.php @@ -33,7 +33,7 @@ public function find(bool $includeArgs = true): string|false { if ($php = getenv('PHP_BINARY')) { if (!is_executable($php)) { - $command = '\\' === \DIRECTORY_SEPARATOR ? 'where' : 'command -v'; + $command = '\\' === \DIRECTORY_SEPARATOR ? 'where' : 'command -v --'; if (\function_exists('exec') && $php = strtok(exec($command.' '.escapeshellarg($php)), \PHP_EOL)) { if (!is_executable($php)) { return false; diff --git a/src/Symfony/Component/Process/PhpProcess.php b/src/Symfony/Component/Process/PhpProcess.php index 0d31e26fa87cf..01d88954dc891 100644 --- a/src/Symfony/Component/Process/PhpProcess.php +++ b/src/Symfony/Component/Process/PhpProcess.php @@ -32,7 +32,7 @@ class PhpProcess extends Process * @param int $timeout The timeout in seconds * @param array|null $php Path to the PHP binary to use with any additional arguments */ - public function __construct(string $script, string $cwd = null, array $env = null, int $timeout = 60, array $php = null) + public function __construct(string $script, ?string $cwd = null, ?array $env = null, int $timeout = 60, ?array $php = null) { if (null === $php) { $executableFinder = new PhpExecutableFinder(); @@ -50,12 +50,12 @@ public function __construct(string $script, string $cwd = null, array $env = nul parent::__construct($php, $cwd, $env, $script, $timeout); } - public static function fromShellCommandline(string $command, string $cwd = null, array $env = null, mixed $input = null, ?float $timeout = 60): static + public static function fromShellCommandline(string $command, ?string $cwd = null, ?array $env = null, mixed $input = null, ?float $timeout = 60): static { throw new LogicException(sprintf('The "%s()" method cannot be called when using "%s".', __METHOD__, self::class)); } - public function start(callable $callback = null, array $env = []): void + public function start(?callable $callback = null, array $env = []): void { if (null === $this->getCommandLine()) { throw new RuntimeException('Unable to find the PHP executable.'); diff --git a/src/Symfony/Component/Process/PhpSubprocess.php b/src/Symfony/Component/Process/PhpSubprocess.php index 0720520f314fa..a97f8b26e0c55 100644 --- a/src/Symfony/Component/Process/PhpSubprocess.php +++ b/src/Symfony/Component/Process/PhpSubprocess.php @@ -51,7 +51,7 @@ class PhpSubprocess extends Process * @param int $timeout The timeout in seconds * @param array|null $php Path to the PHP binary to use with any additional arguments */ - public function __construct(array $command, string $cwd = null, array $env = null, int $timeout = 60, array $php = null) + public function __construct(array $command, ?string $cwd = null, ?array $env = null, int $timeout = 60, ?array $php = null) { if (null === $php) { $executableFinder = new PhpExecutableFinder(); @@ -73,12 +73,12 @@ public function __construct(array $command, string $cwd = null, array $env = nul parent::__construct($command, $cwd, $env, null, $timeout); } - public static function fromShellCommandline(string $command, string $cwd = null, array $env = null, mixed $input = null, ?float $timeout = 60): static + public static function fromShellCommandline(string $command, ?string $cwd = null, ?array $env = null, mixed $input = null, ?float $timeout = 60): static { throw new LogicException(sprintf('The "%s()" method cannot be called when using "%s".', __METHOD__, self::class)); } - public function start(callable $callback = null, array $env = []): void + public function start(?callable $callback = null, array $env = []): void { if (null === $this->getCommandLine()) { throw new RuntimeException('Unable to find the PHP executable.'); diff --git a/src/Symfony/Component/Process/Process.php b/src/Symfony/Component/Process/Process.php index 0b29a646c65dc..7c4e925004dbf 100644 --- a/src/Symfony/Component/Process/Process.php +++ b/src/Symfony/Component/Process/Process.php @@ -141,7 +141,7 @@ class Process implements \IteratorAggregate * * @throws LogicException When proc_open is not installed */ - public function __construct(array $command, string $cwd = null, array $env = null, mixed $input = null, ?float $timeout = 60) + public function __construct(array $command, ?string $cwd = null, ?array $env = null, mixed $input = null, ?float $timeout = 60) { if (!\function_exists('proc_open')) { throw new LogicException('The Process class relies on proc_open, which is not available on your PHP installation.'); @@ -187,7 +187,7 @@ public function __construct(array $command, string $cwd = null, array $env = nul * * @throws LogicException When proc_open is not installed */ - public static function fromShellCommandline(string $command, string $cwd = null, array $env = null, mixed $input = null, ?float $timeout = 60): static + public static function fromShellCommandline(string $command, ?string $cwd = null, ?array $env = null, mixed $input = null, ?float $timeout = 60): static { $process = new static([], $cwd, $env, $input, $timeout); $process->commandline = $command; @@ -242,7 +242,7 @@ public function __clone() * * @final */ - public function run(callable $callback = null, array $env = []): int + public function run(?callable $callback = null, array $env = []): int { $this->start($callback, $env); @@ -261,7 +261,7 @@ public function run(callable $callback = null, array $env = []): int * * @final */ - public function mustRun(callable $callback = null, array $env = []): static + public function mustRun(?callable $callback = null, array $env = []): static { if (0 !== $this->run($callback, $env)) { throw new ProcessFailedException($this); @@ -289,7 +289,7 @@ public function mustRun(callable $callback = null, array $env = []): static * @throws RuntimeException When process is already running * @throws LogicException In case a callback is provided and output has been disabled */ - public function start(callable $callback = null, array $env = []): void + public function start(?callable $callback = null, array $env = []): void { if ($this->isRunning()) { throw new RuntimeException('Process is already running.'); @@ -384,7 +384,7 @@ public function start(callable $callback = null, array $env = []): void * * @final */ - public function restart(callable $callback = null, array $env = []): static + public function restart(?callable $callback = null, array $env = []): static { if ($this->isRunning()) { throw new RuntimeException('Process is already running.'); @@ -411,7 +411,7 @@ public function restart(callable $callback = null, array $env = []): static * @throws ProcessSignaledException When process stopped after receiving signal * @throws LogicException When process is not yet started */ - public function wait(callable $callback = null): int + public function wait(?callable $callback = null): int { $this->requireProcessIsStarted(__FUNCTION__); @@ -884,7 +884,7 @@ public function getStatus(): string * * @return int|null The exit-code of the process or null if it's not running */ - public function stop(float $timeout = 10, int $signal = null): ?int + public function stop(float $timeout = 10, ?int $signal = null): ?int { $timeoutMicro = microtime(true) + $timeout; if ($this->isRunning()) { @@ -1258,7 +1258,7 @@ private function getDescriptors(bool $hasCallback): array * * @param callable|null $callback The user defined PHP callback */ - protected function buildCallback(callable $callback = null): \Closure + protected function buildCallback(?callable $callback = null): \Closure { if ($this->outputDisabled) { return fn ($type, $data): bool => null !== $callback && $callback($type, $data); diff --git a/src/Symfony/Component/Process/Tests/ErrorProcessInitiator.php b/src/Symfony/Component/Process/Tests/ErrorProcessInitiator.php index 541680224d740..0b75add63cf01 100644 --- a/src/Symfony/Component/Process/Tests/ErrorProcessInitiator.php +++ b/src/Symfony/Component/Process/Tests/ErrorProcessInitiator.php @@ -25,7 +25,7 @@ while (!str_contains($process->getOutput(), 'ready')) { usleep(1000); } - $process->signal(\SIGSTOP); + $process->isRunning() && $process->signal(\SIGSTOP); $process->wait(); return $process->getExitCode(); diff --git a/src/Symfony/Component/Process/Tests/ProcessTest.php b/src/Symfony/Component/Process/Tests/ProcessTest.php index 63ad90d95356a..dacf50ac310ad 100644 --- a/src/Symfony/Component/Process/Tests/ProcessTest.php +++ b/src/Symfony/Component/Process/Tests/ProcessTest.php @@ -349,7 +349,7 @@ public static function provideInvalidInputValues() /** * @dataProvider provideInputValues */ - public function testValidInput(?string $expected, null|float|string $value) + public function testValidInput(?string $expected, float|string|null $value) { $process = $this->getProcess('foo'); $process->setInput($value); @@ -1609,7 +1609,7 @@ public function testNotTerminableInputPipe() $this->assertFalse($process->isRunning()); } - private function getProcess(string|array $commandline, string $cwd = null, array $env = null, mixed $input = null, ?int $timeout = 60): Process + private function getProcess(string|array $commandline, ?string $cwd = null, ?array $env = null, mixed $input = null, ?int $timeout = 60): Process { if (\is_string($commandline)) { $process = Process::fromShellCommandline($commandline, $cwd, $env, $input, $timeout); @@ -1622,7 +1622,7 @@ private function getProcess(string|array $commandline, string $cwd = null, array return self::$process = $process; } - private function getProcessForCode(string $code, string $cwd = null, array $env = null, $input = null, ?int $timeout = 60): Process + private function getProcessForCode(string $code, ?string $cwd = null, ?array $env = null, $input = null, ?int $timeout = 60): Process { return $this->getProcess([self::$phpBin, '-r', $code], $cwd, $env, $input, $timeout); } diff --git a/src/Symfony/Component/PropertyAccess/Exception/InvalidTypeException.php b/src/Symfony/Component/PropertyAccess/Exception/InvalidTypeException.php index f659ffd07e6da..07f607021e381 100644 --- a/src/Symfony/Component/PropertyAccess/Exception/InvalidTypeException.php +++ b/src/Symfony/Component/PropertyAccess/Exception/InvalidTypeException.php @@ -22,7 +22,7 @@ public function __construct( public readonly string $expectedType, public readonly string $actualType, public readonly string $propertyPath, - \Throwable $previous = null, + ?\Throwable $previous = null, ) { parent::__construct( sprintf('Expected argument of type "%s", "%s" given at property path "%s".', $expectedType, 'NULL' === $actualType ? 'null' : $actualType, $propertyPath), diff --git a/src/Symfony/Component/PropertyAccess/PropertyAccessor.php b/src/Symfony/Component/PropertyAccess/PropertyAccessor.php index 36b05040693dc..a9acc9c1710e9 100644 --- a/src/Symfony/Component/PropertyAccess/PropertyAccessor.php +++ b/src/Symfony/Component/PropertyAccess/PropertyAccessor.php @@ -79,7 +79,7 @@ class PropertyAccessor implements PropertyAccessorInterface * @param int $throw A bitwise combination of the THROW_* constants * to specify when exceptions should be thrown */ - public function __construct(int $magicMethods = self::MAGIC_GET | self::MAGIC_SET, int $throw = self::THROW_ON_INVALID_PROPERTY_PATH, CacheItemPoolInterface $cacheItemPool = null, PropertyReadInfoExtractorInterface $readInfoExtractor = null, PropertyWriteInfoExtractorInterface $writeInfoExtractor = null) + public function __construct(int $magicMethods = self::MAGIC_GET | self::MAGIC_SET, int $throw = self::THROW_ON_INVALID_PROPERTY_PATH, ?CacheItemPoolInterface $cacheItemPool = null, ?PropertyReadInfoExtractorInterface $readInfoExtractor = null, ?PropertyWriteInfoExtractorInterface $writeInfoExtractor = null) { $this->magicMethodsFlags = $magicMethods; $this->ignoreInvalidIndices = 0 === ($throw & self::THROW_ON_INVALID_INDEX); @@ -184,7 +184,7 @@ public function setValue(object|array &$objectOrArray, string|PropertyPathInterf } } - private static function throwInvalidArgumentException(string $message, array $trace, int $i, string $propertyPath, \Throwable $previous = null): void + private static function throwInvalidArgumentException(string $message, array $trace, int $i, string $propertyPath, ?\Throwable $previous = null): void { if (!isset($trace[$i]['file']) || __FILE__ !== $trace[$i]['file']) { return; @@ -648,7 +648,7 @@ private function getPropertyPath(string|PropertyPath $propertyPath): PropertyPat * * @throws \LogicException When the Cache Component isn't available */ - public static function createCache(string $namespace, int $defaultLifetime, string $version, LoggerInterface $logger = null): AdapterInterface + public static function createCache(string $namespace, int $defaultLifetime, string $version, ?LoggerInterface $logger = null): AdapterInterface { if (!class_exists(ApcuAdapter::class)) { throw new \LogicException(sprintf('The Symfony Cache component must be installed to use "%s()".', __METHOD__)); diff --git a/src/Symfony/Component/PropertyAccess/PropertyPathBuilder.php b/src/Symfony/Component/PropertyAccess/PropertyPathBuilder.php index bcd8b9efa84c3..2c439f049f479 100644 --- a/src/Symfony/Component/PropertyAccess/PropertyPathBuilder.php +++ b/src/Symfony/Component/PropertyAccess/PropertyPathBuilder.php @@ -21,7 +21,7 @@ class PropertyPathBuilder private array $elements = []; private array $isIndex = []; - public function __construct(PropertyPathInterface|string $path = null) + public function __construct(PropertyPathInterface|string|null $path = null) { if (null !== $path) { $this->append($path); @@ -122,7 +122,7 @@ public function replace(int $offset, int $length, PropertyPathInterface|string $ * * @throws OutOfBoundsException If the offset is invalid */ - public function replaceByIndex(int $offset, string $name = null): void + public function replaceByIndex(int $offset, ?string $name = null): void { if (!isset($this->elements[$offset])) { throw new OutOfBoundsException(sprintf('The offset "%s" is not within the property path.', $offset)); @@ -140,7 +140,7 @@ public function replaceByIndex(int $offset, string $name = null): void * * @throws OutOfBoundsException If the offset is invalid */ - public function replaceByProperty(int $offset, string $name = null): void + public function replaceByProperty(int $offset, ?string $name = null): void { if (!isset($this->elements[$offset])) { throw new OutOfBoundsException(sprintf('The offset "%s" is not within the property path.', $offset)); diff --git a/src/Symfony/Component/PropertyInfo/CHANGELOG.md b/src/Symfony/Component/PropertyInfo/CHANGELOG.md index 6e0a2ff449dec..8339418dc7b7b 100644 --- a/src/Symfony/Component/PropertyInfo/CHANGELOG.md +++ b/src/Symfony/Component/PropertyInfo/CHANGELOG.md @@ -5,6 +5,8 @@ CHANGELOG --- * Introduce `PropertyDocBlockExtractorInterface` to extract a property's doc block + * Restrict access to `PhpStanExtractor` based on visibility + * Deprecate the `Type` class, use `Symfony\Component\TypeInfo\Type` class of `symfony/type-info` component instead 6.4 --- diff --git a/src/Symfony/Component/PropertyInfo/Extractor/ConstructorArgumentTypeExtractorInterface.php b/src/Symfony/Component/PropertyInfo/Extractor/ConstructorArgumentTypeExtractorInterface.php index cbde902e98015..0e27f769b8eeb 100644 --- a/src/Symfony/Component/PropertyInfo/Extractor/ConstructorArgumentTypeExtractorInterface.php +++ b/src/Symfony/Component/PropertyInfo/Extractor/ConstructorArgumentTypeExtractorInterface.php @@ -11,7 +11,7 @@ namespace Symfony\Component\PropertyInfo\Extractor; -use Symfony\Component\PropertyInfo\Type; +use Symfony\Component\TypeInfo\Type; /** * Infers the constructor argument type. @@ -23,11 +23,9 @@ interface ConstructorArgumentTypeExtractorInterface { /** - * Gets types of an argument from constructor. + * Gets type of an argument from constructor. * - * @return Type[]|null - * - * @internal + * @param class-string $class */ - public function getTypesFromConstructor(string $class, string $property): ?array; + public function getTypeFromConstructor(string $class, string $property): ?Type; } diff --git a/src/Symfony/Component/PropertyInfo/Extractor/ConstructorExtractor.php b/src/Symfony/Component/PropertyInfo/Extractor/ConstructorExtractor.php index 18e563a71883d..2ddcd7e686536 100644 --- a/src/Symfony/Component/PropertyInfo/Extractor/ConstructorExtractor.php +++ b/src/Symfony/Component/PropertyInfo/Extractor/ConstructorExtractor.php @@ -12,6 +12,8 @@ namespace Symfony\Component\PropertyInfo\Extractor; use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface; +use Symfony\Component\PropertyInfo\Util\BackwardCompatibilityHelper; +use Symfony\Component\TypeInfo\Type; /** * Extracts the constructor argument type using ConstructorArgumentTypeExtractorInterface implementations. @@ -28,10 +30,10 @@ public function __construct( ) { } - public function getTypes(string $class, string $property, array $context = []): ?array + public function getType(string $class, string $property, array $context = []): ?Type { foreach ($this->extractors as $extractor) { - $value = $extractor->getTypesFromConstructor($class, $property); + $value = $extractor->getTypeFromConstructor($class, $property); if (null !== $value) { return $value; } @@ -39,4 +41,11 @@ public function getTypes(string $class, string $property, array $context = []): return null; } + + public function getTypes(string $class, string $property, array $context = []): ?array + { + trigger_deprecation('symfony/property-info', '7.1', 'The "%s()" method is deprecated, use "%s::getType()" instead.', __METHOD__, self::class); + + return BackwardCompatibilityHelper::convertTypeToLegacyTypes($this->getType($class, $property, $context)); + } } diff --git a/src/Symfony/Component/PropertyInfo/Extractor/PhpDocExtractor.php b/src/Symfony/Component/PropertyInfo/Extractor/PhpDocExtractor.php index 8be810e6da958..406ed6909bcb5 100644 --- a/src/Symfony/Component/PropertyInfo/Extractor/PhpDocExtractor.php +++ b/src/Symfony/Component/PropertyInfo/Extractor/PhpDocExtractor.php @@ -20,8 +20,13 @@ use Symfony\Component\PropertyInfo\PropertyDescriptionExtractorInterface; use Symfony\Component\PropertyInfo\PropertyDocBlockExtractorInterface; use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface; -use Symfony\Component\PropertyInfo\Type; +use Symfony\Component\PropertyInfo\Type as LegacyType; +use Symfony\Component\PropertyInfo\Util\BackwardCompatibilityHelper; use Symfony\Component\PropertyInfo\Util\PhpDocTypeHelper; +use Symfony\Component\TypeInfo\Exception\LogicException; +use Symfony\Component\TypeInfo\Type; +use Symfony\Component\TypeInfo\Type\ObjectType; +use Symfony\Component\TypeInfo\TypeContext\TypeContextFactory; /** * Extracts data using a PHPDoc parser. @@ -48,6 +53,7 @@ class PhpDocExtractor implements PropertyDescriptionExtractorInterface, Property private DocBlockFactoryInterface $docBlockFactory; private ContextFactory $contextFactory; + private TypeContextFactory $typeContextFactory; private PhpDocTypeHelper $phpDocTypeHelper; private array $mutatorPrefixes; private array $accessorPrefixes; @@ -58,7 +64,7 @@ class PhpDocExtractor implements PropertyDescriptionExtractorInterface, Property * @param string[]|null $accessorPrefixes * @param string[]|null $arrayMutatorPrefixes */ - public function __construct(DocBlockFactoryInterface $docBlockFactory = null, array $mutatorPrefixes = null, array $accessorPrefixes = null, array $arrayMutatorPrefixes = null) + public function __construct(?DocBlockFactoryInterface $docBlockFactory = null, ?array $mutatorPrefixes = null, ?array $accessorPrefixes = null, ?array $arrayMutatorPrefixes = null) { if (!class_exists(DocBlockFactory::class)) { throw new \LogicException(sprintf('Unable to use the "%s" class as the "phpdocumentor/reflection-docblock" package is not installed. Try running composer require "phpdocumentor/reflection-docblock".', __CLASS__)); @@ -66,6 +72,7 @@ public function __construct(DocBlockFactoryInterface $docBlockFactory = null, ar $this->docBlockFactory = $docBlockFactory ?: DocBlockFactory::createInstance(); $this->contextFactory = new ContextFactory(); + $this->typeContextFactory = new TypeContextFactory(); $this->phpDocTypeHelper = new PhpDocTypeHelper(); $this->mutatorPrefixes = $mutatorPrefixes ?? ReflectionExtractor::$defaultMutatorPrefixes; $this->accessorPrefixes = $accessorPrefixes ?? ReflectionExtractor::$defaultAccessorPrefixes; @@ -112,7 +119,7 @@ public function getLongDescription(string $class, string $property, array $conte return '' === $contents ? null : $contents; } - public function getTypes(string $class, string $property, array $context = []): ?array + public function getType(string $class, string $property, array $context = []): ?Type { /** @var $docBlock DocBlock */ [$docBlock, $source, $prefix] = $this->findDocBlock($class, $property); @@ -126,46 +133,59 @@ public function getTypes(string $class, string $property, array $context = []): self::MUTATOR => 'param', }; - $parentClass = null; $types = []; + $typeContext = $this->typeContextFactory->createFromClassName($class); + /** @var DocBlock\Tags\Var_|DocBlock\Tags\Return_|DocBlock\Tags\Param $tag */ foreach ($docBlock->getTagsByName($tag) as $tag) { - if ($tag && !$tag instanceof InvalidTag && null !== $tag->getType()) { - foreach ($this->phpDocTypeHelper->getTypes($tag->getType()) as $type) { - switch ($type->getClassName()) { - case 'self': - case 'static': - $resolvedClass = $class; - break; - - case 'parent': - if (false !== $resolvedClass = $parentClass ??= get_parent_class($class)) { - break; - } - // no break - - default: - $types[] = $type; - continue 2; - } - - $types[] = new Type(Type::BUILTIN_TYPE_OBJECT, $type->isNullable(), $resolvedClass, $type->isCollection(), $type->getCollectionKeyTypes(), $type->getCollectionValueTypes()); + if ($tag instanceof InvalidTag || !$tagType = $tag->getType()) { + continue; + } + + $type = $this->phpDocTypeHelper->getType($tagType); + + if (!$type instanceof ObjectType) { + $types[] = $type; + + continue; + } + + $normalizedClassName = match ($type->getClassName()) { + 'self' => $typeContext->getDeclaringClass(), + 'static' => $typeContext->getCalledClass(), + default => $type->getClassName(), + }; + + if ('parent' === $normalizedClassName) { + try { + $normalizedClassName = $typeContext->getParentClass(); + } catch (LogicException) { + // if there is no parent for the current class, we keep the "parent" raw string } } + + $types[] = $type->isNullable() ? Type::nullable(Type::object($normalizedClassName)) : Type::object($normalizedClassName); } - if (!isset($types[0])) { + if (null === ($type = $types[0] ?? null)) { return null; } if (!\in_array($prefix, $this->arrayMutatorPrefixes, true)) { - return $types; + return $type; } - return [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), $types[0])]; + return Type::list($type); } - public function getTypesFromConstructor(string $class, string $property): ?array + public function getTypes(string $class, string $property, array $context = []): ?array + { + trigger_deprecation('symfony/property-info', '7.1', 'The "%s()" method is deprecated, use "%s::getType()" instead.', __METHOD__, self::class); + + return BackwardCompatibilityHelper::convertTypeToLegacyTypes($this->getType($class, $property, $context)); + } + + public function getTypeFromConstructor(string $class, string $property): ?Type { $docBlock = $this->getDocBlockFromConstructor($class, $property); @@ -176,16 +196,26 @@ public function getTypesFromConstructor(string $class, string $property): ?array $types = []; /** @var DocBlock\Tags\Var_|DocBlock\Tags\Return_|DocBlock\Tags\Param $tag */ foreach ($docBlock->getTagsByName('param') as $tag) { - if ($tag && null !== $tag->getType()) { - $types[] = $this->phpDocTypeHelper->getTypes($tag->getType()); + if ($tag instanceof InvalidTag || !$tagType = $tag->getType()) { + continue; } - } - if (!isset($types[0]) || [] === $types[0]) { - return null; + $types[] = $this->phpDocTypeHelper->getType($tagType); } - return array_merge([], ...$types); + return $types[0] ?? null; + } + + /** + * @deprecated since Symfony 7.1, use "getTypeFromConstructor" instead. + * + * @return LegacyType[]|null + */ + public function getTypesFromConstructor(string $class, string $property): ?array + { + trigger_deprecation('symfony/property-info', '7.1', 'The "%s()" method is deprecated, use "%s::getTypeFromConstructor()" instead.', __METHOD__, self::class); + + return BackwardCompatibilityHelper::convertTypeToLegacyTypes($this->getTypeFromConstructor($class, $property)); } public function getDocBlock(string $class, string $property): ?DocBlock diff --git a/src/Symfony/Component/PropertyInfo/Extractor/PhpStanExtractor.php b/src/Symfony/Component/PropertyInfo/Extractor/PhpStanExtractor.php index ba28943583129..3e81308126f62 100644 --- a/src/Symfony/Component/PropertyInfo/Extractor/PhpStanExtractor.php +++ b/src/Symfony/Component/PropertyInfo/Extractor/PhpStanExtractor.php @@ -21,10 +21,14 @@ use PHPStan\PhpDocParser\Parser\PhpDocParser; use PHPStan\PhpDocParser\Parser\TokenIterator; use PHPStan\PhpDocParser\Parser\TypeParser; -use Symfony\Component\PropertyInfo\PhpStan\NameScopeFactory; use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface; -use Symfony\Component\PropertyInfo\Type; -use Symfony\Component\PropertyInfo\Util\PhpStanTypeHelper; +use Symfony\Component\PropertyInfo\Type as LegacyType; +use Symfony\Component\PropertyInfo\Util\BackwardCompatibilityHelper; +use Symfony\Component\TypeInfo\Exception\LogicException; +use Symfony\Component\TypeInfo\Exception\UnsupportedException; +use Symfony\Component\TypeInfo\Type; +use Symfony\Component\TypeInfo\TypeContext\TypeContextFactory; +use Symfony\Component\TypeInfo\TypeResolver\StringTypeResolver; /** * Extracts data using PHPStan parser. @@ -39,11 +43,12 @@ final class PhpStanExtractor implements PropertyTypeExtractorInterface, Construc private PhpDocParser $phpDocParser; private Lexer $lexer; - private NameScopeFactory $nameScopeFactory; + + private StringTypeResolver $stringTypeResolver; + private TypeContextFactory $typeContextFactory; /** @var array */ private array $docBlocks = []; - private PhpStanTypeHelper $phpStanTypeHelper; private array $mutatorPrefixes; private array $accessorPrefixes; private array $arrayMutatorPrefixes; @@ -53,7 +58,7 @@ final class PhpStanExtractor implements PropertyTypeExtractorInterface, Construc * @param list|null $accessorPrefixes * @param list|null $arrayMutatorPrefixes */ - public function __construct(array $mutatorPrefixes = null, array $accessorPrefixes = null, array $arrayMutatorPrefixes = null) + public function __construct(?array $mutatorPrefixes = null, ?array $accessorPrefixes = null, ?array $arrayMutatorPrefixes = null, private bool $allowPrivateAccess = true) { if (!class_exists(ContextFactory::class)) { throw new \LogicException(sprintf('Unable to use the "%s" class as the "phpdocumentor/type-resolver" package is not installed. Try running composer require "phpdocumentor/type-resolver".', __CLASS__)); @@ -63,103 +68,105 @@ public function __construct(array $mutatorPrefixes = null, array $accessorPrefix throw new \LogicException(sprintf('Unable to use the "%s" class as the "phpstan/phpdoc-parser" package is not installed. Try running composer require "phpstan/phpdoc-parser".', __CLASS__)); } - $this->phpStanTypeHelper = new PhpStanTypeHelper(); $this->mutatorPrefixes = $mutatorPrefixes ?? ReflectionExtractor::$defaultMutatorPrefixes; $this->accessorPrefixes = $accessorPrefixes ?? ReflectionExtractor::$defaultAccessorPrefixes; $this->arrayMutatorPrefixes = $arrayMutatorPrefixes ?? ReflectionExtractor::$defaultArrayMutatorPrefixes; $this->phpDocParser = new PhpDocParser(new TypeParser(new ConstExprParser()), new ConstExprParser()); $this->lexer = new Lexer(); - $this->nameScopeFactory = new NameScopeFactory(); + $this->stringTypeResolver = new StringTypeResolver(); + $this->typeContextFactory = new TypeContextFactory($this->stringTypeResolver); } - public function getTypes(string $class, string $property, array $context = []): ?array + public function getType(string $class, string $property, array $context = []): ?Type { + $backwardCompatible = \func_get_args()[3] ?? false; + /** @var PhpDocNode|null $docNode */ [$docNode, $source, $prefix, $declaringClass] = $this->getDocBlock($class, $property); - $nameScope = $this->nameScopeFactory->create($class, $declaringClass); + if (null === $docNode) { return null; } - switch ($source) { - case self::PROPERTY: - $tag = '@var'; - break; + $typeContext = $this->typeContextFactory->createFromClassName($class, $declaringClass); - case self::ACCESSOR: - $tag = '@return'; - break; + $tag = match ($source) { + self::PROPERTY => '@var', + self::ACCESSOR => '@return', + self::MUTATOR => '@param', + default => null, + }; - case self::MUTATOR: - $tag = '@param'; - break; - } - - $parentClass = null; $types = []; + foreach ($docNode->getTagsByName($tag) as $tagDocNode) { if ($tagDocNode->value instanceof InvalidTagValueNode) { continue; } - if ( - $tagDocNode->value instanceof ParamTagValueNode - && null === $prefix - && $tagDocNode->value->parameterName !== '$'.$property - ) { + if ($tagDocNode->value instanceof ParamTagValueNode && null === $prefix && $tagDocNode->value->parameterName !== '$'.$property) { continue; } - foreach ($this->phpStanTypeHelper->getTypes($tagDocNode->value, $nameScope) as $type) { - switch ($type->getClassName()) { - case 'self': - case 'static': - $resolvedClass = $class; - break; - - case 'parent': - if (false !== $resolvedClass = $parentClass ??= get_parent_class($class)) { - break; - } - // no break - - default: - $types[] = $type; - continue 2; + try { + $types[] = $this->stringTypeResolver->resolve((string) $tagDocNode->value->type, $typeContext, $backwardCompatible); + } catch (UnsupportedException) { + // BC layer to handle "void" type in "getTypes" + if ('void' === (string) $tagDocNode->value->type && $backwardCompatible) { + return Type::void(); + } + } catch (LogicException $e) { + // BC layer to handle "parent" type without existing parent + if ('parent' === (string) $tagDocNode->value->type && $backwardCompatible) { + return Type::object('parent'); } - $types[] = new Type(Type::BUILTIN_TYPE_OBJECT, $type->isNullable(), $resolvedClass, $type->isCollection(), $type->getCollectionKeyTypes(), $type->getCollectionValueTypes()); + throw $e; } } - if (!isset($types[0])) { + if (null === ($type = $types[0] ?? null)) { return null; } - if (!\in_array($prefix, $this->arrayMutatorPrefixes, true)) { - return $types; + if (!\in_array($prefix, $this->arrayMutatorPrefixes)) { + return $type; } - return [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), $types[0])]; + return Type::list($type); } - public function getTypesFromConstructor(string $class, string $property): ?array + public function getTypes(string $class, string $property, array $context = []): ?array + { + trigger_deprecation('symfony/property-info', '7.1', 'The "%s()" method is deprecated, use "%s::getType()" instead.', __METHOD__, self::class); + + return BackwardCompatibilityHelper::convertTypeToLegacyTypes($this->getType($class, $property, $context, true)); + } + + public function getTypeFromConstructor(string $class, string $property): ?Type { + $backwardCompatible = \func_get_args()[2] ?? false; + if (null === $tagDocNode = $this->getDocBlockFromConstructor($class, $property)) { return null; } - $types = []; - foreach ($this->phpStanTypeHelper->getTypes($tagDocNode, $this->nameScopeFactory->create($class)) as $type) { - $types[] = $type; - } + $typeContext = $this->typeContextFactory->createFromClassName($class); - if (!isset($types[0])) { - return null; - } + return $this->stringTypeResolver->resolve((string) $tagDocNode->type, $typeContext, $backwardCompatible); + } + + /** + * @deprecated since Symfony 7.1, use "getTypeFromConstructor" instead. + * + * @return LegacyType[]|null + */ + public function getTypesFromConstructor(string $class, string $property): ?array + { + trigger_deprecation('symfony/property-info', '7.1', 'The "%s()" method is deprecated, use "%s::getTypeFromConstructor()" instead.', __METHOD__, self::class); - return $types; + return BackwardCompatibilityHelper::convertTypeToLegacyTypes($this->getTypeFromConstructor($class, $property, true)); } private function getDocBlockFromConstructor(string $class, string $property): ?ParamTagValueNode @@ -232,6 +239,10 @@ private function getDocBlockFromProperty(string $class, string $property): ?arra return null; } + if (!$this->canAccessMemberBasedOnItsVisibility($reflectionProperty)) { + return null; + } + // Type can be inside property docblock as `@var` $rawDocNode = $reflectionProperty->getDocComment(); $phpDocNode = $rawDocNode ? $this->getPhpDocNode($rawDocNode) : null; @@ -274,8 +285,11 @@ private function getDocBlockFromMethod(string $class, string $ucFirstProperty, i } if ( - (self::ACCESSOR === $type && 0 === $reflectionMethod->getNumberOfRequiredParameters()) - || (self::MUTATOR === $type && $reflectionMethod->getNumberOfParameters() >= 1) + ( + (self::ACCESSOR === $type && 0 === $reflectionMethod->getNumberOfRequiredParameters()) + || (self::MUTATOR === $type && $reflectionMethod->getNumberOfParameters() >= 1) + ) + && $this->canAccessMemberBasedOnItsVisibility($reflectionMethod) ) { break; } @@ -305,4 +319,9 @@ private function getPhpDocNode(string $rawDocNode): PhpDocNode return $phpDocNode; } + + private function canAccessMemberBasedOnItsVisibility(\ReflectionProperty|\ReflectionMethod $member): bool + { + return $this->allowPrivateAccess || $member->isPublic(); + } } diff --git a/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php b/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php index acea8660912a0..5a4d8195987a3 100644 --- a/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php +++ b/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php @@ -19,9 +19,16 @@ use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface; use Symfony\Component\PropertyInfo\PropertyWriteInfo; use Symfony\Component\PropertyInfo\PropertyWriteInfoExtractorInterface; -use Symfony\Component\PropertyInfo\Type; +use Symfony\Component\PropertyInfo\Type as LegacyType; +use Symfony\Component\PropertyInfo\Util\BackwardCompatibilityHelper; use Symfony\Component\String\Inflector\EnglishInflector; use Symfony\Component\String\Inflector\InflectorInterface; +use Symfony\Component\TypeInfo\Exception\UnsupportedException; +use Symfony\Component\TypeInfo\Type; +use Symfony\Component\TypeInfo\Type\CollectionType; +use Symfony\Component\TypeInfo\TypeIdentifier; +use Symfony\Component\TypeInfo\TypeResolver\TypeResolver; +use Symfony\Component\TypeInfo\TypeResolver\TypeResolverInterface; /** * Extracts data using the reflection API. @@ -61,9 +68,9 @@ class ReflectionExtractor implements PropertyListExtractorInterface, PropertyTyp public const ALLOW_MAGIC_CALL = 1 << 2; private const MAP_TYPES = [ - 'integer' => Type::BUILTIN_TYPE_INT, - 'boolean' => Type::BUILTIN_TYPE_BOOL, - 'double' => Type::BUILTIN_TYPE_FLOAT, + 'integer' => TypeIdentifier::INT->value, + 'boolean' => TypeIdentifier::BOOL->value, + 'double' => TypeIdentifier::FLOAT->value, ]; private array $mutatorPrefixes; @@ -76,13 +83,14 @@ class ReflectionExtractor implements PropertyListExtractorInterface, PropertyTyp private InflectorInterface $inflector; private array $arrayMutatorPrefixesFirst; private array $arrayMutatorPrefixesLast; + private TypeResolverInterface $typeResolver; /** * @param string[]|null $mutatorPrefixes * @param string[]|null $accessorPrefixes * @param string[]|null $arrayMutatorPrefixes */ - public function __construct(array $mutatorPrefixes = null, array $accessorPrefixes = null, array $arrayMutatorPrefixes = null, bool $enableConstructorExtraction = true, int $accessFlags = self::ALLOW_PUBLIC, InflectorInterface $inflector = null, int $magicMethodsFlags = self::ALLOW_MAGIC_GET | self::ALLOW_MAGIC_SET) + public function __construct(?array $mutatorPrefixes = null, ?array $accessorPrefixes = null, ?array $arrayMutatorPrefixes = null, bool $enableConstructorExtraction = true, int $accessFlags = self::ALLOW_PUBLIC, ?InflectorInterface $inflector = null, int $magicMethodsFlags = self::ALLOW_MAGIC_GET | self::ALLOW_MAGIC_SET) { $this->mutatorPrefixes = $mutatorPrefixes ?? self::$defaultMutatorPrefixes; $this->accessorPrefixes = $accessorPrefixes ?? self::$defaultAccessorPrefixes; @@ -92,6 +100,7 @@ public function __construct(array $mutatorPrefixes = null, array $accessorPrefix $this->propertyReflectionFlags = $this->getPropertyFlags($accessFlags); $this->magicMethodsFlags = $magicMethodsFlags; $this->inflector = $inflector ?? new EnglishInflector(); + $this->typeResolver = TypeResolver::create(); $this->arrayMutatorPrefixesFirst = array_merge($this->arrayMutatorPrefixes, array_diff($this->mutatorPrefixes, $this->arrayMutatorPrefixes)); $this->arrayMutatorPrefixesLast = array_reverse($this->arrayMutatorPrefixesFirst); @@ -132,13 +141,15 @@ public function getProperties(string $class, array $context = []): ?array return $properties ? array_values($properties) : null; } - public function getTypes(string $class, string $property, array $context = []): ?array + public function getType(string $class, string $property, array $context = []): ?Type { + $backwardCompatible = \func_get_args()[3] ?? false; + if ($fromMutator = $this->extractFromMutator($class, $property)) { return $fromMutator; } - if ($fromAccessor = $this->extractFromAccessor($class, $property)) { + if ($fromAccessor = $this->extractFromAccessor($class, $property, $backwardCompatible)) { return $fromAccessor; } @@ -156,7 +167,14 @@ public function getTypes(string $class, string $property, array $context = []): return null; } - public function getTypesFromConstructor(string $class, string $property): ?array + public function getTypes(string $class, string $property, array $context = []): ?array + { + trigger_deprecation('symfony/property-info', '7.1', 'The "%s()" method is deprecated, use "%s::getType()" instead.', __METHOD__, self::class); + + return BackwardCompatibilityHelper::convertTypeToLegacyTypes($this->getType($class, $property, $context, true), keepNullType: false); + } + + public function getTypeFromConstructor(string $class, string $property): ?Type { try { $reflection = new \ReflectionClass($class); @@ -169,14 +187,23 @@ public function getTypesFromConstructor(string $class, string $property): ?array if (!$reflectionParameter = $this->getReflectionParameterFromConstructor($property, $reflectionConstructor)) { return null; } - if (!$reflectionType = $reflectionParameter->getType()) { - return null; - } - if (!$types = $this->extractFromReflectionType($reflectionType, $reflectionConstructor->getDeclaringClass())) { + try { + return $this->typeResolver->resolve($reflectionParameter); + } catch (UnsupportedException) { return null; } + } - return $types; + /** + * @deprecated since Symfony 7.1, use "getTypeFromConstructor" instead. + * + * @return LegacyType[]|null + */ + public function getTypesFromConstructor(string $class, string $property): ?array + { + trigger_deprecation('symfony/property-info', '7.1', 'The "%s()" method is deprecated, use "%s::getTypeFromConstructor()" instead.', __METHOD__, self::class); + + return BackwardCompatibilityHelper::convertTypeToLegacyTypes($this->getTypeFromConstructor($class, $property)); } private function getReflectionParameterFromConstructor(string $property, \ReflectionMethod $reflectionConstructor): ?\ReflectionParameter @@ -406,26 +433,21 @@ public function getWriteInfo(string $class, string $property, array $context = [ return $noneProperty; } - /** - * @return Type[]|null - */ - private function extractFromMutator(string $class, string $property): ?array + private function extractFromMutator(string $class, string $property): ?Type { [$reflectionMethod, $prefix] = $this->getMutatorMethod($class, $property); if (null === $reflectionMethod) { return null; } - $reflectionParameters = $reflectionMethod->getParameters(); - $reflectionParameter = $reflectionParameters[0]; - - if (!$reflectionType = $reflectionParameter->getType()) { + try { + $type = $this->typeResolver->resolve($reflectionMethod->getParameters()[0]); + } catch (UnsupportedException) { return null; } - $type = $this->extractFromReflectionType($reflectionType, $reflectionMethod->getDeclaringClass()); - if (1 === \count($type) && \in_array($prefix, $this->arrayMutatorPrefixes, true)) { - $type = [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), $type[0])]; + if (!$type instanceof CollectionType && \in_array($prefix, $this->arrayMutatorPrefixes, true)) { + $type = Type::list($type); } return $type; @@ -433,22 +455,27 @@ private function extractFromMutator(string $class, string $property): ?array /** * Tries to extract type information from accessors. - * - * @return Type[]|null */ - private function extractFromAccessor(string $class, string $property): ?array + private function extractFromAccessor(string $class, string $property): ?Type { + $backwardCompatible = \func_get_args()[2] ?? false; + [$reflectionMethod, $prefix] = $this->getAccessorMethod($class, $property); if (null === $reflectionMethod) { return null; } - if ($reflectionType = $reflectionMethod->getReturnType()) { - return $this->extractFromReflectionType($reflectionType, $reflectionMethod->getDeclaringClass()); + try { + return $this->typeResolver->resolve($reflectionMethod); + } catch (UnsupportedException) { + // BC layer to handle "void" type in "getTypes" + if ('void' === (string) $reflectionMethod->getReturnType() && $backwardCompatible) { + return Type::void(); + } } if (\in_array($prefix, ['is', 'can', 'has'])) { - return [new Type(Type::BUILTIN_TYPE_BOOL)]; + return Type::bool(); } return null; @@ -456,10 +483,8 @@ private function extractFromAccessor(string $class, string $property): ?array /** * Tries to extract type information from constructor. - * - * @return Type[]|null */ - private function extractFromConstructor(string $class, string $property): ?array + private function extractFromConstructor(string $class, string $property): ?Type { try { $reflectionClass = new \ReflectionClass($class); @@ -477,9 +502,12 @@ private function extractFromConstructor(string $class, string $property): ?array if ($property !== $parameter->name) { continue; } - $reflectionType = $parameter->getType(); - return $reflectionType ? $this->extractFromReflectionType($reflectionType, $constructor->getDeclaringClass()) : null; + try { + return $this->typeResolver->resolve($parameter); + } catch (UnsupportedException) { + return null; + } } if ($parentClass = $reflectionClass->getParentClass()) { @@ -489,16 +517,15 @@ private function extractFromConstructor(string $class, string $property): ?array return null; } - private function extractFromPropertyDeclaration(string $class, string $property): ?array + private function extractFromPropertyDeclaration(string $class, string $property): ?Type { try { $reflectionClass = new \ReflectionClass($class); - $reflectionProperty = $reflectionClass->getProperty($property); - $reflectionPropertyType = $reflectionProperty->getType(); - if (null !== $reflectionPropertyType && $types = $this->extractFromReflectionType($reflectionPropertyType, $reflectionProperty->getDeclaringClass())) { - return $types; + try { + return $this->typeResolver->resolve($reflectionProperty); + } catch (UnsupportedException) { } } catch (\ReflectionException) { return null; @@ -510,52 +537,14 @@ private function extractFromPropertyDeclaration(string $class, string $property) return null; } - $type = \gettype($defaultValue); - $type = static::MAP_TYPES[$type] ?? $type; - - return [new Type($type, $this->isNullableProperty($class, $property), null, Type::BUILTIN_TYPE_ARRAY === $type)]; - } - - private function extractFromReflectionType(\ReflectionType $reflectionType, \ReflectionClass $declaringClass): array - { - $types = []; - $nullable = $reflectionType->allowsNull(); - - foreach (($reflectionType instanceof \ReflectionUnionType || $reflectionType instanceof \ReflectionIntersectionType) ? $reflectionType->getTypes() : [$reflectionType] as $type) { - if (!$type instanceof \ReflectionNamedType) { - // Nested composite types are not supported yet. - return []; - } + $typeIdentifier = TypeIdentifier::from(static::MAP_TYPES[\gettype($defaultValue)] ?? \gettype($defaultValue)); + $type = 'array' === $typeIdentifier->value ? Type::array() : Type::builtin($typeIdentifier); - $phpTypeOrClass = $type->getName(); - if ('null' === $phpTypeOrClass || 'mixed' === $phpTypeOrClass || 'never' === $phpTypeOrClass) { - continue; - } - - if (Type::BUILTIN_TYPE_ARRAY === $phpTypeOrClass) { - $types[] = new Type(Type::BUILTIN_TYPE_ARRAY, $nullable, null, true); - } elseif ('void' === $phpTypeOrClass) { - $types[] = new Type(Type::BUILTIN_TYPE_NULL, $nullable); - } elseif ($type->isBuiltin()) { - $types[] = new Type($phpTypeOrClass, $nullable); - } else { - $types[] = new Type(Type::BUILTIN_TYPE_OBJECT, $nullable, $this->resolveTypeName($phpTypeOrClass, $declaringClass)); - } - } - - return $types; - } - - private function resolveTypeName(string $name, \ReflectionClass $declaringClass): string - { - if ('self' === $lcName = strtolower($name)) { - return $declaringClass->name; - } - if ('parent' === $lcName && $parent = $declaringClass->getParentClass()) { - return $parent->name; + if ($this->isNullableProperty($class, $property)) { + $type = Type::nullable($type); } - return $name; + return $type; } private function isNullableProperty(string $class, string $property): bool diff --git a/src/Symfony/Component/PropertyInfo/Extractor/SerializerExtractor.php b/src/Symfony/Component/PropertyInfo/Extractor/SerializerExtractor.php index 7ef020cefef23..620032f5b161b 100644 --- a/src/Symfony/Component/PropertyInfo/Extractor/SerializerExtractor.php +++ b/src/Symfony/Component/PropertyInfo/Extractor/SerializerExtractor.php @@ -38,11 +38,15 @@ public function getProperties(string $class, array $context = []): ?array return null; } + $groups = $context['serializer_groups'] ?? []; + $groupsHasBeenDefined = [] !== $groups; + $groups = array_merge($groups, ['Default', (false !== $nsSep = strrpos($class, '\\')) ? substr($class, $nsSep + 1) : $class]); + $properties = []; $serializerClassMetadata = $this->classMetadataFactory->getMetadataFor($class); foreach ($serializerClassMetadata->getAttributesMetadata() as $serializerAttributeMetadata) { - if (!$serializerAttributeMetadata->isIgnored() && (null === $context['serializer_groups'] || array_intersect($context['serializer_groups'], $serializerAttributeMetadata->getGroups()))) { + if (!$serializerAttributeMetadata->isIgnored() && (!$groupsHasBeenDefined || array_intersect(array_merge($serializerAttributeMetadata->getGroups(), ['*']), $groups))) { $properties[] = $serializerAttributeMetadata->getName(); } } diff --git a/src/Symfony/Component/PropertyInfo/PhpStan/NameScope.php b/src/Symfony/Component/PropertyInfo/PhpStan/NameScope.php deleted file mode 100644 index e4e713c4480d3..0000000000000 --- a/src/Symfony/Component/PropertyInfo/PhpStan/NameScope.php +++ /dev/null @@ -1,61 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\PropertyInfo\PhpStan; - -/** - * NameScope class adapted from PHPStan code. - * - * @copyright Copyright (c) 2016, PHPStan https://github.com/phpstan/phpstan-src - * @copyright Copyright (c) 2016, Ondřej Mirtes - * @author Baptiste Leduc - * - * @internal - */ -final class NameScope -{ - private string $calledClassName; - private string $namespace; - /** @var array alias(string) => fullName(string) */ - private array $uses; - - public function __construct(string $calledClassName, string $namespace, array $uses = []) - { - $this->calledClassName = $calledClassName; - $this->namespace = $namespace; - $this->uses = $uses; - } - - public function resolveStringName(string $name): string - { - if (str_starts_with($name, '\\')) { - return ltrim($name, '\\'); - } - - $nameParts = explode('\\', $name); - $firstNamePart = $nameParts[0]; - if (isset($this->uses[$firstNamePart])) { - if (1 === \count($nameParts)) { - return $this->uses[$firstNamePart]; - } - array_shift($nameParts); - - return sprintf('%s\\%s', $this->uses[$firstNamePart], implode('\\', $nameParts)); - } - - return sprintf('%s\\%s', $this->namespace, $name); - } - - public function resolveRootClass(): string - { - return $this->resolveStringName($this->calledClassName); - } -} diff --git a/src/Symfony/Component/PropertyInfo/PhpStan/NameScopeFactory.php b/src/Symfony/Component/PropertyInfo/PhpStan/NameScopeFactory.php deleted file mode 100644 index 9c3e0a3fd5eaf..0000000000000 --- a/src/Symfony/Component/PropertyInfo/PhpStan/NameScopeFactory.php +++ /dev/null @@ -1,70 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\PropertyInfo\PhpStan; - -use phpDocumentor\Reflection\Types\ContextFactory; - -/** - * @author Baptiste Leduc - * - * @internal - */ -final class NameScopeFactory -{ - public function create(string $calledClassName, string $declaringClassName = null): NameScope - { - $declaringClassName ??= $calledClassName; - - $path = explode('\\', $calledClassName); - $calledClassName = array_pop($path); - - $declaringReflection = new \ReflectionClass($declaringClassName); - [$declaringNamespace, $declaringUses] = $this->extractFromFullClassName($declaringReflection); - $declaringUses = array_merge($declaringUses, $this->collectUses($declaringReflection)); - - return new NameScope($calledClassName, $declaringNamespace, $declaringUses); - } - - private function collectUses(\ReflectionClass $reflection): array - { - $uses = [$this->extractFromFullClassName($reflection)[1]]; - - foreach ($reflection->getTraits() as $traitReflection) { - $uses[] = $this->extractFromFullClassName($traitReflection)[1]; - } - - if (false !== $parentClass = $reflection->getParentClass()) { - $uses[] = $this->collectUses($parentClass); - } - - return $uses ? array_merge(...$uses) : []; - } - - private function extractFromFullClassName(\ReflectionClass $reflection): array - { - $namespace = trim($reflection->getNamespaceName(), '\\'); - $fileName = $reflection->getFileName(); - - if (\is_string($fileName) && is_file($fileName)) { - if (false === $contents = file_get_contents($fileName)) { - throw new \RuntimeException(sprintf('Unable to read file "%s".', $fileName)); - } - - $factory = new ContextFactory(); - $context = $factory->createForNamespace($namespace, $contents); - - return [$namespace, $context->getNamespaceAliases()]; - } - - return [$namespace, []]; - } -} diff --git a/src/Symfony/Component/PropertyInfo/PropertyInfoCacheExtractor.php b/src/Symfony/Component/PropertyInfo/PropertyInfoCacheExtractor.php index b4543eace7d6e..2ea447deed41c 100644 --- a/src/Symfony/Component/PropertyInfo/PropertyInfoCacheExtractor.php +++ b/src/Symfony/Component/PropertyInfo/PropertyInfoCacheExtractor.php @@ -12,6 +12,7 @@ namespace Symfony\Component\PropertyInfo; use Psr\Cache\CacheItemPoolInterface; +use Symfony\Component\TypeInfo\Type; /** * Adds a PSR-6 cache layer on top of an extractor. @@ -55,6 +56,11 @@ public function getProperties(string $class, array $context = []): ?array return $this->extract('getProperties', [$class, $context]); } + public function getType(string $class, string $property, array $context = []): ?Type + { + return $this->extract('getType', [$class, $property, $context]); + } + public function getTypes(string $class, string $property, array $context = []): ?array { return $this->extract('getTypes', [$class, $property, $context]); diff --git a/src/Symfony/Component/PropertyInfo/PropertyInfoExtractor.php b/src/Symfony/Component/PropertyInfo/PropertyInfoExtractor.php index 7416849a0a843..5e1c6a2a71e63 100644 --- a/src/Symfony/Component/PropertyInfo/PropertyInfoExtractor.php +++ b/src/Symfony/Component/PropertyInfo/PropertyInfoExtractor.php @@ -11,6 +11,8 @@ namespace Symfony\Component\PropertyInfo; +use Symfony\Component\TypeInfo\Type; + /** * Default {@see PropertyInfoExtractorInterface} implementation. * @@ -51,6 +53,11 @@ public function getLongDescription(string $class, string $property, array $conte return $this->extract($this->descriptionExtractors, 'getLongDescription', [$class, $property, $context]); } + public function getType(string $class, string $property, array $context = []): ?Type + { + return $this->extract($this->typeExtractors, 'getType', [$class, $property, $context]); + } + public function getTypes(string $class, string $property, array $context = []): ?array { return $this->extract($this->typeExtractors, 'getTypes', [$class, $property, $context]); diff --git a/src/Symfony/Component/PropertyInfo/PropertyTypeExtractorInterface.php b/src/Symfony/Component/PropertyInfo/PropertyTypeExtractorInterface.php index 16e9765b1d5b9..6c261df117687 100644 --- a/src/Symfony/Component/PropertyInfo/PropertyTypeExtractorInterface.php +++ b/src/Symfony/Component/PropertyInfo/PropertyTypeExtractorInterface.php @@ -11,17 +11,24 @@ namespace Symfony\Component\PropertyInfo; +use Symfony\Component\PropertyInfo\Type as LegacyType; +use Symfony\Component\TypeInfo\Type; + /** * Type Extractor Interface. * * @author Kévin Dunglas + * + * @method Type|null getType(string $class, string $property, array $context = []) */ interface PropertyTypeExtractorInterface { /** * Gets types of a property. * - * @return Type[]|null + * @deprecated since Symfony 7.1, use "getType" instead. + * + * @return LegacyType[]|null */ public function getTypes(string $class, string $property, array $context = []): ?array; } diff --git a/src/Symfony/Component/PropertyInfo/Tests/AbstractPropertyInfoExtractorTest.php b/src/Symfony/Component/PropertyInfo/Tests/AbstractPropertyInfoExtractorTest.php index d53172ef1504f..6f5c67131124e 100644 --- a/src/Symfony/Component/PropertyInfo/Tests/AbstractPropertyInfoExtractorTest.php +++ b/src/Symfony/Component/PropertyInfo/Tests/AbstractPropertyInfoExtractorTest.php @@ -20,7 +20,8 @@ use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface; use Symfony\Component\PropertyInfo\Tests\Fixtures\DummyExtractor; use Symfony\Component\PropertyInfo\Tests\Fixtures\NullExtractor; -use Symfony\Component\PropertyInfo\Type; +use Symfony\Component\PropertyInfo\Type as LegacyType; +use Symfony\Component\TypeInfo\Type; /** * @author Kévin Dunglas @@ -54,9 +55,17 @@ public function testGetLongDescription() $this->assertSame('long', $this->propertyInfo->getLongDescription('Foo', 'bar', [])); } + public function testGetType() + { + $this->assertEquals(Type::int(), $this->propertyInfo->getType('Foo', 'bar', [])); + } + + /** + * @group legacy + */ public function testGetTypes() { - $this->assertEquals([new Type(Type::BUILTIN_TYPE_INT)], $this->propertyInfo->getTypes('Foo', 'bar', [])); + $this->assertEquals([new LegacyType(LegacyType::BUILTIN_TYPE_INT)], $this->propertyInfo->getTypes('Foo', 'bar', [])); } public function testIsReadable() diff --git a/src/Symfony/Component/PropertyInfo/Tests/Extractor/ConstructorExtractorTest.php b/src/Symfony/Component/PropertyInfo/Tests/Extractor/ConstructorExtractorTest.php index 6bd8318ed6229..fcfa7bb86a078 100644 --- a/src/Symfony/Component/PropertyInfo/Tests/Extractor/ConstructorExtractorTest.php +++ b/src/Symfony/Component/PropertyInfo/Tests/Extractor/ConstructorExtractorTest.php @@ -14,7 +14,8 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\PropertyInfo\Extractor\ConstructorExtractor; use Symfony\Component\PropertyInfo\Tests\Fixtures\DummyExtractor; -use Symfony\Component\PropertyInfo\Type; +use Symfony\Component\PropertyInfo\Type as LegacyType; +use Symfony\Component\TypeInfo\Type; /** * @author Dmitrii Poddubnyi @@ -33,11 +34,28 @@ public function testInstanceOf() $this->assertInstanceOf(\Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface::class, $this->extractor); } + public function testGetType() + { + $this->assertEquals(Type::string(), $this->extractor->getType('Foo', 'bar', [])); + } + + public function testGetTypeIfNoExtractors() + { + $extractor = new ConstructorExtractor([]); + $this->assertNull($extractor->getType('Foo', 'bar', [])); + } + + /** + * @group legacy + */ public function testGetTypes() { - $this->assertEquals([new Type(Type::BUILTIN_TYPE_STRING)], $this->extractor->getTypes('Foo', 'bar', [])); + $this->assertEquals([new LegacyType(LegacyType::BUILTIN_TYPE_STRING)], $this->extractor->getTypes('Foo', 'bar', [])); } + /** + * @group legacy + */ public function testGetTypesIfNoExtractors() { $extractor = new ConstructorExtractor([]); diff --git a/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpDocExtractorTest.php b/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpDocExtractorTest.php index db39180b0a899..3a28ef8b5e2c5 100644 --- a/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpDocExtractorTest.php +++ b/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpDocExtractorTest.php @@ -14,13 +14,18 @@ use phpDocumentor\Reflection\DocBlock; use PHPUnit\Framework\TestCase; use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor; +use Symfony\Component\PropertyInfo\Tests\Fixtures\ConstructorDummy; +use Symfony\Component\PropertyInfo\Tests\Fixtures\DockBlockFallback; use Symfony\Component\PropertyInfo\Tests\Fixtures\Dummy; +use Symfony\Component\PropertyInfo\Tests\Fixtures\InvalidDummy; use Symfony\Component\PropertyInfo\Tests\Fixtures\ParentDummy; use Symfony\Component\PropertyInfo\Tests\Fixtures\Php80Dummy; use Symfony\Component\PropertyInfo\Tests\Fixtures\PseudoTypeDummy; +use Symfony\Component\PropertyInfo\Tests\Fixtures\PseudoTypesDummy; use Symfony\Component\PropertyInfo\Tests\Fixtures\TraitUsage\DummyUsedInTrait; use Symfony\Component\PropertyInfo\Tests\Fixtures\TraitUsage\DummyUsingTrait; -use Symfony\Component\PropertyInfo\Type; +use Symfony\Component\PropertyInfo\Type as LegacyType; +use Symfony\Component\TypeInfo\Type; /** * @author Kévin Dunglas @@ -35,9 +40,11 @@ protected function setUp(): void } /** - * @dataProvider typesProvider + * @group legacy + * + * @dataProvider provideLegacyTypes */ - public function testExtract($property, ?array $type, $shortDescription, $longDescription) + public function testExtractLegacy($property, ?array $type, $shortDescription, $longDescription) { $this->assertEquals($type, $this->extractor->getTypes(Dummy::class, $property)); $this->assertSame($shortDescription, $this->extractor->getShortDescription(Dummy::class, $property)); @@ -57,12 +64,15 @@ public function testGetDocBlock() $this->assertNull($docBlock); } - public function testParamTagTypeIsOmitted() + /** + * @group legacy + */ + public function testParamTagTypeIsOmittedLegacy() { $this->assertNull($this->extractor->getTypes(OmittedParamTagTypeDocBlock::class, 'omittedType')); } - public static function invalidTypesProvider() + public static function provideLegacyInvalidTypes() { return [ 'pub' => ['pub', null, null], @@ -73,9 +83,11 @@ public static function invalidTypesProvider() } /** - * @dataProvider invalidTypesProvider + * @group legacy + * + * @dataProvider provideLegacyInvalidTypes */ - public function testInvalid($property, $shortDescription, $longDescription) + public function testInvalidLegacy($property, $shortDescription, $longDescription) { $this->assertNull($this->extractor->getTypes('Symfony\Component\PropertyInfo\Tests\Fixtures\InvalidDummy', $property)); $this->assertSame($shortDescription, $this->extractor->getShortDescription('Symfony\Component\PropertyInfo\Tests\Fixtures\InvalidDummy', $property)); @@ -83,119 +95,123 @@ public function testInvalid($property, $shortDescription, $longDescription) } /** - * @dataProvider typesWithNoPrefixesProvider + * @group legacy + * + * @dataProvider provideLegacyTypesWithNoPrefixes */ - public function testExtractTypesWithNoPrefixes($property, array $type = null) + public function testExtractTypesWithNoPrefixesLegacy($property, array $type = null) { $noPrefixExtractor = new PhpDocExtractor(null, [], [], []); $this->assertEquals($type, $noPrefixExtractor->getTypes(Dummy::class, $property)); } - public static function typesProvider() + public static function provideLegacyTypes() { return [ ['foo', null, 'Short description.', 'Long description.'], - ['bar', [new Type(Type::BUILTIN_TYPE_STRING)], 'This is bar', null], - ['baz', [new Type(Type::BUILTIN_TYPE_INT)], 'Should be used.', null], - ['foo2', [new Type(Type::BUILTIN_TYPE_FLOAT)], null, null], - ['foo3', [new Type(Type::BUILTIN_TYPE_CALLABLE)], null, null], - ['foo4', [new Type(Type::BUILTIN_TYPE_NULL)], null, null], + ['bar', [new LegacyType(LegacyType::BUILTIN_TYPE_STRING)], 'This is bar', null], + ['baz', [new LegacyType(LegacyType::BUILTIN_TYPE_INT)], 'Should be used.', null], + ['foo2', [new LegacyType(LegacyType::BUILTIN_TYPE_FLOAT)], null, null], + ['foo3', [new LegacyType(LegacyType::BUILTIN_TYPE_CALLABLE)], null, null], + ['foo4', [new LegacyType(LegacyType::BUILTIN_TYPE_NULL)], null, null], ['foo5', null, null, null], [ 'files', [ - new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_OBJECT, false, 'SplFileInfo')), - new Type(Type::BUILTIN_TYPE_RESOURCE), + new LegacyType(LegacyType::BUILTIN_TYPE_ARRAY, false, null, true, new LegacyType(LegacyType::BUILTIN_TYPE_INT), new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, 'SplFileInfo')), + new LegacyType(LegacyType::BUILTIN_TYPE_RESOURCE), ], null, null, ], - ['bal', [new Type(Type::BUILTIN_TYPE_OBJECT, false, 'DateTimeImmutable')], null, null], - ['parent', [new Type(Type::BUILTIN_TYPE_OBJECT, false, 'Symfony\Component\PropertyInfo\Tests\Fixtures\ParentDummy')], null, null], - ['collection', [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_OBJECT, false, 'DateTimeImmutable'))], null, null], - ['nestedCollection', [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_STRING, false)))], null, null], - ['mixedCollection', [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, null, null)], null, null], - ['a', [new Type(Type::BUILTIN_TYPE_INT)], 'A.', null], - ['b', [new Type(Type::BUILTIN_TYPE_OBJECT, true, 'Symfony\Component\PropertyInfo\Tests\Fixtures\ParentDummy')], 'B.', null], - ['c', [new Type(Type::BUILTIN_TYPE_BOOL, true)], null, null], - ['ct', [new Type(Type::BUILTIN_TYPE_TRUE, true)], null, null], - ['cf', [new Type(Type::BUILTIN_TYPE_FALSE, true)], null, null], - ['d', [new Type(Type::BUILTIN_TYPE_BOOL)], null, null], - ['dt', [new Type(Type::BUILTIN_TYPE_TRUE)], null, null], - ['df', [new Type(Type::BUILTIN_TYPE_FALSE)], null, null], - ['e', [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_RESOURCE))], null, null], - ['f', [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_OBJECT, false, 'DateTimeImmutable'))], null, null], - ['g', [new Type(Type::BUILTIN_TYPE_ARRAY, true, null, true)], 'Nullable array.', null], - ['h', [new Type(Type::BUILTIN_TYPE_STRING, true)], null, null], - ['i', [new Type(Type::BUILTIN_TYPE_STRING, true), new Type(Type::BUILTIN_TYPE_INT, true)], null, null], - ['j', [new Type(Type::BUILTIN_TYPE_OBJECT, true, 'DateTimeImmutable')], null, null], - ['nullableCollectionOfNonNullableElements', [new Type(Type::BUILTIN_TYPE_ARRAY, true, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_INT, false))], null, null], + ['bal', [new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, 'DateTimeImmutable')], null, null], + ['parent', [new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, 'Symfony\Component\PropertyInfo\Tests\Fixtures\ParentDummy')], null, null], + ['collection', [new LegacyType(LegacyType::BUILTIN_TYPE_ARRAY, false, null, true, new LegacyType(LegacyType::BUILTIN_TYPE_INT), new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, 'DateTimeImmutable'))], null, null], + ['nestedCollection', [new LegacyType(LegacyType::BUILTIN_TYPE_ARRAY, false, null, true, new LegacyType(LegacyType::BUILTIN_TYPE_INT), new LegacyType(LegacyType::BUILTIN_TYPE_ARRAY, false, null, true, new LegacyType(LegacyType::BUILTIN_TYPE_INT), new LegacyType(LegacyType::BUILTIN_TYPE_STRING, false)))], null, null], + ['mixedCollection', [new LegacyType(LegacyType::BUILTIN_TYPE_ARRAY, false, null, true, null, null)], null, null], + ['a', [new LegacyType(LegacyType::BUILTIN_TYPE_INT)], 'A.', null], + ['b', [new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, true, 'Symfony\Component\PropertyInfo\Tests\Fixtures\ParentDummy')], 'B.', null], + ['c', [new LegacyType(LegacyType::BUILTIN_TYPE_BOOL, true)], null, null], + ['ct', [new LegacyType(LegacyType::BUILTIN_TYPE_TRUE, true)], null, null], + ['cf', [new LegacyType(LegacyType::BUILTIN_TYPE_FALSE, true)], null, null], + ['d', [new LegacyType(LegacyType::BUILTIN_TYPE_BOOL)], null, null], + ['dt', [new LegacyType(LegacyType::BUILTIN_TYPE_TRUE)], null, null], + ['df', [new LegacyType(LegacyType::BUILTIN_TYPE_FALSE)], null, null], + ['e', [new LegacyType(LegacyType::BUILTIN_TYPE_ARRAY, false, null, true, new LegacyType(LegacyType::BUILTIN_TYPE_INT), new LegacyType(LegacyType::BUILTIN_TYPE_RESOURCE))], null, null], + ['f', [new LegacyType(LegacyType::BUILTIN_TYPE_ARRAY, false, null, true, new LegacyType(LegacyType::BUILTIN_TYPE_INT), new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, 'DateTimeImmutable'))], null, null], + ['g', [new LegacyType(LegacyType::BUILTIN_TYPE_ARRAY, true, null, true)], 'Nullable array.', null], + ['h', [new LegacyType(LegacyType::BUILTIN_TYPE_STRING, true)], null, null], + ['i', [new LegacyType(LegacyType::BUILTIN_TYPE_INT, true), new LegacyType(LegacyType::BUILTIN_TYPE_STRING, true)], null, null], + ['j', [new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, true, 'DateTimeImmutable')], null, null], + ['nullableCollectionOfNonNullableElements', [new LegacyType(LegacyType::BUILTIN_TYPE_ARRAY, true, null, true, new LegacyType(LegacyType::BUILTIN_TYPE_INT), new LegacyType(LegacyType::BUILTIN_TYPE_INT, false))], null, null], ['donotexist', null, null, null], ['staticGetter', null, null, null], ['staticSetter', null, null, null], ['emptyVar', null, 'This should not be removed.', null], - ['arrayWithKeys', [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_STRING), new Type(Type::BUILTIN_TYPE_STRING))], null, null], - ['arrayOfMixed', [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_STRING), null)], null, null], - ['listOfStrings', [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_STRING))], null, null], - ['self', [new Type(Type::BUILTIN_TYPE_OBJECT, false, Dummy::class)], null, null], + ['arrayWithKeys', [new LegacyType(LegacyType::BUILTIN_TYPE_ARRAY, false, null, true, new LegacyType(LegacyType::BUILTIN_TYPE_STRING), new LegacyType(LegacyType::BUILTIN_TYPE_STRING))], null, null], + ['arrayOfMixed', [new LegacyType(LegacyType::BUILTIN_TYPE_ARRAY, false, null, true, new LegacyType(LegacyType::BUILTIN_TYPE_STRING), null)], null, null], + ['listOfStrings', [new LegacyType(LegacyType::BUILTIN_TYPE_ARRAY, false, null, true, new LegacyType(LegacyType::BUILTIN_TYPE_INT), new LegacyType(LegacyType::BUILTIN_TYPE_STRING))], null, null], + ['self', [new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, Dummy::class)], null, null], ]; } /** - * @dataProvider provideCollectionTypes + * @group legacy + * + * @dataProvider provideLegacyCollectionTypes */ - public function testExtractCollection($property, ?array $type, $shortDescription, $longDescription) + public function testExtractCollectionLegacy($property, ?array $type, $shortDescription, $longDescription) { - $this->testExtract($property, $type, $shortDescription, $longDescription); + $this->testExtractLegacy($property, $type, $shortDescription, $longDescription); } - public static function provideCollectionTypes() + public static function provideLegacyCollectionTypes() { return [ - ['iteratorCollection', [new Type(Type::BUILTIN_TYPE_OBJECT, false, 'Iterator', true, [new Type(Type::BUILTIN_TYPE_STRING), new Type(Type::BUILTIN_TYPE_INT)], new Type(Type::BUILTIN_TYPE_STRING))], null, null], - ['iteratorCollectionWithKey', [new Type(Type::BUILTIN_TYPE_OBJECT, false, 'Iterator', true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_STRING))], null, null], + ['iteratorCollection', [new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, 'Iterator', true, [new LegacyType(LegacyType::BUILTIN_TYPE_STRING), new LegacyType(LegacyType::BUILTIN_TYPE_INT)], new LegacyType(LegacyType::BUILTIN_TYPE_STRING))], null, null], + ['iteratorCollectionWithKey', [new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, 'Iterator', true, new LegacyType(LegacyType::BUILTIN_TYPE_INT), new LegacyType(LegacyType::BUILTIN_TYPE_STRING))], null, null], [ 'nestedIterators', - [new Type( - Type::BUILTIN_TYPE_OBJECT, + [new LegacyType( + LegacyType::BUILTIN_TYPE_OBJECT, false, 'Iterator', true, - new Type(Type::BUILTIN_TYPE_INT), - new Type(Type::BUILTIN_TYPE_OBJECT, false, 'Iterator', true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_STRING)) + new LegacyType(LegacyType::BUILTIN_TYPE_INT), + new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, 'Iterator', true, new LegacyType(LegacyType::BUILTIN_TYPE_INT), new LegacyType(LegacyType::BUILTIN_TYPE_STRING)) )], null, null, ], [ 'arrayWithKeys', - [new Type( - Type::BUILTIN_TYPE_ARRAY, + [new LegacyType( + LegacyType::BUILTIN_TYPE_ARRAY, false, null, true, - new Type(Type::BUILTIN_TYPE_STRING), - new Type(Type::BUILTIN_TYPE_STRING) + new LegacyType(LegacyType::BUILTIN_TYPE_STRING), + new LegacyType(LegacyType::BUILTIN_TYPE_STRING) )], null, null, ], [ 'arrayWithKeysAndComplexValue', - [new Type( - Type::BUILTIN_TYPE_ARRAY, + [new LegacyType( + LegacyType::BUILTIN_TYPE_ARRAY, false, null, true, - new Type(Type::BUILTIN_TYPE_STRING), - new Type( - Type::BUILTIN_TYPE_ARRAY, + new LegacyType(LegacyType::BUILTIN_TYPE_STRING), + new LegacyType( + LegacyType::BUILTIN_TYPE_ARRAY, true, null, true, - new Type(Type::BUILTIN_TYPE_INT), - new Type(Type::BUILTIN_TYPE_STRING, true) + new LegacyType(LegacyType::BUILTIN_TYPE_INT), + new LegacyType(LegacyType::BUILTIN_TYPE_STRING, true) ) )], null, @@ -205,93 +221,95 @@ public static function provideCollectionTypes() } /** - * @dataProvider typesWithCustomPrefixesProvider + * @group legacy + * + * @dataProvider provideLegacyTypesWithCustomPrefixes */ - public function testExtractTypesWithCustomPrefixes($property, array $type = null) + public function testExtractTypesWithCustomPrefixesLegacy($property, array $type = null) { $customExtractor = new PhpDocExtractor(null, ['add', 'remove'], ['is', 'can']); $this->assertEquals($type, $customExtractor->getTypes(Dummy::class, $property)); } - public static function typesWithCustomPrefixesProvider() + public static function provideLegacyTypesWithCustomPrefixes() { return [ ['foo', null, 'Short description.', 'Long description.'], - ['bar', [new Type(Type::BUILTIN_TYPE_STRING)], 'This is bar', null], - ['baz', [new Type(Type::BUILTIN_TYPE_INT)], 'Should be used.', null], - ['foo2', [new Type(Type::BUILTIN_TYPE_FLOAT)], null, null], - ['foo3', [new Type(Type::BUILTIN_TYPE_CALLABLE)], null, null], - ['foo4', [new Type(Type::BUILTIN_TYPE_NULL)], null, null], + ['bar', [new LegacyType(LegacyType::BUILTIN_TYPE_STRING)], 'This is bar', null], + ['baz', [new LegacyType(LegacyType::BUILTIN_TYPE_INT)], 'Should be used.', null], + ['foo2', [new LegacyType(LegacyType::BUILTIN_TYPE_FLOAT)], null, null], + ['foo3', [new LegacyType(LegacyType::BUILTIN_TYPE_CALLABLE)], null, null], + ['foo4', [new LegacyType(LegacyType::BUILTIN_TYPE_NULL)], null, null], ['foo5', null, null, null], [ 'files', [ - new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_OBJECT, false, 'SplFileInfo')), - new Type(Type::BUILTIN_TYPE_RESOURCE), + new LegacyType(LegacyType::BUILTIN_TYPE_ARRAY, false, null, true, new LegacyType(LegacyType::BUILTIN_TYPE_INT), new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, 'SplFileInfo')), + new LegacyType(LegacyType::BUILTIN_TYPE_RESOURCE), ], null, null, ], - ['bal', [new Type(Type::BUILTIN_TYPE_OBJECT, false, 'DateTimeImmutable')], null, null], - ['parent', [new Type(Type::BUILTIN_TYPE_OBJECT, false, 'Symfony\Component\PropertyInfo\Tests\Fixtures\ParentDummy')], null, null], - ['collection', [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_OBJECT, false, 'DateTimeImmutable'))], null, null], - ['nestedCollection', [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_STRING, false)))], null, null], - ['mixedCollection', [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, null, null)], null, null], + ['bal', [new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, 'DateTimeImmutable')], null, null], + ['parent', [new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, 'Symfony\Component\PropertyInfo\Tests\Fixtures\ParentDummy')], null, null], + ['collection', [new LegacyType(LegacyType::BUILTIN_TYPE_ARRAY, false, null, true, new LegacyType(LegacyType::BUILTIN_TYPE_INT), new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, 'DateTimeImmutable'))], null, null], + ['nestedCollection', [new LegacyType(LegacyType::BUILTIN_TYPE_ARRAY, false, null, true, new LegacyType(LegacyType::BUILTIN_TYPE_INT), new LegacyType(LegacyType::BUILTIN_TYPE_ARRAY, false, null, true, new LegacyType(LegacyType::BUILTIN_TYPE_INT), new LegacyType(LegacyType::BUILTIN_TYPE_STRING, false)))], null, null], + ['mixedCollection', [new LegacyType(LegacyType::BUILTIN_TYPE_ARRAY, false, null, true, null, null)], null, null], ['a', null, 'A.', null], ['b', null, 'B.', null], - ['c', [new Type(Type::BUILTIN_TYPE_BOOL, true)], null, null], - ['d', [new Type(Type::BUILTIN_TYPE_BOOL)], null, null], - ['e', [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_RESOURCE))], null, null], - ['f', [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_OBJECT, false, 'DateTimeImmutable'))], null, null], - ['g', [new Type(Type::BUILTIN_TYPE_ARRAY, true, null, true)], 'Nullable array.', null], - ['h', [new Type(Type::BUILTIN_TYPE_STRING, true)], null, null], - ['i', [new Type(Type::BUILTIN_TYPE_STRING, true), new Type(Type::BUILTIN_TYPE_INT, true)], null, null], - ['j', [new Type(Type::BUILTIN_TYPE_OBJECT, true, 'DateTimeImmutable')], null, null], - ['nullableCollectionOfNonNullableElements', [new Type(Type::BUILTIN_TYPE_ARRAY, true, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_INT, false))], null, null], - ['nonNullableCollectionOfNullableElements', [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_INT, true))], null, null], - ['nullableCollectionOfMultipleNonNullableElementTypes', [new Type(Type::BUILTIN_TYPE_ARRAY, true, null, true, new Type(Type::BUILTIN_TYPE_INT), [new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_STRING)])], null, null], + ['c', [new LegacyType(LegacyType::BUILTIN_TYPE_BOOL, true)], null, null], + ['d', [new LegacyType(LegacyType::BUILTIN_TYPE_BOOL)], null, null], + ['e', [new LegacyType(LegacyType::BUILTIN_TYPE_ARRAY, false, null, true, new LegacyType(LegacyType::BUILTIN_TYPE_INT), new LegacyType(LegacyType::BUILTIN_TYPE_RESOURCE))], null, null], + ['f', [new LegacyType(LegacyType::BUILTIN_TYPE_ARRAY, false, null, true, new LegacyType(LegacyType::BUILTIN_TYPE_INT), new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, 'DateTimeImmutable'))], null, null], + ['g', [new LegacyType(LegacyType::BUILTIN_TYPE_ARRAY, true, null, true)], 'Nullable array.', null], + ['h', [new LegacyType(LegacyType::BUILTIN_TYPE_STRING, true)], null, null], + ['i', [new LegacyType(LegacyType::BUILTIN_TYPE_INT, true), new LegacyType(LegacyType::BUILTIN_TYPE_STRING, true)], null, null], + ['j', [new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, true, 'DateTimeImmutable')], null, null], + ['nullableCollectionOfNonNullableElements', [new LegacyType(LegacyType::BUILTIN_TYPE_ARRAY, true, null, true, new LegacyType(LegacyType::BUILTIN_TYPE_INT), new LegacyType(LegacyType::BUILTIN_TYPE_INT, false))], null, null], + ['nonNullableCollectionOfNullableElements', [new LegacyType(LegacyType::BUILTIN_TYPE_ARRAY, false, null, true, new LegacyType(LegacyType::BUILTIN_TYPE_INT), new LegacyType(LegacyType::BUILTIN_TYPE_INT, true))], null, null], + ['nullableCollectionOfMultipleNonNullableElementTypes', [new LegacyType(LegacyType::BUILTIN_TYPE_ARRAY, true, null, true, new LegacyType(LegacyType::BUILTIN_TYPE_INT), [new LegacyType(LegacyType::BUILTIN_TYPE_INT), new LegacyType(LegacyType::BUILTIN_TYPE_STRING)])], null, null], ['donotexist', null, null, null], ['staticGetter', null, null, null], ['staticSetter', null, null, null], ]; } - public static function typesWithNoPrefixesProvider() + public static function provideLegacyTypesWithNoPrefixes() { return [ ['foo', null, 'Short description.', 'Long description.'], - ['bar', [new Type(Type::BUILTIN_TYPE_STRING)], 'This is bar', null], - ['baz', [new Type(Type::BUILTIN_TYPE_INT)], 'Should be used.', null], - ['foo2', [new Type(Type::BUILTIN_TYPE_FLOAT)], null, null], - ['foo3', [new Type(Type::BUILTIN_TYPE_CALLABLE)], null, null], - ['foo4', [new Type(Type::BUILTIN_TYPE_NULL)], null, null], + ['bar', [new LegacyType(LegacyType::BUILTIN_TYPE_STRING)], 'This is bar', null], + ['baz', [new LegacyType(LegacyType::BUILTIN_TYPE_INT)], 'Should be used.', null], + ['foo2', [new LegacyType(LegacyType::BUILTIN_TYPE_FLOAT)], null, null], + ['foo3', [new LegacyType(LegacyType::BUILTIN_TYPE_CALLABLE)], null, null], + ['foo4', [new LegacyType(LegacyType::BUILTIN_TYPE_NULL)], null, null], ['foo5', null, null, null], [ 'files', [ - new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_OBJECT, false, 'SplFileInfo')), - new Type(Type::BUILTIN_TYPE_RESOURCE), + new LegacyType(LegacyType::BUILTIN_TYPE_ARRAY, false, null, true, new LegacyType(LegacyType::BUILTIN_TYPE_INT), new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, 'SplFileInfo')), + new LegacyType(LegacyType::BUILTIN_TYPE_RESOURCE), ], null, null, ], - ['bal', [new Type(Type::BUILTIN_TYPE_OBJECT, false, 'DateTimeImmutable')], null, null], - ['parent', [new Type(Type::BUILTIN_TYPE_OBJECT, false, 'Symfony\Component\PropertyInfo\Tests\Fixtures\ParentDummy')], null, null], - ['collection', [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_OBJECT, false, 'DateTimeImmutable'))], null, null], - ['nestedCollection', [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_STRING, false)))], null, null], - ['mixedCollection', [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, null, null)], null, null], + ['bal', [new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, 'DateTimeImmutable')], null, null], + ['parent', [new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, 'Symfony\Component\PropertyInfo\Tests\Fixtures\ParentDummy')], null, null], + ['collection', [new LegacyType(LegacyType::BUILTIN_TYPE_ARRAY, false, null, true, new LegacyType(LegacyType::BUILTIN_TYPE_INT), new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, 'DateTimeImmutable'))], null, null], + ['nestedCollection', [new LegacyType(LegacyType::BUILTIN_TYPE_ARRAY, false, null, true, new LegacyType(LegacyType::BUILTIN_TYPE_INT), new LegacyType(LegacyType::BUILTIN_TYPE_ARRAY, false, null, true, new LegacyType(LegacyType::BUILTIN_TYPE_INT), new LegacyType(LegacyType::BUILTIN_TYPE_STRING, false)))], null, null], + ['mixedCollection', [new LegacyType(LegacyType::BUILTIN_TYPE_ARRAY, false, null, true, null, null)], null, null], ['a', null, 'A.', null], ['b', null, 'B.', null], ['c', null, null, null], ['d', null, null, null], ['e', null, null, null], ['f', null, null, null], - ['g', [new Type(Type::BUILTIN_TYPE_ARRAY, true, null, true)], 'Nullable array.', null], - ['h', [new Type(Type::BUILTIN_TYPE_STRING, true)], null, null], - ['i', [new Type(Type::BUILTIN_TYPE_STRING, true), new Type(Type::BUILTIN_TYPE_INT, true)], null, null], - ['j', [new Type(Type::BUILTIN_TYPE_OBJECT, true, 'DateTimeImmutable')], null, null], - ['nullableCollectionOfNonNullableElements', [new Type(Type::BUILTIN_TYPE_ARRAY, true, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_INT, false))], null, null], + ['g', [new LegacyType(LegacyType::BUILTIN_TYPE_ARRAY, true, null, true)], 'Nullable array.', null], + ['h', [new LegacyType(LegacyType::BUILTIN_TYPE_STRING, true)], null, null], + ['i', [new LegacyType(LegacyType::BUILTIN_TYPE_INT, true), new LegacyType(LegacyType::BUILTIN_TYPE_STRING, true)], null, null], + ['j', [new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, true, 'DateTimeImmutable')], null, null], + ['nullableCollectionOfNonNullableElements', [new LegacyType(LegacyType::BUILTIN_TYPE_ARRAY, true, null, true, new LegacyType(LegacyType::BUILTIN_TYPE_INT), new LegacyType(LegacyType::BUILTIN_TYPE_INT, false))], null, null], ['donotexist', null, null, null], ['staticGetter', null, null, null], ['staticSetter', null, null, null], @@ -303,120 +321,135 @@ public function testReturnNullOnEmptyDocBlock() $this->assertNull($this->extractor->getShortDescription(EmptyDocBlock::class, 'foo')); } - public static function dockBlockFallbackTypesProvider() + public static function provideLegacyDockBlockFallbackTypes() { return [ 'pub' => [ - 'pub', [new Type(Type::BUILTIN_TYPE_STRING)], + 'pub', [new LegacyType(LegacyType::BUILTIN_TYPE_STRING)], ], 'protAcc' => [ - 'protAcc', [new Type(Type::BUILTIN_TYPE_INT)], + 'protAcc', [new LegacyType(LegacyType::BUILTIN_TYPE_INT)], ], 'protMut' => [ - 'protMut', [new Type(Type::BUILTIN_TYPE_BOOL)], + 'protMut', [new LegacyType(LegacyType::BUILTIN_TYPE_BOOL)], ], ]; } /** - * @dataProvider dockBlockFallbackTypesProvider + * @group legacy + * + * @dataProvider provideLegacyDockBlockFallbackTypes */ - public function testDocBlockFallback($property, $types) + public function testDocBlockFallbackLegacy($property, $types) { $this->assertEquals($types, $this->extractor->getTypes('Symfony\Component\PropertyInfo\Tests\Fixtures\DockBlockFallback', $property)); } /** - * @dataProvider propertiesDefinedByTraitsProvider + * @group legacy + * + * @dataProvider provideLegacyPropertiesDefinedByTraits */ - public function testPropertiesDefinedByTraits(string $property, Type $type) + public function testPropertiesDefinedByTraitsLegacy(string $property, LegacyType $type) { $this->assertEquals([$type], $this->extractor->getTypes(DummyUsingTrait::class, $property)); } - public static function propertiesDefinedByTraitsProvider(): array + public static function provideLegacyPropertiesDefinedByTraits(): array { return [ - ['propertyInTraitPrimitiveType', new Type(Type::BUILTIN_TYPE_STRING)], - ['propertyInTraitObjectSameNamespace', new Type(Type::BUILTIN_TYPE_OBJECT, false, DummyUsedInTrait::class)], - ['propertyInTraitObjectDifferentNamespace', new Type(Type::BUILTIN_TYPE_OBJECT, false, Dummy::class)], - ['propertyInExternalTraitPrimitiveType', new Type(Type::BUILTIN_TYPE_STRING)], - ['propertyInExternalTraitObjectSameNamespace', new Type(Type::BUILTIN_TYPE_OBJECT, false, Dummy::class)], - ['propertyInExternalTraitObjectDifferentNamespace', new Type(Type::BUILTIN_TYPE_OBJECT, false, DummyUsedInTrait::class)], + ['propertyInTraitPrimitiveType', new LegacyType(LegacyType::BUILTIN_TYPE_STRING)], + ['propertyInTraitObjectSameNamespace', new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, DummyUsedInTrait::class)], + ['propertyInTraitObjectDifferentNamespace', new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, Dummy::class)], + ['propertyInExternalTraitPrimitiveType', new LegacyType(LegacyType::BUILTIN_TYPE_STRING)], + ['propertyInExternalTraitObjectSameNamespace', new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, Dummy::class)], + ['propertyInExternalTraitObjectDifferentNamespace', new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, DummyUsedInTrait::class)], ]; } /** - * @dataProvider methodsDefinedByTraitsProvider + * @group legacy + * + * @dataProvider provideLegacyMethodsDefinedByTraits */ - public function testMethodsDefinedByTraits(string $property, Type $type) + public function testMethodsDefinedByTraitsLegacy(string $property, LegacyType $type) { $this->assertEquals([$type], $this->extractor->getTypes(DummyUsingTrait::class, $property)); } - public static function methodsDefinedByTraitsProvider(): array + public static function provideLegacyMethodsDefinedByTraits(): array { return [ - ['methodInTraitPrimitiveType', new Type(Type::BUILTIN_TYPE_STRING)], - ['methodInTraitObjectSameNamespace', new Type(Type::BUILTIN_TYPE_OBJECT, false, DummyUsedInTrait::class)], - ['methodInTraitObjectDifferentNamespace', new Type(Type::BUILTIN_TYPE_OBJECT, false, Dummy::class)], - ['methodInExternalTraitPrimitiveType', new Type(Type::BUILTIN_TYPE_STRING)], - ['methodInExternalTraitObjectSameNamespace', new Type(Type::BUILTIN_TYPE_OBJECT, false, Dummy::class)], - ['methodInExternalTraitObjectDifferentNamespace', new Type(Type::BUILTIN_TYPE_OBJECT, false, DummyUsedInTrait::class)], + ['methodInTraitPrimitiveType', new LegacyType(LegacyType::BUILTIN_TYPE_STRING)], + ['methodInTraitObjectSameNamespace', new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, DummyUsedInTrait::class)], + ['methodInTraitObjectDifferentNamespace', new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, Dummy::class)], + ['methodInExternalTraitPrimitiveType', new LegacyType(LegacyType::BUILTIN_TYPE_STRING)], + ['methodInExternalTraitObjectSameNamespace', new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, Dummy::class)], + ['methodInExternalTraitObjectDifferentNamespace', new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, DummyUsedInTrait::class)], ]; } /** - * @dataProvider propertiesStaticTypeProvider + * @group legacy + * + * @dataProvider provideLegacyPropertiesStaticType */ - public function testPropertiesStaticType(string $class, string $property, Type $type) + public function testPropertiesStaticTypeLegacy(string $class, string $property, LegacyType $type) { $this->assertEquals([$type], $this->extractor->getTypes($class, $property)); } - public static function propertiesStaticTypeProvider(): array + public static function provideLegacyPropertiesStaticType(): array { return [ - [ParentDummy::class, 'propertyTypeStatic', new Type(Type::BUILTIN_TYPE_OBJECT, false, ParentDummy::class)], - [Dummy::class, 'propertyTypeStatic', new Type(Type::BUILTIN_TYPE_OBJECT, false, Dummy::class)], + [ParentDummy::class, 'propertyTypeStatic', new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, ParentDummy::class)], + [Dummy::class, 'propertyTypeStatic', new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, Dummy::class)], ]; } /** - * @dataProvider propertiesParentTypeProvider + * @group legacy + * + * @dataProvider provideLegacyPropertiesParentType */ - public function testPropertiesParentType(string $class, string $property, ?array $types) + public function testPropertiesParentTypeLegacy(string $class, string $property, ?array $types) { $this->assertEquals($types, $this->extractor->getTypes($class, $property)); } - public static function propertiesParentTypeProvider(): array + public static function provideLegacyPropertiesParentType(): array { return [ - [ParentDummy::class, 'parentAnnotationNoParent', [new Type(Type::BUILTIN_TYPE_OBJECT, false, 'parent')]], - [Dummy::class, 'parentAnnotation', [new Type(Type::BUILTIN_TYPE_OBJECT, false, ParentDummy::class)]], + [ParentDummy::class, 'parentAnnotationNoParent', [new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, 'parent')]], + [Dummy::class, 'parentAnnotation', [new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, ParentDummy::class)]], ]; } - public function testUnknownPseudoType() + /** + * @group legacy + */ + public function testUnknownPseudoTypeLegacy() { - $this->assertEquals([new Type(Type::BUILTIN_TYPE_OBJECT, false, 'scalar')], $this->extractor->getTypes(PseudoTypeDummy::class, 'unknownPseudoType')); + $this->assertEquals([new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, 'scalar')], $this->extractor->getTypes(PseudoTypeDummy::class, 'unknownPseudoType')); } /** - * @dataProvider constructorTypesProvider + * @group legacy + * + * @dataProvider provideLegacyConstructorTypes */ - public function testExtractConstructorTypes($property, array $type = null) + public function testExtractConstructorTypesLegacy($property, array $type = null) { $this->assertEquals($type, $this->extractor->getTypesFromConstructor('Symfony\Component\PropertyInfo\Tests\Fixtures\ConstructorDummy', $property)); } - public static function constructorTypesProvider() + public static function provideLegacyConstructorTypes() { return [ - ['date', [new Type(Type::BUILTIN_TYPE_INT)]], - ['timezone', [new Type(Type::BUILTIN_TYPE_OBJECT, false, 'DateTimeZone')]], - ['dateObject', [new Type(Type::BUILTIN_TYPE_OBJECT, false, 'DateTimeInterface')]], + ['date', [new LegacyType(LegacyType::BUILTIN_TYPE_INT)]], + ['timezone', [new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, 'DateTimeZone')]], + ['dateObject', [new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, 'DateTimeInterface')]], ['dateTime', null], ['ddd', null], ['mixed', null], @@ -424,43 +457,398 @@ public static function constructorTypesProvider() } /** - * @dataProvider pseudoTypesProvider + * @group legacy + * + * @dataProvider provideLegacyPseudoTypes */ - public function testPseudoTypes($property, array $type) + public function testPseudoTypesLegacy($property, array $type) { $this->assertEquals($type, $this->extractor->getTypes('Symfony\Component\PropertyInfo\Tests\Fixtures\PseudoTypesDummy', $property)); } - public static function pseudoTypesProvider(): array + public static function provideLegacyPseudoTypes(): array { return [ - ['classString', [new Type(Type::BUILTIN_TYPE_STRING, false, null)]], - ['classStringGeneric', [new Type(Type::BUILTIN_TYPE_STRING, false, null)]], - ['htmlEscapedString', [new Type(Type::BUILTIN_TYPE_STRING, false, null)]], - ['lowercaseString', [new Type(Type::BUILTIN_TYPE_STRING, false, null)]], - ['nonEmptyLowercaseString', [new Type(Type::BUILTIN_TYPE_STRING, false, null)]], - ['nonEmptyString', [new Type(Type::BUILTIN_TYPE_STRING, false, null)]], - ['numericString', [new Type(Type::BUILTIN_TYPE_STRING, false, null)]], - ['traitString', [new Type(Type::BUILTIN_TYPE_STRING, false, null)]], - ['positiveInt', [new Type(Type::BUILTIN_TYPE_INT, false, null)]], + ['classString', [new LegacyType(LegacyType::BUILTIN_TYPE_STRING, false, null)]], + ['classStringGeneric', [new LegacyType(LegacyType::BUILTIN_TYPE_STRING, false, null)]], + ['htmlEscapedString', [new LegacyType(LegacyType::BUILTIN_TYPE_STRING, false, null)]], + ['lowercaseString', [new LegacyType(LegacyType::BUILTIN_TYPE_STRING, false, null)]], + ['nonEmptyLowercaseString', [new LegacyType(LegacyType::BUILTIN_TYPE_STRING, false, null)]], + ['nonEmptyString', [new LegacyType(LegacyType::BUILTIN_TYPE_STRING, false, null)]], + ['numericString', [new LegacyType(LegacyType::BUILTIN_TYPE_STRING, false, null)]], + ['traitString', [new LegacyType(LegacyType::BUILTIN_TYPE_STRING, false, null)]], + ['positiveInt', [new LegacyType(LegacyType::BUILTIN_TYPE_INT, false, null)]], ]; } /** - * @dataProvider promotedPropertyProvider + * @group legacy + * + * @dataProvider provideLegacyPromotedProperty */ - public function testExtractPromotedProperty(string $property, ?array $types) + public function testExtractPromotedPropertyLegacy(string $property, ?array $types) { $this->assertEquals($types, $this->extractor->getTypes(Php80Dummy::class, $property)); } - public static function promotedPropertyProvider(): array + public static function provideLegacyPromotedProperty(): array { return [ ['promoted', null], - ['promotedAndMutated', [new Type(Type::BUILTIN_TYPE_STRING)]], + ['promotedAndMutated', [new LegacyType(LegacyType::BUILTIN_TYPE_STRING)]], ]; } + + public function testParamTagTypeIsOmitted() + { + $this->assertNull($this->extractor->getType(OmittedParamTagTypeDocBlock::class, 'omittedType')); + } + + /** + * @dataProvider typeProvider + */ + public function testExtract(string $property, ?Type $type, ?string $shortDescription, ?string $longDescription) + { + $this->assertEquals($type, $this->extractor->getType(Dummy::class, $property)); + $this->assertSame($shortDescription, $this->extractor->getShortDescription(Dummy::class, $property)); + $this->assertSame($longDescription, $this->extractor->getLongDescription(Dummy::class, $property)); + } + + /** + * @return iterable + */ + public static function typeProvider(): iterable + { + yield ['foo', null, 'Short description.', 'Long description.']; + yield ['bar', Type::string(), 'This is bar', null]; + yield ['baz', Type::int(), 'Should be used.', null]; + yield ['foo2', Type::float(), null, null]; + yield ['foo3', Type::callable(), null, null]; + yield ['foo4', Type::null(), null, null]; + yield ['foo5', Type::mixed(), null, null]; + yield ['files', Type::union(Type::list(Type::object(\SplFileInfo::class)), Type::resource()), null, null]; + yield ['bal', Type::object(\DateTimeImmutable::class), null, null]; + yield ['parent', Type::object(ParentDummy::class), null, null]; + yield ['collection', Type::list(Type::object(\DateTimeImmutable::class)), null, null]; + yield ['nestedCollection', Type::list(Type::list(Type::string())), null, null]; + yield ['mixedCollection', Type::array(), null, null]; + yield ['a', Type::int(), 'A.', null]; + yield ['b', Type::nullable(Type::object(ParentDummy::class)), 'B.', null]; + yield ['c', Type::nullable(Type::bool()), null, null]; + yield ['ct', Type::nullable(Type::true()), null, null]; + yield ['cf', Type::nullable(Type::false()), null, null]; + yield ['d', Type::bool(), null, null]; + yield ['dt', Type::true(), null, null]; + yield ['df', Type::false(), null, null]; + yield ['e', Type::list(Type::resource()), null, null]; + yield ['f', Type::list(Type::object(\DateTimeImmutable::class)), null, null]; + yield ['g', Type::nullable(Type::array()), 'Nullable array.', null]; + yield ['h', Type::nullable(Type::string()), null, null]; + yield ['i', Type::union(Type::int(), Type::string(), Type::null()), null, null]; + yield ['j', Type::nullable(Type::object(\DateTimeImmutable::class)), null, null]; + yield ['nullableCollectionOfNonNullableElements', Type::nullable(Type::list(Type::int())), null, null]; + yield ['donotexist', null, null, null]; + yield ['staticGetter', null, null, null]; + yield ['staticSetter', null, null, null]; + yield ['emptyVar', null, 'This should not be removed.', null]; + yield ['arrayWithKeys', Type::dict(Type::string()), null, null]; + yield ['arrayOfMixed', Type::dict(Type::mixed()), null, null]; + yield ['listOfStrings', Type::list(Type::string()), null, null]; + yield ['self', Type::object(Dummy::class), null, null]; + } + + /** + * @dataProvider invalidTypeProvider + */ + public function testInvalid(string $property, ?string $shortDescription, ?string $longDescription) + { + $this->assertNull($this->extractor->getType(InvalidDummy::class, $property)); + $this->assertSame($shortDescription, $this->extractor->getShortDescription(InvalidDummy::class, $property)); + $this->assertSame($longDescription, $this->extractor->getLongDescription(InvalidDummy::class, $property)); + } + + /** + * @return iterable + */ + public static function invalidTypeProvider(): iterable + { + yield 'pub' => ['pub', null, null]; + yield 'stat' => ['stat', null, null]; + yield 'foo' => ['foo', 'Foo.', null]; + yield 'bar' => ['bar', 'Bar.', null]; + } + + /** + * @dataProvider typeWithNoPrefixesProvider + */ + public function testExtractTypesWithNoPrefixes(string $property, ?Type $type) + { + $noPrefixExtractor = new PhpDocExtractor(null, [], [], []); + + $this->assertEquals($type, $noPrefixExtractor->getType(Dummy::class, $property)); + } + + public static function typeWithNoPrefixesProvider() + { + yield ['foo', null]; + yield ['bar', Type::string()]; + yield ['baz', Type::int()]; + yield ['foo2', Type::float()]; + yield ['foo3', Type::callable()]; + yield ['foo4', Type::null()]; + yield ['foo5', Type::mixed()]; + yield ['files', Type::union(Type::list(Type::object(\SplFileInfo::class)), Type::resource())]; + yield ['bal', Type::object(\DateTimeImmutable::class)]; + yield ['parent', Type::object(ParentDummy::class)]; + yield ['collection', Type::list(Type::object(\DateTimeImmutable::class))]; + yield ['nestedCollection', Type::list(Type::list(Type::string()))]; + yield ['mixedCollection', Type::array()]; + yield ['a', null]; + yield ['b', null]; + yield ['c', null]; + yield ['d', null]; + yield ['e', null]; + yield ['f', null]; + yield ['g', Type::nullable(Type::array())]; + yield ['h', Type::nullable(Type::string())]; + yield ['i', Type::union(Type::int(), Type::string(), Type::null())]; + yield ['j', Type::nullable(Type::object(\DateTimeImmutable::class))]; + yield ['nullableCollectionOfNonNullableElements', Type::nullable(Type::list(Type::int()))]; + yield ['donotexist', null]; + yield ['staticGetter', null]; + yield ['staticSetter', null]; + } + + /** + * @dataProvider provideCollectionTypes + */ + public function testExtractCollection(string $property, ?Type $type) + { + $this->testExtract($property, $type, null, null); + } + + /** + * @return iterable + */ + public static function provideCollectionTypes(): iterable + { + yield ['iteratorCollection', Type::collection(Type::object(\Iterator::class), Type::string())]; + yield ['iteratorCollectionWithKey', Type::collection(Type::object(\Iterator::class), Type::string(), Type::int())]; + yield ['nestedIterators', Type::collection(Type::object(\Iterator::class), Type::collection(Type::object(\Iterator::class), Type::string(), Type::int()), Type::int())]; + yield ['arrayWithKeys', Type::dict(Type::string()), null, null]; + yield ['arrayWithKeysAndComplexValue', Type::dict(Type::nullable(Type::array(Type::nullable(Type::string()), Type::int()))), null, null]; + } + + /** + * @dataProvider typeWithCustomPrefixesProvider + */ + public function testExtractTypeWithCustomPrefixes(string $property, ?Type $type) + { + $customExtractor = new PhpDocExtractor(null, ['add', 'remove'], ['is', 'can']); + + $this->assertEquals($type, $customExtractor->getType(Dummy::class, $property)); + } + + /** + * @return iterable + */ + public static function typeWithCustomPrefixesProvider(): iterable + { + yield ['foo', null]; + yield ['bar', Type::string()]; + yield ['baz', Type::int()]; + yield ['foo2', Type::float()]; + yield ['foo3', Type::callable()]; + yield ['foo4', Type::null()]; + yield ['foo5', Type::mixed()]; + yield ['files', Type::union(Type::list(Type::object(\SplFileInfo::class)), Type::resource())]; + yield ['bal', Type::object(\DateTimeImmutable::class)]; + yield ['parent', Type::object(ParentDummy::class)]; + yield ['collection', Type::list(Type::object(\DateTimeImmutable::class))]; + yield ['nestedCollection', Type::list(Type::list(Type::string()))]; + yield ['mixedCollection', Type::array()]; + yield ['a', null]; + yield ['b', null]; + yield ['c', Type::nullable(Type::bool())]; + yield ['d', Type::bool()]; + yield ['e', Type::list(Type::resource())]; + yield ['f', Type::list(Type::object(\DateTimeImmutable::class))]; + yield ['g', Type::nullable(Type::array())]; + yield ['h', Type::nullable(Type::string())]; + yield ['i', Type::union(Type::int(), Type::string(), Type::null())]; + yield ['j', Type::nullable(Type::object(\DateTimeImmutable::class))]; + yield ['nullableCollectionOfNonNullableElements', Type::nullable(Type::list(Type::int()))]; + yield ['nonNullableCollectionOfNullableElements', Type::list(Type::nullable(Type::int()))]; + yield ['nullableCollectionOfMultipleNonNullableElementTypes', Type::nullable(Type::list(Type::union(Type::int(), Type::string())))]; + yield ['donotexist', null]; + yield ['staticGetter', null]; + yield ['staticSetter', null]; + } + + /** + * @dataProvider dockBlockFallbackTypesProvider + */ + public function testDocBlockFallback(string $property, ?Type $type) + { + $this->assertEquals($type, $this->extractor->getType(DockBlockFallback::class, $property)); + } + + /** + * @return iterable + */ + public static function dockBlockFallbackTypesProvider(): iterable + { + yield ['pub', Type::string()]; + yield ['protAcc', Type::int()]; + yield ['protMut', Type::bool()]; + } + + /** + * @dataProvider propertiesDefinedByTraitsProvider + */ + public function testPropertiesDefinedByTraits(string $property, ?Type $type) + { + $this->assertEquals($type, $this->extractor->getType(DummyUsingTrait::class, $property)); + } + + /** + * @return iterable + */ + public static function propertiesDefinedByTraitsProvider(): iterable + { + yield ['propertyInTraitPrimitiveType', Type::string()]; + yield ['propertyInTraitObjectSameNamespace', Type::object(DummyUsedInTrait::class)]; + yield ['propertyInTraitObjectDifferentNamespace', Type::object(Dummy::class)]; + yield ['propertyInExternalTraitPrimitiveType', Type::string()]; + yield ['propertyInExternalTraitObjectSameNamespace', Type::object(Dummy::class)]; + yield ['propertyInExternalTraitObjectDifferentNamespace', Type::object(DummyUsedInTrait::class)]; + } + + /** + * @dataProvider methodsDefinedByTraitsProvider + */ + public function testMethodsDefinedByTraits(string $property, ?Type $type) + { + $this->assertEquals($type, $this->extractor->getType(DummyUsingTrait::class, $property)); + } + + /** + * @return iterable + */ + public static function methodsDefinedByTraitsProvider(): iterable + { + yield ['methodInTraitPrimitiveType', Type::string()]; + yield ['methodInTraitObjectSameNamespace', Type::object(DummyUsedInTrait::class)]; + yield ['methodInTraitObjectDifferentNamespace', Type::object(Dummy::class)]; + yield ['methodInExternalTraitPrimitiveType', Type::string()]; + yield ['methodInExternalTraitObjectSameNamespace', Type::object(Dummy::class)]; + yield ['methodInExternalTraitObjectDifferentNamespace', Type::object(DummyUsedInTrait::class)]; + } + + /** + * @param class-string $class + * + * @dataProvider propertiesStaticTypeProvider + */ + public function testPropertiesStaticType(string $class, string $property, ?Type $type) + { + $this->assertEquals($type, $this->extractor->getType($class, $property)); + } + + /** + * @return iterable + */ + public static function propertiesStaticTypeProvider(): iterable + { + yield [ParentDummy::class, 'propertyTypeStatic', Type::object(ParentDummy::class)]; + yield [Dummy::class, 'propertyTypeStatic', Type::object(Dummy::class)]; + } + + /** + * @param class-string $class + * + * @dataProvider propertiesParentTypeProvider + */ + public function testPropertiesParentType(string $class, string $property, ?Type $type) + { + $this->assertEquals($type, $this->extractor->getType($class, $property)); + } + + /** + * @return iterable + */ + public static function propertiesParentTypeProvider(): iterable + { + yield [ParentDummy::class, 'parentAnnotationNoParent', Type::object('parent')]; + yield [Dummy::class, 'parentAnnotation', Type::object(ParentDummy::class)]; + } + + public function testUnknownPseudoType() + { + $this->assertEquals(Type::object('scalar'), $this->extractor->getType(PseudoTypeDummy::class, 'unknownPseudoType')); + } + + /** + * @dataProvider constructorTypesProvider + */ + public function testExtractConstructorType(string $property, ?Type $type) + { + $this->assertEquals($type, $this->extractor->getTypeFromConstructor(ConstructorDummy::class, $property)); + } + + /** + * @return iterable + */ + public static function constructorTypesProvider(): iterable + { + yield ['date', Type::int()]; + yield ['timezone', Type::object(\DateTimeZone::class)]; + yield ['dateObject', Type::object(\DateTimeInterface::class)]; + yield ['dateTime', null]; + yield ['ddd', null]; + yield ['mixed', Type::mixed()]; + } + + /** + * @dataProvider pseudoTypeProvider + */ + public function testPseudoType(string $property, ?Type $type) + { + $this->assertEquals($type, $this->extractor->getType(PseudoTypesDummy::class, $property)); + } + + /** + * @return iterable + */ + public static function pseudoTypeProvider(): iterable + { + yield ['classString', Type::string()]; + yield ['classStringGeneric', Type::string()]; + yield ['htmlEscapedString', Type::string()]; + yield ['lowercaseString', Type::string()]; + yield ['nonEmptyLowercaseString', Type::string()]; + yield ['nonEmptyString', Type::string()]; + yield ['numericString', Type::string()]; + yield ['traitString', Type::string()]; + yield ['positiveInt', Type::int()]; + } + + /** + * @dataProvider promotedPropertyProvider + */ + public function testExtractPromotedProperty(string $property, ?Type $type) + { + $this->assertEquals($type, $this->extractor->getType(Php80Dummy::class, $property)); + } + + /** + * @return iterable + */ + public static function promotedPropertyProvider(): iterable + { + yield ['promoted', null]; + yield ['promotedAndMutated', Type::string()]; + } } class EmptyDocBlock diff --git a/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpStanExtractorTest.php b/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpStanExtractorTest.php index 06fdd9104e60c..8d95bf9e580ed 100644 --- a/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpStanExtractorTest.php +++ b/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpStanExtractorTest.php @@ -14,16 +14,26 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor; use Symfony\Component\PropertyInfo\Extractor\PhpStanExtractor; +use Symfony\Component\PropertyInfo\Tests\Fixtures\ConstructorDummy; use Symfony\Component\PropertyInfo\Tests\Fixtures\ConstructorDummyWithoutDocBlock; use Symfony\Component\PropertyInfo\Tests\Fixtures\DefaultValue; +use Symfony\Component\PropertyInfo\Tests\Fixtures\DockBlockFallback; use Symfony\Component\PropertyInfo\Tests\Fixtures\Dummy; +use Symfony\Component\PropertyInfo\Tests\Fixtures\DummyNamespace; +use Symfony\Component\PropertyInfo\Tests\Fixtures\DummyPropertyAndGetterWithDifferentTypes; +use Symfony\Component\PropertyInfo\Tests\Fixtures\DummyUnionType; +use Symfony\Component\PropertyInfo\Tests\Fixtures\IntRangeDummy; +use Symfony\Component\PropertyInfo\Tests\Fixtures\InvalidDummy; use Symfony\Component\PropertyInfo\Tests\Fixtures\ParentDummy; use Symfony\Component\PropertyInfo\Tests\Fixtures\Php80Dummy; use Symfony\Component\PropertyInfo\Tests\Fixtures\Php80PromotedDummy; +use Symfony\Component\PropertyInfo\Tests\Fixtures\PhpStanPseudoTypesDummy; use Symfony\Component\PropertyInfo\Tests\Fixtures\RootDummy\RootDummyItem; use Symfony\Component\PropertyInfo\Tests\Fixtures\TraitUsage\DummyUsedInTrait; use Symfony\Component\PropertyInfo\Tests\Fixtures\TraitUsage\DummyUsingTrait; -use Symfony\Component\PropertyInfo\Type; +use Symfony\Component\PropertyInfo\Type as LegacyType; +use Symfony\Component\TypeInfo\Exception\LogicException; +use Symfony\Component\TypeInfo\Type; require_once __DIR__.'/../Fixtures/Extractor/DummyNamespace.php'; @@ -42,19 +52,24 @@ protected function setUp(): void } /** - * @dataProvider typesProvider + * @group legacy + * + * @dataProvider provideLegacyTypes */ - public function testExtract($property, array $type = null) + public function testExtractLegacy($property, array $type = null) { $this->assertEquals($type, $this->extractor->getTypes('Symfony\Component\PropertyInfo\Tests\Fixtures\Dummy', $property)); } - public function testParamTagTypeIsOmitted() + /** + * @group legacy + */ + public function testParamTagTypeIsOmittedLegacy() { $this->assertNull($this->extractor->getTypes(PhpStanOmittedParamTagTypeDocBlock::class, 'omittedType')); } - public static function invalidTypesProvider() + public static function provideLegacyInvalidTypes() { return [ 'pub' => ['pub'], @@ -65,118 +80,124 @@ public static function invalidTypesProvider() } /** - * @dataProvider invalidTypesProvider + * @group legacy + * + * @dataProvider provideLegacyInvalidTypes */ - public function testInvalid($property) + public function testInvalidLegacy($property) { $this->assertNull($this->extractor->getTypes('Symfony\Component\PropertyInfo\Tests\Fixtures\InvalidDummy', $property)); } /** - * @dataProvider typesWithNoPrefixesProvider + * @group legacy + * + * @dataProvider provideLegacyTypesWithNoPrefixes */ - public function testExtractTypesWithNoPrefixes($property, array $type = null) + public function testExtractTypesWithNoPrefixesLegacy($property, array $type = null) { $noPrefixExtractor = new PhpStanExtractor([], [], []); $this->assertEquals($type, $noPrefixExtractor->getTypes('Symfony\Component\PropertyInfo\Tests\Fixtures\Dummy', $property)); } - public static function typesProvider() + public static function provideLegacyTypes() { return [ ['foo', null], - ['bar', [new Type(Type::BUILTIN_TYPE_STRING)]], - ['baz', [new Type(Type::BUILTIN_TYPE_INT)]], - ['foo2', [new Type(Type::BUILTIN_TYPE_FLOAT)]], - ['foo3', [new Type(Type::BUILTIN_TYPE_CALLABLE)]], - ['foo4', [new Type(Type::BUILTIN_TYPE_NULL)]], + ['bar', [new LegacyType(LegacyType::BUILTIN_TYPE_STRING)]], + ['baz', [new LegacyType(LegacyType::BUILTIN_TYPE_INT)]], + ['foo2', [new LegacyType(LegacyType::BUILTIN_TYPE_FLOAT)]], + ['foo3', [new LegacyType(LegacyType::BUILTIN_TYPE_CALLABLE)]], + ['foo4', [new LegacyType(LegacyType::BUILTIN_TYPE_NULL)]], ['foo5', null], [ 'files', [ - new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_OBJECT, false, 'SplFileInfo')), - new Type(Type::BUILTIN_TYPE_RESOURCE), + new LegacyType(LegacyType::BUILTIN_TYPE_ARRAY, false, null, true, new LegacyType(LegacyType::BUILTIN_TYPE_INT), new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, 'SplFileInfo')), + new LegacyType(LegacyType::BUILTIN_TYPE_RESOURCE), ], ], - ['bal', [new Type(Type::BUILTIN_TYPE_OBJECT, false, 'DateTimeImmutable')]], - ['parent', [new Type(Type::BUILTIN_TYPE_OBJECT, false, 'Symfony\Component\PropertyInfo\Tests\Fixtures\ParentDummy')]], - ['collection', [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_OBJECT, false, 'DateTimeImmutable'))]], - ['nestedCollection', [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_STRING, false)))]], - ['mixedCollection', [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, [new Type(Type::BUILTIN_TYPE_INT)], null)]], - ['a', [new Type(Type::BUILTIN_TYPE_INT)]], - ['b', [new Type(Type::BUILTIN_TYPE_OBJECT, true, 'Symfony\Component\PropertyInfo\Tests\Fixtures\ParentDummy')]], - ['c', [new Type(Type::BUILTIN_TYPE_BOOL, true)]], - ['d', [new Type(Type::BUILTIN_TYPE_BOOL)]], - ['e', [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_RESOURCE))]], - ['f', [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_OBJECT, false, 'DateTimeImmutable'))]], - ['g', [new Type(Type::BUILTIN_TYPE_ARRAY, true, null, true)]], - ['h', [new Type(Type::BUILTIN_TYPE_STRING, true)]], - ['j', [new Type(Type::BUILTIN_TYPE_OBJECT, true, 'DateTimeImmutable')]], - ['nullableCollectionOfNonNullableElements', [new Type(Type::BUILTIN_TYPE_ARRAY, true, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_INT, false))]], + ['bal', [new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, 'DateTimeImmutable')]], + ['parent', [new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, 'Symfony\Component\PropertyInfo\Tests\Fixtures\ParentDummy')]], + ['collection', [new LegacyType(LegacyType::BUILTIN_TYPE_ARRAY, false, null, true, new LegacyType(LegacyType::BUILTIN_TYPE_INT), new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, 'DateTimeImmutable'))]], + ['nestedCollection', [new LegacyType(LegacyType::BUILTIN_TYPE_ARRAY, false, null, true, new LegacyType(LegacyType::BUILTIN_TYPE_INT), new LegacyType(LegacyType::BUILTIN_TYPE_ARRAY, false, null, true, new LegacyType(LegacyType::BUILTIN_TYPE_INT), new LegacyType(LegacyType::BUILTIN_TYPE_STRING, false)))]], + ['mixedCollection', [new LegacyType(LegacyType::BUILTIN_TYPE_ARRAY, false, null, true, [new LegacyType(LegacyType::BUILTIN_TYPE_INT)], null)]], + ['a', [new LegacyType(LegacyType::BUILTIN_TYPE_INT)]], + ['b', [new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, true, 'Symfony\Component\PropertyInfo\Tests\Fixtures\ParentDummy')]], + ['c', [new LegacyType(LegacyType::BUILTIN_TYPE_BOOL, true)]], + ['d', [new LegacyType(LegacyType::BUILTIN_TYPE_BOOL)]], + ['e', [new LegacyType(LegacyType::BUILTIN_TYPE_ARRAY, false, null, true, new LegacyType(LegacyType::BUILTIN_TYPE_INT), new LegacyType(LegacyType::BUILTIN_TYPE_RESOURCE))]], + ['f', [new LegacyType(LegacyType::BUILTIN_TYPE_ARRAY, false, null, true, new LegacyType(LegacyType::BUILTIN_TYPE_INT), new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, 'DateTimeImmutable'))]], + ['g', [new LegacyType(LegacyType::BUILTIN_TYPE_ARRAY, true, null, true)]], + ['h', [new LegacyType(LegacyType::BUILTIN_TYPE_STRING, true)]], + ['j', [new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, true, 'DateTimeImmutable')]], + ['nullableCollectionOfNonNullableElements', [new LegacyType(LegacyType::BUILTIN_TYPE_ARRAY, true, null, true, new LegacyType(LegacyType::BUILTIN_TYPE_INT), new LegacyType(LegacyType::BUILTIN_TYPE_INT, false))]], ['donotexist', null], ['staticGetter', null], ['staticSetter', null], ['emptyVar', null], - ['arrayWithKeys', [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_STRING), new Type(Type::BUILTIN_TYPE_STRING))]], - ['arrayOfMixed', [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_STRING), null)]], - ['listOfStrings', [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_STRING))]], - ['self', [new Type(Type::BUILTIN_TYPE_OBJECT, false, Dummy::class)]], - ['rootDummyItems', [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_OBJECT, false, RootDummyItem::class))]], - ['rootDummyItem', [new Type(Type::BUILTIN_TYPE_OBJECT, false, RootDummyItem::class)]], + ['arrayWithKeys', [new LegacyType(LegacyType::BUILTIN_TYPE_ARRAY, false, null, true, new LegacyType(LegacyType::BUILTIN_TYPE_STRING), new LegacyType(LegacyType::BUILTIN_TYPE_STRING))]], + ['arrayOfMixed', [new LegacyType(LegacyType::BUILTIN_TYPE_ARRAY, false, null, true, new LegacyType(LegacyType::BUILTIN_TYPE_STRING), null)]], + ['listOfStrings', [new LegacyType(LegacyType::BUILTIN_TYPE_ARRAY, false, null, true, new LegacyType(LegacyType::BUILTIN_TYPE_INT), new LegacyType(LegacyType::BUILTIN_TYPE_STRING))]], + ['self', [new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, Dummy::class)]], + ['rootDummyItems', [new LegacyType(LegacyType::BUILTIN_TYPE_ARRAY, false, null, true, new LegacyType(LegacyType::BUILTIN_TYPE_INT), new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, RootDummyItem::class))]], + ['rootDummyItem', [new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, RootDummyItem::class)]], ]; } /** - * @dataProvider provideCollectionTypes + * @group legacy + * + * @dataProvider provideLegacyCollectionTypes */ - public function testExtractCollection($property, array $type = null) + public function testExtractCollectionLegacy($property, array $type = null) { - $this->testExtract($property, $type); + $this->testExtractLegacy($property, $type); } - public static function provideCollectionTypes() + public static function provideLegacyCollectionTypes() { return [ - ['iteratorCollection', [new Type(Type::BUILTIN_TYPE_OBJECT, false, 'Iterator', true, null, new Type(Type::BUILTIN_TYPE_STRING))]], - ['iteratorCollectionWithKey', [new Type(Type::BUILTIN_TYPE_OBJECT, false, 'Iterator', true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_STRING))]], + ['iteratorCollection', [new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, 'Iterator', true, null, new LegacyType(LegacyType::BUILTIN_TYPE_STRING))]], + ['iteratorCollectionWithKey', [new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, 'Iterator', true, new LegacyType(LegacyType::BUILTIN_TYPE_INT), new LegacyType(LegacyType::BUILTIN_TYPE_STRING))]], [ 'nestedIterators', - [new Type( - Type::BUILTIN_TYPE_OBJECT, + [new LegacyType( + LegacyType::BUILTIN_TYPE_OBJECT, false, 'Iterator', true, - new Type(Type::BUILTIN_TYPE_INT), - new Type(Type::BUILTIN_TYPE_OBJECT, false, 'Iterator', true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_STRING)) + new LegacyType(LegacyType::BUILTIN_TYPE_INT), + new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, 'Iterator', true, new LegacyType(LegacyType::BUILTIN_TYPE_INT), new LegacyType(LegacyType::BUILTIN_TYPE_STRING)) )], ], [ 'arrayWithKeys', - [new Type( - Type::BUILTIN_TYPE_ARRAY, + [new LegacyType( + LegacyType::BUILTIN_TYPE_ARRAY, false, null, true, - new Type(Type::BUILTIN_TYPE_STRING), - new Type(Type::BUILTIN_TYPE_STRING) + new LegacyType(LegacyType::BUILTIN_TYPE_STRING), + new LegacyType(LegacyType::BUILTIN_TYPE_STRING) )], ], [ 'arrayWithKeysAndComplexValue', - [new Type( - Type::BUILTIN_TYPE_ARRAY, + [new LegacyType( + LegacyType::BUILTIN_TYPE_ARRAY, false, null, true, - new Type(Type::BUILTIN_TYPE_STRING), - new Type( - Type::BUILTIN_TYPE_ARRAY, + new LegacyType(LegacyType::BUILTIN_TYPE_STRING), + new LegacyType( + LegacyType::BUILTIN_TYPE_ARRAY, true, null, true, - new Type(Type::BUILTIN_TYPE_INT), - new Type(Type::BUILTIN_TYPE_STRING, true) + new LegacyType(LegacyType::BUILTIN_TYPE_INT), + new LegacyType(LegacyType::BUILTIN_TYPE_STRING, true) ) )], ], @@ -184,253 +205,277 @@ public static function provideCollectionTypes() } /** - * @dataProvider typesWithCustomPrefixesProvider + * @group legacy + * + * @dataProvider provideLegacyTypesWithCustomPrefixes */ - public function testExtractTypesWithCustomPrefixes($property, array $type = null) + public function testExtractTypesWithCustomPrefixesLegacy($property, array $type = null) { $customExtractor = new PhpStanExtractor(['add', 'remove'], ['is', 'can']); $this->assertEquals($type, $customExtractor->getTypes('Symfony\Component\PropertyInfo\Tests\Fixtures\Dummy', $property)); } - public static function typesWithCustomPrefixesProvider() + public static function provideLegacyTypesWithCustomPrefixes() { return [ ['foo', null], - ['bar', [new Type(Type::BUILTIN_TYPE_STRING)]], - ['baz', [new Type(Type::BUILTIN_TYPE_INT)]], - ['foo2', [new Type(Type::BUILTIN_TYPE_FLOAT)]], - ['foo3', [new Type(Type::BUILTIN_TYPE_CALLABLE)]], - ['foo4', [new Type(Type::BUILTIN_TYPE_NULL)]], + ['bar', [new LegacyType(LegacyType::BUILTIN_TYPE_STRING)]], + ['baz', [new LegacyType(LegacyType::BUILTIN_TYPE_INT)]], + ['foo2', [new LegacyType(LegacyType::BUILTIN_TYPE_FLOAT)]], + ['foo3', [new LegacyType(LegacyType::BUILTIN_TYPE_CALLABLE)]], + ['foo4', [new LegacyType(LegacyType::BUILTIN_TYPE_NULL)]], ['foo5', null], [ 'files', [ - new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_OBJECT, false, 'SplFileInfo')), - new Type(Type::BUILTIN_TYPE_RESOURCE), + new LegacyType(LegacyType::BUILTIN_TYPE_ARRAY, false, null, true, new LegacyType(LegacyType::BUILTIN_TYPE_INT), new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, 'SplFileInfo')), + new LegacyType(LegacyType::BUILTIN_TYPE_RESOURCE), ], ], - ['bal', [new Type(Type::BUILTIN_TYPE_OBJECT, false, 'DateTimeImmutable')]], - ['parent', [new Type(Type::BUILTIN_TYPE_OBJECT, false, 'Symfony\Component\PropertyInfo\Tests\Fixtures\ParentDummy')]], - ['collection', [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_OBJECT, false, 'DateTimeImmutable'))]], - ['nestedCollection', [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_STRING, false)))]], - ['mixedCollection', [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, [new Type(Type::BUILTIN_TYPE_INT)], null)]], + ['bal', [new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, 'DateTimeImmutable')]], + ['parent', [new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, 'Symfony\Component\PropertyInfo\Tests\Fixtures\ParentDummy')]], + ['collection', [new LegacyType(LegacyType::BUILTIN_TYPE_ARRAY, false, null, true, new LegacyType(LegacyType::BUILTIN_TYPE_INT), new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, 'DateTimeImmutable'))]], + ['nestedCollection', [new LegacyType(LegacyType::BUILTIN_TYPE_ARRAY, false, null, true, new LegacyType(LegacyType::BUILTIN_TYPE_INT), new LegacyType(LegacyType::BUILTIN_TYPE_ARRAY, false, null, true, new LegacyType(LegacyType::BUILTIN_TYPE_INT), new LegacyType(LegacyType::BUILTIN_TYPE_STRING, false)))]], + ['mixedCollection', [new LegacyType(LegacyType::BUILTIN_TYPE_ARRAY, false, null, true, [new LegacyType(LegacyType::BUILTIN_TYPE_INT)], null)]], ['a', null], ['b', null], - ['c', [new Type(Type::BUILTIN_TYPE_BOOL, true)]], - ['d', [new Type(Type::BUILTIN_TYPE_BOOL)]], - ['e', [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_RESOURCE))]], - ['f', [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_OBJECT, false, 'DateTimeImmutable'))]], - ['g', [new Type(Type::BUILTIN_TYPE_ARRAY, true, null, true)]], - ['h', [new Type(Type::BUILTIN_TYPE_STRING, true)]], - ['j', [new Type(Type::BUILTIN_TYPE_OBJECT, true, 'DateTimeImmutable')]], - ['nullableCollectionOfNonNullableElements', [new Type(Type::BUILTIN_TYPE_ARRAY, true, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_INT, false))]], + ['c', [new LegacyType(LegacyType::BUILTIN_TYPE_BOOL, true)]], + ['d', [new LegacyType(LegacyType::BUILTIN_TYPE_BOOL)]], + ['e', [new LegacyType(LegacyType::BUILTIN_TYPE_ARRAY, false, null, true, new LegacyType(LegacyType::BUILTIN_TYPE_INT), new LegacyType(LegacyType::BUILTIN_TYPE_RESOURCE))]], + ['f', [new LegacyType(LegacyType::BUILTIN_TYPE_ARRAY, false, null, true, new LegacyType(LegacyType::BUILTIN_TYPE_INT), new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, 'DateTimeImmutable'))]], + ['g', [new LegacyType(LegacyType::BUILTIN_TYPE_ARRAY, true, null, true)]], + ['h', [new LegacyType(LegacyType::BUILTIN_TYPE_STRING, true)]], + ['j', [new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, true, 'DateTimeImmutable')]], + ['nullableCollectionOfNonNullableElements', [new LegacyType(LegacyType::BUILTIN_TYPE_ARRAY, true, null, true, new LegacyType(LegacyType::BUILTIN_TYPE_INT), new LegacyType(LegacyType::BUILTIN_TYPE_INT, false))]], ['donotexist', null], ['staticGetter', null], ['staticSetter', null], ]; } - public static function typesWithNoPrefixesProvider() + public static function provideLegacyTypesWithNoPrefixes() { return [ ['foo', null], - ['bar', [new Type(Type::BUILTIN_TYPE_STRING)]], - ['baz', [new Type(Type::BUILTIN_TYPE_INT)]], - ['foo2', [new Type(Type::BUILTIN_TYPE_FLOAT)]], - ['foo3', [new Type(Type::BUILTIN_TYPE_CALLABLE)]], - ['foo4', [new Type(Type::BUILTIN_TYPE_NULL)]], + ['bar', [new LegacyType(LegacyType::BUILTIN_TYPE_STRING)]], + ['baz', [new LegacyType(LegacyType::BUILTIN_TYPE_INT)]], + ['foo2', [new LegacyType(LegacyType::BUILTIN_TYPE_FLOAT)]], + ['foo3', [new LegacyType(LegacyType::BUILTIN_TYPE_CALLABLE)]], + ['foo4', [new LegacyType(LegacyType::BUILTIN_TYPE_NULL)]], ['foo5', null], [ 'files', [ - new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_OBJECT, false, 'SplFileInfo')), - new Type(Type::BUILTIN_TYPE_RESOURCE), + new LegacyType(LegacyType::BUILTIN_TYPE_ARRAY, false, null, true, new LegacyType(LegacyType::BUILTIN_TYPE_INT), new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, 'SplFileInfo')), + new LegacyType(LegacyType::BUILTIN_TYPE_RESOURCE), ], ], - ['bal', [new Type(Type::BUILTIN_TYPE_OBJECT, false, 'DateTimeImmutable')]], - ['parent', [new Type(Type::BUILTIN_TYPE_OBJECT, false, 'Symfony\Component\PropertyInfo\Tests\Fixtures\ParentDummy')]], - ['collection', [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_OBJECT, false, 'DateTimeImmutable'))]], - ['nestedCollection', [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_STRING, false)))]], - ['mixedCollection', [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, [new Type(Type::BUILTIN_TYPE_INT)], null)]], + ['bal', [new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, 'DateTimeImmutable')]], + ['parent', [new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, 'Symfony\Component\PropertyInfo\Tests\Fixtures\ParentDummy')]], + ['collection', [new LegacyType(LegacyType::BUILTIN_TYPE_ARRAY, false, null, true, new LegacyType(LegacyType::BUILTIN_TYPE_INT), new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, 'DateTimeImmutable'))]], + ['nestedCollection', [new LegacyType(LegacyType::BUILTIN_TYPE_ARRAY, false, null, true, new LegacyType(LegacyType::BUILTIN_TYPE_INT), new LegacyType(LegacyType::BUILTIN_TYPE_ARRAY, false, null, true, new LegacyType(LegacyType::BUILTIN_TYPE_INT), new LegacyType(LegacyType::BUILTIN_TYPE_STRING, false)))]], + ['mixedCollection', [new LegacyType(LegacyType::BUILTIN_TYPE_ARRAY, false, null, true, [new LegacyType(LegacyType::BUILTIN_TYPE_INT)], null)]], ['a', null], ['b', null], ['c', null], ['d', null], ['e', null], ['f', null], - ['g', [new Type(Type::BUILTIN_TYPE_ARRAY, true, null, true)]], - ['h', [new Type(Type::BUILTIN_TYPE_STRING, true)]], - ['j', [new Type(Type::BUILTIN_TYPE_OBJECT, true, 'DateTimeImmutable')]], - ['nullableCollectionOfNonNullableElements', [new Type(Type::BUILTIN_TYPE_ARRAY, true, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_INT, false))]], + ['g', [new LegacyType(LegacyType::BUILTIN_TYPE_ARRAY, true, null, true)]], + ['h', [new LegacyType(LegacyType::BUILTIN_TYPE_STRING, true)]], + ['j', [new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, true, 'DateTimeImmutable')]], + ['nullableCollectionOfNonNullableElements', [new LegacyType(LegacyType::BUILTIN_TYPE_ARRAY, true, null, true, new LegacyType(LegacyType::BUILTIN_TYPE_INT), new LegacyType(LegacyType::BUILTIN_TYPE_INT, false))]], ['donotexist', null], ['staticGetter', null], ['staticSetter', null], ]; } - public static function dockBlockFallbackTypesProvider() + public static function provideLegacyDockBlockFallbackTypes() { return [ 'pub' => [ - 'pub', [new Type(Type::BUILTIN_TYPE_STRING)], + 'pub', [new LegacyType(LegacyType::BUILTIN_TYPE_STRING)], ], 'protAcc' => [ - 'protAcc', [new Type(Type::BUILTIN_TYPE_INT)], + 'protAcc', [new LegacyType(LegacyType::BUILTIN_TYPE_INT)], ], 'protMut' => [ - 'protMut', [new Type(Type::BUILTIN_TYPE_BOOL)], + 'protMut', [new LegacyType(LegacyType::BUILTIN_TYPE_BOOL)], ], ]; } /** - * @dataProvider dockBlockFallbackTypesProvider + * @group legacy + * + * @dataProvider provideLegacyDockBlockFallbackTypes */ - public function testDocBlockFallback($property, $types) + public function testDocBlockFallbackLegacy($property, $types) { $this->assertEquals($types, $this->extractor->getTypes('Symfony\Component\PropertyInfo\Tests\Fixtures\DockBlockFallback', $property)); } /** - * @dataProvider propertiesDefinedByTraitsProvider + * @group legacy + * + * @dataProvider provideLegacyPropertiesDefinedByTraits */ - public function testPropertiesDefinedByTraits(string $property, Type $type) + public function testPropertiesDefinedByTraitsLegacy(string $property, LegacyType $type) { $this->assertEquals([$type], $this->extractor->getTypes(DummyUsingTrait::class, $property)); } - public static function propertiesDefinedByTraitsProvider(): array + public static function provideLegacyPropertiesDefinedByTraits(): array { return [ - ['propertyInTraitPrimitiveType', new Type(Type::BUILTIN_TYPE_STRING)], - ['propertyInTraitObjectSameNamespace', new Type(Type::BUILTIN_TYPE_OBJECT, false, DummyUsedInTrait::class)], - ['propertyInTraitObjectDifferentNamespace', new Type(Type::BUILTIN_TYPE_OBJECT, false, Dummy::class)], + ['propertyInTraitPrimitiveType', new LegacyType(LegacyType::BUILTIN_TYPE_STRING)], + ['propertyInTraitObjectSameNamespace', new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, DummyUsedInTrait::class)], + ['propertyInTraitObjectDifferentNamespace', new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, Dummy::class)], ]; } /** - * @dataProvider propertiesStaticTypeProvider + * @group legacy + * + * @dataProvider provideLegacyPropertiesStaticType */ - public function testPropertiesStaticType(string $class, string $property, Type $type) + public function testPropertiesStaticTypeLegacy(string $class, string $property, LegacyType $type) { $this->assertEquals([$type], $this->extractor->getTypes($class, $property)); } - public static function propertiesStaticTypeProvider(): array + public static function provideLegacyPropertiesStaticType(): array { return [ - [ParentDummy::class, 'propertyTypeStatic', new Type(Type::BUILTIN_TYPE_OBJECT, false, ParentDummy::class)], - [Dummy::class, 'propertyTypeStatic', new Type(Type::BUILTIN_TYPE_OBJECT, false, Dummy::class)], + [ParentDummy::class, 'propertyTypeStatic', new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, ParentDummy::class)], + [Dummy::class, 'propertyTypeStatic', new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, Dummy::class)], ]; } /** - * @dataProvider propertiesParentTypeProvider + * @group legacy + * + * @dataProvider provideLegacyPropertiesParentType */ - public function testPropertiesParentType(string $class, string $property, ?array $types) + public function testPropertiesParentTypeLegacy(string $class, string $property, ?array $types) { $this->assertEquals($types, $this->extractor->getTypes($class, $property)); } - public static function propertiesParentTypeProvider(): array + public static function provideLegacyPropertiesParentType(): array { return [ - [ParentDummy::class, 'parentAnnotationNoParent', [new Type(Type::BUILTIN_TYPE_OBJECT, false, 'parent')]], - [Dummy::class, 'parentAnnotation', [new Type(Type::BUILTIN_TYPE_OBJECT, false, ParentDummy::class)]], + [ParentDummy::class, 'parentAnnotationNoParent', [new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, 'parent')]], + [Dummy::class, 'parentAnnotation', [new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, ParentDummy::class)]], ]; } /** - * @dataProvider constructorTypesProvider + * @group legacy + * + * @dataProvider provideLegacyConstructorTypes */ - public function testExtractConstructorTypes($property, array $type = null) + public function testExtractConstructorTypesLegacy($property, array $type = null) { $this->assertEquals($type, $this->extractor->getTypesFromConstructor('Symfony\Component\PropertyInfo\Tests\Fixtures\ConstructorDummy', $property)); } /** - * @dataProvider constructorTypesProvider + * @group legacy + * + * @dataProvider provideLegacyConstructorTypes */ - public function testExtractConstructorTypesReturnNullOnEmptyDocBlock($property) + public function testExtractConstructorTypesReturnNullOnEmptyDocBlockLegacy($property) { $this->assertNull($this->extractor->getTypesFromConstructor(ConstructorDummyWithoutDocBlock::class, $property)); } - public static function constructorTypesProvider() + public static function provideLegacyConstructorTypes() { return [ - ['date', [new Type(Type::BUILTIN_TYPE_INT)]], - ['timezone', [new Type(Type::BUILTIN_TYPE_OBJECT, false, 'DateTimeZone')]], - ['dateObject', [new Type(Type::BUILTIN_TYPE_OBJECT, false, 'DateTimeInterface')]], + ['date', [new LegacyType(LegacyType::BUILTIN_TYPE_INT)]], + ['timezone', [new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, 'DateTimeZone')]], + ['dateObject', [new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, 'DateTimeInterface')]], ['dateTime', null], ['ddd', null], ]; } /** - * @dataProvider unionTypesProvider + * @group legacy + * + * @dataProvider provideLegacyUnionTypes */ - public function testExtractorUnionTypes(string $property, ?array $types) + public function testExtractorUnionTypesLegacy(string $property, ?array $types) { $this->assertEquals($types, $this->extractor->getTypes('Symfony\Component\PropertyInfo\Tests\Fixtures\DummyUnionType', $property)); } - public static function unionTypesProvider(): array + public static function provideLegacyUnionTypes(): array { return [ - ['a', [new Type(Type::BUILTIN_TYPE_STRING), new Type(Type::BUILTIN_TYPE_INT)]], - ['b', [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, [new Type(Type::BUILTIN_TYPE_INT)], [new Type(Type::BUILTIN_TYPE_STRING), new Type(Type::BUILTIN_TYPE_INT)])]], - ['c', [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, [], [new Type(Type::BUILTIN_TYPE_STRING), new Type(Type::BUILTIN_TYPE_INT)])]], - ['d', [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, [new Type(Type::BUILTIN_TYPE_STRING), new Type(Type::BUILTIN_TYPE_INT)], [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, [], [new Type(Type::BUILTIN_TYPE_STRING)])])]], - ['e', [new Type(Type::BUILTIN_TYPE_OBJECT, true, Dummy::class, true, [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, [], [new Type(Type::BUILTIN_TYPE_STRING)])], [new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, [new Type(Type::BUILTIN_TYPE_INT)], [new Type(Type::BUILTIN_TYPE_STRING, false, null, true, [], [new Type(Type::BUILTIN_TYPE_OBJECT, false, DefaultValue::class)])])]), new Type(Type::BUILTIN_TYPE_OBJECT, false, ParentDummy::class)]], + ['a', [new LegacyType(LegacyType::BUILTIN_TYPE_INT), new LegacyType(LegacyType::BUILTIN_TYPE_STRING)]], + ['b', [new LegacyType(LegacyType::BUILTIN_TYPE_ARRAY, false, null, true, [new LegacyType(LegacyType::BUILTIN_TYPE_INT)], [new LegacyType(LegacyType::BUILTIN_TYPE_STRING), new LegacyType(LegacyType::BUILTIN_TYPE_INT)])]], + ['c', [new LegacyType(LegacyType::BUILTIN_TYPE_ARRAY, false, null, true, [], [new LegacyType(LegacyType::BUILTIN_TYPE_STRING), new LegacyType(LegacyType::BUILTIN_TYPE_INT)])]], + ['d', [new LegacyType(LegacyType::BUILTIN_TYPE_ARRAY, false, null, true, [new LegacyType(LegacyType::BUILTIN_TYPE_STRING), new LegacyType(LegacyType::BUILTIN_TYPE_INT)], [new LegacyType(LegacyType::BUILTIN_TYPE_ARRAY, false, null, true, [], [new LegacyType(LegacyType::BUILTIN_TYPE_STRING)])])]], + ['e', [new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, true, Dummy::class, true, [new LegacyType(LegacyType::BUILTIN_TYPE_ARRAY, false, null, true, [], [new LegacyType(LegacyType::BUILTIN_TYPE_STRING)])], [new LegacyType(LegacyType::BUILTIN_TYPE_INT), new LegacyType(LegacyType::BUILTIN_TYPE_ARRAY, false, null, true, [new LegacyType(LegacyType::BUILTIN_TYPE_INT)], [new LegacyType(LegacyType::BUILTIN_TYPE_STRING, false, null, true, [], [new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, DefaultValue::class)])])]), new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, true, ParentDummy::class)]], ['f', null], - ['g', [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, [], [new Type(Type::BUILTIN_TYPE_STRING), new Type(Type::BUILTIN_TYPE_INT)])]], + ['g', [new LegacyType(LegacyType::BUILTIN_TYPE_ARRAY, false, null, true, [], [new LegacyType(LegacyType::BUILTIN_TYPE_INT), new LegacyType(LegacyType::BUILTIN_TYPE_STRING)])]], ]; } /** - * @dataProvider pseudoTypesProvider + * @group legacy + * + * @dataProvider provideLegacyPseudoTypes */ - public function testPseudoTypes($property, array $type) + public function testPseudoTypesLegacy($property, array $type) { $this->assertEquals($type, $this->extractor->getTypes('Symfony\Component\PropertyInfo\Tests\Fixtures\PhpStanPseudoTypesDummy', $property)); } - public static function pseudoTypesProvider(): array + public static function provideLegacyPseudoTypes(): array { return [ - ['classString', [new Type(Type::BUILTIN_TYPE_STRING, false, null)]], - ['classStringGeneric', [new Type(Type::BUILTIN_TYPE_STRING, false, null)]], - ['htmlEscapedString', [new Type(Type::BUILTIN_TYPE_STRING, false, null)]], - ['lowercaseString', [new Type(Type::BUILTIN_TYPE_STRING, false, null)]], - ['nonEmptyLowercaseString', [new Type(Type::BUILTIN_TYPE_STRING, false, null)]], - ['nonEmptyString', [new Type(Type::BUILTIN_TYPE_STRING, false, null)]], - ['numericString', [new Type(Type::BUILTIN_TYPE_STRING, false, null)]], - ['traitString', [new Type(Type::BUILTIN_TYPE_STRING, false, null)]], - ['interfaceString', [new Type(Type::BUILTIN_TYPE_STRING, false, null)]], - ['literalString', [new Type(Type::BUILTIN_TYPE_STRING, false, null)]], - ['positiveInt', [new Type(Type::BUILTIN_TYPE_INT, false, null)]], - ['negativeInt', [new Type(Type::BUILTIN_TYPE_INT, false, null)]], - ['nonEmptyArray', [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true)]], - ['nonEmptyList', [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT))]], - ['scalar', [new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_FLOAT), new Type(Type::BUILTIN_TYPE_STRING), new Type(Type::BUILTIN_TYPE_BOOL)]], - ['number', [new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_FLOAT)]], - ['numeric', [new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_FLOAT), new Type(Type::BUILTIN_TYPE_STRING)]], - ['arrayKey', [new Type(Type::BUILTIN_TYPE_STRING), new Type(Type::BUILTIN_TYPE_INT)]], - ['double', [new Type(Type::BUILTIN_TYPE_FLOAT)]], + ['classString', [new LegacyType(LegacyType::BUILTIN_TYPE_STRING, false, null)]], + ['classStringGeneric', [new LegacyType(LegacyType::BUILTIN_TYPE_STRING, false, null)]], + ['htmlEscapedString', [new LegacyType(LegacyType::BUILTIN_TYPE_STRING, false, null)]], + ['lowercaseString', [new LegacyType(LegacyType::BUILTIN_TYPE_STRING, false, null)]], + ['nonEmptyLowercaseString', [new LegacyType(LegacyType::BUILTIN_TYPE_STRING, false, null)]], + ['nonEmptyString', [new LegacyType(LegacyType::BUILTIN_TYPE_STRING, false, null)]], + ['numericString', [new LegacyType(LegacyType::BUILTIN_TYPE_STRING, false, null)]], + ['traitString', [new LegacyType(LegacyType::BUILTIN_TYPE_STRING, false, null)]], + ['interfaceString', [new LegacyType(LegacyType::BUILTIN_TYPE_STRING, false, null)]], + ['literalString', [new LegacyType(LegacyType::BUILTIN_TYPE_STRING, false, null)]], + ['positiveInt', [new LegacyType(LegacyType::BUILTIN_TYPE_INT, false, null)]], + ['negativeInt', [new LegacyType(LegacyType::BUILTIN_TYPE_INT, false, null)]], + ['nonEmptyArray', [new LegacyType(LegacyType::BUILTIN_TYPE_ARRAY, false, null, true)]], + ['nonEmptyList', [new LegacyType(LegacyType::BUILTIN_TYPE_ARRAY, false, null, true, new LegacyType(LegacyType::BUILTIN_TYPE_INT))]], + ['scalar', [new LegacyType(LegacyType::BUILTIN_TYPE_BOOL), new LegacyType(LegacyType::BUILTIN_TYPE_FLOAT), new LegacyType(LegacyType::BUILTIN_TYPE_INT), new LegacyType(LegacyType::BUILTIN_TYPE_STRING)]], + ['number', [new LegacyType(LegacyType::BUILTIN_TYPE_FLOAT), new LegacyType(LegacyType::BUILTIN_TYPE_INT)]], + ['numeric', [new LegacyType(LegacyType::BUILTIN_TYPE_FLOAT), new LegacyType(LegacyType::BUILTIN_TYPE_INT), new LegacyType(LegacyType::BUILTIN_TYPE_STRING)]], + ['arrayKey', [new LegacyType(LegacyType::BUILTIN_TYPE_INT), new LegacyType(LegacyType::BUILTIN_TYPE_STRING)]], + ['double', [new LegacyType(LegacyType::BUILTIN_TYPE_FLOAT)]], ]; } - public function testDummyNamespace() + /** + * @group legacy + */ + public function testDummyNamespaceLegacy() { $this->assertEquals( - [new Type(Type::BUILTIN_TYPE_OBJECT, false, 'Symfony\Component\PropertyInfo\Tests\Fixtures\Dummy')], + [new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, 'Symfony\Component\PropertyInfo\Tests\Fixtures\Dummy')], $this->extractor->getTypes('Symfony\Component\PropertyInfo\Tests\Fixtures\DummyNamespace', 'dummy') ); } - public function testDummyNamespaceWithProperty() + /** + * @group legacy + */ + public function testDummyNamespaceWithPropertyLegacy() { $phpStanTypes = $this->extractor->getTypes(\B\Dummy::class, 'property'); $phpDocTypes = $this->phpDocExtractor->getTypes(\B\Dummy::class, 'property'); @@ -440,41 +485,469 @@ public function testDummyNamespaceWithProperty() } /** - * @dataProvider intRangeTypeProvider + * @group legacy + * + * @dataProvider provideLegacyIntRangeType */ - public function testExtractorIntRangeType(string $property, ?array $types) + public function testExtractorIntRangeTypeLegacy(string $property, ?array $types) { $this->assertEquals($types, $this->extractor->getTypes('Symfony\Component\PropertyInfo\Tests\Fixtures\IntRangeDummy', $property)); } - public static function intRangeTypeProvider(): array + public static function provideLegacyIntRangeType(): array { return [ - ['a', [new Type(Type::BUILTIN_TYPE_INT)]], - ['b', [new Type(Type::BUILTIN_TYPE_INT, true)]], - ['c', [new Type(Type::BUILTIN_TYPE_INT)]], + ['a', [new LegacyType(LegacyType::BUILTIN_TYPE_INT)]], + ['b', [new LegacyType(LegacyType::BUILTIN_TYPE_INT, true)]], + ['c', [new LegacyType(LegacyType::BUILTIN_TYPE_INT)]], ]; } /** - * @dataProvider php80TypesProvider + * @group legacy + * + * @dataProvider provideLegacyPhp80Types */ - public function testExtractPhp80Type(string $class, $property, array $type = null) + public function testExtractPhp80TypeLegacy(string $class, $property, array $type = null) { $this->assertEquals($type, $this->extractor->getTypes($class, $property, [])); } - public static function php80TypesProvider() + public static function provideLegacyPhp80Types() { return [ - [Php80Dummy::class, 'promotedWithDocCommentAndType', [new Type(Type::BUILTIN_TYPE_INT)]], - [Php80Dummy::class, 'promotedWithDocComment', [new Type(Type::BUILTIN_TYPE_STRING)]], - [Php80Dummy::class, 'promotedAndMutated', [new Type(Type::BUILTIN_TYPE_STRING)]], + [Php80Dummy::class, 'promotedWithDocCommentAndType', [new LegacyType(LegacyType::BUILTIN_TYPE_INT)]], + [Php80Dummy::class, 'promotedWithDocComment', [new LegacyType(LegacyType::BUILTIN_TYPE_STRING)]], + [Php80Dummy::class, 'promotedAndMutated', [new LegacyType(LegacyType::BUILTIN_TYPE_STRING)]], [Php80Dummy::class, 'promoted', null], - [Php80Dummy::class, 'collection', [new Type(Type::BUILTIN_TYPE_ARRAY, collection: true, collectionValueType: new Type(Type::BUILTIN_TYPE_STRING))]], + [Php80Dummy::class, 'collection', [new LegacyType(LegacyType::BUILTIN_TYPE_ARRAY, collection: true, collectionValueType: new LegacyType(LegacyType::BUILTIN_TYPE_STRING))]], [Php80PromotedDummy::class, 'promoted', null], ]; } + + /** + * @group legacy + * + * @dataProvider allowPrivateAccessLegacyProvider + */ + public function testAllowPrivateAccessLegacy(bool $allowPrivateAccess, array $expectedTypes) + { + $extractor = new PhpStanExtractor(allowPrivateAccess: $allowPrivateAccess); + $this->assertEquals( + $expectedTypes, + $extractor->getTypes(DummyPropertyAndGetterWithDifferentTypes::class, 'foo') + ); + } + + public static function allowPrivateAccessLegacyProvider(): array + { + return [ + [true, [new LegacyType('string')]], + [false, [new LegacyType('array', collection: true, collectionKeyType: new LegacyType('int'), collectionValueType: new LegacyType('string'))]], + ]; + } + + /** + * @dataProvider typesProvider + */ + public function testExtract(string $property, ?Type $type) + { + $this->assertEquals($type, $this->extractor->getType(Dummy::class, $property)); + } + + public static function typesProvider(): iterable + { + yield ['foo', null]; + yield ['bar', Type::string()]; + yield ['baz', Type::int()]; + yield ['foo2', Type::float()]; + yield ['foo3', Type::callable()]; + yield ['foo5', Type::mixed()]; + yield ['files', Type::union(Type::list(Type::object(\SplFileInfo::class)), Type::resource()), null, null]; + yield ['bal', Type::object(\DateTimeImmutable::class)]; + yield ['parent', Type::object(ParentDummy::class)]; + yield ['collection', Type::list(Type::object(\DateTimeImmutable::class))]; + yield ['nestedCollection', Type::list(Type::list(Type::string()))]; + yield ['mixedCollection', Type::list()]; + yield ['a', Type::int()]; + yield ['b', Type::nullable(Type::object(ParentDummy::class))]; + yield ['c', Type::nullable(Type::bool())]; + yield ['d', Type::bool()]; + yield ['e', Type::list(Type::resource())]; + yield ['f', Type::list(Type::object(\DateTimeImmutable::class))]; + yield ['g', Type::nullable(Type::array())]; + yield ['h', Type::nullable(Type::string())]; + yield ['i', Type::union(Type::int(), Type::string(), Type::null())]; + yield ['j', Type::nullable(Type::object(\DateTimeImmutable::class))]; + yield ['nullableCollectionOfNonNullableElements', Type::nullable(Type::list(Type::int()))]; + yield ['donotexist', null]; + yield ['staticGetter', null]; + yield ['staticSetter', null]; + yield ['emptyVar', null]; + yield ['arrayWithKeys', Type::dict(Type::string())]; + yield ['arrayOfMixed', Type::dict(Type::mixed())]; + yield ['listOfStrings', Type::list(Type::string())]; + yield ['self', Type::object(Dummy::class)]; + yield ['rootDummyItems', Type::list(Type::object(RootDummyItem::class))]; + yield ['rootDummyItem', Type::object(RootDummyItem::class)]; + } + + public function testParamTagTypeIsOmitted() + { + $this->assertNull($this->extractor->getType(PhpStanOmittedParamTagTypeDocBlock::class, 'omittedType')); + } + + /** + * @dataProvider invalidTypesProvider + */ + public function testInvalid(string $property) + { + $this->assertNull($this->extractor->getType(InvalidDummy::class, $property)); + } + + /** + * @return iterable + */ + public static function invalidTypesProvider(): iterable + { + yield 'pub' => ['pub']; + yield 'stat' => ['stat']; + yield 'foo' => ['foo']; + yield 'bar' => ['bar']; + } + + /** + * @dataProvider typesWithNoPrefixesProvider + */ + public function testExtractTypesWithNoPrefixes(string $property, ?Type $type) + { + $noPrefixExtractor = new PhpStanExtractor([], [], []); + + $this->assertEquals($type, $noPrefixExtractor->getType(Dummy::class, $property)); + } + + /** + * @return iterable + */ + public static function typesWithNoPrefixesProvider(): iterable + { + yield ['foo', null]; + yield ['bar', Type::string()]; + yield ['baz', Type::int()]; + yield ['foo2', Type::float()]; + yield ['foo3', Type::callable()]; + yield ['foo5', Type::mixed()]; + yield ['files', Type::union(Type::list(Type::object(\SplFileInfo::class)), Type::resource())]; + yield ['bal', Type::object(\DateTimeImmutable::class)]; + yield ['parent', Type::object(ParentDummy::class)]; + yield ['collection', Type::list(Type::object(\DateTimeImmutable::class))]; + yield ['nestedCollection', Type::list(Type::list(Type::string()))]; + yield ['mixedCollection', Type::list()]; + yield ['a', null]; + yield ['b', null]; + yield ['c', null]; + yield ['d', null]; + yield ['e', null]; + yield ['f', null]; + yield ['g', Type::nullable(Type::array())]; + yield ['h', Type::nullable(Type::string())]; + yield ['i', Type::union(Type::int(), Type::string(), Type::null())]; + yield ['j', Type::nullable(Type::object(\DateTimeImmutable::class))]; + yield ['nullableCollectionOfNonNullableElements', Type::nullable(Type::list(Type::int()))]; + yield ['donotexist', null]; + yield ['staticGetter', null]; + yield ['staticSetter', null]; + } + + /** + * @dataProvider provideCollectionTypes + */ + public function testExtractCollection($property, ?Type $type) + { + $this->testExtract($property, $type); + } + + /** + * @return iterable + */ + public static function provideCollectionTypes(): iterable + { + yield ['iteratorCollection', Type::collection(Type::object(\Iterator::class), Type::string())]; + yield ['iteratorCollectionWithKey', Type::collection(Type::object(\Iterator::class), Type::string(), Type::int())]; + yield ['nestedIterators', Type::collection(Type::object(\Iterator::class), Type::collection(Type::object(\Iterator::class), Type::string(), Type::int()), Type::int())]; + yield ['arrayWithKeys', Type::dict(Type::string()), null, null]; + yield ['arrayWithKeysAndComplexValue', Type::dict(Type::nullable(Type::array(Type::nullable(Type::string()), Type::int()))), null, null]; + } + + /** + * @dataProvider typesWithCustomPrefixesProvider + */ + public function testExtractTypesWithCustomPrefixes(string $property, ?Type $type) + { + $customExtractor = new PhpStanExtractor(['add', 'remove'], ['is', 'can']); + + $this->assertEquals($type, $customExtractor->getType(Dummy::class, $property)); + } + + /** + * @return iterable + */ + public static function typesWithCustomPrefixesProvider(): iterable + { + yield ['foo', null]; + yield ['bar', Type::string()]; + yield ['baz', Type::int()]; + yield ['foo2', Type::float()]; + yield ['foo3', Type::callable()]; + yield ['foo5', Type::mixed()]; + yield ['files', Type::union(Type::list(Type::object(\SplFileInfo::class)), Type::resource())]; + yield ['bal', Type::object(\DateTimeImmutable::class)]; + yield ['parent', Type::object(ParentDummy::class)]; + yield ['collection', Type::list(Type::object(\DateTimeImmutable::class))]; + yield ['nestedCollection', Type::list(Type::list(Type::string()))]; + yield ['mixedCollection', Type::list()]; + yield ['a', null]; + yield ['b', null]; + yield ['c', Type::nullable(Type::bool())]; + yield ['d', Type::bool()]; + yield ['e', Type::list(Type::resource())]; + yield ['f', Type::list(Type::object(\DateTimeImmutable::class))]; + yield ['g', Type::nullable(Type::array())]; + yield ['h', Type::nullable(Type::string())]; + yield ['i', Type::union(Type::int(), Type::string(), Type::null())]; + yield ['j', Type::nullable(Type::object(\DateTimeImmutable::class))]; + yield ['nullableCollectionOfNonNullableElements', Type::nullable(Type::list(Type::int()))]; + yield ['nonNullableCollectionOfNullableElements', Type::array(Type::nullable(Type::int()))]; + yield ['nullableCollectionOfMultipleNonNullableElementTypes', Type::nullable(Type::array(Type::union(Type::int(), Type::string())))]; + yield ['donotexist', null]; + yield ['staticGetter', null]; + yield ['staticSetter', null]; + } + + /** + * @dataProvider dockBlockFallbackTypesProvider + */ + public function testDocBlockFallback(string $property, ?Type $type) + { + $this->assertEquals($type, $this->extractor->getType(DockBlockFallback::class, $property)); + } + + /** + * @return iterable + */ + public static function dockBlockFallbackTypesProvider(): iterable + { + yield ['pub', Type::string()]; + yield ['protAcc', Type::int()]; + yield ['protMut', Type::bool()]; + } + + /** + * @dataProvider propertiesDefinedByTraitsProvider + */ + public function testPropertiesDefinedByTraits(string $property, ?Type $type) + { + $this->assertEquals($type, $this->extractor->getType(DummyUsingTrait::class, $property)); + } + + /** + * @return iterable + */ + public static function propertiesDefinedByTraitsProvider(): iterable + { + yield ['propertyInTraitPrimitiveType', Type::string()]; + yield ['propertyInTraitObjectSameNamespace', Type::object(DummyUsedInTrait::class)]; + yield ['propertyInTraitObjectDifferentNamespace', Type::object(Dummy::class)]; + } + + /** + * @dataProvider propertiesStaticTypeProvider + */ + public function testPropertiesStaticType(string $class, string $property, ?Type $type) + { + $this->assertEquals($type, $this->extractor->getType($class, $property)); + } + + /** + * @return iterable + */ + public static function propertiesStaticTypeProvider(): iterable + { + yield [ParentDummy::class, 'propertyTypeStatic', Type::object(ParentDummy::class)]; + yield [Dummy::class, 'propertyTypeStatic', Type::object(Dummy::class)]; + } + + public function testPropertiesParentType() + { + $this->assertEquals(Type::object(ParentDummy::class), $this->extractor->getType(Dummy::class, 'parentAnnotation')); + } + + public function testPropertiesParentTypeThrowWithoutParent() + { + $this->expectException(LogicException::class); + $this->extractor->getType(ParentDummy::class, 'parentAnnotationNoParent'); + } + + /** + * @dataProvider constructorTypesProvider + */ + public function testExtractConstructorTypes(string $property, ?Type $type) + { + $this->assertEquals($type, $this->extractor->getTypeFromConstructor(ConstructorDummy::class, $property)); + } + + /** + * @dataProvider constructorTypesProvider + */ + public function testExtractConstructorTypesReturnNullOnEmptyDocBlock(string $property) + { + $this->assertNull($this->extractor->getTypeFromConstructor(ConstructorDummyWithoutDocBlock::class, $property)); + } + + /** + * @return iterable + */ + public static function constructorTypesProvider(): iterable + { + yield ['date', Type::int()]; + yield ['timezone', Type::object(\DateTimeZone::class)]; + yield ['dateObject', Type::object(\DateTimeInterface::class)]; + yield ['dateTime', null]; + yield ['ddd', null]; + } + + /** + * @dataProvider unionTypesProvider + */ + public function testExtractorUnionTypes(string $property, ?Type $type) + { + $this->assertEquals($type, $this->extractor->getType(DummyUnionType::class, $property)); + } + + /** + * @return iterable + */ + public static function unionTypesProvider(): iterable + { + yield ['a', Type::union(Type::string(), Type::int())]; + yield ['b', Type::list(Type::union(Type::string(), Type::int()))]; + yield ['c', Type::array(Type::union(Type::string(), Type::int()))]; + yield ['d', Type::array(Type::array(Type::string()), Type::union(Type::string(), Type::int()))]; + yield ['e', Type::union( + Type::generic( + Type::object(Dummy::class), + Type::array(Type::string(), Type::mixed()), + Type::union(Type::int(), Type::list(Type::generic(Type::string(), Type::object(DefaultValue::class)))), + ), + Type::object(ParentDummy::class), + Type::null(), + )]; + yield ['f', null]; + yield ['g', Type::array(Type::union(Type::string(), Type::int()))]; + } + + /** + * @dataProvider pseudoTypesProvider + */ + public function testPseudoTypes(string $property, ?Type $type) + { + $this->assertEquals($type, $this->extractor->getType(PhpStanPseudoTypesDummy::class, $property)); + } + + /** + * @return iterable + */ + public static function pseudoTypesProvider(): iterable + { + yield ['classString', Type::string()]; + yield ['classStringGeneric', Type::generic(Type::string(), Type::object(\stdClass::class))]; + yield ['htmlEscapedString', Type::string()]; + yield ['lowercaseString', Type::string()]; + yield ['nonEmptyLowercaseString', Type::string()]; + yield ['nonEmptyString', Type::string()]; + yield ['numericString', Type::string()]; + yield ['traitString', Type::string()]; + yield ['interfaceString', Type::string()]; + yield ['literalString', Type::string()]; + yield ['positiveInt', Type::int()]; + yield ['negativeInt', Type::int()]; + yield ['nonEmptyArray', Type::array()]; + yield ['nonEmptyList', Type::list()]; + yield ['scalar', Type::union(Type::int(), Type::float(), Type::string(), Type::bool())]; + yield ['number', Type::union(Type::int(), Type::float())]; + yield ['numeric', Type::union(Type::int(), Type::float(), Type::string())]; + yield ['arrayKey', Type::union(Type::int(), Type::string())]; + yield ['double', Type::float()]; + } + + public function testDummyNamespace() + { + $this->assertEquals(Type::object(Dummy::class), $this->extractor->getType(DummyNamespace::class, 'dummy')); + } + + public function testDummyNamespaceWithProperty() + { + $phpStanType = $this->extractor->getType(\B\Dummy::class, 'property'); + $phpDocType = $this->phpDocExtractor->getType(\B\Dummy::class, 'property'); + + $this->assertEquals('A\Property', $phpStanType->getClassName()); + $this->assertEquals($phpDocType->getClassName(), $phpStanType->getClassName()); + } + + /** + * @dataProvider intRangeTypeProvider + */ + public function testExtractorIntRangeType(string $property, ?Type $type) + { + $this->assertEquals($type, $this->extractor->getType(IntRangeDummy::class, $property)); + } + + /** + * @return iterable + */ + public static function intRangeTypeProvider(): iterable + { + yield ['a', Type::int()]; + yield ['b', Type::nullable(Type::int())]; + yield ['c', Type::int()]; + } + + /** + * @dataProvider php80TypesProvider + */ + public function testExtractPhp80Type(string $class, string $property, ?Type $type) + { + $this->assertEquals($type, $this->extractor->getType($class, $property)); + } + + /** + * @return iterable + */ + public static function php80TypesProvider(): iterable + { + yield [Php80Dummy::class, 'promotedAndMutated', Type::string()]; + yield [Php80Dummy::class, 'promoted', null]; + yield [Php80Dummy::class, 'collection', Type::array(Type::string())]; + yield [Php80PromotedDummy::class, 'promoted', null]; + } + + /** + * @dataProvider allowPrivateAccessProvider + */ + public function testAllowPrivateAccess(bool $allowPrivateAccess, Type $expectedType) + { + $extractor = new PhpStanExtractor(allowPrivateAccess: $allowPrivateAccess); + + $this->assertEquals($expectedType, $extractor->getType(DummyPropertyAndGetterWithDifferentTypes::class, 'foo')); + } + + public static function allowPrivateAccessProvider(): array + { + return [ + [true, Type::string()], + [false, Type::array(Type::string(), Type::int())], + ]; + } } class PhpStanOmittedParamTagTypeDocBlock diff --git a/src/Symfony/Component/PropertyInfo/Tests/Extractor/ReflectionExtractorTest.php b/src/Symfony/Component/PropertyInfo/Tests/Extractor/ReflectionExtractorTest.php index 1a312921671e3..28295ad65b71d 100644 --- a/src/Symfony/Component/PropertyInfo/Tests/Extractor/ReflectionExtractorTest.php +++ b/src/Symfony/Component/PropertyInfo/Tests/Extractor/ReflectionExtractorTest.php @@ -16,18 +16,23 @@ use Symfony\Component\PropertyInfo\PropertyReadInfo; use Symfony\Component\PropertyInfo\PropertyWriteInfo; use Symfony\Component\PropertyInfo\Tests\Fixtures\AdderRemoverDummy; +use Symfony\Component\PropertyInfo\Tests\Fixtures\ConstructorDummy; use Symfony\Component\PropertyInfo\Tests\Fixtures\DefaultValue; use Symfony\Component\PropertyInfo\Tests\Fixtures\Dummy; use Symfony\Component\PropertyInfo\Tests\Fixtures\NotInstantiable; +use Symfony\Component\PropertyInfo\Tests\Fixtures\ParentDummy; use Symfony\Component\PropertyInfo\Tests\Fixtures\Php71Dummy; use Symfony\Component\PropertyInfo\Tests\Fixtures\Php71DummyExtended; use Symfony\Component\PropertyInfo\Tests\Fixtures\Php71DummyExtended2; use Symfony\Component\PropertyInfo\Tests\Fixtures\Php74Dummy; use Symfony\Component\PropertyInfo\Tests\Fixtures\Php7Dummy; use Symfony\Component\PropertyInfo\Tests\Fixtures\Php7ParentDummy; +use Symfony\Component\PropertyInfo\Tests\Fixtures\Php80Dummy; use Symfony\Component\PropertyInfo\Tests\Fixtures\Php81Dummy; +use Symfony\Component\PropertyInfo\Tests\Fixtures\Php82Dummy; use Symfony\Component\PropertyInfo\Tests\Fixtures\SnakeCaseDummy; -use Symfony\Component\PropertyInfo\Type; +use Symfony\Component\PropertyInfo\Type as LegacyType; +use Symfony\Component\TypeInfo\Type; /** * @author Kévin Dunglas @@ -204,88 +209,96 @@ public function testGetPropertiesWithNoPrefixes() } /** - * @dataProvider typesProvider + * @group legacy + * + * @dataProvider provideLegacyTypes */ - public function testExtractors($property, array $type = null) + public function testExtractorsLegacy($property, ?array $type = null) { $this->assertEquals($type, $this->extractor->getTypes('Symfony\Component\PropertyInfo\Tests\Fixtures\Dummy', $property, [])); } - public static function typesProvider() + public static function provideLegacyTypes() { return [ ['a', null], - ['b', [new Type(Type::BUILTIN_TYPE_OBJECT, true, 'Symfony\Component\PropertyInfo\Tests\Fixtures\ParentDummy')]], - ['c', [new Type(Type::BUILTIN_TYPE_BOOL)]], - ['d', [new Type(Type::BUILTIN_TYPE_BOOL)]], + ['b', [new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, true, 'Symfony\Component\PropertyInfo\Tests\Fixtures\ParentDummy')]], + ['c', [new LegacyType(LegacyType::BUILTIN_TYPE_BOOL)]], + ['d', [new LegacyType(LegacyType::BUILTIN_TYPE_BOOL)]], ['e', null], - ['f', [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_OBJECT, false, 'DateTimeImmutable'))]], + ['f', [new LegacyType(LegacyType::BUILTIN_TYPE_ARRAY, false, null, true, new LegacyType(LegacyType::BUILTIN_TYPE_INT), new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, 'DateTimeImmutable'))]], ['donotexist', null], ['staticGetter', null], ['staticSetter', null], - ['self', [new Type(Type::BUILTIN_TYPE_OBJECT, false, 'Symfony\Component\PropertyInfo\Tests\Fixtures\Dummy')]], - ['realParent', [new Type(Type::BUILTIN_TYPE_OBJECT, false, 'Symfony\Component\PropertyInfo\Tests\Fixtures\ParentDummy')]], - ['date', [new Type(Type::BUILTIN_TYPE_OBJECT, false, \DateTimeImmutable::class)]], - ['dates', [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_OBJECT, false, \DateTimeImmutable::class))]], + ['self', [new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, 'Symfony\Component\PropertyInfo\Tests\Fixtures\Dummy')]], + ['realParent', [new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, 'Symfony\Component\PropertyInfo\Tests\Fixtures\ParentDummy')]], + ['date', [new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, \DateTimeImmutable::class)]], + ['dates', [new LegacyType(LegacyType::BUILTIN_TYPE_ARRAY, false, null, true, new LegacyType(LegacyType::BUILTIN_TYPE_INT), new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, \DateTimeImmutable::class))]], ]; } /** - * @dataProvider php7TypesProvider + * @group legacy + * + * @dataProvider provideLegacyPhp7Types */ - public function testExtractPhp7Type(string $class, string $property, array $type = null) + public function testExtractPhp7TypeLegacy(string $class, string $property, ?array $type = null) { $this->assertEquals($type, $this->extractor->getTypes($class, $property, [])); } - public static function php7TypesProvider() + public static function provideLegacyPhp7Types() { return [ - [Php7Dummy::class, 'foo', [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true)]], - [Php7Dummy::class, 'bar', [new Type(Type::BUILTIN_TYPE_INT)]], - [Php7Dummy::class, 'baz', [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_STRING))]], - [Php7Dummy::class, 'buz', [new Type(Type::BUILTIN_TYPE_OBJECT, false, 'Symfony\Component\PropertyInfo\Tests\Fixtures\Php7Dummy')]], - [Php7Dummy::class, 'biz', [new Type(Type::BUILTIN_TYPE_OBJECT, false, Php7ParentDummy::class)]], + [Php7Dummy::class, 'foo', [new LegacyType(LegacyType::BUILTIN_TYPE_ARRAY, false, null, true)]], + [Php7Dummy::class, 'bar', [new LegacyType(LegacyType::BUILTIN_TYPE_INT)]], + [Php7Dummy::class, 'baz', [new LegacyType(LegacyType::BUILTIN_TYPE_ARRAY, false, null, true, new LegacyType(LegacyType::BUILTIN_TYPE_INT), new LegacyType(LegacyType::BUILTIN_TYPE_STRING))]], + [Php7Dummy::class, 'buz', [new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, 'Symfony\Component\PropertyInfo\Tests\Fixtures\Php7Dummy')]], + [Php7Dummy::class, 'biz', [new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, Php7ParentDummy::class)]], [Php7Dummy::class, 'donotexist', null], - [Php7ParentDummy::class, 'parent', [new Type(Type::BUILTIN_TYPE_OBJECT, false, \stdClass::class)]], + [Php7ParentDummy::class, 'parent', [new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, \stdClass::class)]], ]; } /** - * @dataProvider php71TypesProvider + * @group legacy + * + * @dataProvider provideLegacyPhp71Types */ - public function testExtractPhp71Type($property, array $type = null) + public function testExtractPhp71TypeLegacy($property, ?array $type = null) { $this->assertEquals($type, $this->extractor->getTypes('Symfony\Component\PropertyInfo\Tests\Fixtures\Php71Dummy', $property, [])); } - public static function php71TypesProvider() + public static function provideLegacyPhp71Types() { return [ - ['foo', [new Type(Type::BUILTIN_TYPE_ARRAY, true, null, true)]], - ['buz', [new Type(Type::BUILTIN_TYPE_NULL)]], - ['bar', [new Type(Type::BUILTIN_TYPE_INT, true)]], - ['baz', [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_STRING))]], + ['foo', [new LegacyType(LegacyType::BUILTIN_TYPE_ARRAY, true, null, true)]], + ['buz', [new LegacyType(LegacyType::BUILTIN_TYPE_NULL)]], + ['bar', [new LegacyType(LegacyType::BUILTIN_TYPE_INT, true)]], + ['baz', [new LegacyType(LegacyType::BUILTIN_TYPE_ARRAY, false, null, true, new LegacyType(LegacyType::BUILTIN_TYPE_INT), new LegacyType(LegacyType::BUILTIN_TYPE_STRING))]], ['donotexist', null], ]; } /** - * @dataProvider php80TypesProvider + * @group legacy + * + * @dataProvider provideLegacyPhp80Types */ - public function testExtractPhp80Type($property, array $type = null) + public function testExtractPhp80TypeLegacy(string $property, ?array $type = null) { $this->assertEquals($type, $this->extractor->getTypes('Symfony\Component\PropertyInfo\Tests\Fixtures\Php80Dummy', $property, [])); } - public static function php80TypesProvider() + public static function provideLegacyPhp80Types() { return [ - ['foo', [new Type(Type::BUILTIN_TYPE_ARRAY, true, null, true)]], - ['bar', [new Type(Type::BUILTIN_TYPE_INT, true)]], - ['timeout', [new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_FLOAT)]], - ['optional', [new Type(Type::BUILTIN_TYPE_INT, true), new Type(Type::BUILTIN_TYPE_FLOAT, true)]], - ['string', [new Type(Type::BUILTIN_TYPE_OBJECT, false, 'Stringable'), new Type(Type::BUILTIN_TYPE_STRING)]], + ['foo', [new LegacyType(LegacyType::BUILTIN_TYPE_ARRAY, true, null, true)]], + ['bar', [new LegacyType(LegacyType::BUILTIN_TYPE_INT, true)]], + ['timeout', [new LegacyType(LegacyType::BUILTIN_TYPE_FLOAT), new LegacyType(LegacyType::BUILTIN_TYPE_INT)]], + ['optional', [new LegacyType(LegacyType::BUILTIN_TYPE_FLOAT, true), new LegacyType(LegacyType::BUILTIN_TYPE_INT, true)]], + ['string', [new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, 'Stringable'), new LegacyType(LegacyType::BUILTIN_TYPE_STRING)]], ['payload', null], ['data', null], ['mixedProperty', null], @@ -293,18 +306,20 @@ public static function php80TypesProvider() } /** - * @dataProvider php81TypesProvider + * @group legacy + * + * @dataProvider provideLegacyPhp81Types */ - public function testExtractPhp81Type($property, array $type = null) + public function testExtractPhp81TypeLegacy(string $property, ?array $type = null) { $this->assertEquals($type, $this->extractor->getTypes('Symfony\Component\PropertyInfo\Tests\Fixtures\Php81Dummy', $property, [])); } - public static function php81TypesProvider() + public static function provideLegacyPhp81Types() { return [ ['nothing', null], - ['collection', [new Type(Type::BUILTIN_TYPE_OBJECT, false, 'Traversable'), new Type(Type::BUILTIN_TYPE_OBJECT, false, 'Countable')]], + ['collection', [new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, 'Countable'), new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, 'Traversable')]], ]; } @@ -314,18 +329,20 @@ public function testReadonlyPropertiesAreNotWriteable() } /** - * @dataProvider php82TypesProvider + * @group legacy + * + * @dataProvider provideLegacyPhp82Types */ - public function testExtractPhp82Type($property, array $type = null) + public function testExtractPhp82TypeLegacy(string $property, ?array $type = null) { $this->assertEquals($type, $this->extractor->getTypes('Symfony\Component\PropertyInfo\Tests\Fixtures\Php82Dummy', $property, [])); } - public static function php82TypesProvider(): iterable + public static function provideLegacyPhp82Types(): iterable { yield ['nil', null]; - yield ['false', [new Type(Type::BUILTIN_TYPE_FALSE)]]; - yield ['true', [new Type(Type::BUILTIN_TYPE_TRUE)]]; + yield ['false', [new LegacyType(LegacyType::BUILTIN_TYPE_FALSE)]]; + yield ['true', [new LegacyType(LegacyType::BUILTIN_TYPE_TRUE)]]; // Nesting intersection and union types is not supported yet, // but we should make sure this kind of composite types does not crash the extractor. @@ -333,20 +350,22 @@ public static function php82TypesProvider(): iterable } /** - * @dataProvider defaultValueProvider + * @group legacy + * + * @dataProvider provideLegacyDefaultValue */ - public function testExtractWithDefaultValue($property, $type) + public function testExtractWithDefaultValueLegacy($property, $type) { $this->assertEquals($type, $this->extractor->getTypes(DefaultValue::class, $property, [])); } - public static function defaultValueProvider() + public static function provideLegacyDefaultValue() { return [ - ['defaultInt', [new Type(Type::BUILTIN_TYPE_INT, false)]], - ['defaultFloat', [new Type(Type::BUILTIN_TYPE_FLOAT, false)]], - ['defaultString', [new Type(Type::BUILTIN_TYPE_STRING, false)]], - ['defaultArray', [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true)]], + ['defaultInt', [new LegacyType(LegacyType::BUILTIN_TYPE_INT, false)]], + ['defaultFloat', [new LegacyType(LegacyType::BUILTIN_TYPE_FLOAT, false)]], + ['defaultString', [new LegacyType(LegacyType::BUILTIN_TYPE_STRING, false)]], + ['defaultArray', [new LegacyType(LegacyType::BUILTIN_TYPE_ARRAY, false, null, true)]], ['defaultNull', null], ]; } @@ -474,9 +493,11 @@ public static function getInitializableProperties(): array } /** - * @dataProvider constructorTypesProvider + * @group legacy + * + * @dataProvider provideLegacyConstructorTypes */ - public function testExtractTypeConstructor(string $class, string $property, array $type = null) + public function testExtractTypeConstructorLegacy(string $class, string $property, ?array $type = null) { /* Check that constructor extractions works by default, and if passed in via context. Check that null is returned if constructor extraction is disabled */ @@ -485,15 +506,15 @@ public function testExtractTypeConstructor(string $class, string $property, arra $this->assertNull($this->extractor->getTypes($class, $property, ['enable_constructor_extraction' => false])); } - public static function constructorTypesProvider(): array + public static function provideLegacyConstructorTypes(): array { return [ // php71 dummy has following constructor: __construct(string $string, int $intPrivate) - [Php71Dummy::class, 'string', [new Type(Type::BUILTIN_TYPE_STRING, false)]], - [Php71Dummy::class, 'intPrivate', [new Type(Type::BUILTIN_TYPE_INT, false)]], + [Php71Dummy::class, 'string', [new LegacyType(LegacyType::BUILTIN_TYPE_STRING, false)]], + [Php71Dummy::class, 'intPrivate', [new LegacyType(LegacyType::BUILTIN_TYPE_INT, false)]], // Php71DummyExtended2 adds int $intWithAccessor - [Php71DummyExtended2::class, 'intWithAccessor', [new Type(Type::BUILTIN_TYPE_INT, false)]], - [Php71DummyExtended2::class, 'intPrivate', [new Type(Type::BUILTIN_TYPE_INT, false)]], + [Php71DummyExtended2::class, 'intWithAccessor', [new LegacyType(LegacyType::BUILTIN_TYPE_INT, false)]], + [Php71DummyExtended2::class, 'intPrivate', [new LegacyType(LegacyType::BUILTIN_TYPE_INT, false)]], [DefaultValue::class, 'foo', null], ]; } @@ -511,13 +532,16 @@ public function testNullOnPrivateProtectedAccessor() $this->assertEquals(PropertyWriteInfo::TYPE_NONE, $bazMutator->getType()); } - public function testTypedProperties() + /** + * @group legacy + */ + public function testTypedPropertiesLegacy() { - $this->assertEquals([new Type(Type::BUILTIN_TYPE_OBJECT, false, Dummy::class)], $this->extractor->getTypes(Php74Dummy::class, 'dummy')); - $this->assertEquals([new Type(Type::BUILTIN_TYPE_BOOL, true)], $this->extractor->getTypes(Php74Dummy::class, 'nullableBoolProp')); - $this->assertEquals([new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_STRING))], $this->extractor->getTypes(Php74Dummy::class, 'stringCollection')); - $this->assertEquals([new Type(Type::BUILTIN_TYPE_INT, true)], $this->extractor->getTypes(Php74Dummy::class, 'nullableWithDefault')); - $this->assertEquals([new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true)], $this->extractor->getTypes(Php74Dummy::class, 'collection')); + $this->assertEquals([new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, Dummy::class)], $this->extractor->getTypes(Php74Dummy::class, 'dummy')); + $this->assertEquals([new LegacyType(LegacyType::BUILTIN_TYPE_BOOL, true)], $this->extractor->getTypes(Php74Dummy::class, 'nullableBoolProp')); + $this->assertEquals([new LegacyType(LegacyType::BUILTIN_TYPE_ARRAY, false, null, true, new LegacyType(LegacyType::BUILTIN_TYPE_INT), new LegacyType(LegacyType::BUILTIN_TYPE_STRING))], $this->extractor->getTypes(Php74Dummy::class, 'stringCollection')); + $this->assertEquals([new LegacyType(LegacyType::BUILTIN_TYPE_INT, true)], $this->extractor->getTypes(Php74Dummy::class, 'nullableWithDefault')); + $this->assertEquals([new LegacyType(LegacyType::BUILTIN_TYPE_ARRAY, false, null, true)], $this->extractor->getTypes(Php74Dummy::class, 'collection')); } /** @@ -633,21 +657,229 @@ public function testGetWriteInfoReadonlyProperties() } /** - * @dataProvider extractConstructorTypesProvider + * @group legacy + * + * @dataProvider provideLegacyExtractConstructorTypes */ - public function testExtractConstructorTypes(string $property, array $type = null) + public function testExtractConstructorTypesLegacy(string $property, ?array $type = null) { $this->assertEquals($type, $this->extractor->getTypesFromConstructor('Symfony\Component\PropertyInfo\Tests\Fixtures\ConstructorDummy', $property)); } - public static function extractConstructorTypesProvider(): array + public static function provideLegacyExtractConstructorTypes(): array { return [ - ['timezone', [new Type(Type::BUILTIN_TYPE_OBJECT, false, 'DateTimeZone')]], + ['timezone', [new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, 'DateTimeZone')]], ['date', null], ['dateObject', null], - ['dateTime', [new Type(Type::BUILTIN_TYPE_OBJECT, false, 'DateTimeImmutable')]], + ['dateTime', [new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, 'DateTimeImmutable')]], ['ddd', null], ]; } + + /** + * @dataProvider typesProvider + */ + public function testExtractors(string $property, ?Type $type) + { + $this->assertEquals($type, $this->extractor->getType(Dummy::class, $property)); + } + + /** + * @return iterable + */ + public static function typesProvider(): iterable + { + yield ['a', null]; + yield ['b', Type::nullable(Type::object(ParentDummy::class))]; + yield ['c', Type::bool()]; + yield ['d', Type::bool()]; + yield ['e', null]; + yield ['f', Type::list(Type::object(\DateTimeImmutable::class))]; + yield ['donotexist', null]; + yield ['staticGetter', null]; + yield ['staticSetter', null]; + yield ['self', Type::object(Dummy::class)]; + yield ['realParent', Type::object(ParentDummy::class)]; + yield ['date', Type::object(\DateTimeImmutable::class)]; + yield ['dates', Type::list(Type::object(\DateTimeImmutable::class))]; + } + + /** + * @dataProvider php7TypesProvider + */ + public function testExtractPhp7Type(string $class, string $property, ?Type $type) + { + $this->assertEquals($type, $this->extractor->getType($class, $property)); + } + + /** + * @return iterable + */ + public static function php7TypesProvider(): iterable + { + yield [Php7Dummy::class, 'foo', Type::array()]; + yield [Php7Dummy::class, 'bar', Type::int()]; + yield [Php7Dummy::class, 'baz', Type::list(Type::string())]; + yield [Php7Dummy::class, 'buz', Type::object(Php7Dummy::class)]; + yield [Php7Dummy::class, 'biz', Type::object(Php7ParentDummy::class)]; + yield [Php7Dummy::class, 'donotexist', null]; + yield [Php7ParentDummy::class, 'parent', Type::object(\stdClass::class)]; + } + + /** + * @dataProvider php71TypesProvider + */ + public function testExtractPhp71Type(string $property, ?Type $type) + { + $this->assertEquals($type, $this->extractor->getType(Php71Dummy::class, $property)); + } + + /** + * @return iterable + */ + public static function php71TypesProvider(): iterable + { + yield ['foo', Type::nullable(Type::array())]; + yield ['buz', Type::void()]; + yield ['bar', Type::nullable(Type::int())]; + yield ['baz', Type::list(Type::string())]; + yield ['donotexist', null]; + } + + /** + * @dataProvider php80TypesProvider + */ + public function testExtractPhp80Type(string $property, ?Type $type) + { + $this->assertEquals($type, $this->extractor->getType(Php80Dummy::class, $property)); + } + + /** + * @return iterable + */ + public static function php80TypesProvider(): iterable + { + yield ['foo', Type::nullable(Type::array())]; + yield ['bar', Type::nullable(Type::int())]; + yield ['timeout', Type::union(Type::int(), Type::float())]; + yield ['optional', Type::union(Type::nullable(Type::int()), Type::nullable(Type::float()))]; + yield ['string', Type::union(Type::string(), Type::object(\Stringable::class))]; + yield ['payload', Type::mixed()]; + yield ['data', Type::mixed()]; + yield ['mixedProperty', Type::mixed()]; + } + + /** + * @dataProvider php81TypesProvider + */ + public function testExtractPhp81Type(string $property, ?Type $type) + { + $this->assertEquals($type, $this->extractor->getType(Php81Dummy::class, $property)); + } + + /** + * @return iterable + */ + public static function php81TypesProvider(): iterable + { + yield ['nothing', Type::never()]; + yield ['collection', Type::intersection(Type::object(\Traversable::class), Type::object(\Countable::class))]; + } + + /** + * @dataProvider php82TypesProvider + */ + public function testExtractPhp82Type(string $property, ?Type $type) + { + $this->assertEquals($type, $this->extractor->getType(Php82Dummy::class, $property)); + } + + /** + * @return iterable + */ + public static function php82TypesProvider(): iterable + { + yield ['nil', Type::null()]; + yield ['false', Type::false()]; + yield ['true', Type::true()]; + yield ['someCollection', Type::union(Type::intersection(Type::object(\Traversable::class), Type::object(\Countable::class)), Type::null())]; + } + + /** + * @dataProvider defaultValueProvider + */ + public function testExtractWithDefaultValue(string $property, ?Type $type) + { + $this->assertEquals($type, $this->extractor->getType(DefaultValue::class, $property)); + } + + /** + * @return iterable + */ + public static function defaultValueProvider(): iterable + { + yield ['defaultInt', Type::int()]; + yield ['defaultFloat', Type::float()]; + yield ['defaultString', Type::string()]; + yield ['defaultArray', Type::array()]; + yield ['defaultNull', null]; + } + + /** + * @dataProvider constructorTypesProvider + */ + public function testExtractTypeConstructor(string $class, string $property, ?Type $type) + { + /* Check that constructor extractions works by default, and if passed in via context. + Check that null is returned if constructor extraction is disabled */ + $this->assertEquals($type, $this->extractor->getType($class, $property)); + $this->assertEquals($type, $this->extractor->getType($class, $property, ['enable_constructor_extraction' => true])); + $this->assertNull($this->extractor->getType($class, $property, ['enable_constructor_extraction' => false])); + } + + /** + * @return iterable + */ + public static function constructorTypesProvider(): iterable + { + // php71 dummy has following constructor: __construct(string $string, int $intPrivate) + yield [Php71Dummy::class, 'string', Type::string()]; + + // Php71DummyExtended2 adds int $intWithAccessor + yield [Php71DummyExtended2::class, 'intWithAccessor', Type::int()]; + + yield [Php71Dummy::class, 'intPrivate', Type::int()]; + yield [Php71DummyExtended2::class, 'intPrivate', Type::int()]; + yield [DefaultValue::class, 'foo', null]; + } + + public function testTypedProperties() + { + $this->assertEquals(Type::object(Dummy::class), $this->extractor->getType(Php74Dummy::class, 'dummy')); + $this->assertEquals(Type::nullable(Type::bool()), $this->extractor->getType(Php74Dummy::class, 'nullableBoolProp')); + $this->assertEquals(Type::list(Type::string()), $this->extractor->getType(Php74Dummy::class, 'stringCollection')); + $this->assertEquals(Type::nullable(Type::int()), $this->extractor->getType(Php74Dummy::class, 'nullableWithDefault')); + $this->assertEquals(Type::array(), $this->extractor->getType(Php74Dummy::class, 'collection')); + } + + /** + * @dataProvider extractConstructorTypesProvider + */ + public function testExtractConstructorType(string $property, ?Type $type) + { + $this->assertEquals($type, $this->extractor->getTypeFromConstructor(ConstructorDummy::class, $property)); + } + + /** + * @return iterable + */ + public static function extractConstructorTypesProvider(): iterable + { + yield ['timezone', Type::object(\DateTimeZone::class)]; + yield ['date', null]; + yield ['dateObject', null]; + yield ['dateTime', Type::object(\DateTimeImmutable::class)]; + yield ['ddd', null]; + } } diff --git a/src/Symfony/Component/PropertyInfo/Tests/Extractor/SerializerExtractorTest.php b/src/Symfony/Component/PropertyInfo/Tests/Extractor/SerializerExtractorTest.php index ec3f949bbeb69..48d3ec957d167 100644 --- a/src/Symfony/Component/PropertyInfo/Tests/Extractor/SerializerExtractorTest.php +++ b/src/Symfony/Component/PropertyInfo/Tests/Extractor/SerializerExtractorTest.php @@ -43,6 +43,8 @@ public function testGetProperties() public function testGetPropertiesWithIgnoredProperties() { $this->assertSame(['visibleProperty'], $this->extractor->getProperties(IgnorePropertyDummy::class, ['serializer_groups' => ['a']])); + $this->assertSame(['visibleProperty'], $this->extractor->getProperties(IgnorePropertyDummy::class, ['serializer_groups' => ['Default']])); + $this->assertSame(['visibleProperty'], $this->extractor->getProperties(IgnorePropertyDummy::class, ['serializer_groups' => ['IgnorePropertyDummy']])); } public function testGetPropertiesWithAnyGroup() diff --git a/src/Symfony/Component/PropertyInfo/Tests/Fixtures/DummyExtractor.php b/src/Symfony/Component/PropertyInfo/Tests/Fixtures/DummyExtractor.php index 31cd4a7f8fffc..a9344980a3993 100644 --- a/src/Symfony/Component/PropertyInfo/Tests/Fixtures/DummyExtractor.php +++ b/src/Symfony/Component/PropertyInfo/Tests/Fixtures/DummyExtractor.php @@ -17,7 +17,8 @@ use Symfony\Component\PropertyInfo\PropertyInitializableExtractorInterface; use Symfony\Component\PropertyInfo\PropertyListExtractorInterface; use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface; -use Symfony\Component\PropertyInfo\Type; +use Symfony\Component\PropertyInfo\Util\BackwardCompatibilityHelper; +use Symfony\Component\TypeInfo\Type; /** * @author Kévin Dunglas @@ -36,12 +37,17 @@ public function getLongDescription($class, $property, array $context = []): ?str public function getTypes($class, $property, array $context = []): ?array { - return [new Type(Type::BUILTIN_TYPE_INT)]; + return BackwardCompatibilityHelper::convertTypeToLegacyTypes($this->getType($class, $property, $context)); } - public function getTypesFromConstructor(string $class, string $property): ?array + public function getType($class, $property, array $context = []): ?Type { - return [new Type(Type::BUILTIN_TYPE_STRING)]; + return Type::int(); + } + + public function getTypeFromConstructor(string $class, string $property): ?Type + { + return Type::string(); } public function isReadable($class, $property, array $context = []): ?bool diff --git a/src/Symfony/Component/PropertyInfo/Tests/Fixtures/DummyPropertyAndGetterWithDifferentTypes.php b/src/Symfony/Component/PropertyInfo/Tests/Fixtures/DummyPropertyAndGetterWithDifferentTypes.php new file mode 100644 index 0000000000000..0e08a16dd5591 --- /dev/null +++ b/src/Symfony/Component/PropertyInfo/Tests/Fixtures/DummyPropertyAndGetterWithDifferentTypes.php @@ -0,0 +1,24 @@ + + */ + public function getFoo(): array + { + return (array)$this->foo; + } +} diff --git a/src/Symfony/Component/PropertyInfo/Tests/Fixtures/IgnorePropertyDummy.php b/src/Symfony/Component/PropertyInfo/Tests/Fixtures/IgnorePropertyDummy.php index 9216ff801b27d..0cc4606241f20 100644 --- a/src/Symfony/Component/PropertyInfo/Tests/Fixtures/IgnorePropertyDummy.php +++ b/src/Symfony/Component/PropertyInfo/Tests/Fixtures/IgnorePropertyDummy.php @@ -19,7 +19,7 @@ */ class IgnorePropertyDummy { - #[Groups(['a'])] + #[Groups(['a', 'Default', 'IgnorePropertyDummy'])] public $visibleProperty; #[Groups(['a']), Ignore] diff --git a/src/Symfony/Component/PropertyInfo/Tests/Fixtures/NullExtractor.php b/src/Symfony/Component/PropertyInfo/Tests/Fixtures/NullExtractor.php index 3a18b0d8eaef0..1d924044531ff 100644 --- a/src/Symfony/Component/PropertyInfo/Tests/Fixtures/NullExtractor.php +++ b/src/Symfony/Component/PropertyInfo/Tests/Fixtures/NullExtractor.php @@ -16,6 +16,7 @@ use Symfony\Component\PropertyInfo\PropertyInitializableExtractorInterface; use Symfony\Component\PropertyInfo\PropertyListExtractorInterface; use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface; +use Symfony\Component\TypeInfo\Type; /** * Not able to guess anything. @@ -48,6 +49,14 @@ public function getTypes($class, $property, array $context = []): ?array return null; } + public function getType($class, $property, array $context = []): ?Type + { + $this->assertIsString($class); + $this->assertIsString($property); + + return null; + } + public function isReadable($class, $property, array $context = []): ?bool { $this->assertIsString($class); diff --git a/src/Symfony/Component/PropertyInfo/Tests/TypeTest.php b/src/Symfony/Component/PropertyInfo/Tests/LegacyTypeTest.php similarity index 99% rename from src/Symfony/Component/PropertyInfo/Tests/TypeTest.php rename to src/Symfony/Component/PropertyInfo/Tests/LegacyTypeTest.php index e871ed49f7b2a..ce4f85715aa9b 100644 --- a/src/Symfony/Component/PropertyInfo/Tests/TypeTest.php +++ b/src/Symfony/Component/PropertyInfo/Tests/LegacyTypeTest.php @@ -17,7 +17,7 @@ /** * @author Kévin Dunglas */ -class TypeTest extends TestCase +class LegacyTypeTest extends TestCase { public function testConstruct() { diff --git a/src/Symfony/Component/PropertyInfo/Tests/PropertyInfoCacheExtractorTest.php b/src/Symfony/Component/PropertyInfo/Tests/PropertyInfoCacheExtractorTest.php index 5a5de4727e3ba..f9b1a8fc3358e 100644 --- a/src/Symfony/Component/PropertyInfo/Tests/PropertyInfoCacheExtractorTest.php +++ b/src/Symfony/Component/PropertyInfo/Tests/PropertyInfoCacheExtractorTest.php @@ -38,6 +38,15 @@ public function testGetLongDescription() parent::testGetLongDescription(); } + public function testGetType() + { + parent::testGetType(); + parent::testGetType(); + } + + /** + * @group legacy + */ public function testGetTypes() { parent::testGetTypes(); diff --git a/src/Symfony/Component/PropertyInfo/Type.php b/src/Symfony/Component/PropertyInfo/Type.php index 0750ae6b4a8fc..0a484e398fca9 100644 --- a/src/Symfony/Component/PropertyInfo/Type.php +++ b/src/Symfony/Component/PropertyInfo/Type.php @@ -11,27 +11,38 @@ namespace Symfony\Component\PropertyInfo; +use Symfony\Component\PropertyInfo\Util\BackwardCompatibilityHelper; +use Symfony\Component\TypeInfo\Exception\InvalidArgumentException; +use Symfony\Component\TypeInfo\Type as TypeInfoType; +use Symfony\Component\TypeInfo\Type\GenericType; +use Symfony\Component\TypeInfo\Type\ObjectType; +use Symfony\Component\TypeInfo\TypeIdentifier; + +trigger_deprecation('symfony/property-info', '7.1', 'The "%s" class is deprecated. Use "%s" of "symfony/type-info" component instead.', Type::class, TypeInfoType::class); + /** * Type value object (immutable). * * @author Kévin Dunglas * + * @deprecated since Symfony 7.1, use "Symfony\Component\TypeInfo\Type" of "symfony/type-info" component instead. + * * @final */ class Type { - public const BUILTIN_TYPE_INT = 'int'; - public const BUILTIN_TYPE_FLOAT = 'float'; - public const BUILTIN_TYPE_STRING = 'string'; - public const BUILTIN_TYPE_BOOL = 'bool'; - public const BUILTIN_TYPE_RESOURCE = 'resource'; - public const BUILTIN_TYPE_OBJECT = 'object'; - public const BUILTIN_TYPE_ARRAY = 'array'; - public const BUILTIN_TYPE_NULL = 'null'; - public const BUILTIN_TYPE_FALSE = 'false'; - public const BUILTIN_TYPE_TRUE = 'true'; - public const BUILTIN_TYPE_CALLABLE = 'callable'; - public const BUILTIN_TYPE_ITERABLE = 'iterable'; + public const BUILTIN_TYPE_INT = TypeIdentifier::INT->value; + public const BUILTIN_TYPE_FLOAT = TypeIdentifier::FLOAT->value; + public const BUILTIN_TYPE_STRING = TypeIdentifier::STRING->value; + public const BUILTIN_TYPE_BOOL = TypeIdentifier::BOOL->value; + public const BUILTIN_TYPE_RESOURCE = TypeIdentifier::RESOURCE->value; + public const BUILTIN_TYPE_OBJECT = TypeIdentifier::OBJECT->value; + public const BUILTIN_TYPE_ARRAY = TypeIdentifier::ARRAY->value; + public const BUILTIN_TYPE_NULL = TypeIdentifier::NULL->value; + public const BUILTIN_TYPE_FALSE = TypeIdentifier::FALSE->value; + public const BUILTIN_TYPE_TRUE = TypeIdentifier::TRUE->value; + public const BUILTIN_TYPE_CALLABLE = TypeIdentifier::CALLABLE->value; + public const BUILTIN_TYPE_ITERABLE = TypeIdentifier::ITERABLE->value; /** * List of PHP builtin types. @@ -63,12 +74,10 @@ class Type self::BUILTIN_TYPE_ITERABLE, ]; - private string $builtinType; - private bool $nullable; - private ?string $class; - private bool $collection; - private array $collectionKeyType; - private array $collectionValueType; + /** + * @internal + */ + public TypeInfoType $internalType; /** * @param Type[]|Type|null $collectionKeyType @@ -76,37 +85,55 @@ class Type * * @throws \InvalidArgumentException */ - public function __construct(string $builtinType, bool $nullable = false, string $class = null, bool $collection = false, array|self $collectionKeyType = null, array|self $collectionValueType = null) + public function __construct(string $builtinType, bool $nullable = false, ?string $class = null, bool $collection = false, array|self|null $collectionKeyType = null, array|self|null $collectionValueType = null) { - if (!\in_array($builtinType, self::$builtinTypes, true)) { - throw new \InvalidArgumentException(sprintf('"%s" is not a valid PHP type.', $builtinType)); + $typeIdentifier = $builtinType; + $variableTypes = []; + + $collectionKeyType = $this->validateCollectionArgument($collectionKeyType, 5, '$collectionKeyType') ?? []; + $collectionValueType = $this->validateCollectionArgument($collectionValueType, 6, '$collectionValueType') ?? []; + + if ($collectionKeyType) { + if (\is_array($collectionKeyType)) { + $collectionKeyType = array_unique(array_map(fn ($t): TypeInfoType => $t->internalType, $collectionKeyType)); + $variableTypes[] = \count($collectionKeyType) > 1 ? TypeInfoType::union(...$collectionKeyType) : $collectionKeyType[0]; + } else { + $variableTypes[] = $collectionKeyType->internalType; + } } - $this->builtinType = $builtinType; - $this->nullable = $nullable; - $this->class = $class; - $this->collection = $collection; - $this->collectionKeyType = $this->validateCollectionArgument($collectionKeyType, 5, '$collectionKeyType') ?? []; - $this->collectionValueType = $this->validateCollectionArgument($collectionValueType, 6, '$collectionValueType') ?? []; - } + if ($collectionValueType) { + if (!$collectionKeyType) { + $variableTypes[] = [] === $collectionKeyType ? TypeInfoType::mixed() : TypeInfoType::union(TypeInfoType::int(), TypeInfoType::string()); + } - private function validateCollectionArgument(array|self|null $collectionArgument, int $argumentIndex, string $argumentName): ?array - { - if (null === $collectionArgument) { - return null; + if (\is_array($collectionValueType)) { + $collectionValueType = array_unique(array_map(fn ($t): TypeInfoType => $t->internalType, $collectionValueType)); + $variableTypes[] = \count($collectionValueType) > 1 ? TypeInfoType::union(...$collectionValueType) : $collectionValueType[0]; + } else { + $variableTypes[] = $collectionValueType->internalType; + } } - if (\is_array($collectionArgument)) { - foreach ($collectionArgument as $type) { - if (!$type instanceof self) { - throw new \TypeError(sprintf('"%s()": Argument #%d (%s) must be of type "%s[]", "%s" or "null", array value "%s" given.', __METHOD__, $argumentIndex, $argumentName, self::class, self::class, get_debug_type($collectionArgument))); - } - } + if ($collectionKeyType && !$collectionValueType) { + $variableTypes[] = TypeInfoType::mixed(); + } - return $collectionArgument; + try { + $this->internalType = null !== $class ? TypeInfoType::object($class) : TypeInfoType::builtin(TypeIdentifier::from($typeIdentifier)); + } catch (\ValueError) { + throw new InvalidArgumentException(sprintf('"%s" is not a valid PHP type.', $typeIdentifier)); } - return [$collectionArgument]; + if (\count($variableTypes)) { + $this->internalType = TypeInfoType::generic($this->internalType, ...$variableTypes); + } + + if ($nullable && !$this->internalType->isNullable) { + $this->internalType = TypeInfoType::nullable($this->internalType); + } + + $this->internalType->setCollection($collection); } /** @@ -116,12 +143,14 @@ private function validateCollectionArgument(array|self|null $collectionArgument, */ public function getBuiltinType(): string { - return $this->builtinType; + $internalType = BackwardCompatibilityHelper::unwrapNullableType($this->internalType); + + return $internalType->getBaseType()->getTypeIdentifier()->value; } public function isNullable(): bool { - return $this->nullable; + return $this->internalType->isNullable(); } /** @@ -131,12 +160,19 @@ public function isNullable(): bool */ public function getClassName(): ?string { - return $this->class; + $internalType = BackwardCompatibilityHelper::unwrapNullableType($this->internalType); + $internalType = $internalType->getBaseType(); + + if (!$internalType instanceof ObjectType) { + return null; + } + + return $internalType->getClassName(); } public function isCollection(): bool { - return $this->collection; + return $this->internalType->isCollection; } /** @@ -148,7 +184,17 @@ public function isCollection(): bool */ public function getCollectionKeyTypes(): array { - return $this->collectionKeyType; + $internalType = BackwardCompatibilityHelper::unwrapNullableType($this->internalType); + + if (!$internalType instanceof GenericType) { + return []; + } + + if (null === ($collectionKeyType = $internalType->getVariableTypes()[0] ?? null)) { + return []; + } + + return BackwardCompatibilityHelper::convertTypeToLegacyTypes($collectionKeyType) ?? []; } /** @@ -160,6 +206,35 @@ public function getCollectionKeyTypes(): array */ public function getCollectionValueTypes(): array { - return $this->collectionValueType; + $internalType = BackwardCompatibilityHelper::unwrapNullableType($this->internalType); + + if (!$internalType instanceof GenericType) { + return []; + } + + if (null === ($collectionValueType = $internalType->getVariableTypes()[1] ?? null)) { + return []; + } + + return BackwardCompatibilityHelper::convertTypeToLegacyTypes($collectionValueType) ?? []; + } + + private function validateCollectionArgument(array|self|null $collectionArgument, int $argumentIndex, string $argumentName): ?array + { + if (null === $collectionArgument) { + return null; + } + + if (\is_array($collectionArgument)) { + foreach ($collectionArgument as $type) { + if (!$type instanceof self) { + throw new \TypeError(sprintf('"%s()": Argument #%d (%s) must be of type "%s[]", "%s" or "null", array value "%s" given.', __METHOD__, $argumentIndex, $argumentName, self::class, self::class, get_debug_type($collectionArgument))); + } + } + + return $collectionArgument; + } + + return [$collectionArgument]; } } diff --git a/src/Symfony/Component/PropertyInfo/Util/BackwardCompatibilityHelper.php b/src/Symfony/Component/PropertyInfo/Util/BackwardCompatibilityHelper.php new file mode 100644 index 0000000000000..f12497cfb3428 --- /dev/null +++ b/src/Symfony/Component/PropertyInfo/Util/BackwardCompatibilityHelper.php @@ -0,0 +1,184 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\PropertyInfo\Util; + +use Symfony\Component\PropertyInfo\Type as LegacyType; +use Symfony\Component\TypeInfo\Exception\LogicException; +use Symfony\Component\TypeInfo\Type; +use Symfony\Component\TypeInfo\Type\BuiltinType; +use Symfony\Component\TypeInfo\Type\CollectionType; +use Symfony\Component\TypeInfo\Type\GenericType; +use Symfony\Component\TypeInfo\Type\IntersectionType; +use Symfony\Component\TypeInfo\Type\ObjectType; +use Symfony\Component\TypeInfo\Type\UnionType; +use Symfony\Component\TypeInfo\TypeIdentifier; + +/** + * @author Mathias Arlaud + * @author Baptiste Leduc + * + * @internal + */ +final class BackwardCompatibilityHelper +{ + /** + * Converts a {@see Type} to what is should have been in the "symfony/property-info" component. + * + * @return list|null + */ + public static function convertTypeToLegacyTypes(?Type $type, bool $keepNullType = true): ?array + { + if (null === $type) { + return null; + } + + try { + $typeIdentifier = $type->getBaseType()->getTypeIdentifier(); + } catch (LogicException) { + $typeIdentifier = null; + } + + if (\in_array($typeIdentifier, [TypeIdentifier::MIXED, TypeIdentifier::NEVER, true])) { + return null; + } + + if (TypeIdentifier::NULL === $typeIdentifier) { + return $keepNullType ? [new LegacyType('null')] : null; + } + + if (TypeIdentifier::VOID === $typeIdentifier) { + return [new LegacyType('null')]; + } + + try { + $legacyType = self::convertTypeToLegacy($type); + } catch (LogicException) { + return null; + } + + if (!\is_array($legacyType)) { + $legacyType = [$legacyType]; + } + + return $legacyType; + } + + /** + * Recursive method that converts {@see Type} to its related {@see LegacyType} (or list of {@see @LegacyType}). + * + * @return LegacyType|list + */ + private static function convertTypeToLegacy(Type $type): LegacyType|array + { + if ($type instanceof UnionType) { + $nullable = $type->isNullable(); + + $unionTypes = []; + foreach ($type->getTypes() as $unionType) { + if ('null' === (string) $unionType) { + continue; + } + + if ($unionType instanceof IntersectionType) { + throw new LogicException(sprintf('DNF types are not supported by "%s".', LegacyType::class)); + } + + $unionType->setNullable($nullable); + $unionTypes[] = $unionType; + } + + /** @var list $legacyTypes */ + $legacyTypes = array_map(self::convertTypeToLegacy(...), $unionTypes); + + if (1 === \count($legacyTypes)) { + return $legacyTypes[0]; + } + + return $legacyTypes; + } + + if ($type instanceof IntersectionType) { + foreach ($type->getTypes() as $intersectionType) { + if ($intersectionType instanceof UnionType) { + throw new LogicException(sprintf('DNF types are not supported by "%s".', LegacyType::class)); + } + } + + /** @var list $legacyTypes */ + $legacyTypes = array_map(self::convertTypeToLegacy(...), $type->getTypes()); + + if (1 === \count($legacyTypes)) { + return $legacyTypes[0]; + } + + return $legacyTypes; + } + + if ($type instanceof CollectionType) { + $nestedType = $type->getType(); + $nestedType->setCollection(true); + + return self::convertTypeToLegacy($nestedType); + } + + $typeIdentifier = TypeIdentifier::MIXED; + $className = null; + $collectionKeyType = $collectionValueType = null; + + if ($type instanceof ObjectType) { + $typeIdentifier = $type->getTypeIdentifier(); + $className = $type->getClassName(); + } + + if ($type instanceof GenericType) { + $nestedType = self::unwrapNullableType($type->getType()); + + if ($nestedType instanceof BuiltinType) { + $typeIdentifier = $nestedType->getTypeIdentifier(); + } elseif ($nestedType instanceof ObjectType) { + $typeIdentifier = $nestedType->getTypeIdentifier(); + $className = $nestedType->getClassName(); + } + + $variableTypes = $type->getVariableTypes(); + + if (2 === \count($variableTypes)) { + $collectionKeyType = self::convertTypeToLegacy($variableTypes[0]); + $collectionValueType = self::convertTypeToLegacy($variableTypes[1]); + } elseif (1 === \count($variableTypes)) { + $collectionValueType = self::convertTypeToLegacy($variableTypes[0]); + } + } + + if ($type instanceof BuiltinType) { + $typeIdentifier = $type->getTypeIdentifier(); + } + + return new LegacyType( + builtinType: $typeIdentifier->value, + nullable: $type->isNullable(), + class: $className, + collection: $type instanceof GenericType || $type->isCollection, // legacy generic is always considered as a collection + collectionKeyType: $collectionKeyType, + collectionValueType: $collectionValueType, + ); + } + + public static function unwrapNullableType(Type $type): Type + { + if (!$type instanceof UnionType) { + return $type; + } + + return $type->asNonNullable(); + } +} diff --git a/src/Symfony/Component/PropertyInfo/Util/PhpDocTypeHelper.php b/src/Symfony/Component/PropertyInfo/Util/PhpDocTypeHelper.php index f56d64623360e..3c2ecd9d6f18f 100644 --- a/src/Symfony/Component/PropertyInfo/Util/PhpDocTypeHelper.php +++ b/src/Symfony/Component/PropertyInfo/Util/PhpDocTypeHelper.php @@ -22,7 +22,9 @@ use phpDocumentor\Reflection\Types\Null_; use phpDocumentor\Reflection\Types\Nullable; use phpDocumentor\Reflection\Types\String_; -use Symfony\Component\PropertyInfo\Type; +use Symfony\Component\PropertyInfo\Type as LegacyType; +use Symfony\Component\TypeInfo\Type; +use Symfony\Component\TypeInfo\TypeIdentifier; // Workaround for phpdocumentor/type-resolver < 1.6 // We trigger the autoloader here, so we don't need to trigger it inside the loop later. @@ -37,18 +39,29 @@ class_exists(List_::class); final class PhpDocTypeHelper { /** - * Creates a {@see Type} from a PHPDoc type. + * Creates a {@see LegacyType} from a PHPDoc type. + * + * @deprecated since Symfony 7.1, use "getType" instead. * - * @return Type[] + * @return LegacyType[] */ public function getTypes(DocType $varType): array + { + trigger_deprecation('symfony/property-info', '7.1', 'The "%s()" method is deprecated, use "%s::getType()" instead.', __METHOD__, self::class); + + return BackwardCompatibilityHelper::convertTypeToLegacyTypes($this->getType($varType)) ?? []; + } + + /** + * Creates a {@see Type} from a PHPDoc type. + */ + public function getType(DocType $varType): ?Type { if ($varType instanceof ConstExpression) { // It's safer to fall back to other extractors here, as resolving const types correctly is not easy at the moment - return []; + return null; } - $types = []; $nullable = false; if ($varType instanceof Nullable) { @@ -61,12 +74,7 @@ public function getTypes(DocType $varType): array $nullable = true; } - $type = $this->createType($varType, $nullable); - if (null !== $type) { - $types[] = $type; - } - - return $types; + return $this->createType($varType, $nullable); } $varTypes = []; @@ -75,7 +83,7 @@ public function getTypes(DocType $varType): array if ($type instanceof ConstExpression) { // It's safer to fall back to other extractors here, as resolving const types correctly is not easy at the moment - return []; + return null; } // If null is present, all types are nullable @@ -92,20 +100,23 @@ public function getTypes(DocType $varType): array $varTypes[] = $type; } + $unionTypes = []; foreach ($varTypes as $varType) { - $type = $this->createType($varType, $nullable); - if (null !== $type) { - $types[] = $type; + $t = $this->createType($varType, $nullable); + if (null !== $t) { + $unionTypes[] = $t; } } - return $types; + $type = 1 === \count($unionTypes) ? $unionTypes[0] : Type::union(...$unionTypes); + + return $nullable ? Type::nullable($type) : $type; } /** * Creates a {@see Type} from a PHPDoc type. */ - private function createType(DocType $type, bool $nullable, string $docType = null): ?Type + private function createType(DocType $type, bool $nullable, ?string $docType = null): ?Type { $docType ??= (string) $type; @@ -113,79 +124,99 @@ private function createType(DocType $type, bool $nullable, string $docType = nul $fqsen = $type->getFqsen(); if ($fqsen && 'list' === $fqsen->getName() && !class_exists(List_::class, false) && !class_exists((string) $fqsen)) { // Workaround for phpdocumentor/type-resolver < 1.6 - return new Type(Type::BUILTIN_TYPE_ARRAY, $nullable, null, true, new Type(Type::BUILTIN_TYPE_INT), $this->getTypes($type->getValueType())); + return Type::list($this->getType($type->getValueType())); } [$phpType, $class] = $this->getPhpTypeAndClass((string) $fqsen); - $keys = $this->getTypes($type->getKeyType()); - $values = $this->getTypes($type->getValueType()); + $variableTypes = []; + + if (null !== $valueType = $this->getType($type->getValueType())) { + $variableTypes[] = $valueType; + } + + if (null !== $keyType = $this->getType($type->getKeyType())) { + $variableTypes[] = $keyType; + } + + $t = null !== $class ? Type::object($class) : Type::builtin($phpType); + $t = Type::collection($t, ...$variableTypes); - return new Type($phpType, $nullable, $class, true, $keys, $values); + return $nullable ? Type::nullable($t) : $t; } // Cannot guess - if (!$docType || 'mixed' === $docType) { + if (!$docType) { return null; } if (str_ends_with($docType, '[]') && $type instanceof Array_) { - $collectionKeyTypes = new Type(Type::BUILTIN_TYPE_INT); - $collectionValueTypes = $this->getTypes($type->getValueType()); + return Type::list($this->getType($type->getValueType())); + } + + if (str_starts_with($docType, 'list<') && $type instanceof Array_) { + $collectionValueType = $this->getType($type->getValueType()); + + $t = Type::list($collectionValueType); - return new Type(Type::BUILTIN_TYPE_ARRAY, $nullable, null, true, $collectionKeyTypes, $collectionValueTypes); + return $nullable ? Type::nullable($t) : $t; } - if ((str_starts_with($docType, 'list<') || str_starts_with($docType, 'array<')) && $type instanceof Array_) { + if (str_starts_with($docType, 'array<') && $type instanceof Array_) { // array is converted to x[] which is handled above // so it's only necessary to handle array here - $collectionKeyTypes = $this->getTypes($type->getKeyType()); - $collectionValueTypes = $this->getTypes($type->getValueType()); + $collectionKeyType = $this->getType($type->getKeyType()); + $collectionValueType = $this->getType($type->getValueType()); - return new Type(Type::BUILTIN_TYPE_ARRAY, $nullable, null, true, $collectionKeyTypes, $collectionValueTypes); + $t = Type::array($collectionValueType, $collectionKeyType); + + return $nullable ? Type::nullable($t) : $t; } if ($type instanceof PseudoType) { if ($type->underlyingType() instanceof Integer) { - return new Type(Type::BUILTIN_TYPE_INT, $nullable, null); + return $nullable ? Type::nullable(Type::int()) : Type::int(); } elseif ($type->underlyingType() instanceof String_) { - return new Type(Type::BUILTIN_TYPE_STRING, $nullable, null); + return $nullable ? Type::nullable(Type::string()) : Type::string(); } } $docType = $this->normalizeType($docType); + [$phpType, $class] = $this->getPhpTypeAndClass($docType); if ('array' === $docType) { - return new Type(Type::BUILTIN_TYPE_ARRAY, $nullable, null, true, null, null); + return $nullable ? Type::nullable(Type::array()) : Type::array(); } - return new Type($phpType, $nullable, $class); + $t = null !== $class ? Type::object($class) : Type::builtin($phpType); + + return $nullable ? Type::nullable($t) : $t; } private function normalizeType(string $docType): string { return match ($docType) { - 'integer' => 'int', - 'boolean' => 'bool', + 'integer' => TypeIdentifier::INT->value, + 'boolean' => TypeIdentifier::BOOL->value, // real is not part of the PHPDoc standard, so we ignore it - 'double' => 'float', - 'callback' => 'callable', - 'void' => 'null', + 'double' => TypeIdentifier::FLOAT->value, + 'callback' => TypeIdentifier::CALLABLE->value, + 'void' => TypeIdentifier::NULL->value, default => $docType, }; } private function getPhpTypeAndClass(string $docType): array { - if (\in_array($docType, Type::$builtinTypes, true)) { + if (\in_array($docType, TypeIdentifier::values(), true)) { return [$docType, null]; } if (\in_array($docType, ['parent', 'self', 'static'], true)) { - return ['object', $docType]; + return [TypeIdentifier::OBJECT->value, $docType]; } - return ['object', ltrim($docType, '\\')]; + return [TypeIdentifier::OBJECT->value, ltrim($docType, '\\')]; } } diff --git a/src/Symfony/Component/PropertyInfo/Util/PhpStanTypeHelper.php b/src/Symfony/Component/PropertyInfo/Util/PhpStanTypeHelper.php deleted file mode 100644 index d9b1a7a3ccd31..0000000000000 --- a/src/Symfony/Component/PropertyInfo/Util/PhpStanTypeHelper.php +++ /dev/null @@ -1,204 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\PropertyInfo\Util; - -use PHPStan\PhpDocParser\Ast\PhpDoc\ParamTagValueNode; -use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagValueNode; -use PHPStan\PhpDocParser\Ast\PhpDoc\ReturnTagValueNode; -use PHPStan\PhpDocParser\Ast\PhpDoc\VarTagValueNode; -use PHPStan\PhpDocParser\Ast\Type\ArrayShapeNode; -use PHPStan\PhpDocParser\Ast\Type\ArrayTypeNode; -use PHPStan\PhpDocParser\Ast\Type\CallableTypeNode; -use PHPStan\PhpDocParser\Ast\Type\CallableTypeParameterNode; -use PHPStan\PhpDocParser\Ast\Type\ConstTypeNode; -use PHPStan\PhpDocParser\Ast\Type\GenericTypeNode; -use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode; -use PHPStan\PhpDocParser\Ast\Type\NullableTypeNode; -use PHPStan\PhpDocParser\Ast\Type\ThisTypeNode; -use PHPStan\PhpDocParser\Ast\Type\TypeNode; -use PHPStan\PhpDocParser\Ast\Type\UnionTypeNode; -use Symfony\Component\PropertyInfo\PhpStan\NameScope; -use Symfony\Component\PropertyInfo\Type; - -/** - * Transforms a php doc tag value to a {@link Type} instance. - * - * @author Baptiste Leduc - * - * @internal - */ -final class PhpStanTypeHelper -{ - /** - * Creates a {@see Type} from a PhpDocTagValueNode type. - * - * @return Type[] - */ - public function getTypes(PhpDocTagValueNode $node, NameScope $nameScope): array - { - if ($node instanceof ParamTagValueNode || $node instanceof ReturnTagValueNode || $node instanceof VarTagValueNode) { - return $this->compressNullableType($this->extractTypes($node->type, $nameScope)); - } - - return []; - } - - /** - * Because PhpStan extract null as a separated type when Symfony / PHP compress it in the first available type we - * need this method to mimic how Symfony want null types. - * - * @param Type[] $types - * - * @return Type[] - */ - private function compressNullableType(array $types): array - { - $firstTypeIndex = null; - $nullableTypeIndex = null; - - foreach ($types as $k => $type) { - if (null === $firstTypeIndex && Type::BUILTIN_TYPE_NULL !== $type->getBuiltinType() && !$type->isNullable()) { - $firstTypeIndex = $k; - } - - if (null === $nullableTypeIndex && Type::BUILTIN_TYPE_NULL === $type->getBuiltinType()) { - $nullableTypeIndex = $k; - } - - if (null !== $firstTypeIndex && null !== $nullableTypeIndex) { - break; - } - } - - if (null !== $firstTypeIndex && null !== $nullableTypeIndex) { - $firstType = $types[$firstTypeIndex]; - $types[$firstTypeIndex] = new Type( - $firstType->getBuiltinType(), - true, - $firstType->getClassName(), - $firstType->isCollection(), - $firstType->getCollectionKeyTypes(), - $firstType->getCollectionValueTypes() - ); - unset($types[$nullableTypeIndex]); - } - - return array_values($types); - } - - /** - * @return Type[] - */ - private function extractTypes(TypeNode $node, NameScope $nameScope): array - { - if ($node instanceof UnionTypeNode) { - $types = []; - foreach ($node->types as $type) { - if ($type instanceof ConstTypeNode) { - // It's safer to fall back to other extractors here, as resolving const types correctly is not easy at the moment - return []; - } - foreach ($this->extractTypes($type, $nameScope) as $subType) { - $types[] = $subType; - } - } - - return $this->compressNullableType($types); - } - if ($node instanceof GenericTypeNode) { - if ('class-string' === $node->type->name) { - return [new Type(Type::BUILTIN_TYPE_STRING)]; - } - - [$mainType] = $this->extractTypes($node->type, $nameScope); - - if (Type::BUILTIN_TYPE_INT === $mainType->getBuiltinType()) { - return [$mainType]; - } - - $collectionKeyTypes = $mainType->getCollectionKeyTypes(); - $collectionKeyValues = []; - if (1 === \count($node->genericTypes)) { - foreach ($this->extractTypes($node->genericTypes[0], $nameScope) as $subType) { - $collectionKeyValues[] = $subType; - } - } elseif (2 === \count($node->genericTypes)) { - foreach ($this->extractTypes($node->genericTypes[0], $nameScope) as $keySubType) { - $collectionKeyTypes[] = $keySubType; - } - foreach ($this->extractTypes($node->genericTypes[1], $nameScope) as $valueSubType) { - $collectionKeyValues[] = $valueSubType; - } - } - - return [new Type($mainType->getBuiltinType(), $mainType->isNullable(), $mainType->getClassName(), true, $collectionKeyTypes, $collectionKeyValues)]; - } - if ($node instanceof ArrayShapeNode) { - return [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true)]; - } - if ($node instanceof ArrayTypeNode) { - return [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, [new Type(Type::BUILTIN_TYPE_INT)], $this->extractTypes($node->type, $nameScope))]; - } - if ($node instanceof CallableTypeNode || $node instanceof CallableTypeParameterNode) { - return [new Type(Type::BUILTIN_TYPE_CALLABLE)]; - } - if ($node instanceof NullableTypeNode) { - $subTypes = $this->extractTypes($node->type, $nameScope); - if (\count($subTypes) > 1) { - $subTypes[] = new Type(Type::BUILTIN_TYPE_NULL); - - return $subTypes; - } - - return [new Type($subTypes[0]->getBuiltinType(), true, $subTypes[0]->getClassName(), $subTypes[0]->isCollection(), $subTypes[0]->getCollectionKeyTypes(), $subTypes[0]->getCollectionValueTypes())]; - } - if ($node instanceof ThisTypeNode) { - return [new Type(Type::BUILTIN_TYPE_OBJECT, false, $nameScope->resolveRootClass())]; - } - if ($node instanceof IdentifierTypeNode) { - if (\in_array($node->name, Type::$builtinTypes, true)) { - return [new Type($node->name, false, null, \in_array($node->name, Type::$builtinCollectionTypes, true))]; - } - - return match ($node->name) { - 'integer', - 'positive-int', - 'negative-int' => [new Type(Type::BUILTIN_TYPE_INT)], - 'double' => [new Type(Type::BUILTIN_TYPE_FLOAT)], - 'list', - 'non-empty-list' => [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT))], - 'non-empty-array' => [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true)], - 'mixed' => [], // mixed seems to be ignored in all other extractors - 'parent' => [new Type(Type::BUILTIN_TYPE_OBJECT, false, $node->name)], - 'static', - 'self' => [new Type(Type::BUILTIN_TYPE_OBJECT, false, $nameScope->resolveRootClass())], - 'class-string', - 'html-escaped-string', - 'lowercase-string', - 'non-empty-lowercase-string', - 'non-empty-string', - 'numeric-string', - 'trait-string', - 'interface-string', - 'literal-string' => [new Type(Type::BUILTIN_TYPE_STRING)], - 'void' => [new Type(Type::BUILTIN_TYPE_NULL)], - 'scalar' => [new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_FLOAT), new Type(Type::BUILTIN_TYPE_STRING), new Type(Type::BUILTIN_TYPE_BOOL)], - 'number' => [new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_FLOAT)], - 'numeric' => [new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_FLOAT), new Type(Type::BUILTIN_TYPE_STRING)], - 'array-key' => [new Type(Type::BUILTIN_TYPE_STRING), new Type(Type::BUILTIN_TYPE_INT)], - default => [new Type(Type::BUILTIN_TYPE_OBJECT, false, $nameScope->resolveStringName($node->name))], - }; - } - - return []; - } -} diff --git a/src/Symfony/Component/PropertyInfo/composer.json b/src/Symfony/Component/PropertyInfo/composer.json index 8032b752a108a..5c7901623e398 100644 --- a/src/Symfony/Component/PropertyInfo/composer.json +++ b/src/Symfony/Component/PropertyInfo/composer.json @@ -24,7 +24,9 @@ ], "require": { "php": ">=8.2", - "symfony/string": "^6.4|^7.0" + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/string": "^6.4|^7.0", + "symfony/type-info": "^7.1" }, "require-dev": { "symfony/serializer": "^6.4|^7.0", diff --git a/src/Symfony/Component/RateLimiter/CompoundLimiter.php b/src/Symfony/Component/RateLimiter/CompoundLimiter.php index 1c4f5608f0299..1dc08544a89dc 100644 --- a/src/Symfony/Component/RateLimiter/CompoundLimiter.php +++ b/src/Symfony/Component/RateLimiter/CompoundLimiter.php @@ -31,7 +31,7 @@ public function __construct(array $limiters) $this->limiters = $limiters; } - public function reserve(int $tokens = 1, float $maxTime = null): Reservation + public function reserve(int $tokens = 1, ?float $maxTime = null): Reservation { throw new ReserveNotSupportedException(__CLASS__); } diff --git a/src/Symfony/Component/RateLimiter/Exception/MaxWaitDurationExceededException.php b/src/Symfony/Component/RateLimiter/Exception/MaxWaitDurationExceededException.php index 53029acefe850..25a868d61477a 100644 --- a/src/Symfony/Component/RateLimiter/Exception/MaxWaitDurationExceededException.php +++ b/src/Symfony/Component/RateLimiter/Exception/MaxWaitDurationExceededException.php @@ -20,7 +20,7 @@ class MaxWaitDurationExceededException extends \RuntimeException { private RateLimit $rateLimit; - public function __construct(string $message, RateLimit $rateLimit, int $code = 0, \Throwable $previous = null) + public function __construct(string $message, RateLimit $rateLimit, int $code = 0, ?\Throwable $previous = null) { parent::__construct($message, $code, $previous); diff --git a/src/Symfony/Component/RateLimiter/Exception/RateLimitExceededException.php b/src/Symfony/Component/RateLimiter/Exception/RateLimitExceededException.php index adbe576610b94..15b52b9c5bddc 100644 --- a/src/Symfony/Component/RateLimiter/Exception/RateLimitExceededException.php +++ b/src/Symfony/Component/RateLimiter/Exception/RateLimitExceededException.php @@ -20,7 +20,7 @@ class RateLimitExceededException extends \RuntimeException { private RateLimit $rateLimit; - public function __construct(RateLimit $rateLimit, int $code = 0, \Throwable $previous = null) + public function __construct(RateLimit $rateLimit, int $code = 0, ?\Throwable $previous = null) { parent::__construct('Rate Limit Exceeded', $code, $previous); diff --git a/src/Symfony/Component/RateLimiter/Exception/ReserveNotSupportedException.php b/src/Symfony/Component/RateLimiter/Exception/ReserveNotSupportedException.php index cb7a306004045..07a0fac1052e4 100644 --- a/src/Symfony/Component/RateLimiter/Exception/ReserveNotSupportedException.php +++ b/src/Symfony/Component/RateLimiter/Exception/ReserveNotSupportedException.php @@ -16,7 +16,7 @@ */ class ReserveNotSupportedException extends \BadMethodCallException { - public function __construct(string $limiterClass, int $code = 0, \Throwable $previous = null) + public function __construct(string $limiterClass, int $code = 0, ?\Throwable $previous = null) { parent::__construct(sprintf('Reserving tokens is not supported by "%s".', $limiterClass), $code, $previous); } diff --git a/src/Symfony/Component/RateLimiter/LimiterInterface.php b/src/Symfony/Component/RateLimiter/LimiterInterface.php index 6f0ae7db0678f..f95cf8c464476 100644 --- a/src/Symfony/Component/RateLimiter/LimiterInterface.php +++ b/src/Symfony/Component/RateLimiter/LimiterInterface.php @@ -33,7 +33,7 @@ interface LimiterInterface * @throws ReserveNotSupportedException if this limiter implementation doesn't support reserving tokens * @throws \InvalidArgumentException if $tokens is larger than the maximum burst size */ - public function reserve(int $tokens = 1, float $maxTime = null): Reservation; + public function reserve(int $tokens = 1, ?float $maxTime = null): Reservation; /** * Use this method if you intend to drop if the required number diff --git a/src/Symfony/Component/RateLimiter/Policy/FixedWindowLimiter.php b/src/Symfony/Component/RateLimiter/Policy/FixedWindowLimiter.php index 6e71fe2f85a65..1d116e037c9ee 100644 --- a/src/Symfony/Component/RateLimiter/Policy/FixedWindowLimiter.php +++ b/src/Symfony/Component/RateLimiter/Policy/FixedWindowLimiter.php @@ -29,7 +29,7 @@ final class FixedWindowLimiter implements LimiterInterface private int $limit; private int $interval; - public function __construct(string $id, int $limit, \DateInterval $interval, StorageInterface $storage, LockInterface $lock = null) + public function __construct(string $id, int $limit, \DateInterval $interval, StorageInterface $storage, ?LockInterface $lock = null) { if ($limit < 1) { throw new \InvalidArgumentException(sprintf('Cannot set the limit of "%s" to 0, as that would never accept any hit.', __CLASS__)); @@ -42,7 +42,7 @@ public function __construct(string $id, int $limit, \DateInterval $interval, Sto $this->interval = TimeUtil::dateIntervalToSeconds($interval); } - public function reserve(int $tokens = 1, float $maxTime = null): Reservation + public function reserve(int $tokens = 1, ?float $maxTime = null): Reservation { if ($tokens > $this->limit) { throw new \InvalidArgumentException(sprintf('Cannot reserve more tokens (%d) than the size of the rate limiter (%d).', $tokens, $this->limit)); diff --git a/src/Symfony/Component/RateLimiter/Policy/NoLimiter.php b/src/Symfony/Component/RateLimiter/Policy/NoLimiter.php index 4878e4abde7a8..56339d372ad95 100644 --- a/src/Symfony/Component/RateLimiter/Policy/NoLimiter.php +++ b/src/Symfony/Component/RateLimiter/Policy/NoLimiter.php @@ -25,7 +25,7 @@ */ final class NoLimiter implements LimiterInterface { - public function reserve(int $tokens = 1, float $maxTime = null): Reservation + public function reserve(int $tokens = 1, ?float $maxTime = null): Reservation { return new Reservation(microtime(true), new RateLimit(\PHP_INT_MAX, new \DateTimeImmutable(), true, \PHP_INT_MAX)); } diff --git a/src/Symfony/Component/RateLimiter/Policy/SlidingWindowLimiter.php b/src/Symfony/Component/RateLimiter/Policy/SlidingWindowLimiter.php index 468b0d05ba585..fc9173de49277 100644 --- a/src/Symfony/Component/RateLimiter/Policy/SlidingWindowLimiter.php +++ b/src/Symfony/Component/RateLimiter/Policy/SlidingWindowLimiter.php @@ -37,7 +37,7 @@ final class SlidingWindowLimiter implements LimiterInterface private int $limit; private int $interval; - public function __construct(string $id, int $limit, \DateInterval $interval, StorageInterface $storage, LockInterface $lock = null) + public function __construct(string $id, int $limit, \DateInterval $interval, StorageInterface $storage, ?LockInterface $lock = null) { $this->storage = $storage; $this->lock = $lock; @@ -46,7 +46,7 @@ public function __construct(string $id, int $limit, \DateInterval $interval, Sto $this->interval = TimeUtil::dateIntervalToSeconds($interval); } - public function reserve(int $tokens = 1, float $maxTime = null): Reservation + public function reserve(int $tokens = 1, ?float $maxTime = null): Reservation { if ($tokens > $this->limit) { throw new \InvalidArgumentException(sprintf('Cannot reserve more tokens (%d) than the size of the rate limiter (%d).', $tokens, $this->limit)); diff --git a/src/Symfony/Component/RateLimiter/Policy/TokenBucket.php b/src/Symfony/Component/RateLimiter/Policy/TokenBucket.php index 517c669c60729..f3905999e936d 100644 --- a/src/Symfony/Component/RateLimiter/Policy/TokenBucket.php +++ b/src/Symfony/Component/RateLimiter/Policy/TokenBucket.php @@ -32,7 +32,7 @@ final class TokenBucket implements LimiterStateInterface * @param Rate $rate the fill rate and time of this bucket * @param float|null $timer the current timer of the bucket, defaulting to microtime(true) */ - public function __construct(string $id, int $initialTokens, Rate $rate, float $timer = null) + public function __construct(string $id, int $initialTokens, Rate $rate, ?float $timer = null) { if ($initialTokens < 1) { throw new \InvalidArgumentException(sprintf('Cannot set the limit of "%s" to 0, as that would never accept any hit.', TokenBucketLimiter::class)); diff --git a/src/Symfony/Component/RateLimiter/Policy/TokenBucketLimiter.php b/src/Symfony/Component/RateLimiter/Policy/TokenBucketLimiter.php index d1ebeb2e6ca9f..dec472074f5e8 100644 --- a/src/Symfony/Component/RateLimiter/Policy/TokenBucketLimiter.php +++ b/src/Symfony/Component/RateLimiter/Policy/TokenBucketLimiter.php @@ -28,7 +28,7 @@ final class TokenBucketLimiter implements LimiterInterface private int $maxBurst; private Rate $rate; - public function __construct(string $id, int $maxBurst, Rate $rate, StorageInterface $storage, LockInterface $lock = null) + public function __construct(string $id, int $maxBurst, Rate $rate, StorageInterface $storage, ?LockInterface $lock = null) { $this->id = $id; $this->maxBurst = $maxBurst; @@ -50,7 +50,7 @@ public function __construct(string $id, int $maxBurst, Rate $rate, StorageInterf * @throws MaxWaitDurationExceededException if $maxTime is set and the process needs to wait longer than its value (in seconds) * @throws \InvalidArgumentException if $tokens is larger than the maximum burst size */ - public function reserve(int $tokens = 1, float $maxTime = null): Reservation + public function reserve(int $tokens = 1, ?float $maxTime = null): Reservation { if ($tokens > $this->maxBurst) { throw new \InvalidArgumentException(sprintf('Cannot reserve more tokens (%d) than the burst size of the rate limiter (%d).', $tokens, $this->maxBurst)); diff --git a/src/Symfony/Component/RateLimiter/Policy/Window.php b/src/Symfony/Component/RateLimiter/Policy/Window.php index 42d00de913c0e..6a103fcdbaa7b 100644 --- a/src/Symfony/Component/RateLimiter/Policy/Window.php +++ b/src/Symfony/Component/RateLimiter/Policy/Window.php @@ -26,7 +26,7 @@ final class Window implements LimiterStateInterface private int $maxSize; private float $timer; - public function __construct(string $id, int $intervalInSeconds, int $windowSize, float $timer = null) + public function __construct(string $id, int $intervalInSeconds, int $windowSize, ?float $timer = null) { $this->id = $id; $this->intervalInSeconds = $intervalInSeconds; @@ -44,7 +44,7 @@ public function getExpirationTime(): ?int return $this->intervalInSeconds; } - public function add(int $hits = 1, float $now = null): void + public function add(int $hits = 1, ?float $now = null): void { $now ??= microtime(true); if (($now - $this->timer) > $this->intervalInSeconds) { diff --git a/src/Symfony/Component/RateLimiter/RateLimiterFactory.php b/src/Symfony/Component/RateLimiter/RateLimiterFactory.php index 1b397ac8af0bc..e9afcb0703ba4 100644 --- a/src/Symfony/Component/RateLimiter/RateLimiterFactory.php +++ b/src/Symfony/Component/RateLimiter/RateLimiterFactory.php @@ -30,7 +30,7 @@ final class RateLimiterFactory private StorageInterface $storage; private ?LockFactory $lockFactory; - public function __construct(array $config, StorageInterface $storage, LockFactory $lockFactory = null) + public function __construct(array $config, StorageInterface $storage, ?LockFactory $lockFactory = null) { $this->storage = $storage; $this->lockFactory = $lockFactory; @@ -41,7 +41,7 @@ public function __construct(array $config, StorageInterface $storage, LockFactor $this->config = $options->resolve($config); } - public function create(string $key = null): LimiterInterface + public function create(?string $key = null): LimiterInterface { $id = $this->config['id'].'-'.$key; $lock = $this->lockFactory?->createLock($id); diff --git a/src/Symfony/Component/RateLimiter/Tests/Policy/TokenBucketLimiterTest.php b/src/Symfony/Component/RateLimiter/Tests/Policy/TokenBucketLimiterTest.php index a4add8f653f0a..5068e4231c293 100644 --- a/src/Symfony/Component/RateLimiter/Tests/Policy/TokenBucketLimiterTest.php +++ b/src/Symfony/Component/RateLimiter/Tests/Policy/TokenBucketLimiterTest.php @@ -184,7 +184,7 @@ public function testBucketRefilledWithStrictFrequency() } } - private function createLimiter($initialTokens = 10, Rate $rate = null) + private function createLimiter($initialTokens = 10, ?Rate $rate = null) { return new TokenBucketLimiter('test', $initialTokens, $rate ?? Rate::perSecond(10), $this->storage); } diff --git a/src/Symfony/Component/RemoteEvent/Attribute/AsRemoteEventConsumer.php b/src/Symfony/Component/RemoteEvent/Attribute/AsRemoteEventConsumer.php index 92d3c45384cc8..db08cba8b6c48 100644 --- a/src/Symfony/Component/RemoteEvent/Attribute/AsRemoteEventConsumer.php +++ b/src/Symfony/Component/RemoteEvent/Attribute/AsRemoteEventConsumer.php @@ -17,6 +17,9 @@ #[\Attribute(\Attribute::TARGET_CLASS)] class AsRemoteEventConsumer { + /** + * @param string $name The name of the remote event consumer, used to identify it when defining remote events + */ public function __construct( public string $name, ) { diff --git a/src/Symfony/Component/Routing/Attribute/Route.php b/src/Symfony/Component/Routing/Attribute/Route.php index 16900f626e93b..07abc556ce893 100644 --- a/src/Symfony/Component/Routing/Attribute/Route.php +++ b/src/Symfony/Component/Routing/Attribute/Route.php @@ -14,8 +14,6 @@ /** * @author Fabien Potencier * @author Alexander M. Turek - * - * @final */ #[\Attribute(\Attribute::IS_REPEATABLE | \Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD)] class Route @@ -26,12 +24,24 @@ class Route private array $schemes; /** - * @param array $requirements - * @param string[]|string $methods - * @param string[]|string $schemes + * @param string|array|null $path The route path (i.e. "/user/login") + * @param string|null $name The route name (i.e. "app_user_login") + * @param array $requirements Requirements for the route attributes, @see https://symfony.com/doc/current/routing.html#parameters-validation + * @param array $options Options for the route (i.e. ['prefix' => '/api']) + * @param array $defaults Default values for the route attributes and query parameters + * @param string|null $host The host for which this route should be active (i.e. "localhost") + * @param string|string[] $methods The list of HTTP methods allowed by this route + * @param string|string[] $schemes The list of schemes allowed by this route (i.e. "https") + * @param string|null $condition An expression that must evaluate to true for the route to be matched, @see https://symfony.com/doc/current/routing.html#matching-expressions + * @param int|null $priority The priority of the route if multiple ones are defined for the same path + * @param string|null $locale The locale accepted by the route + * @param string|null $format The format returned by the route (i.e. "json", "xml") + * @param bool|null $utf8 Whether the route accepts UTF-8 in its parameters + * @param bool|null $stateless Whether the route is defined as stateless or stateful, @see https://symfony.com/doc/current/routing.html#stateless-routes + * @param string|null $env The env in which the route is defined (i.e. "dev", "test", "prod") */ public function __construct( - string|array $path = null, + string|array|null $path = null, private ?string $name = null, private array $requirements = [], private array $options = [], @@ -41,10 +51,10 @@ public function __construct( array|string $schemes = [], private ?string $condition = null, private ?int $priority = null, - string $locale = null, - string $format = null, - bool $utf8 = null, - bool $stateless = null, + ?string $locale = null, + ?string $format = null, + ?bool $utf8 = null, + ?bool $stateless = null, private ?string $env = null, ) { if (\is_array($path)) { diff --git a/src/Symfony/Component/Routing/CompiledRoute.php b/src/Symfony/Component/Routing/CompiledRoute.php index 8664158db4e40..03215e36837cf 100644 --- a/src/Symfony/Component/Routing/CompiledRoute.php +++ b/src/Symfony/Component/Routing/CompiledRoute.php @@ -37,7 +37,7 @@ class CompiledRoute implements \Serializable * @param array $hostVariables An array of host variables * @param array $variables An array of variables (variables defined in the path and in the host patterns) */ - public function __construct(string $staticPrefix, string $regex, array $tokens, array $pathVariables, string $hostRegex = null, array $hostTokens = [], array $hostVariables = [], array $variables = []) + public function __construct(string $staticPrefix, string $regex, array $tokens, array $pathVariables, ?string $hostRegex = null, array $hostTokens = [], array $hostVariables = [], array $variables = []) { $this->staticPrefix = $staticPrefix; $this->regex = $regex; diff --git a/src/Symfony/Component/Routing/Exception/MethodNotAllowedException.php b/src/Symfony/Component/Routing/Exception/MethodNotAllowedException.php index 060a06c78c641..31f482fda4867 100644 --- a/src/Symfony/Component/Routing/Exception/MethodNotAllowedException.php +++ b/src/Symfony/Component/Routing/Exception/MethodNotAllowedException.php @@ -25,7 +25,7 @@ class MethodNotAllowedException extends \RuntimeException implements ExceptionIn /** * @param string[] $allowedMethods */ - public function __construct(array $allowedMethods, string $message = '', int $code = 0, \Throwable $previous = null) + public function __construct(array $allowedMethods, string $message = '', int $code = 0, ?\Throwable $previous = null) { $this->allowedMethods = array_map('strtoupper', $allowedMethods); diff --git a/src/Symfony/Component/Routing/Exception/MissingMandatoryParametersException.php b/src/Symfony/Component/Routing/Exception/MissingMandatoryParametersException.php index 266d6e643d0e4..59d446ea0ba76 100644 --- a/src/Symfony/Component/Routing/Exception/MissingMandatoryParametersException.php +++ b/src/Symfony/Component/Routing/Exception/MissingMandatoryParametersException.php @@ -25,7 +25,7 @@ class MissingMandatoryParametersException extends \InvalidArgumentException impl /** * @param string[] $missingParameters */ - public function __construct(string $routeName = '', array $missingParameters = [], int $code = 0, \Throwable $previous = null) + public function __construct(string $routeName = '', array $missingParameters = [], int $code = 0, ?\Throwable $previous = null) { $this->routeName = $routeName; $this->missingParameters = $missingParameters; diff --git a/src/Symfony/Component/Routing/Generator/CompiledUrlGenerator.php b/src/Symfony/Component/Routing/Generator/CompiledUrlGenerator.php index 2a3d5fd2d51b1..de209cdcfcabf 100644 --- a/src/Symfony/Component/Routing/Generator/CompiledUrlGenerator.php +++ b/src/Symfony/Component/Routing/Generator/CompiledUrlGenerator.php @@ -23,7 +23,7 @@ class CompiledUrlGenerator extends UrlGenerator private array $compiledRoutes = []; private ?string $defaultLocale; - public function __construct(array $compiledRoutes, RequestContext $context, LoggerInterface $logger = null, string $defaultLocale = null) + public function __construct(array $compiledRoutes, RequestContext $context, ?LoggerInterface $logger = null, ?string $defaultLocale = null) { $this->compiledRoutes = $compiledRoutes; $this->context = $context; diff --git a/src/Symfony/Component/Routing/Generator/UrlGenerator.php b/src/Symfony/Component/Routing/Generator/UrlGenerator.php index 7d69b5baa03eb..b981a8bef796c 100644 --- a/src/Symfony/Component/Routing/Generator/UrlGenerator.php +++ b/src/Symfony/Component/Routing/Generator/UrlGenerator.php @@ -78,7 +78,7 @@ class UrlGenerator implements UrlGeneratorInterface, ConfigurableRequirementsInt '%7C' => '|', ]; - public function __construct(RouteCollection $routes, RequestContext $context, LoggerInterface $logger = null, string $defaultLocale = null) + public function __construct(RouteCollection $routes, RequestContext $context, ?LoggerInterface $logger = null, ?string $defaultLocale = null) { $this->routes = $routes; $this->context = $context; diff --git a/src/Symfony/Component/Routing/Loader/AttributeClassLoader.php b/src/Symfony/Component/Routing/Loader/AttributeClassLoader.php index 4e08f61c27bb9..3b895a9f8006e 100644 --- a/src/Symfony/Component/Routing/Loader/AttributeClassLoader.php +++ b/src/Symfony/Component/Routing/Loader/AttributeClassLoader.php @@ -71,7 +71,7 @@ public function setRouteAnnotationClass(string $class): void /** * @throws \InvalidArgumentException When route can't be parsed */ - public function load(mixed $class, string $type = null): RouteCollection + public function load(mixed $class, ?string $type = null): RouteCollection { if (!class_exists($class)) { throw new \InvalidArgumentException(sprintf('Class "%s" does not exist.', $class)); @@ -215,7 +215,7 @@ protected function addRoute(RouteCollection $collection, object $annot, array $g } } - public function supports(mixed $resource, string $type = null): bool + public function supports(mixed $resource, ?string $type = null): bool { return \is_string($resource) && preg_match('/^(?:\\\\?[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)+$/', $resource) && (!$type || 'attribute' === $type); } diff --git a/src/Symfony/Component/Routing/Loader/AttributeDirectoryLoader.php b/src/Symfony/Component/Routing/Loader/AttributeDirectoryLoader.php index 3254127f23e70..8bb59823f8e36 100644 --- a/src/Symfony/Component/Routing/Loader/AttributeDirectoryLoader.php +++ b/src/Symfony/Component/Routing/Loader/AttributeDirectoryLoader.php @@ -26,7 +26,7 @@ class AttributeDirectoryLoader extends AttributeFileLoader /** * @throws \InvalidArgumentException When the directory does not exist or its routes cannot be parsed */ - public function load(mixed $path, string $type = null): ?RouteCollection + public function load(mixed $path, ?string $type = null): ?RouteCollection { if (!is_dir($dir = $this->locator->locate($path))) { return parent::supports($path, $type) ? parent::load($path, $type) : new RouteCollection(); @@ -61,7 +61,7 @@ public function load(mixed $path, string $type = null): ?RouteCollection return $collection; } - public function supports(mixed $resource, string $type = null): bool + public function supports(mixed $resource, ?string $type = null): bool { if (!\is_string($resource)) { return false; diff --git a/src/Symfony/Component/Routing/Loader/AttributeFileLoader.php b/src/Symfony/Component/Routing/Loader/AttributeFileLoader.php index c654e2d6240a0..da1adc5b8c3df 100644 --- a/src/Symfony/Component/Routing/Loader/AttributeFileLoader.php +++ b/src/Symfony/Component/Routing/Loader/AttributeFileLoader.php @@ -43,7 +43,7 @@ public function __construct(FileLocatorInterface $locator, AttributeClassLoader * * @throws \InvalidArgumentException When the file does not exist or its routes cannot be parsed */ - public function load(mixed $file, string $type = null): ?RouteCollection + public function load(mixed $file, ?string $type = null): ?RouteCollection { $path = $this->locator->locate($file); @@ -63,7 +63,7 @@ public function load(mixed $file, string $type = null): ?RouteCollection return $collection; } - public function supports(mixed $resource, string $type = null): bool + public function supports(mixed $resource, ?string $type = null): bool { return \is_string($resource) && 'php' === pathinfo($resource, \PATHINFO_EXTENSION) && (!$type || 'attribute' === $type); } diff --git a/src/Symfony/Component/Routing/Loader/ClosureLoader.php b/src/Symfony/Component/Routing/Loader/ClosureLoader.php index e079e78c2731b..dcc5ee333e8c6 100644 --- a/src/Symfony/Component/Routing/Loader/ClosureLoader.php +++ b/src/Symfony/Component/Routing/Loader/ClosureLoader.php @@ -26,12 +26,12 @@ class ClosureLoader extends Loader /** * Loads a Closure. */ - public function load(mixed $closure, string $type = null): RouteCollection + public function load(mixed $closure, ?string $type = null): RouteCollection { return $closure($this->env); } - public function supports(mixed $resource, string $type = null): bool + public function supports(mixed $resource, ?string $type = null): bool { return $resource instanceof \Closure && (!$type || 'closure' === $type); } diff --git a/src/Symfony/Component/Routing/Loader/Configurator/CollectionConfigurator.php b/src/Symfony/Component/Routing/Loader/Configurator/CollectionConfigurator.php index 241cee7825643..ed9e0b1bdcc24 100644 --- a/src/Symfony/Component/Routing/Loader/Configurator/CollectionConfigurator.php +++ b/src/Symfony/Component/Routing/Loader/Configurator/CollectionConfigurator.php @@ -28,7 +28,7 @@ class CollectionConfigurator private ?array $parentPrefixes; private string|array|null $host = null; - public function __construct(RouteCollection $parent, string $name, self $parentConfigurator = null, array $parentPrefixes = null) + public function __construct(RouteCollection $parent, string $name, ?self $parentConfigurator = null, ?array $parentPrefixes = null) { $this->parent = $parent; $this->name = $name; diff --git a/src/Symfony/Component/Routing/Loader/Configurator/RouteConfigurator.php b/src/Symfony/Component/Routing/Loader/Configurator/RouteConfigurator.php index bb2f6c6f35246..0ac79b7e44344 100644 --- a/src/Symfony/Component/Routing/Loader/Configurator/RouteConfigurator.php +++ b/src/Symfony/Component/Routing/Loader/Configurator/RouteConfigurator.php @@ -24,7 +24,7 @@ class RouteConfigurator protected ?CollectionConfigurator $parentConfigurator; - public function __construct(RouteCollection $collection, RouteCollection $route, string $name = '', CollectionConfigurator $parentConfigurator = null, array $prefixes = null) + public function __construct(RouteCollection $collection, RouteCollection $route, string $name = '', ?CollectionConfigurator $parentConfigurator = null, ?array $prefixes = null) { $this->collection = $collection; $this->route = $route; diff --git a/src/Symfony/Component/Routing/Loader/Configurator/RoutingConfigurator.php b/src/Symfony/Component/Routing/Loader/Configurator/RoutingConfigurator.php index 80f9330d5b98f..fa88aa677f5fc 100644 --- a/src/Symfony/Component/Routing/Loader/Configurator/RoutingConfigurator.php +++ b/src/Symfony/Component/Routing/Loader/Configurator/RoutingConfigurator.php @@ -26,7 +26,7 @@ class RoutingConfigurator private string $file; private ?string $env; - public function __construct(RouteCollection $collection, PhpFileLoader $loader, string $path, string $file, string $env = null) + public function __construct(RouteCollection $collection, PhpFileLoader $loader, string $path, string $file, ?string $env = null) { $this->collection = $collection; $this->loader = $loader; @@ -38,7 +38,7 @@ public function __construct(RouteCollection $collection, PhpFileLoader $loader, /** * @param string|string[]|null $exclude Glob patterns to exclude from the import */ - final public function import(string|array $resource, string $type = null, bool $ignoreErrors = false, string|array $exclude = null): ImportConfigurator + final public function import(string|array $resource, ?string $type = null, bool $ignoreErrors = false, string|array|null $exclude = null): ImportConfigurator { $this->loader->setCurrentDir(\dirname($this->path)); diff --git a/src/Symfony/Component/Routing/Loader/Configurator/Traits/LocalizedRouteTrait.php b/src/Symfony/Component/Routing/Loader/Configurator/Traits/LocalizedRouteTrait.php index c7c40e9801a0d..a26a73420ba56 100644 --- a/src/Symfony/Component/Routing/Loader/Configurator/Traits/LocalizedRouteTrait.php +++ b/src/Symfony/Component/Routing/Loader/Configurator/Traits/LocalizedRouteTrait.php @@ -27,7 +27,7 @@ trait LocalizedRouteTrait * * @param string|array $path the path, or the localized paths of the route */ - final protected function createLocalizedRoute(RouteCollection $collection, string $name, string|array $path, string $namePrefix = '', array $prefixes = null): RouteCollection + final protected function createLocalizedRoute(RouteCollection $collection, string $name, string|array $path, string $namePrefix = '', ?array $prefixes = null): RouteCollection { $paths = []; diff --git a/src/Symfony/Component/Routing/Loader/Configurator/Traits/PrefixTrait.php b/src/Symfony/Component/Routing/Loader/Configurator/Traits/PrefixTrait.php index 58686380bcf04..89a65d8f77d20 100644 --- a/src/Symfony/Component/Routing/Loader/Configurator/Traits/PrefixTrait.php +++ b/src/Symfony/Component/Routing/Loader/Configurator/Traits/PrefixTrait.php @@ -29,6 +29,7 @@ final protected function addPrefix(RouteCollection $routes, string|array $prefix } foreach ($routes->all() as $name => $route) { if (null === $locale = $route->getDefault('_locale')) { + $priority = $routes->getPriority($name) ?? 0; $routes->remove($name); foreach ($prefix as $locale => $localePrefix) { $localizedRoute = clone $route; @@ -36,13 +37,13 @@ final protected function addPrefix(RouteCollection $routes, string|array $prefix $localizedRoute->setRequirement('_locale', preg_quote($locale)); $localizedRoute->setDefault('_canonical_route', $name); $localizedRoute->setPath($localePrefix.(!$trailingSlashOnRoot && '/' === $route->getPath() ? '' : $route->getPath())); - $routes->add($name.'.'.$locale, $localizedRoute); + $routes->add($name.'.'.$locale, $localizedRoute, $priority); } } elseif (!isset($prefix[$locale])) { throw new \InvalidArgumentException(sprintf('Route "%s" with locale "%s" is missing a corresponding prefix in its parent collection.', $name, $locale)); } else { $route->setPath($prefix[$locale].(!$trailingSlashOnRoot && '/' === $route->getPath() ? '' : $route->getPath())); - $routes->add($name, $route); + $routes->add($name, $route, $routes->getPriority($name) ?? 0); } } diff --git a/src/Symfony/Component/Routing/Loader/ContainerLoader.php b/src/Symfony/Component/Routing/Loader/ContainerLoader.php index 716ec499a9408..af325be08bd67 100644 --- a/src/Symfony/Component/Routing/Loader/ContainerLoader.php +++ b/src/Symfony/Component/Routing/Loader/ContainerLoader.php @@ -22,13 +22,13 @@ class ContainerLoader extends ObjectLoader { private ContainerInterface $container; - public function __construct(ContainerInterface $container, string $env = null) + public function __construct(ContainerInterface $container, ?string $env = null) { $this->container = $container; parent::__construct($env); } - public function supports(mixed $resource, string $type = null): bool + public function supports(mixed $resource, ?string $type = null): bool { return 'service' === $type && \is_string($resource); } diff --git a/src/Symfony/Component/Routing/Loader/DirectoryLoader.php b/src/Symfony/Component/Routing/Loader/DirectoryLoader.php index 4ea7c6ba41a5a..6c6c48e2e19f4 100644 --- a/src/Symfony/Component/Routing/Loader/DirectoryLoader.php +++ b/src/Symfony/Component/Routing/Loader/DirectoryLoader.php @@ -17,7 +17,7 @@ class DirectoryLoader extends FileLoader { - public function load(mixed $file, string $type = null): mixed + public function load(mixed $file, ?string $type = null): mixed { $path = $this->locator->locate($file); @@ -43,7 +43,7 @@ public function load(mixed $file, string $type = null): mixed return $collection; } - public function supports(mixed $resource, string $type = null): bool + public function supports(mixed $resource, ?string $type = null): bool { // only when type is forced to directory, not to conflict with AttributeLoader diff --git a/src/Symfony/Component/Routing/Loader/GlobFileLoader.php b/src/Symfony/Component/Routing/Loader/GlobFileLoader.php index d3974fc7dd92f..65afa5a377936 100644 --- a/src/Symfony/Component/Routing/Loader/GlobFileLoader.php +++ b/src/Symfony/Component/Routing/Loader/GlobFileLoader.php @@ -21,7 +21,7 @@ */ class GlobFileLoader extends FileLoader { - public function load(mixed $resource, string $type = null): mixed + public function load(mixed $resource, ?string $type = null): mixed { $collection = new RouteCollection(); @@ -34,7 +34,7 @@ public function load(mixed $resource, string $type = null): mixed return $collection; } - public function supports(mixed $resource, string $type = null): bool + public function supports(mixed $resource, ?string $type = null): bool { return 'glob' === $type; } diff --git a/src/Symfony/Component/Routing/Loader/ObjectLoader.php b/src/Symfony/Component/Routing/Loader/ObjectLoader.php index a2a57068f14ca..c2ad6a03f04d9 100644 --- a/src/Symfony/Component/Routing/Loader/ObjectLoader.php +++ b/src/Symfony/Component/Routing/Loader/ObjectLoader.php @@ -33,7 +33,7 @@ abstract protected function getObject(string $id): object; /** * Calls the object method that will load the routes. */ - public function load(mixed $resource, string $type = null): RouteCollection + public function load(mixed $resource, ?string $type = null): RouteCollection { if (!preg_match('/^[^\:]+(?:::(?:[^\:]+))?$/', $resource)) { throw new \InvalidArgumentException(sprintf('Invalid resource "%s" passed to the %s route loader: use the format "object_id::method" or "object_id" if your object class has an "__invoke" method.', $resource, \is_string($type) ? '"'.$type.'"' : 'object')); diff --git a/src/Symfony/Component/Routing/Loader/PhpFileLoader.php b/src/Symfony/Component/Routing/Loader/PhpFileLoader.php index 6abee2e82bc4a..adf7eed3f3873 100644 --- a/src/Symfony/Component/Routing/Loader/PhpFileLoader.php +++ b/src/Symfony/Component/Routing/Loader/PhpFileLoader.php @@ -30,7 +30,7 @@ class PhpFileLoader extends FileLoader /** * Loads a PHP file. */ - public function load(mixed $file, string $type = null): RouteCollection + public function load(mixed $file, ?string $type = null): RouteCollection { $path = $this->locator->locate($file); $this->setCurrentDir(\dirname($path)); @@ -54,7 +54,7 @@ public function load(mixed $file, string $type = null): RouteCollection return $collection; } - public function supports(mixed $resource, string $type = null): bool + public function supports(mixed $resource, ?string $type = null): bool { return \is_string($resource) && 'php' === pathinfo($resource, \PATHINFO_EXTENSION) && (!$type || 'php' === $type); } diff --git a/src/Symfony/Component/Routing/Loader/Psr4DirectoryLoader.php b/src/Symfony/Component/Routing/Loader/Psr4DirectoryLoader.php index 2a41557043997..738b56f499cf8 100644 --- a/src/Symfony/Component/Routing/Loader/Psr4DirectoryLoader.php +++ b/src/Symfony/Component/Routing/Loader/Psr4DirectoryLoader.php @@ -36,7 +36,7 @@ public function __construct( /** * @param array{path: string, namespace: string} $resource */ - public function load(mixed $resource, string $type = null): ?RouteCollection + public function load(mixed $resource, ?string $type = null): ?RouteCollection { $path = $this->locator->locate($resource['path'], $this->currentDirectory); if (!is_dir($path)) { @@ -46,7 +46,7 @@ public function load(mixed $resource, string $type = null): ?RouteCollection return $this->loadFromDirectory($path, trim($resource['namespace'], '\\')); } - public function supports(mixed $resource, string $type = null): bool + public function supports(mixed $resource, ?string $type = null): bool { return 'attribute' === $type && \is_array($resource) && isset($resource['path'], $resource['namespace']); } diff --git a/src/Symfony/Component/Routing/Loader/XmlFileLoader.php b/src/Symfony/Component/Routing/Loader/XmlFileLoader.php index 540c020f6df6c..296c2fedfc471 100644 --- a/src/Symfony/Component/Routing/Loader/XmlFileLoader.php +++ b/src/Symfony/Component/Routing/Loader/XmlFileLoader.php @@ -38,7 +38,7 @@ class XmlFileLoader extends FileLoader * @throws \InvalidArgumentException when the file cannot be loaded or when the XML cannot be * parsed because it does not validate against the scheme */ - public function load(mixed $file, string $type = null): RouteCollection + public function load(mixed $file, ?string $type = null): RouteCollection { $path = $this->locator->locate($file); @@ -92,7 +92,7 @@ protected function parseNode(RouteCollection $collection, \DOMElement $node, str } } - public function supports(mixed $resource, string $type = null): bool + public function supports(mixed $resource, ?string $type = null): bool { return \is_string($resource) && 'xml' === pathinfo($resource, \PATHINFO_EXTENSION) && (!$type || 'xml' === $type); } diff --git a/src/Symfony/Component/Routing/Loader/YamlFileLoader.php b/src/Symfony/Component/Routing/Loader/YamlFileLoader.php index 50e991cb1918d..f5ea8e8afc854 100644 --- a/src/Symfony/Component/Routing/Loader/YamlFileLoader.php +++ b/src/Symfony/Component/Routing/Loader/YamlFileLoader.php @@ -41,7 +41,7 @@ class YamlFileLoader extends FileLoader /** * @throws \InvalidArgumentException When a route can't be parsed because YAML is invalid */ - public function load(mixed $file, string $type = null): RouteCollection + public function load(mixed $file, ?string $type = null): RouteCollection { $path = $this->locator->locate($file); @@ -105,7 +105,7 @@ public function load(mixed $file, string $type = null): RouteCollection return $collection; } - public function supports(mixed $resource, string $type = null): bool + public function supports(mixed $resource, ?string $type = null): bool { return \is_string($resource) && \in_array(pathinfo($resource, \PATHINFO_EXTENSION), ['yml', 'yaml'], true) && (!$type || 'yaml' === $type); } diff --git a/src/Symfony/Component/Routing/Matcher/RedirectableUrlMatcherInterface.php b/src/Symfony/Component/Routing/Matcher/RedirectableUrlMatcherInterface.php index cd7e8c2c1a942..e4bcedda0f7e5 100644 --- a/src/Symfony/Component/Routing/Matcher/RedirectableUrlMatcherInterface.php +++ b/src/Symfony/Component/Routing/Matcher/RedirectableUrlMatcherInterface.php @@ -25,5 +25,5 @@ interface RedirectableUrlMatcherInterface * @param string $route The route name that matched * @param string|null $scheme The URL scheme (null to keep the current one) */ - public function redirect(string $path, string $route, string $scheme = null): array; + public function redirect(string $path, string $route, ?string $scheme = null): array; } diff --git a/src/Symfony/Component/Routing/Matcher/TraceableUrlMatcher.php b/src/Symfony/Component/Routing/Matcher/TraceableUrlMatcher.php index a5d871d83be22..b7aa2b6c9b76e 100644 --- a/src/Symfony/Component/Routing/Matcher/TraceableUrlMatcher.php +++ b/src/Symfony/Component/Routing/Matcher/TraceableUrlMatcher.php @@ -154,7 +154,7 @@ protected function matchCollection(string $pathinfo, RouteCollection $routes): a return []; } - private function addTrace(string $log, int $level = self::ROUTE_DOES_NOT_MATCH, string $name = null, Route $route = null): void + private function addTrace(string $log, int $level = self::ROUTE_DOES_NOT_MATCH, ?string $name = null, ?Route $route = null): void { $this->traces[] = [ 'log' => $log, diff --git a/src/Symfony/Component/Routing/RouteCollection.php b/src/Symfony/Component/Routing/RouteCollection.php index 0d1884a45f997..df8e3370c38ae 100644 --- a/src/Symfony/Component/Routing/RouteCollection.php +++ b/src/Symfony/Component/Routing/RouteCollection.php @@ -380,4 +380,9 @@ public function getAlias(string $name): ?Alias { return $this->aliases[$name] ?? null; } + + public function getPriority(string $name): ?int + { + return $this->priorities[$name] ?? null; + } } diff --git a/src/Symfony/Component/Routing/Router.php b/src/Symfony/Component/Routing/Router.php index 11c430714013d..cc3d351db3f97 100644 --- a/src/Symfony/Component/Routing/Router.php +++ b/src/Symfony/Component/Routing/Router.php @@ -56,7 +56,7 @@ class Router implements RouterInterface, RequestMatcherInterface private static ?array $cache = []; - public function __construct(LoaderInterface $loader, mixed $resource, array $options = [], RequestContext $context = null, LoggerInterface $logger = null, string $defaultLocale = null) + public function __construct(LoaderInterface $loader, mixed $resource, array $options = [], ?RequestContext $context = null, ?LoggerInterface $logger = null, ?string $defaultLocale = null) { $this->loader = $loader; $this->resource = $resource; diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/AttributeFixtures/ExtendedRoute.php b/src/Symfony/Component/Routing/Tests/Fixtures/AttributeFixtures/ExtendedRoute.php new file mode 100644 index 0000000000000..72232cbf6d50a --- /dev/null +++ b/src/Symfony/Component/Routing/Tests/Fixtures/AttributeFixtures/ExtendedRoute.php @@ -0,0 +1,14 @@ +}" . $path, $name, [], [], array_merge(['section' => 'foo'], $defaults)); + } +} diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/AttributeFixtures/ExtendedRouteOnClassController.php b/src/Symfony/Component/Routing/Tests/Fixtures/AttributeFixtures/ExtendedRouteOnClassController.php new file mode 100644 index 0000000000000..29ec190f8b5d9 --- /dev/null +++ b/src/Symfony/Component/Routing/Tests/Fixtures/AttributeFixtures/ExtendedRouteOnClassController.php @@ -0,0 +1,14 @@ + 'cs'])] + public function alsoImportant() + { + } +} diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/localized/localized-prefix.yml b/src/Symfony/Component/Routing/Tests/Fixtures/localized/localized-prefix.yml new file mode 100644 index 0000000000000..031fc71951452 --- /dev/null +++ b/src/Symfony/Component/Routing/Tests/Fixtures/localized/localized-prefix.yml @@ -0,0 +1,6 @@ +important_controllers: + resource: Symfony\Component\Routing\Tests\Fixtures\AttributeFixtures\RouteWithPriorityController + type: attribute + prefix: + cs: /cs + en: /en diff --git a/src/Symfony/Component/Routing/Tests/Generator/UrlGeneratorTest.php b/src/Symfony/Component/Routing/Tests/Generator/UrlGeneratorTest.php index 4db2f9596764b..839394485b8e1 100644 --- a/src/Symfony/Component/Routing/Tests/Generator/UrlGeneratorTest.php +++ b/src/Symfony/Component/Routing/Tests/Generator/UrlGeneratorTest.php @@ -1054,7 +1054,7 @@ public function testUtf8VarName() $this->assertSame('/app.php/foo/baz', $this->getGenerator($routes)->generate('test', ['bär' => 'baz'])); } - protected function getGenerator(RouteCollection $routes, array $parameters = [], $logger = null, string $defaultLocale = null) + protected function getGenerator(RouteCollection $routes, array $parameters = [], $logger = null, ?string $defaultLocale = null) { $context = new RequestContext('/app.php'); foreach ($parameters as $key => $value) { diff --git a/src/Symfony/Component/Routing/Tests/Loader/AttributeClassLoaderTestCase.php b/src/Symfony/Component/Routing/Tests/Loader/AttributeClassLoaderTest.php similarity index 87% rename from src/Symfony/Component/Routing/Tests/Loader/AttributeClassLoaderTestCase.php rename to src/Symfony/Component/Routing/Tests/Loader/AttributeClassLoaderTest.php index 53791d3ba9960..9b8c5d27397ed 100644 --- a/src/Symfony/Component/Routing/Tests/Loader/AttributeClassLoaderTestCase.php +++ b/src/Symfony/Component/Routing/Tests/Loader/AttributeClassLoaderTest.php @@ -13,13 +13,14 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\Routing\Alias; -use Symfony\Component\Routing\Loader\AttributeClassLoader; use Symfony\Component\Routing\Tests\Fixtures\AttributeFixtures\AbstractClassController; use Symfony\Component\Routing\Tests\Fixtures\AttributeFixtures\ActionPathController; use Symfony\Component\Routing\Tests\Fixtures\AttributeFixtures\BazClass; use Symfony\Component\Routing\Tests\Fixtures\AttributeFixtures\DefaultValueController; use Symfony\Component\Routing\Tests\Fixtures\AttributeFixtures\EncodingClass; use Symfony\Component\Routing\Tests\Fixtures\AttributeFixtures\ExplicitLocalizedActionPathController; +use Symfony\Component\Routing\Tests\Fixtures\AttributeFixtures\ExtendedRouteOnClassController; +use Symfony\Component\Routing\Tests\Fixtures\AttributeFixtures\ExtendedRouteOnMethodController; use Symfony\Component\Routing\Tests\Fixtures\AttributeFixtures\GlobalDefaultsClass; use Symfony\Component\Routing\Tests\Fixtures\AttributeFixtures\InvokableController; use Symfony\Component\Routing\Tests\Fixtures\AttributeFixtures\InvokableLocalizedController; @@ -40,10 +41,18 @@ use Symfony\Component\Routing\Tests\Fixtures\AttributeFixtures\RouteWithEnv; use Symfony\Component\Routing\Tests\Fixtures\AttributeFixtures\RouteWithPrefixController; use Symfony\Component\Routing\Tests\Fixtures\AttributeFixtures\Utf8ActionControllers; +use Symfony\Component\Routing\Tests\Fixtures\TraceableAttributeClassLoader; -abstract class AttributeClassLoaderTestCase extends TestCase +class AttributeClassLoaderTest extends TestCase { - protected AttributeClassLoader $loader; + protected TraceableAttributeClassLoader $loader; + + protected function setUp(?string $env = null): void + { + parent::setUp(); + + $this->loader = new TraceableAttributeClassLoader($env); + } /** * @dataProvider provideTestSupportsChecksResource @@ -77,7 +86,7 @@ public function testSimplePathRoute() $routes = $this->loader->load(ActionPathController::class); $this->assertCount(1, $routes); $this->assertEquals('/path', $routes->get('action')->getPath()); - $this->assertEquals(new Alias('action'), $routes->getAlias('Symfony\Component\Routing\Tests\Fixtures\AttributeFixtures' .'\ActionPathController::action')); + $this->assertEquals(new Alias('action'), $routes->getAlias('Symfony\Component\Routing\Tests\Fixtures\AttributeFixtures\ActionPathController::action')); } public function testRequirementsWithoutPlaceholderName() @@ -101,11 +110,11 @@ public function testInvokableControllerLoader() public function testInvokableFQCNAliasConflictController() { - $routes = $this->loader->load($this->getNamespace().'\InvokableFQCNAliasConflictController'); + $routes = $this->loader->load('Symfony\Component\Routing\Tests\Fixtures\AttributeFixtures\InvokableFQCNAliasConflictController'); $this->assertCount(1, $routes); - $this->assertEquals('/foobarccc', $routes->get($this->getNamespace().'\InvokableFQCNAliasConflictController')->getPath()); - $this->assertNull($routes->getAlias($this->getNamespace().'\InvokableFQCNAliasConflictController')); - $this->assertEquals(new Alias($this->getNamespace().'\InvokableFQCNAliasConflictController'), $routes->getAlias($this->getNamespace().'\InvokableFQCNAliasConflictController::__invoke')); + $this->assertEquals('/foobarccc', $routes->get('Symfony\Component\Routing\Tests\Fixtures\AttributeFixtures\InvokableFQCNAliasConflictController')->getPath()); + $this->assertNull($routes->getAlias('Symfony\Component\Routing\Tests\Fixtures\AttributeFixtures\InvokableFQCNAliasConflictController')); + $this->assertEquals(new Alias('Symfony\Component\Routing\Tests\Fixtures\AttributeFixtures\InvokableFQCNAliasConflictController'), $routes->getAlias('Symfony\Component\Routing\Tests\Fixtures\AttributeFixtures\InvokableFQCNAliasConflictController::__invoke')); } public function testInvokableMethodControllerLoader() @@ -164,8 +173,8 @@ public function testMethodActionControllers() $this->assertSame(['put', 'post'], array_keys($routes->all())); $this->assertEquals('/the/path', $routes->get('put')->getPath()); $this->assertEquals('/the/path', $routes->get('post')->getPath()); - $this->assertEquals(new Alias('post'), $routes->getAlias('Symfony\Component\Routing\Tests\Fixtures\AttributeFixtures' .'\MethodActionControllers::post')); - $this->assertEquals(new Alias('put'), $routes->getAlias('Symfony\Component\Routing\Tests\Fixtures\AttributeFixtures' .'\MethodActionControllers::put')); + $this->assertEquals(new Alias('post'), $routes->getAlias('Symfony\Component\Routing\Tests\Fixtures\AttributeFixtures\MethodActionControllers::post')); + $this->assertEquals(new Alias('put'), $routes->getAlias('Symfony\Component\Routing\Tests\Fixtures\AttributeFixtures\MethodActionControllers::put')); } public function testInvokableClassRouteLoadWithMethodAnnotation() @@ -325,6 +334,22 @@ public function testMethodsAndSchemes() $this->assertSame(['https'], $routes->get('string')->getSchemes()); } + public function testLoadingExtendedRouteOnClass() + { + $routes = $this->loader->load(ExtendedRouteOnClassController::class); + $this->assertCount(1, $routes); + $this->assertSame('/{section}/class-level/method-level', $routes->get('action')->getPath()); + $this->assertSame(['section' => 'foo'], $routes->get('action')->getDefaults()); + } + + public function testLoadingExtendedRouteOnMethod() + { + $routes = $this->loader->load(ExtendedRouteOnMethodController::class); + $this->assertCount(1, $routes); + $this->assertSame('/{section}/method-level', $routes->get('action')->getPath()); + $this->assertSame(['section' => 'foo'], $routes->get('action')->getDefaults()); + } + public function testDefaultRouteName() { $routeCollection = $this->loader->load(EncodingClass::class); diff --git a/src/Symfony/Component/Routing/Tests/Loader/ClosureLoaderTest.php b/src/Symfony/Component/Routing/Tests/Loader/ClosureLoaderTest.php index da8ad090dd4d8..85ecd8769169a 100644 --- a/src/Symfony/Component/Routing/Tests/Loader/ClosureLoaderTest.php +++ b/src/Symfony/Component/Routing/Tests/Loader/ClosureLoaderTest.php @@ -36,7 +36,7 @@ public function testLoad() $loader = new ClosureLoader('some-env'); $route = new Route('/'); - $routes = $loader->load(function (string $env = null) use ($route) { + $routes = $loader->load(function (?string $env = null) use ($route) { $this->assertSame('some-env', $env); $routes = new RouteCollection(); diff --git a/src/Symfony/Component/Routing/Tests/Loader/ContainerLoaderTest.php b/src/Symfony/Component/Routing/Tests/Loader/ContainerLoaderTest.php index 6a3e4c516c6c4..e4f9923861e35 100644 --- a/src/Symfony/Component/Routing/Tests/Loader/ContainerLoaderTest.php +++ b/src/Symfony/Component/Routing/Tests/Loader/ContainerLoaderTest.php @@ -20,7 +20,7 @@ class ContainerLoaderTest extends TestCase /** * @dataProvider supportsProvider */ - public function testSupports(bool $expected, string $type = null) + public function testSupports(bool $expected, ?string $type = null) { $this->assertSame($expected, (new ContainerLoader(new Container()))->supports('foo', $type)); } diff --git a/src/Symfony/Component/Routing/Tests/Loader/FileLocatorStub.php b/src/Symfony/Component/Routing/Tests/Loader/FileLocatorStub.php index 2f4d2c9adf25b..c9e15c2fd5f09 100644 --- a/src/Symfony/Component/Routing/Tests/Loader/FileLocatorStub.php +++ b/src/Symfony/Component/Routing/Tests/Loader/FileLocatorStub.php @@ -15,7 +15,7 @@ class FileLocatorStub implements FileLocatorInterface { - public function locate(string $name, string $currentPath = null, bool $first = true): string|array + public function locate(string $name, ?string $currentPath = null, bool $first = true): string|array { if (str_starts_with($name, 'http')) { return $name; diff --git a/src/Symfony/Component/Routing/Tests/Loader/GlobFileLoaderTest.php b/src/Symfony/Component/Routing/Tests/Loader/GlobFileLoaderTest.php index b08eb53c57c80..61402ada7228f 100644 --- a/src/Symfony/Component/Routing/Tests/Loader/GlobFileLoaderTest.php +++ b/src/Symfony/Component/Routing/Tests/Loader/GlobFileLoaderTest.php @@ -38,7 +38,7 @@ public function testLoadAddsTheGlobResourceToTheContainer() class GlobFileLoaderWithoutImport extends GlobFileLoader { - public function import(mixed $resource, string $type = null, bool $ignoreErrors = false, string $sourceResource = null, $exclude = null): mixed + public function import(mixed $resource, ?string $type = null, bool $ignoreErrors = false, ?string $sourceResource = null, $exclude = null): mixed { return new RouteCollection(); } diff --git a/src/Symfony/Component/Routing/Tests/Loader/ObjectLoaderTest.php b/src/Symfony/Component/Routing/Tests/Loader/ObjectLoaderTest.php index c5aeff9f7f658..fa8dc96e9ab12 100644 --- a/src/Symfony/Component/Routing/Tests/Loader/ObjectLoaderTest.php +++ b/src/Symfony/Component/Routing/Tests/Loader/ObjectLoaderTest.php @@ -106,7 +106,7 @@ class TestObjectLoader extends ObjectLoader { public array $loaderMap = []; - public function supports(mixed $resource, string $type = null): bool + public function supports(mixed $resource, ?string $type = null): bool { return 'service'; } @@ -122,13 +122,13 @@ class TestObjectLoaderRouteService private RouteCollection $collection; private ?string $env; - public function __construct($collection, string $env = null) + public function __construct($collection, ?string $env = null) { $this->collection = $collection; $this->env = $env; } - public function loadRoutes(TestObjectLoader $loader, string $env = null) + public function loadRoutes(TestObjectLoader $loader, ?string $env = null) { if ($this->env !== $env) { throw new \InvalidArgumentException(sprintf('Expected env "%s", "%s" given.', $this->env, $env)); diff --git a/src/Symfony/Component/Routing/Tests/Loader/YamlFileLoaderTest.php b/src/Symfony/Component/Routing/Tests/Loader/YamlFileLoaderTest.php index 5e19254d8737a..b7bf8169cabfb 100644 --- a/src/Symfony/Component/Routing/Tests/Loader/YamlFileLoaderTest.php +++ b/src/Symfony/Component/Routing/Tests/Loader/YamlFileLoaderTest.php @@ -470,6 +470,25 @@ public function testImportingAliases() $this->assertEquals($expectedRoutes('yaml'), $routes); } + public function testPriorityWithPrefix() + { + new LoaderResolver([ + $loader = new YamlFileLoader(new FileLocator(\dirname(__DIR__).'/Fixtures/localized')), + new class() extends AttributeClassLoader { + protected function configureRoute(Route $route, \ReflectionClass $class, \ReflectionMethod $method, object $annot): void + { + $route->setDefault('_controller', $class->getName().'::'.$method->getName()); + } + }, + ]); + + $routes = $loader->load('localized-prefix.yml'); + + $this->assertSame(2, $routes->getPriority('important.cs')); + $this->assertSame(2, $routes->getPriority('important.en')); + $this->assertSame(1, $routes->getPriority('also_important')); + } + /** * @dataProvider providePsr4ConfigFiles */ diff --git a/src/Symfony/Component/Routing/Tests/Matcher/CompiledRedirectableUrlMatcherTest.php b/src/Symfony/Component/Routing/Tests/Matcher/CompiledRedirectableUrlMatcherTest.php index 2dcadc27e5cc3..9e94a1d0374b7 100644 --- a/src/Symfony/Component/Routing/Tests/Matcher/CompiledRedirectableUrlMatcherTest.php +++ b/src/Symfony/Component/Routing/Tests/Matcher/CompiledRedirectableUrlMatcherTest.php @@ -19,7 +19,7 @@ class CompiledRedirectableUrlMatcherTest extends RedirectableUrlMatcherTest { - protected function getUrlMatcher(RouteCollection $routes, RequestContext $context = null) + protected function getUrlMatcher(RouteCollection $routes, ?RequestContext $context = null) { $dumper = new CompiledUrlMatcherDumper($routes); $compiledRoutes = $dumper->getCompiledRoutes(); @@ -33,7 +33,7 @@ protected function getUrlMatcher(RouteCollection $routes, RequestContext $contex class TestCompiledRedirectableUrlMatcher extends CompiledUrlMatcher implements RedirectableUrlMatcherInterface { - public function redirect(string $path, string $route, string $scheme = null): array + public function redirect(string $path, string $route, ?string $scheme = null): array { return []; } diff --git a/src/Symfony/Component/Routing/Tests/Matcher/CompiledUrlMatcherTest.php b/src/Symfony/Component/Routing/Tests/Matcher/CompiledUrlMatcherTest.php index c8cd40cc26430..fd8e694e64c1b 100644 --- a/src/Symfony/Component/Routing/Tests/Matcher/CompiledUrlMatcherTest.php +++ b/src/Symfony/Component/Routing/Tests/Matcher/CompiledUrlMatcherTest.php @@ -18,7 +18,7 @@ class CompiledUrlMatcherTest extends UrlMatcherTest { - protected function getUrlMatcher(RouteCollection $routes, RequestContext $context = null) + protected function getUrlMatcher(RouteCollection $routes, ?RequestContext $context = null) { $dumper = new CompiledUrlMatcherDumper($routes); diff --git a/src/Symfony/Component/Routing/Tests/Matcher/Dumper/CompiledUrlMatcherDumperTest.php b/src/Symfony/Component/Routing/Tests/Matcher/Dumper/CompiledUrlMatcherDumperTest.php index d61d736ad0ebb..8508532d65ffb 100644 --- a/src/Symfony/Component/Routing/Tests/Matcher/Dumper/CompiledUrlMatcherDumperTest.php +++ b/src/Symfony/Component/Routing/Tests/Matcher/Dumper/CompiledUrlMatcherDumperTest.php @@ -506,7 +506,7 @@ public function testGenerateDumperMatcherWithObject() class TestCompiledUrlMatcher extends CompiledUrlMatcher implements RedirectableUrlMatcherInterface { - public function redirect(string $path, string $route, string $scheme = null): array + public function redirect(string $path, string $route, ?string $scheme = null): array { return []; } diff --git a/src/Symfony/Component/Routing/Tests/Matcher/RedirectableUrlMatcherTest.php b/src/Symfony/Component/Routing/Tests/Matcher/RedirectableUrlMatcherTest.php index dc8126a43cb42..01e25ed4c89e9 100644 --- a/src/Symfony/Component/Routing/Tests/Matcher/RedirectableUrlMatcherTest.php +++ b/src/Symfony/Component/Routing/Tests/Matcher/RedirectableUrlMatcherTest.php @@ -211,7 +211,7 @@ public function testTrailingRequirementWithDefaultA() $this->assertEquals(['_route' => 'a', 'a' => 'aaa'], $matcher->match('/fr-fr/')); } - protected function getUrlMatcher(RouteCollection $routes, RequestContext $context = null) + protected function getUrlMatcher(RouteCollection $routes, ?RequestContext $context = null) { return $this->getMockForAbstractClass(RedirectableUrlMatcher::class, [$routes, $context ?? new RequestContext()]); } diff --git a/src/Symfony/Component/Routing/Tests/Matcher/TraceableUrlMatcherTest.php b/src/Symfony/Component/Routing/Tests/Matcher/TraceableUrlMatcherTest.php index 03b11a697aa0d..17f6a384911b6 100644 --- a/src/Symfony/Component/Routing/Tests/Matcher/TraceableUrlMatcherTest.php +++ b/src/Symfony/Component/Routing/Tests/Matcher/TraceableUrlMatcherTest.php @@ -128,7 +128,7 @@ public function testRoutesWithConditions() $this->assertEquals('Route matches!', $traces[1]['log']); } - protected function getUrlMatcher(RouteCollection $routes, RequestContext $context = null) + protected function getUrlMatcher(RouteCollection $routes, ?RequestContext $context = null) { return new TraceableUrlMatcher($routes, $context ?? new RequestContext()); } diff --git a/src/Symfony/Component/Routing/Tests/Matcher/UrlMatcherTest.php b/src/Symfony/Component/Routing/Tests/Matcher/UrlMatcherTest.php index 34966dfe82fb0..78bf2b3d75a6a 100644 --- a/src/Symfony/Component/Routing/Tests/Matcher/UrlMatcherTest.php +++ b/src/Symfony/Component/Routing/Tests/Matcher/UrlMatcherTest.php @@ -1000,7 +1000,7 @@ public function testUtf8VarName() $this->assertEquals(['_route' => 'foo', 'bär' => 'baz', 'bäz' => 'foo'], $matcher->match('/foo/baz')); } - protected function getUrlMatcher(RouteCollection $routes, RequestContext $context = null) + protected function getUrlMatcher(RouteCollection $routes, ?RequestContext $context = null) { return new UrlMatcher($routes, $context ?? new RequestContext()); } diff --git a/src/Symfony/Component/Runtime/GenericRuntime.php b/src/Symfony/Component/Runtime/GenericRuntime.php index acec06586fc7b..789d0bc1f3549 100644 --- a/src/Symfony/Component/Runtime/GenericRuntime.php +++ b/src/Symfony/Component/Runtime/GenericRuntime.php @@ -83,7 +83,7 @@ public function __construct(array $options = []) $this->options = $options; } - public function getResolver(callable $callable, \ReflectionFunction $reflector = null): ResolverInterface + public function getResolver(callable $callable, ?\ReflectionFunction $reflector = null): ResolverInterface { $callable = $callable(...); $parameters = ($reflector ?? new \ReflectionFunction($callable))->getParameters(); diff --git a/src/Symfony/Component/Runtime/RuntimeInterface.php b/src/Symfony/Component/Runtime/RuntimeInterface.php index 757468c746317..f151757e98f21 100644 --- a/src/Symfony/Component/Runtime/RuntimeInterface.php +++ b/src/Symfony/Component/Runtime/RuntimeInterface.php @@ -23,7 +23,7 @@ interface RuntimeInterface * * The callable itself should return an object that represents the application to pass to the getRunner() method. */ - public function getResolver(callable $callable, \ReflectionFunction $reflector = null): ResolverInterface; + public function getResolver(callable $callable, ?\ReflectionFunction $reflector = null): ResolverInterface; /** * Returns a callable that knows how to run the passed object and that returns its exit status as int. diff --git a/src/Symfony/Component/Scheduler/Attribute/AsCronTask.php b/src/Symfony/Component/Scheduler/Attribute/AsCronTask.php index 076d99169c74f..9b77c70cda8a8 100644 --- a/src/Symfony/Component/Scheduler/Attribute/AsCronTask.php +++ b/src/Symfony/Component/Scheduler/Attribute/AsCronTask.php @@ -19,6 +19,18 @@ #[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] class AsCronTask { + /** + * @param string $expression The cron expression to define the task schedule (i.e. "5 * * * *") + * @param string|null $timezone The timezone used with the cron expression + * @param int|null $jitter The cron jitter, in seconds; for example, if set to 60, the cron + * will randomly wait for a number of seconds between 0 and 60 before + * executing which allows to avoid load spikes that can happen when many tasks + * run at the same time + * @param array|string|null $arguments The arguments to pass to the cron task + * @param string $schedule The name of the schedule responsible for triggering the task + * @param string|null $method The method to run as the task when the attribute target is a class + * @param string[]|string|null $transports One or many transports through which the message scheduling the task will go + */ public function __construct( public readonly string $expression, public readonly ?string $timezone = null, diff --git a/src/Symfony/Component/Scheduler/Attribute/AsPeriodicTask.php b/src/Symfony/Component/Scheduler/Attribute/AsPeriodicTask.php index 560a36fb75b71..e962ecaaf1b08 100644 --- a/src/Symfony/Component/Scheduler/Attribute/AsPeriodicTask.php +++ b/src/Symfony/Component/Scheduler/Attribute/AsPeriodicTask.php @@ -19,6 +19,19 @@ #[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] class AsPeriodicTask { + /** + * @param string|int $frequency A string (i.e. "every hour") or an integer (the number of seconds) representing the frequency of the task + * @param string|null $from A string representing the start time of the periodic task (i.e. "08:00:00") + * @param string|null $until A string representing the end time of the periodic task (i.e. "20:00:00") + * @param int|null $jitter The cron jitter, in seconds; for example, if set to 60, the cron + * will randomly wait for a number of seconds between 0 and 60 before + * executing which allows to avoid load spikes that can happen when many tasks + * run at the same time + * @param array|string|null $arguments The arguments to pass to the cron task + * @param string $schedule The name of the schedule responsible for triggering the task + * @param string|null $method The method to run as the task when the attribute target is a class + * @param string[]|string|null $transports One or many transports through which the message scheduling the task will go + */ public function __construct( public readonly string|int $frequency, public readonly ?string $from = null, diff --git a/src/Symfony/Component/Scheduler/Attribute/AsSchedule.php b/src/Symfony/Component/Scheduler/Attribute/AsSchedule.php index 45854c1cd5abc..2f93dfcb255f2 100644 --- a/src/Symfony/Component/Scheduler/Attribute/AsSchedule.php +++ b/src/Symfony/Component/Scheduler/Attribute/AsSchedule.php @@ -19,6 +19,9 @@ #[\Attribute(\Attribute::TARGET_CLASS)] class AsSchedule { + /** + * @param string $name The name of the schedule that will be used when creating tasks + */ public function __construct( public string $name = 'default', ) { diff --git a/src/Symfony/Component/Scheduler/Event/FailureEvent.php b/src/Symfony/Component/Scheduler/Event/FailureEvent.php index 75f6ecbd63c4e..2352318f24c70 100644 --- a/src/Symfony/Component/Scheduler/Event/FailureEvent.php +++ b/src/Symfony/Component/Scheduler/Event/FailureEvent.php @@ -46,7 +46,7 @@ public function getError(): \Throwable return $this->error; } - public function shouldIgnore(bool $shouldIgnore = null): bool + public function shouldIgnore(?bool $shouldIgnore = null): bool { if (null !== $shouldIgnore) { $this->shouldIgnore = $shouldIgnore; diff --git a/src/Symfony/Component/Scheduler/Event/PreRunEvent.php b/src/Symfony/Component/Scheduler/Event/PreRunEvent.php index 4da4f9732a3ec..d9576d8f337d6 100644 --- a/src/Symfony/Component/Scheduler/Event/PreRunEvent.php +++ b/src/Symfony/Component/Scheduler/Event/PreRunEvent.php @@ -40,7 +40,7 @@ public function getMessage(): object return $this->message; } - public function shouldCancel(bool $shouldCancel = null): bool + public function shouldCancel(?bool $shouldCancel = null): bool { if (null !== $shouldCancel) { $this->shouldCancel = $shouldCancel; diff --git a/src/Symfony/Component/Scheduler/RecurringMessage.php b/src/Symfony/Component/Scheduler/RecurringMessage.php index dd028a0f12225..fff81b1628813 100644 --- a/src/Symfony/Component/Scheduler/RecurringMessage.php +++ b/src/Symfony/Component/Scheduler/RecurringMessage.php @@ -45,7 +45,7 @@ private function __construct( * @see https://en.wikipedia.org/wiki/ISO_8601#Durations * @see https://php.net/datetime.formats.relative */ - public static function every(string|int|\DateInterval $frequency, object $message, string|\DateTimeImmutable $from = null, string|\DateTimeImmutable $until = new \DateTimeImmutable('3000-01-01')): self + public static function every(string|int|\DateInterval $frequency, object $message, string|\DateTimeImmutable|null $from = null, string|\DateTimeImmutable $until = new \DateTimeImmutable('3000-01-01')): self { return self::trigger(new PeriodicalTrigger($frequency, $from, $until), $message); } @@ -53,7 +53,7 @@ public static function every(string|int|\DateInterval $frequency, object $messag /** * @param MessageProviderInterface|object $message A message provider that yields messages or a static message that will be dispatched on every trigger */ - public static function cron(string $expression, object $message, \DateTimeZone|string $timezone = null): self + public static function cron(string $expression, object $message, \DateTimeZone|string|null $timezone = null): self { if (!str_contains($expression, '#')) { return self::trigger(CronExpressionTrigger::fromSpec($expression, null, $timezone), $message); diff --git a/src/Symfony/Component/Scheduler/Tests/EventListener/DispatchSchedulerEventListenerTest.php b/src/Symfony/Component/Scheduler/Tests/EventListener/DispatchSchedulerEventListenerTest.php index 218d128ae874f..be012a027b88e 100644 --- a/src/Symfony/Component/Scheduler/Tests/EventListener/DispatchSchedulerEventListenerTest.php +++ b/src/Symfony/Component/Scheduler/Tests/EventListener/DispatchSchedulerEventListenerTest.php @@ -12,7 +12,7 @@ namespace Symfony\Component\Scheduler\Tests\EventListener; use PHPUnit\Framework\TestCase; -use Psr\Container\ContainerInterface; +use Symfony\Component\DependencyInjection\Container; use Symfony\Component\EventDispatcher\EventDispatcher; use Symfony\Component\Messenger\Envelope; use Symfony\Component\Messenger\Event\WorkerMessageFailedEvent; @@ -36,14 +36,12 @@ public function testDispatchSchedulerEvents() $defaultRecurringMessage = RecurringMessage::trigger($trigger, (object) ['id' => 'default']); $schedulerProvider = new SomeScheduleProvider([$defaultRecurringMessage]); - $scheduleProviderLocator = $this->createMock(ContainerInterface::class); - $scheduleProviderLocator->expects($this->any())->method('has')->willReturn(true); - $scheduleProviderLocator->expects($this->any())->method('get')->willReturn($schedulerProvider); + $scheduleProviderLocator = new Container(); + $scheduleProviderLocator->set('default', $schedulerProvider); $context = new MessageContext('default', 'default', $trigger, $this->createMock(\DateTimeImmutable::class)); $envelope = (new Envelope(new \stdClass()))->with(new ScheduledStamp($context)); - /** @var ContainerInterface $scheduleProviderLocator */ $listener = new DispatchSchedulerEventListener($scheduleProviderLocator, $eventDispatcher = new EventDispatcher()); $workerReceivedEvent = new WorkerMessageReceivedEvent($envelope, 'default'); $workerHandledEvent = new WorkerMessageHandledEvent($envelope, 'default'); diff --git a/src/Symfony/Component/Scheduler/Trigger/CallbackTrigger.php b/src/Symfony/Component/Scheduler/Trigger/CallbackTrigger.php index 60883a78aac56..1b3d3ebd2121d 100644 --- a/src/Symfony/Component/Scheduler/Trigger/CallbackTrigger.php +++ b/src/Symfony/Component/Scheduler/Trigger/CallbackTrigger.php @@ -19,7 +19,7 @@ final class CallbackTrigger implements TriggerInterface private \Closure $callback; private string $description; - public function __construct(callable $callback, string $description = null) + public function __construct(callable $callback, ?string $description = null) { $this->callback = $callback(...); $this->description = $description ?? spl_object_hash($this->callback); diff --git a/src/Symfony/Component/Scheduler/Trigger/CronExpressionTrigger.php b/src/Symfony/Component/Scheduler/Trigger/CronExpressionTrigger.php index edd27d190c755..db990df5a47af 100644 --- a/src/Symfony/Component/Scheduler/Trigger/CronExpressionTrigger.php +++ b/src/Symfony/Component/Scheduler/Trigger/CronExpressionTrigger.php @@ -48,7 +48,7 @@ final class CronExpressionTrigger implements TriggerInterface public function __construct( private readonly CronExpression $expression = new CronExpression('* * * * *'), - \DateTimeZone|string $timezone = null, + \DateTimeZone|string|null $timezone = null, ) { $this->timezone = $timezone instanceof \DateTimeZone ? $timezone->getName() : $timezone; } @@ -58,7 +58,7 @@ public function __toString(): string return $this->expression->getExpression(); } - public static function fromSpec(string $expression = '* * * * *', string $context = null, \DateTimeZone|string $timezone = null): self + public static function fromSpec(string $expression = '* * * * *', ?string $context = null, \DateTimeZone|string|null $timezone = null): self { if (!class_exists(CronExpression::class)) { throw new LogicException(sprintf('You cannot use "%s" as the "cron expression" package is not installed. Try running "composer require dragonmantank/cron-expression".', __CLASS__)); diff --git a/src/Symfony/Component/Scheduler/Trigger/PeriodicalTrigger.php b/src/Symfony/Component/Scheduler/Trigger/PeriodicalTrigger.php index 5a816cfe50952..73ddc7c6251a8 100644 --- a/src/Symfony/Component/Scheduler/Trigger/PeriodicalTrigger.php +++ b/src/Symfony/Component/Scheduler/Trigger/PeriodicalTrigger.php @@ -24,7 +24,7 @@ class PeriodicalTrigger implements StatefulTriggerInterface public function __construct( string|int|float|\DateInterval $interval, - string|\DateTimeImmutable $from = null, + string|\DateTimeImmutable|null $from = null, string|\DateTimeImmutable $until = new \DateTimeImmutable('3000-01-01'), ) { $this->from = \is_string($from) ? new \DateTimeImmutable($from) : $from; diff --git a/src/Symfony/Component/Security/Core/Authentication/AuthenticationTrustResolver.php b/src/Symfony/Component/Security/Core/Authentication/AuthenticationTrustResolver.php index 4f9d46f2cae1f..513f0d5e764e6 100644 --- a/src/Symfony/Component/Security/Core/Authentication/AuthenticationTrustResolver.php +++ b/src/Symfony/Component/Security/Core/Authentication/AuthenticationTrustResolver.php @@ -21,17 +21,17 @@ */ class AuthenticationTrustResolver implements AuthenticationTrustResolverInterface { - public function isAuthenticated(TokenInterface $token = null): bool + public function isAuthenticated(?TokenInterface $token = null): bool { return $token && $token->getUser(); } - public function isRememberMe(TokenInterface $token = null): bool + public function isRememberMe(?TokenInterface $token = null): bool { return $token && $token instanceof RememberMeToken; } - public function isFullFledged(TokenInterface $token = null): bool + public function isFullFledged(?TokenInterface $token = null): bool { return $this->isAuthenticated($token) && !$this->isRememberMe($token); } diff --git a/src/Symfony/Component/Security/Core/Authentication/AuthenticationTrustResolverInterface.php b/src/Symfony/Component/Security/Core/Authentication/AuthenticationTrustResolverInterface.php index 90f2ec2f00964..b508290ccd549 100644 --- a/src/Symfony/Component/Security/Core/Authentication/AuthenticationTrustResolverInterface.php +++ b/src/Symfony/Component/Security/Core/Authentication/AuthenticationTrustResolverInterface.php @@ -23,16 +23,16 @@ interface AuthenticationTrustResolverInterface /** * Resolves whether the passed token implementation is authenticated. */ - public function isAuthenticated(TokenInterface $token = null): bool; + public function isAuthenticated(?TokenInterface $token = null): bool; /** * Resolves whether the passed token implementation is authenticated * using remember-me capabilities. */ - public function isRememberMe(TokenInterface $token = null): bool; + public function isRememberMe(?TokenInterface $token = null): bool; /** * Resolves whether the passed token implementation is fully authenticated. */ - public function isFullFledged(TokenInterface $token = null): bool; + public function isFullFledged(?TokenInterface $token = null): bool; } diff --git a/src/Symfony/Component/Security/Core/Authentication/Token/Storage/UsageTrackingTokenStorage.php b/src/Symfony/Component/Security/Core/Authentication/Token/Storage/UsageTrackingTokenStorage.php index b66671270ee7e..8a4069e7ed948 100644 --- a/src/Symfony/Component/Security/Core/Authentication/Token/Storage/UsageTrackingTokenStorage.php +++ b/src/Symfony/Component/Security/Core/Authentication/Token/Storage/UsageTrackingTokenStorage.php @@ -44,7 +44,7 @@ public function getToken(): ?TokenInterface return $this->storage->getToken(); } - public function setToken(TokenInterface $token = null): void + public function setToken(?TokenInterface $token = null): void { $this->storage->setToken($token); diff --git a/src/Symfony/Component/Security/Core/Authentication/Token/SwitchUserToken.php b/src/Symfony/Component/Security/Core/Authentication/Token/SwitchUserToken.php index ccb3c569785ea..fb632a616d5f7 100644 --- a/src/Symfony/Component/Security/Core/Authentication/Token/SwitchUserToken.php +++ b/src/Symfony/Component/Security/Core/Authentication/Token/SwitchUserToken.php @@ -29,7 +29,7 @@ class SwitchUserToken extends UsernamePasswordToken * * @throws \InvalidArgumentException */ - public function __construct(UserInterface $user, string $firewallName, array $roles, TokenInterface $originalToken, string $originatedFromUri = null) + public function __construct(UserInterface $user, string $firewallName, array $roles, TokenInterface $originalToken, ?string $originatedFromUri = null) { parent::__construct($user, $firewallName, $roles); diff --git a/src/Symfony/Component/Security/Core/Authorization/AccessDecisionManager.php b/src/Symfony/Component/Security/Core/Authorization/AccessDecisionManager.php index 025f6827bdfd1..4a56f943a262d 100644 --- a/src/Symfony/Component/Security/Core/Authorization/AccessDecisionManager.php +++ b/src/Symfony/Component/Security/Core/Authorization/AccessDecisionManager.php @@ -40,7 +40,7 @@ final class AccessDecisionManager implements AccessDecisionManagerInterface /** * @param iterable $voters An array or an iterator of VoterInterface instances */ - public function __construct(iterable $voters = [], AccessDecisionStrategyInterface $strategy = null) + public function __construct(iterable $voters = [], ?AccessDecisionStrategyInterface $strategy = null) { $this->voters = $voters; $this->strategy = $strategy ?? new AffirmativeStrategy(); diff --git a/src/Symfony/Component/Security/Core/Authorization/ExpressionLanguage.php b/src/Symfony/Component/Security/Core/Authorization/ExpressionLanguage.php index 8f87767a28e7e..a48d81481f6f3 100644 --- a/src/Symfony/Component/Security/Core/Authorization/ExpressionLanguage.php +++ b/src/Symfony/Component/Security/Core/Authorization/ExpressionLanguage.php @@ -29,7 +29,7 @@ class_exists(ExpressionLanguageProvider::class); */ class ExpressionLanguage extends BaseExpressionLanguage { - public function __construct(CacheItemPoolInterface $cache = null, array $providers = []) + public function __construct(?CacheItemPoolInterface $cache = null, array $providers = []) { // prepend the default provider to let users override it easily array_unshift($providers, new ExpressionLanguageProvider()); diff --git a/src/Symfony/Component/Security/Core/Authorization/Voter/ExpressionVoter.php b/src/Symfony/Component/Security/Core/Authorization/Voter/ExpressionVoter.php index 9369ef45d2839..6de9c95465d0a 100644 --- a/src/Symfony/Component/Security/Core/Authorization/Voter/ExpressionVoter.php +++ b/src/Symfony/Component/Security/Core/Authorization/Voter/ExpressionVoter.php @@ -31,7 +31,7 @@ class ExpressionVoter implements CacheableVoterInterface private AuthorizationCheckerInterface $authChecker; private ?RoleHierarchyInterface $roleHierarchy; - public function __construct(ExpressionLanguage $expressionLanguage, AuthenticationTrustResolverInterface $trustResolver, AuthorizationCheckerInterface $authChecker, RoleHierarchyInterface $roleHierarchy = null) + public function __construct(ExpressionLanguage $expressionLanguage, AuthenticationTrustResolverInterface $trustResolver, AuthorizationCheckerInterface $authChecker, ?RoleHierarchyInterface $roleHierarchy = null) { $this->expressionLanguage = $expressionLanguage; $this->trustResolver = $trustResolver; diff --git a/src/Symfony/Component/Security/Core/Exception/AccessDeniedException.php b/src/Symfony/Component/Security/Core/Exception/AccessDeniedException.php index 43df719e6638e..93c3869470d05 100644 --- a/src/Symfony/Component/Security/Core/Exception/AccessDeniedException.php +++ b/src/Symfony/Component/Security/Core/Exception/AccessDeniedException.php @@ -24,7 +24,7 @@ class AccessDeniedException extends RuntimeException private array $attributes = []; private mixed $subject = null; - public function __construct(string $message = 'Access Denied.', \Throwable $previous = null, int $code = 403) + public function __construct(string $message = 'Access Denied.', ?\Throwable $previous = null, int $code = 403) { parent::__construct($message, $code, $previous); } diff --git a/src/Symfony/Component/Security/Core/Exception/CustomUserMessageAccountStatusException.php b/src/Symfony/Component/Security/Core/Exception/CustomUserMessageAccountStatusException.php index 84b1c00a50768..f59eff9b5b045 100644 --- a/src/Symfony/Component/Security/Core/Exception/CustomUserMessageAccountStatusException.php +++ b/src/Symfony/Component/Security/Core/Exception/CustomUserMessageAccountStatusException.php @@ -26,7 +26,7 @@ class CustomUserMessageAccountStatusException extends AccountStatusException private string $messageKey; private array $messageData = []; - public function __construct(string $message = '', array $messageData = [], int $code = 0, \Throwable $previous = null) + public function __construct(string $message = '', array $messageData = [], int $code = 0, ?\Throwable $previous = null) { parent::__construct($message, $code, $previous); diff --git a/src/Symfony/Component/Security/Core/Exception/CustomUserMessageAuthenticationException.php b/src/Symfony/Component/Security/Core/Exception/CustomUserMessageAuthenticationException.php index 88c5338fef7f5..eae66c416bffc 100644 --- a/src/Symfony/Component/Security/Core/Exception/CustomUserMessageAuthenticationException.php +++ b/src/Symfony/Component/Security/Core/Exception/CustomUserMessageAuthenticationException.php @@ -25,7 +25,7 @@ class CustomUserMessageAuthenticationException extends AuthenticationException private string $messageKey; private array $messageData = []; - public function __construct(string $message = '', array $messageData = [], int $code = 0, \Throwable $previous = null) + public function __construct(string $message = '', array $messageData = [], int $code = 0, ?\Throwable $previous = null) { parent::__construct($message, $code, $previous); diff --git a/src/Symfony/Component/Security/Core/Exception/LogoutException.php b/src/Symfony/Component/Security/Core/Exception/LogoutException.php index 7058c6244b272..20efdd267de36 100644 --- a/src/Symfony/Component/Security/Core/Exception/LogoutException.php +++ b/src/Symfony/Component/Security/Core/Exception/LogoutException.php @@ -18,7 +18,7 @@ */ class LogoutException extends RuntimeException { - public function __construct(string $message = 'Logout Exception', \Throwable $previous = null) + public function __construct(string $message = 'Logout Exception', ?\Throwable $previous = null) { parent::__construct($message, 403, $previous); } diff --git a/src/Symfony/Component/Security/Core/Exception/TooManyLoginAttemptsAuthenticationException.php b/src/Symfony/Component/Security/Core/Exception/TooManyLoginAttemptsAuthenticationException.php index eab1d5062264f..da1a1a7a68a51 100644 --- a/src/Symfony/Component/Security/Core/Exception/TooManyLoginAttemptsAuthenticationException.php +++ b/src/Symfony/Component/Security/Core/Exception/TooManyLoginAttemptsAuthenticationException.php @@ -21,7 +21,7 @@ class TooManyLoginAttemptsAuthenticationException extends AuthenticationExceptio { private ?int $threshold; - public function __construct(int $threshold = null) + public function __construct(?int $threshold = null) { $this->threshold = $threshold; } diff --git a/src/Symfony/Component/Security/Core/Signature/SignatureHasher.php b/src/Symfony/Component/Security/Core/Signature/SignatureHasher.php index 73dcbb4171816..3f86fce0a2d66 100644 --- a/src/Symfony/Component/Security/Core/Signature/SignatureHasher.php +++ b/src/Symfony/Component/Security/Core/Signature/SignatureHasher.php @@ -36,7 +36,7 @@ class SignatureHasher * @param ExpiredSignatureStorage|null $expiredSignaturesStorage If provided, secures a sequence of hashes that are expired * @param int|null $maxUses Used together with $expiredSignatureStorage to allow a maximum usage of a hash */ - public function __construct(PropertyAccessorInterface $propertyAccessor, array $signatureProperties, #[\SensitiveParameter] string $secret, ExpiredSignatureStorage $expiredSignaturesStorage = null, int $maxUses = null) + public function __construct(PropertyAccessorInterface $propertyAccessor, array $signatureProperties, #[\SensitiveParameter] string $secret, ?ExpiredSignatureStorage $expiredSignaturesStorage = null, ?int $maxUses = null) { if (!$secret) { throw new InvalidArgumentException('A non-empty secret is required.'); diff --git a/src/Symfony/Component/Security/Core/Tests/Authentication/Token/AbstractTokenTest.php b/src/Symfony/Component/Security/Core/Tests/Authentication/Token/AbstractTokenTest.php index 221df8f95cd8e..cc1357a14a968 100644 --- a/src/Symfony/Component/Security/Core/Tests/Authentication/Token/AbstractTokenTest.php +++ b/src/Symfony/Component/Security/Core/Tests/Authentication/Token/AbstractTokenTest.php @@ -98,7 +98,7 @@ class ConcreteToken extends AbstractToken { private string $credentials = 'credentials_value'; - public function __construct(array $roles = [], UserInterface $user = null) + public function __construct(array $roles = [], ?UserInterface $user = null) { parent::__construct($roles); diff --git a/src/Symfony/Component/Security/Core/Validator/Constraints/UserPassword.php b/src/Symfony/Component/Security/Core/Validator/Constraints/UserPassword.php index 6f4024c02c24b..e6741a48f1945 100644 --- a/src/Symfony/Component/Security/Core/Validator/Constraints/UserPassword.php +++ b/src/Symfony/Component/Security/Core/Validator/Constraints/UserPassword.php @@ -25,7 +25,7 @@ class UserPassword extends Constraint public string $message = 'This value should be the user\'s current password.'; public string $service = 'security.validator.user_password'; - public function __construct(array $options = null, string $message = null, string $service = null, array $groups = null, mixed $payload = null) + public function __construct(?array $options = null, ?string $message = null, ?string $service = null, ?array $groups = null, mixed $payload = null) { parent::__construct($options, $groups, $payload); diff --git a/src/Symfony/Component/Security/Csrf/CsrfTokenManager.php b/src/Symfony/Component/Security/Csrf/CsrfTokenManager.php index 2c856f2adee43..96ecc540330f4 100644 --- a/src/Symfony/Component/Security/Csrf/CsrfTokenManager.php +++ b/src/Symfony/Component/Security/Csrf/CsrfTokenManager.php @@ -37,7 +37,7 @@ class CsrfTokenManager implements CsrfTokenManagerInterface * * RequestStack: generates a namespace using the current main request * * callable: uses the result of this callable (must return a string) */ - public function __construct(TokenGeneratorInterface $generator = null, TokenStorageInterface $storage = null, string|RequestStack|callable $namespace = null) + public function __construct(?TokenGeneratorInterface $generator = null, ?TokenStorageInterface $storage = null, string|RequestStack|callable|null $namespace = null) { $this->generator = $generator ?? new UriSafeTokenGenerator(); $this->storage = $storage ?? new NativeSessionTokenStorage(); diff --git a/src/Symfony/Component/Security/Http/AccessMap.php b/src/Symfony/Component/Security/Http/AccessMap.php index 37a50eca5ce99..4913e461b65cb 100644 --- a/src/Symfony/Component/Security/Http/AccessMap.php +++ b/src/Symfony/Component/Security/Http/AccessMap.php @@ -28,7 +28,7 @@ class AccessMap implements AccessMapInterface * @param array $attributes An array of attributes to pass to the access decision manager (like roles) * @param string|null $channel The channel to enforce (http, https, or null) */ - public function add(RequestMatcherInterface $requestMatcher, array $attributes = [], string $channel = null): void + public function add(RequestMatcherInterface $requestMatcher, array $attributes = [], ?string $channel = null): void { $this->map[] = [$requestMatcher, $attributes, $channel]; } diff --git a/src/Symfony/Component/Security/Http/AccessToken/Cas/Cas2Handler.php b/src/Symfony/Component/Security/Http/AccessToken/Cas/Cas2Handler.php new file mode 100644 index 0000000000000..d7e6df9bcf127 --- /dev/null +++ b/src/Symfony/Component/Security/Http/AccessToken/Cas/Cas2Handler.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\Component\Security\Http\AccessToken\Cas; + +use Symfony\Component\HttpClient\HttpClient; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\Security\Core\Exception\AuthenticationException; +use Symfony\Component\Security\Http\AccessToken\AccessTokenHandlerInterface; +use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge; +use Symfony\Contracts\HttpClient\HttpClientInterface; + +/** + * @see https://apereo.github.io/cas/6.6.x/protocol/CAS-Protocol-V2-Specification.html + * + * @author Nicolas Attard + */ +final class Cas2Handler implements AccessTokenHandlerInterface +{ + public function __construct( + private readonly RequestStack $requestStack, + private readonly string $validationUrl, + private readonly string $prefix = 'cas', + private ?HttpClientInterface $client = null, + ) { + if (null === $client) { + if (!class_exists(HttpClient::class)) { + throw new \LogicException(sprintf('You cannot use "%s" as the HttpClient component is not installed. Try running "composer require symfony/http-client".', __CLASS__)); + } + + $this->client = HttpClient::create(); + } + } + + /** + * @throws AuthenticationException + */ + public function getUserBadgeFrom(string $accessToken): UserBadge + { + $response = $this->client->request('GET', $this->getValidationUrl($accessToken)); + + $xml = new \SimpleXMLElement($response->getContent(), 0, false, $this->prefix, true); + + if (isset($xml->authenticationSuccess)) { + return new UserBadge((string) $xml->authenticationSuccess->user); + } + + if (isset($xml->authenticationFailure)) { + throw new AuthenticationException('CAS Authentication Failure: '.trim((string) $xml->authenticationFailure)); + } + + throw new AuthenticationException('Invalid CAS response.'); + } + + private function getValidationUrl(string $accessToken): string + { + $request = $this->requestStack->getCurrentRequest(); + + if (null === $request) { + throw new \LogicException('Request should exist so it can be processed for error.'); + } + + $query = $request->query->all(); + + if (!isset($query['ticket'])) { + throw new AuthenticationException('No ticket found in request.'); + } + unset($query['ticket']); + $queryString = empty($query) ? '' : '?'.http_build_query($query); + + return sprintf('%s?ticket=%s&service=%s', + $this->validationUrl, + urlencode($accessToken), + urlencode($request->getSchemeAndHttpHost().$request->getBaseUrl().$request->getPathInfo().$queryString) + ); + } +} diff --git a/src/Symfony/Component/Security/Http/Authentication/AuthenticationUtils.php b/src/Symfony/Component/Security/Http/Authentication/AuthenticationUtils.php index 78bea03be0008..e9e29b2ba68a4 100644 --- a/src/Symfony/Component/Security/Http/Authentication/AuthenticationUtils.php +++ b/src/Symfony/Component/Security/Http/Authentication/AuthenticationUtils.php @@ -53,10 +53,10 @@ public function getLastUsername(): string $request = $this->getRequest(); if ($request->attributes->has(SecurityRequestAttributes::LAST_USERNAME)) { - return $request->attributes->get(SecurityRequestAttributes::LAST_USERNAME, ''); + return $request->attributes->get(SecurityRequestAttributes::LAST_USERNAME) ?? ''; } - return $request->hasSession() ? $request->getSession()->get(SecurityRequestAttributes::LAST_USERNAME, '') : ''; + return $request->hasSession() ? ($request->getSession()->get(SecurityRequestAttributes::LAST_USERNAME) ?? '') : ''; } /** diff --git a/src/Symfony/Component/Security/Http/Authentication/AuthenticatorManager.php b/src/Symfony/Component/Security/Http/Authentication/AuthenticatorManager.php index cbf73d912958a..b61081cd5f304 100644 --- a/src/Symfony/Component/Security/Http/Authentication/AuthenticatorManager.php +++ b/src/Symfony/Component/Security/Http/Authentication/AuthenticatorManager.php @@ -58,7 +58,7 @@ class AuthenticatorManager implements AuthenticatorManagerInterface, UserAuthent /** * @param iterable $authenticators */ - public function __construct(iterable $authenticators, TokenStorageInterface $tokenStorage, EventDispatcherInterface $eventDispatcher, string $firewallName, LoggerInterface $logger = null, bool $eraseCredentials = true, bool $hideUserNotFoundExceptions = true, array $requiredBadges = []) + public function __construct(iterable $authenticators, TokenStorageInterface $tokenStorage, EventDispatcherInterface $eventDispatcher, string $firewallName, ?LoggerInterface $logger = null, bool $eraseCredentials = true, bool $hideUserNotFoundExceptions = true, array $requiredBadges = []) { $this->authenticators = $authenticators; $this->tokenStorage = $tokenStorage; diff --git a/src/Symfony/Component/Security/Http/Authentication/DefaultAuthenticationFailureHandler.php b/src/Symfony/Component/Security/Http/Authentication/DefaultAuthenticationFailureHandler.php index 245e46dea4860..a2eabbef8e97c 100644 --- a/src/Symfony/Component/Security/Http/Authentication/DefaultAuthenticationFailureHandler.php +++ b/src/Symfony/Component/Security/Http/Authentication/DefaultAuthenticationFailureHandler.php @@ -43,7 +43,7 @@ class DefaultAuthenticationFailureHandler implements AuthenticationFailureHandle 'failure_path_parameter' => '_failure_path', ]; - public function __construct(HttpKernelInterface $httpKernel, HttpUtils $httpUtils, array $options = [], LoggerInterface $logger = null) + public function __construct(HttpKernelInterface $httpKernel, HttpUtils $httpUtils, array $options = [], ?LoggerInterface $logger = null) { $this->httpKernel = $httpKernel; $this->httpUtils = $httpUtils; diff --git a/src/Symfony/Component/Security/Http/Authentication/DefaultAuthenticationSuccessHandler.php b/src/Symfony/Component/Security/Http/Authentication/DefaultAuthenticationSuccessHandler.php index 6dc29d8325cf0..a491adb87d817 100644 --- a/src/Symfony/Component/Security/Http/Authentication/DefaultAuthenticationSuccessHandler.php +++ b/src/Symfony/Component/Security/Http/Authentication/DefaultAuthenticationSuccessHandler.php @@ -45,7 +45,7 @@ class DefaultAuthenticationSuccessHandler implements AuthenticationSuccessHandle /** * @param array $options Options for processing a successful authentication attempt */ - public function __construct(HttpUtils $httpUtils, array $options = [], LoggerInterface $logger = null) + public function __construct(HttpUtils $httpUtils, array $options = [], ?LoggerInterface $logger = null) { $this->httpUtils = $httpUtils; $this->logger = $logger; diff --git a/src/Symfony/Component/Security/Http/Authenticator/AbstractLoginFormAuthenticator.php b/src/Symfony/Component/Security/Http/Authenticator/AbstractLoginFormAuthenticator.php index e5259ab3b55f2..21835bd32166c 100644 --- a/src/Symfony/Component/Security/Http/Authenticator/AbstractLoginFormAuthenticator.php +++ b/src/Symfony/Component/Security/Http/Authenticator/AbstractLoginFormAuthenticator.php @@ -60,7 +60,7 @@ public function onAuthenticationFailure(Request $request, AuthenticationExceptio * Override to control what happens when the user hits a secure page * but isn't logged in yet. */ - public function start(Request $request, AuthenticationException $authException = null): Response + public function start(Request $request, ?AuthenticationException $authException = null): Response { $url = $this->getLoginUrl($request); diff --git a/src/Symfony/Component/Security/Http/Authenticator/AbstractPreAuthenticatedAuthenticator.php b/src/Symfony/Component/Security/Http/Authenticator/AbstractPreAuthenticatedAuthenticator.php index 2e1bac0072cc6..535a5399e9957 100644 --- a/src/Symfony/Component/Security/Http/Authenticator/AbstractPreAuthenticatedAuthenticator.php +++ b/src/Symfony/Component/Security/Http/Authenticator/AbstractPreAuthenticatedAuthenticator.php @@ -41,7 +41,7 @@ abstract class AbstractPreAuthenticatedAuthenticator implements InteractiveAuthe private string $firewallName; private ?LoggerInterface $logger; - public function __construct(UserProviderInterface $userProvider, TokenStorageInterface $tokenStorage, string $firewallName, LoggerInterface $logger = null) + public function __construct(UserProviderInterface $userProvider, TokenStorageInterface $tokenStorage, string $firewallName, ?LoggerInterface $logger = null) { $this->userProvider = $userProvider; $this->tokenStorage = $tokenStorage; diff --git a/src/Symfony/Component/Security/Http/Authenticator/AccessTokenAuthenticator.php b/src/Symfony/Component/Security/Http/Authenticator/AccessTokenAuthenticator.php index 20565b6c73d35..4acadf46ece6b 100644 --- a/src/Symfony/Component/Security/Http/Authenticator/AccessTokenAuthenticator.php +++ b/src/Symfony/Component/Security/Http/Authenticator/AccessTokenAuthenticator.php @@ -103,7 +103,7 @@ public function setTranslator(?TranslatorInterface $translator): void /** * @see https://datatracker.ietf.org/doc/html/rfc6750#section-3 */ - private function getAuthenticateHeader(string $errorDescription = null): string + private function getAuthenticateHeader(?string $errorDescription = null): string { $data = [ 'realm' => $this->realm, diff --git a/src/Symfony/Component/Security/Http/Authenticator/Debug/TraceableAuthenticator.php b/src/Symfony/Component/Security/Http/Authenticator/Debug/TraceableAuthenticator.php index ae43d53460fe7..34c3c62f68848 100644 --- a/src/Symfony/Component/Security/Http/Authenticator/Debug/TraceableAuthenticator.php +++ b/src/Symfony/Component/Security/Http/Authenticator/Debug/TraceableAuthenticator.php @@ -92,7 +92,7 @@ public function onAuthenticationFailure(Request $request, AuthenticationExceptio return $this->authenticator->onAuthenticationFailure($request, $exception); } - public function start(Request $request, AuthenticationException $authException = null): Response + public function start(Request $request, ?AuthenticationException $authException = null): Response { if (!$this->authenticator instanceof AuthenticationEntryPointInterface) { throw new NotAnEntryPointException(); diff --git a/src/Symfony/Component/Security/Http/Authenticator/FormLoginAuthenticator.php b/src/Symfony/Component/Security/Http/Authenticator/FormLoginAuthenticator.php index d1cb7f68c6448..08c265e67129b 100644 --- a/src/Symfony/Component/Security/Http/Authenticator/FormLoginAuthenticator.php +++ b/src/Symfony/Component/Security/Http/Authenticator/FormLoginAuthenticator.php @@ -143,7 +143,7 @@ public function setHttpKernel(HttpKernelInterface $httpKernel): void $this->httpKernel = $httpKernel; } - public function start(Request $request, AuthenticationException $authException = null): Response + public function start(Request $request, ?AuthenticationException $authException = null): Response { if (!$this->options['use_forward']) { return parent::start($request, $authException); diff --git a/src/Symfony/Component/Security/Http/Authenticator/HttpBasicAuthenticator.php b/src/Symfony/Component/Security/Http/Authenticator/HttpBasicAuthenticator.php index 459cea47068a2..f1436740c9f33 100644 --- a/src/Symfony/Component/Security/Http/Authenticator/HttpBasicAuthenticator.php +++ b/src/Symfony/Component/Security/Http/Authenticator/HttpBasicAuthenticator.php @@ -37,14 +37,14 @@ class HttpBasicAuthenticator implements AuthenticatorInterface, AuthenticationEn private UserProviderInterface $userProvider; private ?LoggerInterface $logger; - public function __construct(string $realmName, UserProviderInterface $userProvider, LoggerInterface $logger = null) + public function __construct(string $realmName, UserProviderInterface $userProvider, ?LoggerInterface $logger = null) { $this->realmName = $realmName; $this->userProvider = $userProvider; $this->logger = $logger; } - public function start(Request $request, AuthenticationException $authException = null): Response + public function start(Request $request, ?AuthenticationException $authException = null): Response { $response = new Response(); $response->headers->set('WWW-Authenticate', sprintf('Basic realm="%s"', $this->realmName)); diff --git a/src/Symfony/Component/Security/Http/Authenticator/InteractiveAuthenticatorInterface.php b/src/Symfony/Component/Security/Http/Authenticator/InteractiveAuthenticatorInterface.php index adf6501c30d58..ce125f0f1833a 100644 --- a/src/Symfony/Component/Security/Http/Authenticator/InteractiveAuthenticatorInterface.php +++ b/src/Symfony/Component/Security/Http/Authenticator/InteractiveAuthenticatorInterface.php @@ -16,8 +16,8 @@ * be used by interactive authenticators. * * Interactive login requires explicit user action (e.g. a login - * form or HTTP basic authentication). Implementing this interface - * will dispatch the InteractiveLoginEvent upon successful login. + * form). Implementing this interface will dispatch the InteractiveLoginEvent + * upon successful login. * * @author Wouter de Jong */ diff --git a/src/Symfony/Component/Security/Http/Authenticator/JsonLoginAuthenticator.php b/src/Symfony/Component/Security/Http/Authenticator/JsonLoginAuthenticator.php index 990903c8ae8b6..4c152326bee5e 100644 --- a/src/Symfony/Component/Security/Http/Authenticator/JsonLoginAuthenticator.php +++ b/src/Symfony/Component/Security/Http/Authenticator/JsonLoginAuthenticator.php @@ -52,7 +52,7 @@ class JsonLoginAuthenticator implements InteractiveAuthenticatorInterface private ?AuthenticationFailureHandlerInterface $failureHandler; private ?TranslatorInterface $translator = null; - public function __construct(HttpUtils $httpUtils, UserProviderInterface $userProvider, AuthenticationSuccessHandlerInterface $successHandler = null, AuthenticationFailureHandlerInterface $failureHandler = null, array $options = [], PropertyAccessorInterface $propertyAccessor = null) + public function __construct(HttpUtils $httpUtils, UserProviderInterface $userProvider, ?AuthenticationSuccessHandlerInterface $successHandler = null, ?AuthenticationFailureHandlerInterface $failureHandler = null, array $options = [], ?PropertyAccessorInterface $propertyAccessor = null) { $this->options = array_merge(['username_path' => 'username', 'password_path' => 'password'], $options); $this->httpUtils = $httpUtils; diff --git a/src/Symfony/Component/Security/Http/Authenticator/Passport/Badge/PasswordUpgradeBadge.php b/src/Symfony/Component/Security/Http/Authenticator/Passport/Badge/PasswordUpgradeBadge.php index 9cbf6d6e69343..7dd5ad38ee8fb 100644 --- a/src/Symfony/Component/Security/Http/Authenticator/Passport/Badge/PasswordUpgradeBadge.php +++ b/src/Symfony/Component/Security/Http/Authenticator/Passport/Badge/PasswordUpgradeBadge.php @@ -32,7 +32,7 @@ class PasswordUpgradeBadge implements BadgeInterface * @param string $plaintextPassword The presented password, used in the rehash * @param PasswordUpgraderInterface|null $passwordUpgrader The password upgrader, defaults to the UserProvider if null */ - public function __construct(#[\SensitiveParameter] string $plaintextPassword, PasswordUpgraderInterface $passwordUpgrader = null) + public function __construct(#[\SensitiveParameter] string $plaintextPassword, ?PasswordUpgraderInterface $passwordUpgrader = null) { $this->plaintextPassword = $plaintextPassword; $this->passwordUpgrader = $passwordUpgrader; diff --git a/src/Symfony/Component/Security/Http/Authenticator/Passport/Badge/UserBadge.php b/src/Symfony/Component/Security/Http/Authenticator/Passport/Badge/UserBadge.php index 14c8852a11e98..73ccf4cf452c3 100644 --- a/src/Symfony/Component/Security/Http/Authenticator/Passport/Badge/UserBadge.php +++ b/src/Symfony/Component/Security/Http/Authenticator/Passport/Badge/UserBadge.php @@ -49,7 +49,7 @@ class UserBadge implements BadgeInterface * is thrown). If this is not set, the default user provider will be used with * $userIdentifier as username. */ - public function __construct(string $userIdentifier, callable $userLoader = null, array $attributes = null) + public function __construct(string $userIdentifier, ?callable $userLoader = null, ?array $attributes = null) { if (\strlen($userIdentifier) > self::MAX_USERNAME_LENGTH) { throw new BadCredentialsException('Username too long.'); diff --git a/src/Symfony/Component/Security/Http/Authenticator/Passport/Passport.php b/src/Symfony/Component/Security/Http/Authenticator/Passport/Passport.php index 17f204d51b324..2772080650fbe 100644 --- a/src/Symfony/Component/Security/Http/Authenticator/Passport/Passport.php +++ b/src/Symfony/Component/Security/Http/Authenticator/Passport/Passport.php @@ -72,7 +72,7 @@ public function getUser(): UserInterface * * @return $this */ - public function addBadge(BadgeInterface $badge, string $badgeFqcn = null): static + public function addBadge(BadgeInterface $badge, ?string $badgeFqcn = null): static { $badgeFqcn ??= $badge::class; diff --git a/src/Symfony/Component/Security/Http/Authenticator/RememberMeAuthenticator.php b/src/Symfony/Component/Security/Http/Authenticator/RememberMeAuthenticator.php index 0e3e0e5cc3fe5..0e6db6a12f1b6 100644 --- a/src/Symfony/Component/Security/Http/Authenticator/RememberMeAuthenticator.php +++ b/src/Symfony/Component/Security/Http/Authenticator/RememberMeAuthenticator.php @@ -50,7 +50,7 @@ class RememberMeAuthenticator implements InteractiveAuthenticatorInterface private string $cookieName; private ?LoggerInterface $logger; - public function __construct(RememberMeHandlerInterface $rememberMeHandler, #[\SensitiveParameter] string $secret, TokenStorageInterface $tokenStorage, string $cookieName, LoggerInterface $logger = null) + public function __construct(RememberMeHandlerInterface $rememberMeHandler, #[\SensitiveParameter] string $secret, TokenStorageInterface $tokenStorage, string $cookieName, ?LoggerInterface $logger = null) { if (!$secret) { throw new InvalidArgumentException('A non-empty secret is required.'); diff --git a/src/Symfony/Component/Security/Http/Authenticator/RemoteUserAuthenticator.php b/src/Symfony/Component/Security/Http/Authenticator/RemoteUserAuthenticator.php index 946206c70fadb..9514df37a0d26 100644 --- a/src/Symfony/Component/Security/Http/Authenticator/RemoteUserAuthenticator.php +++ b/src/Symfony/Component/Security/Http/Authenticator/RemoteUserAuthenticator.php @@ -30,7 +30,7 @@ final class RemoteUserAuthenticator extends AbstractPreAuthenticatedAuthenticato { private string $userKey; - public function __construct(UserProviderInterface $userProvider, TokenStorageInterface $tokenStorage, string $firewallName, string $userKey = 'REMOTE_USER', LoggerInterface $logger = null) + public function __construct(UserProviderInterface $userProvider, TokenStorageInterface $tokenStorage, string $firewallName, string $userKey = 'REMOTE_USER', ?LoggerInterface $logger = null) { parent::__construct($userProvider, $tokenStorage, $firewallName, $logger); diff --git a/src/Symfony/Component/Security/Http/Authenticator/X509Authenticator.php b/src/Symfony/Component/Security/Http/Authenticator/X509Authenticator.php index 227d4701190be..c990ba3ee6531 100644 --- a/src/Symfony/Component/Security/Http/Authenticator/X509Authenticator.php +++ b/src/Symfony/Component/Security/Http/Authenticator/X509Authenticator.php @@ -32,7 +32,7 @@ class X509Authenticator extends AbstractPreAuthenticatedAuthenticator private string $credentialsKey; private string $credentialUserIdentifier; - public function __construct(UserProviderInterface $userProvider, TokenStorageInterface $tokenStorage, string $firewallName, string $userKey = 'SSL_CLIENT_S_DN_Email', string $credentialsKey = 'SSL_CLIENT_S_DN', LoggerInterface $logger = null, string $credentialUserIdentifier = 'emailAddress') + public function __construct(UserProviderInterface $userProvider, TokenStorageInterface $tokenStorage, string $firewallName, string $userKey = 'SSL_CLIENT_S_DN_Email', string $credentialsKey = 'SSL_CLIENT_S_DN', ?LoggerInterface $logger = null, string $credentialUserIdentifier = 'emailAddress') { parent::__construct($userProvider, $tokenStorage, $firewallName, $logger); diff --git a/src/Symfony/Component/Security/Http/CHANGELOG.md b/src/Symfony/Component/Security/Http/CHANGELOG.md index 58f227f37383d..24cd13213ddfc 100644 --- a/src/Symfony/Component/Security/Http/CHANGELOG.md +++ b/src/Symfony/Component/Security/Http/CHANGELOG.md @@ -18,6 +18,7 @@ CHANGELOG * `UserValueResolver` no longer implements `ArgumentValueResolverInterface` * Deprecate calling the constructor of `DefaultLoginRateLimiter` with an empty secret + * Add CAS 2.0 access token handler 6.3 --- diff --git a/src/Symfony/Component/Security/Http/EntryPoint/AuthenticationEntryPointInterface.php b/src/Symfony/Component/Security/Http/EntryPoint/AuthenticationEntryPointInterface.php index d4c7acd826028..d02b78efbe3a9 100644 --- a/src/Symfony/Component/Security/Http/EntryPoint/AuthenticationEntryPointInterface.php +++ b/src/Symfony/Component/Security/Http/EntryPoint/AuthenticationEntryPointInterface.php @@ -40,5 +40,5 @@ interface AuthenticationEntryPointInterface * * return new Response('Auth header required', 401); */ - public function start(Request $request, AuthenticationException $authException = null): Response; + public function start(Request $request, ?AuthenticationException $authException = null): Response; } diff --git a/src/Symfony/Component/Security/Http/Event/LoginFailureEvent.php b/src/Symfony/Component/Security/Http/Event/LoginFailureEvent.php index 726b52039afe7..81f685b407b29 100644 --- a/src/Symfony/Component/Security/Http/Event/LoginFailureEvent.php +++ b/src/Symfony/Component/Security/Http/Event/LoginFailureEvent.php @@ -36,7 +36,7 @@ class LoginFailureEvent extends Event private string $firewallName; private ?Passport $passport; - public function __construct(AuthenticationException $exception, AuthenticatorInterface $authenticator, Request $request, ?Response $response, string $firewallName, Passport $passport = null) + public function __construct(AuthenticationException $exception, AuthenticatorInterface $authenticator, Request $request, ?Response $response, string $firewallName, ?Passport $passport = null) { $this->exception = $exception; $this->authenticator = $authenticator; diff --git a/src/Symfony/Component/Security/Http/Event/LoginSuccessEvent.php b/src/Symfony/Component/Security/Http/Event/LoginSuccessEvent.php index 81eafb2ce5546..ff93f007d02b0 100644 --- a/src/Symfony/Component/Security/Http/Event/LoginSuccessEvent.php +++ b/src/Symfony/Component/Security/Http/Event/LoginSuccessEvent.php @@ -40,7 +40,7 @@ class LoginSuccessEvent extends Event private ?Response $response; private string $firewallName; - public function __construct(AuthenticatorInterface $authenticator, Passport $passport, TokenInterface $authenticatedToken, Request $request, ?Response $response, string $firewallName, TokenInterface $previousToken = null) + public function __construct(AuthenticatorInterface $authenticator, Passport $passport, TokenInterface $authenticatedToken, Request $request, ?Response $response, string $firewallName, ?TokenInterface $previousToken = null) { $this->authenticator = $authenticator; $this->passport = $passport; diff --git a/src/Symfony/Component/Security/Http/Event/SwitchUserEvent.php b/src/Symfony/Component/Security/Http/Event/SwitchUserEvent.php index 1b708c4efe30e..f0506a2abc021 100644 --- a/src/Symfony/Component/Security/Http/Event/SwitchUserEvent.php +++ b/src/Symfony/Component/Security/Http/Event/SwitchUserEvent.php @@ -27,7 +27,7 @@ final class SwitchUserEvent extends Event private UserInterface $targetUser; private ?TokenInterface $token; - public function __construct(Request $request, UserInterface $targetUser, TokenInterface $token = null) + public function __construct(Request $request, UserInterface $targetUser, ?TokenInterface $token = null) { $this->request = $request; $this->targetUser = $targetUser; diff --git a/src/Symfony/Component/Security/Http/EventListener/CheckRememberMeConditionsListener.php b/src/Symfony/Component/Security/Http/EventListener/CheckRememberMeConditionsListener.php index e241fa091b5aa..0556638dfb45b 100644 --- a/src/Symfony/Component/Security/Http/EventListener/CheckRememberMeConditionsListener.php +++ b/src/Symfony/Component/Security/Http/EventListener/CheckRememberMeConditionsListener.php @@ -38,7 +38,7 @@ class CheckRememberMeConditionsListener implements EventSubscriberInterface private array $options; private ?LoggerInterface $logger; - public function __construct(array $options = [], LoggerInterface $logger = null) + public function __construct(array $options = [], ?LoggerInterface $logger = null) { $this->options = $options + ['always_remember_me' => false, 'remember_me_parameter' => '_remember_me']; $this->logger = $logger; diff --git a/src/Symfony/Component/Security/Http/EventListener/CookieClearingLogoutListener.php b/src/Symfony/Component/Security/Http/EventListener/CookieClearingLogoutListener.php index 345e8d8b6ace4..cbc85990fafa7 100644 --- a/src/Symfony/Component/Security/Http/EventListener/CookieClearingLogoutListener.php +++ b/src/Symfony/Component/Security/Http/EventListener/CookieClearingLogoutListener.php @@ -40,7 +40,7 @@ public function onLogout(LogoutEvent $event): void } foreach ($this->cookies as $cookieName => $cookieData) { - $response->headers->clearCookie($cookieName, $cookieData['path'], $cookieData['domain'], $cookieData['secure'] ?? false, true, $cookieData['samesite'] ?? null); + $response->headers->clearCookie($cookieName, $cookieData['path'], $cookieData['domain'], $cookieData['secure'] ?? false, true, $cookieData['samesite'] ?? null, $cookieData['partitioned'] ?? false); } } diff --git a/src/Symfony/Component/Security/Http/EventListener/RememberMeListener.php b/src/Symfony/Component/Security/Http/EventListener/RememberMeListener.php index ea4d5d561c7e2..2dd5aa1cd70a9 100644 --- a/src/Symfony/Component/Security/Http/EventListener/RememberMeListener.php +++ b/src/Symfony/Component/Security/Http/EventListener/RememberMeListener.php @@ -37,7 +37,7 @@ class RememberMeListener implements EventSubscriberInterface private RememberMeHandlerInterface $rememberMeHandler; private ?LoggerInterface $logger; - public function __construct(RememberMeHandlerInterface $rememberMeHandler, LoggerInterface $logger = null) + public function __construct(RememberMeHandlerInterface $rememberMeHandler, ?LoggerInterface $logger = null) { $this->rememberMeHandler = $rememberMeHandler; $this->logger = $logger; diff --git a/src/Symfony/Component/Security/Http/Firewall/ChannelListener.php b/src/Symfony/Component/Security/Http/Firewall/ChannelListener.php index 6601338831f3d..6e020bbc4ce11 100644 --- a/src/Symfony/Component/Security/Http/Firewall/ChannelListener.php +++ b/src/Symfony/Component/Security/Http/Firewall/ChannelListener.php @@ -32,7 +32,7 @@ class ChannelListener extends AbstractListener private int $httpPort; private int $httpsPort; - public function __construct(AccessMapInterface $map, LoggerInterface $logger = null, int $httpPort = 80, int $httpsPort = 443) + public function __construct(AccessMapInterface $map, ?LoggerInterface $logger = null, int $httpPort = 80, int $httpsPort = 443) { $this->map = $map; $this->logger = $logger; diff --git a/src/Symfony/Component/Security/Http/Firewall/ContextListener.php b/src/Symfony/Component/Security/Http/Firewall/ContextListener.php index a629c0402fecd..d863554a046b2 100644 --- a/src/Symfony/Component/Security/Http/Firewall/ContextListener.php +++ b/src/Symfony/Component/Security/Http/Firewall/ContextListener.php @@ -55,7 +55,7 @@ class ContextListener extends AbstractListener /** * @param iterable $userProviders */ - public function __construct(TokenStorageInterface $tokenStorage, iterable $userProviders, string $contextKey, LoggerInterface $logger = null, EventDispatcherInterface $dispatcher = null, AuthenticationTrustResolverInterface $trustResolver = null, callable $sessionTrackerEnabler = null) + public function __construct(TokenStorageInterface $tokenStorage, iterable $userProviders, string $contextKey, ?LoggerInterface $logger = null, ?EventDispatcherInterface $dispatcher = null, ?AuthenticationTrustResolverInterface $trustResolver = null, ?callable $sessionTrackerEnabler = null) { if (empty($contextKey)) { throw new \InvalidArgumentException('$contextKey must not be empty.'); diff --git a/src/Symfony/Component/Security/Http/Firewall/ExceptionListener.php b/src/Symfony/Component/Security/Http/Firewall/ExceptionListener.php index 81fa06173faa6..8a2ce8bfd0648 100644 --- a/src/Symfony/Component/Security/Http/Firewall/ExceptionListener.php +++ b/src/Symfony/Component/Security/Http/Firewall/ExceptionListener.php @@ -57,7 +57,7 @@ class ExceptionListener private HttpUtils $httpUtils; private bool $stateless; - public function __construct(TokenStorageInterface $tokenStorage, AuthenticationTrustResolverInterface $trustResolver, HttpUtils $httpUtils, string $firewallName, AuthenticationEntryPointInterface $authenticationEntryPoint = null, string $errorPage = null, AccessDeniedHandlerInterface $accessDeniedHandler = null, LoggerInterface $logger = null, bool $stateless = false) + public function __construct(TokenStorageInterface $tokenStorage, AuthenticationTrustResolverInterface $trustResolver, HttpUtils $httpUtils, string $firewallName, ?AuthenticationEntryPointInterface $authenticationEntryPoint = null, ?string $errorPage = null, ?AccessDeniedHandlerInterface $accessDeniedHandler = null, ?LoggerInterface $logger = null, bool $stateless = false) { $this->tokenStorage = $tokenStorage; $this->accessDeniedHandler = $accessDeniedHandler; diff --git a/src/Symfony/Component/Security/Http/Firewall/LogoutListener.php b/src/Symfony/Component/Security/Http/Firewall/LogoutListener.php index 77810524ef5c6..86c737514f48d 100644 --- a/src/Symfony/Component/Security/Http/Firewall/LogoutListener.php +++ b/src/Symfony/Component/Security/Http/Firewall/LogoutListener.php @@ -41,7 +41,7 @@ class LogoutListener extends AbstractListener /** * @param array $options An array of options to process a logout attempt */ - public function __construct(TokenStorageInterface $tokenStorage, HttpUtils $httpUtils, EventDispatcherInterface $eventDispatcher, array $options = [], CsrfTokenManagerInterface $csrfTokenManager = null) + public function __construct(TokenStorageInterface $tokenStorage, HttpUtils $httpUtils, EventDispatcherInterface $eventDispatcher, array $options = [], ?CsrfTokenManagerInterface $csrfTokenManager = null) { $this->tokenStorage = $tokenStorage; $this->httpUtils = $httpUtils; diff --git a/src/Symfony/Component/Security/Http/Firewall/SwitchUserListener.php b/src/Symfony/Component/Security/Http/Firewall/SwitchUserListener.php index af02a2f2452a2..a8c7a652f6623 100644 --- a/src/Symfony/Component/Security/Http/Firewall/SwitchUserListener.php +++ b/src/Symfony/Component/Security/Http/Firewall/SwitchUserListener.php @@ -55,7 +55,7 @@ class SwitchUserListener extends AbstractListener private ?UrlGeneratorInterface $urlGenerator; private ?string $targetRoute; - public function __construct(TokenStorageInterface $tokenStorage, UserProviderInterface $provider, UserCheckerInterface $userChecker, string $firewallName, AccessDecisionManagerInterface $accessDecisionManager, LoggerInterface $logger = null, string $usernameParameter = '_switch_user', string $role = 'ROLE_ALLOWED_TO_SWITCH', EventDispatcherInterface $dispatcher = null, bool $stateless = false, UrlGeneratorInterface $urlGenerator = null, string $targetRoute = null) + public function __construct(TokenStorageInterface $tokenStorage, UserProviderInterface $provider, UserCheckerInterface $userChecker, string $firewallName, AccessDecisionManagerInterface $accessDecisionManager, ?LoggerInterface $logger = null, string $usernameParameter = '_switch_user', string $role = 'ROLE_ALLOWED_TO_SWITCH', ?EventDispatcherInterface $dispatcher = null, bool $stateless = false, ?UrlGeneratorInterface $urlGenerator = null, ?string $targetRoute = null) { if ('' === $firewallName) { throw new \InvalidArgumentException('$firewallName must not be empty.'); diff --git a/src/Symfony/Component/Security/Http/FirewallMap.php b/src/Symfony/Component/Security/Http/FirewallMap.php index de80319e07805..3b01cbdc161a6 100644 --- a/src/Symfony/Component/Security/Http/FirewallMap.php +++ b/src/Symfony/Component/Security/Http/FirewallMap.php @@ -32,7 +32,7 @@ class FirewallMap implements FirewallMapInterface /** * @param list $listeners */ - public function add(RequestMatcherInterface $requestMatcher = null, array $listeners = [], ExceptionListener $exceptionListener = null, LogoutListener $logoutListener = null): void + public function add(?RequestMatcherInterface $requestMatcher = null, array $listeners = [], ?ExceptionListener $exceptionListener = null, ?LogoutListener $logoutListener = null): void { $this->map[] = [$requestMatcher, $listeners, $exceptionListener, $logoutListener]; } diff --git a/src/Symfony/Component/Security/Http/HttpUtils.php b/src/Symfony/Component/Security/Http/HttpUtils.php index ce69027c86d58..eef4d8d014037 100644 --- a/src/Symfony/Component/Security/Http/HttpUtils.php +++ b/src/Symfony/Component/Security/Http/HttpUtils.php @@ -37,7 +37,7 @@ class HttpUtils * * @throws \InvalidArgumentException */ - public function __construct(UrlGeneratorInterface $urlGenerator = null, UrlMatcherInterface|RequestMatcherInterface $urlMatcher = null, string $domainRegexp = null, string $secureDomainRegexp = null) + public function __construct(?UrlGeneratorInterface $urlGenerator = null, UrlMatcherInterface|RequestMatcherInterface|null $urlMatcher = null, ?string $domainRegexp = null, ?string $secureDomainRegexp = null) { $this->urlGenerator = $urlGenerator; $this->urlMatcher = $urlMatcher; diff --git a/src/Symfony/Component/Security/Http/Impersonate/ImpersonateUrlGenerator.php b/src/Symfony/Component/Security/Http/Impersonate/ImpersonateUrlGenerator.php index f28d063ecbf88..98bf711246e09 100644 --- a/src/Symfony/Component/Security/Http/Impersonate/ImpersonateUrlGenerator.php +++ b/src/Symfony/Component/Security/Http/Impersonate/ImpersonateUrlGenerator.php @@ -50,12 +50,12 @@ public function generateImpersonationUrl(string $identifier): string return $request->getUriForPath($this->buildPath(null, $identifier)); } - public function generateExitPath(string $targetUri = null): string + public function generateExitPath(?string $targetUri = null): string { return $this->buildPath($targetUri); } - public function generateExitUrl(string $targetUri = null): string + public function generateExitUrl(?string $targetUri = null): string { if (null === $request = $this->requestStack->getCurrentRequest()) { return ''; @@ -69,7 +69,7 @@ private function isImpersonatedUser(): bool return $this->tokenStorage->getToken() instanceof SwitchUserToken; } - private function buildPath(string $targetUri = null, string $identifier = SwitchUserListener::EXIT_VALUE): string + private function buildPath(?string $targetUri = null, string $identifier = SwitchUserListener::EXIT_VALUE): string { if (null === ($request = $this->requestStack->getCurrentRequest())) { return ''; diff --git a/src/Symfony/Component/Security/Http/LoginLink/LoginLinkHandler.php b/src/Symfony/Component/Security/Http/LoginLink/LoginLinkHandler.php index 4d0e49adfa94d..176d316607506 100644 --- a/src/Symfony/Component/Security/Http/LoginLink/LoginLinkHandler.php +++ b/src/Symfony/Component/Security/Http/LoginLink/LoginLinkHandler.php @@ -44,7 +44,7 @@ public function __construct(UrlGeneratorInterface $urlGenerator, UserProviderInt ], $options); } - public function createLoginLink(UserInterface $user, Request $request = null, int $lifetime = null): LoginLinkDetails + public function createLoginLink(UserInterface $user, ?Request $request = null, ?int $lifetime = null): LoginLinkDetails { $expires = time() + ($lifetime ?: $this->options['lifetime']); $expiresAt = new \DateTimeImmutable('@'.$expires); diff --git a/src/Symfony/Component/Security/Http/LoginLink/LoginLinkHandlerInterface.php b/src/Symfony/Component/Security/Http/LoginLink/LoginLinkHandlerInterface.php index de1cc69418ab2..8a682e5a1d812 100644 --- a/src/Symfony/Component/Security/Http/LoginLink/LoginLinkHandlerInterface.php +++ b/src/Symfony/Component/Security/Http/LoginLink/LoginLinkHandlerInterface.php @@ -26,7 +26,7 @@ interface LoginLinkHandlerInterface * * @param int|null $lifetime When not null, the argument overrides any default lifetime previously set */ - public function createLoginLink(UserInterface $user, Request $request = null, int $lifetime = null): LoginLinkDetails; + public function createLoginLink(UserInterface $user, ?Request $request = null, ?int $lifetime = null): LoginLinkDetails; /** * Validates if this request contains a login link and returns the associated User. diff --git a/src/Symfony/Component/Security/Http/LoginLink/LoginLinkNotification.php b/src/Symfony/Component/Security/Http/LoginLink/LoginLinkNotification.php index 1f20cdf5f943c..6a126f89980fe 100644 --- a/src/Symfony/Component/Security/Http/LoginLink/LoginLinkNotification.php +++ b/src/Symfony/Component/Security/Http/LoginLink/LoginLinkNotification.php @@ -37,7 +37,7 @@ public function __construct(LoginLinkDetails $loginLinkDetails, string $subject, $this->loginLinkDetails = $loginLinkDetails; } - public function asEmailMessage(EmailRecipientInterface $recipient, string $transport = null): ?EmailMessage + public function asEmailMessage(EmailRecipientInterface $recipient, ?string $transport = null): ?EmailMessage { if (!class_exists(NotificationEmail::class)) { throw new \LogicException(sprintf('The "%s" method requires "symfony/twig-bridge:>4.4".', __METHOD__)); @@ -53,7 +53,7 @@ public function asEmailMessage(EmailRecipientInterface $recipient, string $trans return new EmailMessage($email); } - public function asSmsMessage(SmsRecipientInterface $recipient, string $transport = null): ?SmsMessage + public function asSmsMessage(SmsRecipientInterface $recipient, ?string $transport = null): ?SmsMessage { return new SmsMessage($recipient->getPhone(), $this->getDefaultContent('link').' '.$this->loginLinkDetails->getUrl()); } diff --git a/src/Symfony/Component/Security/Http/Logout/LogoutUrlGenerator.php b/src/Symfony/Component/Security/Http/Logout/LogoutUrlGenerator.php index db2cde72e628b..ae09053e26728 100644 --- a/src/Symfony/Component/Security/Http/Logout/LogoutUrlGenerator.php +++ b/src/Symfony/Component/Security/Http/Logout/LogoutUrlGenerator.php @@ -31,7 +31,7 @@ class LogoutUrlGenerator private ?string $currentFirewallName = null; private ?string $currentFirewallContext = null; - public function __construct(RequestStack $requestStack = null, UrlGeneratorInterface $router = null, TokenStorageInterface $tokenStorage = null) + public function __construct(?RequestStack $requestStack = null, ?UrlGeneratorInterface $router = null, ?TokenStorageInterface $tokenStorage = null) { $this->requestStack = $requestStack; $this->router = $router; @@ -47,7 +47,7 @@ public function __construct(RequestStack $requestStack = null, UrlGeneratorInter * @param string|null $csrfParameter The CSRF token parameter name * @param string|null $context The listener context */ - public function registerListener(string $key, string $logoutPath, ?string $csrfTokenId, ?string $csrfParameter, CsrfTokenManagerInterface $csrfTokenManager = null, string $context = null): void + public function registerListener(string $key, string $logoutPath, ?string $csrfTokenId, ?string $csrfParameter, ?CsrfTokenManagerInterface $csrfTokenManager = null, ?string $context = null): void { $this->listeners[$key] = [$logoutPath, $csrfTokenId, $csrfParameter, $csrfTokenManager, $context]; } @@ -55,7 +55,7 @@ public function registerListener(string $key, string $logoutPath, ?string $csrfT /** * Generates the absolute logout path for the firewall. */ - public function getLogoutPath(string $key = null): string + public function getLogoutPath(?string $key = null): string { return $this->generateLogoutUrl($key, UrlGeneratorInterface::ABSOLUTE_PATH); } @@ -63,12 +63,12 @@ public function getLogoutPath(string $key = null): string /** * Generates the absolute logout URL for the firewall. */ - public function getLogoutUrl(string $key = null): string + public function getLogoutUrl(?string $key = null): string { return $this->generateLogoutUrl($key, UrlGeneratorInterface::ABSOLUTE_URL); } - public function setCurrentFirewall(?string $key, string $context = null): void + public function setCurrentFirewall(?string $key, ?string $context = null): void { $this->currentFirewallName = $key; $this->currentFirewallContext = $context; diff --git a/src/Symfony/Component/Security/Http/RememberMe/AbstractRememberMeHandler.php b/src/Symfony/Component/Security/Http/RememberMe/AbstractRememberMeHandler.php index ea0f9a41b9d7d..3ea69b8711d09 100644 --- a/src/Symfony/Component/Security/Http/RememberMe/AbstractRememberMeHandler.php +++ b/src/Symfony/Component/Security/Http/RememberMe/AbstractRememberMeHandler.php @@ -29,7 +29,7 @@ abstract class AbstractRememberMeHandler implements RememberMeHandlerInterface private UserProviderInterface $userProvider; - public function __construct(UserProviderInterface $userProvider, RequestStack $requestStack, array $options = [], LoggerInterface $logger = null) + public function __construct(UserProviderInterface $userProvider, RequestStack $requestStack, array $options = [], ?LoggerInterface $logger = null) { $this->userProvider = $userProvider; $this->requestStack = $requestStack; diff --git a/src/Symfony/Component/Security/Http/RememberMe/PersistentRememberMeHandler.php b/src/Symfony/Component/Security/Http/RememberMe/PersistentRememberMeHandler.php index eae2031760662..bb5d3864db49c 100644 --- a/src/Symfony/Component/Security/Http/RememberMe/PersistentRememberMeHandler.php +++ b/src/Symfony/Component/Security/Http/RememberMe/PersistentRememberMeHandler.php @@ -35,7 +35,7 @@ final class PersistentRememberMeHandler extends AbstractRememberMeHandler private TokenProviderInterface $tokenProvider; private ?TokenVerifierInterface $tokenVerifier; - public function __construct(TokenProviderInterface $tokenProvider, UserProviderInterface $userProvider, RequestStack $requestStack, array $options, LoggerInterface $logger = null, TokenVerifierInterface $tokenVerifier = null) + public function __construct(TokenProviderInterface $tokenProvider, UserProviderInterface $userProvider, RequestStack $requestStack, array $options, ?LoggerInterface $logger = null, ?TokenVerifierInterface $tokenVerifier = null) { parent::__construct($userProvider, $requestStack, $options, $logger); diff --git a/src/Symfony/Component/Security/Http/RememberMe/SignatureRememberMeHandler.php b/src/Symfony/Component/Security/Http/RememberMe/SignatureRememberMeHandler.php index 85686c5bba6c8..f62cb258a7e5a 100644 --- a/src/Symfony/Component/Security/Http/RememberMe/SignatureRememberMeHandler.php +++ b/src/Symfony/Component/Security/Http/RememberMe/SignatureRememberMeHandler.php @@ -34,7 +34,7 @@ final class SignatureRememberMeHandler extends AbstractRememberMeHandler { private SignatureHasher $signatureHasher; - public function __construct(SignatureHasher $signatureHasher, UserProviderInterface $userProvider, RequestStack $requestStack, array $options, LoggerInterface $logger = null) + public function __construct(SignatureHasher $signatureHasher, UserProviderInterface $userProvider, RequestStack $requestStack, array $options, ?LoggerInterface $logger = null) { parent::__construct($userProvider, $requestStack, $options, $logger); diff --git a/src/Symfony/Component/Security/Http/Session/SessionAuthenticationStrategy.php b/src/Symfony/Component/Security/Http/Session/SessionAuthenticationStrategy.php index 7f04d6ce3531c..1f51c7896a517 100644 --- a/src/Symfony/Component/Security/Http/Session/SessionAuthenticationStrategy.php +++ b/src/Symfony/Component/Security/Http/Session/SessionAuthenticationStrategy.php @@ -34,7 +34,7 @@ class SessionAuthenticationStrategy implements SessionAuthenticationStrategyInte private string $strategy; private ?ClearableTokenStorageInterface $csrfTokenStorage = null; - public function __construct(string $strategy, ClearableTokenStorageInterface $csrfTokenStorage = null) + public function __construct(string $strategy, ?ClearableTokenStorageInterface $csrfTokenStorage = null) { $this->strategy = $strategy; diff --git a/src/Symfony/Component/Security/Http/Tests/AccessToken/Cas/Cas2HandlerTest.php b/src/Symfony/Component/Security/Http/Tests/AccessToken/Cas/Cas2HandlerTest.php new file mode 100644 index 0000000000000..728b7ca529a79 --- /dev/null +++ b/src/Symfony/Component/Security/Http/Tests/AccessToken/Cas/Cas2HandlerTest.php @@ -0,0 +1,123 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\Tests\AccessToken\Cas; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpClient\MockHttpClient; +use Symfony\Component\HttpClient\Response\MockResponse; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\Security\Core\Exception\AuthenticationException; +use Symfony\Component\Security\Http\AccessToken\Cas\Cas2Handler; +use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge; + +final class Cas2HandlerTest extends TestCase +{ + public function testWithValidTicket() + { + $response = new MockResponse(<< + + lobster + PGTIOU-84678-8a9d + + + BODY + ); + + $httpClient = new MockHttpClient([$response]); + $requestStack = new RequestStack(); + $requestStack->push(new Request(['ticket' => 'PGTIOU-84678-8a9d'])); + + $cas2Handler = new Cas2Handler(requestStack: $requestStack, validationUrl: 'https://www.example.com/cas', client: $httpClient); + $userbadge = $cas2Handler->getUserBadgeFrom('PGTIOU-84678-8a9d'); + $this->assertEquals(new UserBadge('lobster'), $userbadge); + } + + public function testWithInvalidTicket() + { + $this->expectException(AuthenticationException::class); + $this->expectExceptionMessage('CAS Authentication Failure: Ticket ST-1856339 not recognized'); + + $response = new MockResponse(<< + + Ticket ST-1856339 not recognized + + + BODY + ); + + $httpClient = new MockHttpClient([$response]); + $requestStack = new RequestStack(); + $requestStack->push(new Request(['ticket' => 'ST-1856339'])); + + $cas2Handler = new Cas2Handler(requestStack: $requestStack, validationUrl: 'https://www.example.com/cas', client: $httpClient); + $cas2Handler->getUserBadgeFrom('should-not-work'); + } + + public function testWithInvalidCasResponse() + { + $this->expectException(AuthenticationException::class); + $this->expectExceptionMessage('Invalid CAS response.'); + + $response = new MockResponse(<< + + BODY + ); + + $httpClient = new MockHttpClient([$response]); + $requestStack = new RequestStack(); + $requestStack->push(new Request(['ticket' => 'ST-1856339'])); + + $cas2Handler = new Cas2Handler(requestStack: $requestStack, validationUrl: 'https://www.example.com/cas', client: $httpClient); + $cas2Handler->getUserBadgeFrom('should-not-work'); + } + + public function testWithoutTicket() + { + $this->expectException(AuthenticationException::class); + $this->expectExceptionMessage('No ticket found in request.'); + + $httpClient = new MockHttpClient(); + $requestStack = new RequestStack(); + $requestStack->push(new Request()); + + $cas2Handler = new Cas2Handler(requestStack: $requestStack, validationUrl: 'https://www.example.com/cas', client: $httpClient); + $cas2Handler->getUserBadgeFrom('should-not-work'); + } + + public function testWithInvalidPrefix() + { + $this->expectException(AuthenticationException::class); + $this->expectExceptionMessage('Invalid CAS response.'); + + $response = new MockResponse(<< + + lobster + PGTIOU-84678-8a9d + + + BODY + ); + + $httpClient = new MockHttpClient([$response]); + $requestStack = new RequestStack(); + $requestStack->push(new Request(['ticket' => 'PGTIOU-84678-8a9d'])); + + $cas2Handler = new Cas2Handler(requestStack: $requestStack, validationUrl: 'https://www.example.com/cas', prefix: 'invalid-one', client: $httpClient); + $username = $cas2Handler->getUserBadgeFrom('PGTIOU-84678-8a9d'); + $this->assertEquals('lobster', $username); + } +} diff --git a/src/Symfony/Component/Security/Http/Tests/Authentication/AuthenticationUtilsTest.php b/src/Symfony/Component/Security/Http/Tests/Authentication/AuthenticationUtilsTest.php new file mode 100644 index 0000000000000..b0c37fce732d6 --- /dev/null +++ b/src/Symfony/Component/Security/Http/Tests/Authentication/AuthenticationUtilsTest.php @@ -0,0 +1,127 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\Tests\Authentication; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\HttpFoundation\Session\Session; +use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage; +use Symfony\Component\Security\Core\Exception\AuthenticationException; +use Symfony\Component\Security\Http\Authentication\AuthenticationUtils; +use Symfony\Component\Security\Http\SecurityRequestAttributes; + +class AuthenticationUtilsTest extends TestCase +{ + public function testLastAuthenticationErrorWhenRequestHasAttribute() + { + $authenticationError = new AuthenticationException(); + $request = Request::create('/'); + $request->attributes->set(SecurityRequestAttributes::AUTHENTICATION_ERROR, $authenticationError); + + $requestStack = new RequestStack(); + $requestStack->push($request); + + $utils = new AuthenticationUtils($requestStack); + $this->assertSame($authenticationError, $utils->getLastAuthenticationError()); + } + + public function testLastAuthenticationErrorInSession() + { + $authenticationError = new AuthenticationException(); + + $request = Request::create('/'); + + $session = new Session(new MockArraySessionStorage()); + $session->set(SecurityRequestAttributes::AUTHENTICATION_ERROR, $authenticationError); + $request->setSession($session); + + $requestStack = new RequestStack(); + $requestStack->push($request); + + $utils = new AuthenticationUtils($requestStack); + $this->assertSame($authenticationError, $utils->getLastAuthenticationError()); + $this->assertFalse($session->has(SecurityRequestAttributes::AUTHENTICATION_ERROR)); + } + + public function testLastAuthenticationErrorInSessionWithoutClearing() + { + $authenticationError = new AuthenticationException(); + + $request = Request::create('/'); + + $session = new Session(new MockArraySessionStorage()); + $session->set(SecurityRequestAttributes::AUTHENTICATION_ERROR, $authenticationError); + $request->setSession($session); + + $requestStack = new RequestStack(); + $requestStack->push($request); + + $utils = new AuthenticationUtils($requestStack); + $this->assertSame($authenticationError, $utils->getLastAuthenticationError(false)); + $this->assertTrue($session->has(SecurityRequestAttributes::AUTHENTICATION_ERROR)); + } + + public function testLastUserNameIsDefinedButNull() + { + $request = Request::create('/'); + $request->attributes->set(SecurityRequestAttributes::LAST_USERNAME, null); + + $requestStack = new RequestStack(); + $requestStack->push($request); + + $utils = new AuthenticationUtils($requestStack); + $this->assertSame('', $utils->getLastUsername()); + } + + public function testLastUserNameIsDefined() + { + $request = Request::create('/'); + $request->attributes->set(SecurityRequestAttributes::LAST_USERNAME, 'user'); + + $requestStack = new RequestStack(); + $requestStack->push($request); + + $utils = new AuthenticationUtils($requestStack); + $this->assertSame('user', $utils->getLastUsername()); + } + + public function testLastUserNameIsDefinedInSessionButNull() + { + $request = Request::create('/'); + + $session = new Session(new MockArraySessionStorage()); + $session->set(SecurityRequestAttributes::LAST_USERNAME, null); + $request->setSession($session); + + $requestStack = new RequestStack(); + $requestStack->push($request); + + $utils = new AuthenticationUtils($requestStack); + $this->assertSame('', $utils->getLastUsername()); + } + + public function testLastUserNameIsDefinedInSession() + { + $request = Request::create('/'); + + $session = new Session(new MockArraySessionStorage()); + $session->set(SecurityRequestAttributes::LAST_USERNAME, 'user'); + $request->setSession($session); + + $requestStack = new RequestStack(); + $requestStack->push($request); + + $utils = new AuthenticationUtils($requestStack); + $this->assertSame('user', $utils->getLastUsername()); + } +} diff --git a/src/Symfony/Component/Security/Http/Tests/Authentication/AuthenticatorManagerTest.php b/src/Symfony/Component/Security/Http/Tests/Authentication/AuthenticatorManagerTest.php index c61b85c58cb4d..c1a3b5cf12043 100644 --- a/src/Symfony/Component/Security/Http/Tests/Authentication/AuthenticatorManagerTest.php +++ b/src/Symfony/Component/Security/Http/Tests/Authentication/AuthenticatorManagerTest.php @@ -373,7 +373,7 @@ private static function createDummySupportsAuthenticator(?bool $supports = true) return new DummySupportsAuthenticator($supports); } - private function createManager($authenticators, $firewallName = 'main', $eraseCredentials = true, array $requiredBadges = [], LoggerInterface $logger = null) + private function createManager($authenticators, $firewallName = 'main', $eraseCredentials = true, array $requiredBadges = [], ?LoggerInterface $logger = null) { return new AuthenticatorManager($authenticators, $this->tokenStorage, $this->eventDispatcher, $firewallName, $logger, $eraseCredentials, true, $requiredBadges); } diff --git a/src/Symfony/Component/Security/Http/Tests/Controller/UserValueResolverTest.php b/src/Symfony/Component/Security/Http/Tests/Controller/UserValueResolverTest.php index 7819d32f2723f..79f8136612bba 100644 --- a/src/Symfony/Component/Security/Http/Tests/Controller/UserValueResolverTest.php +++ b/src/Symfony/Component/Security/Http/Tests/Controller/UserValueResolverTest.php @@ -164,6 +164,6 @@ public function testIntegrationNoUser() $tokenStorage = new TokenStorage(); $argumentResolver = new ArgumentResolver(null, [new UserValueResolver($tokenStorage), new DefaultValueResolver()]); - $this->assertSame([null], $argumentResolver->getArguments(Request::create('/'), function (UserInterface $user = null) {})); + $this->assertSame([null], $argumentResolver->getArguments(Request::create('/'), function (?UserInterface $user = null) {})); } } diff --git a/src/Symfony/Component/Security/Http/Tests/EventListener/CheckRememberMeConditionsListenerTest.php b/src/Symfony/Component/Security/Http/Tests/EventListener/CheckRememberMeConditionsListenerTest.php index ab95a25a7b324..218d09c1a499d 100644 --- a/src/Symfony/Component/Security/Http/Tests/EventListener/CheckRememberMeConditionsListenerTest.php +++ b/src/Symfony/Component/Security/Http/Tests/EventListener/CheckRememberMeConditionsListenerTest.php @@ -146,7 +146,7 @@ private function createLoginSuccessfulEvent(Passport $passport) return new LoginSuccessEvent($this->createMock(AuthenticatorInterface::class), $passport, $this->createMock(TokenInterface::class), $this->request, $this->response, 'main_firewall'); } - private function createPassport(array $badges = null) + private function createPassport(?array $badges = null) { return new SelfValidatingPassport(new UserBadge('test', fn ($username) => new InMemoryUser($username, null)), $badges ?? [new RememberMeBadge(['_remember_me' => true])]); } diff --git a/src/Symfony/Component/Security/Http/Tests/EventListener/CookieClearingLogoutListenerTest.php b/src/Symfony/Component/Security/Http/Tests/EventListener/CookieClearingLogoutListenerTest.php index f4c0e3d89b611..8f7d4660b8850 100644 --- a/src/Symfony/Component/Security/Http/Tests/EventListener/CookieClearingLogoutListenerTest.php +++ b/src/Symfony/Component/Security/Http/Tests/EventListener/CookieClearingLogoutListenerTest.php @@ -27,7 +27,7 @@ public function testLogout() $event = new LogoutEvent(new Request(), null); $event->setResponse($response); - $listener = new CookieClearingLogoutListener(['foo' => ['path' => '/foo', 'domain' => 'foo.foo', 'secure' => true, 'samesite' => Cookie::SAMESITE_STRICT], 'foo2' => ['path' => null, 'domain' => null]]); + $listener = new CookieClearingLogoutListener(['foo' => ['path' => '/foo', 'domain' => 'foo.foo', 'secure' => true, 'samesite' => Cookie::SAMESITE_STRICT, 'partitioned' => true], 'foo2' => ['path' => null, 'domain' => null]]); $cookies = $response->headers->getCookies(); $this->assertCount(0, $cookies); @@ -43,6 +43,9 @@ public function testLogout() $this->assertEquals('foo.foo', $cookie->getDomain()); $this->assertEquals(Cookie::SAMESITE_STRICT, $cookie->getSameSite()); $this->assertTrue($cookie->isSecure()); + if (self::doesResponseHeaderBagClearChipsCookies()) { + $this->assertTrue($cookie->isPartitioned()); + } $this->assertTrue($cookie->isCleared()); $cookie = $cookies['']['/']['foo2']; @@ -51,6 +54,20 @@ public function testLogout() $this->assertNull($cookie->getDomain()); $this->assertNull($cookie->getSameSite()); $this->assertFalse($cookie->isSecure()); + if (self::doesResponseHeaderBagClearChipsCookies()) { + $this->assertFalse($cookie->isPartitioned()); + } $this->assertTrue($cookie->isCleared()); } + + /** + * Checks if the patch from https://github.com/symfony/symfony/pull/53703 is available. + */ + private static function doesResponseHeaderBagClearChipsCookies(): bool + { + $bag = new ResponseHeaderBag(); + $bag->clearCookie('foo', '/', null, false, true, null, true); + + return $bag->getCookies()[0]->isPartitioned(); + } } diff --git a/src/Symfony/Component/Security/Http/Tests/EventListener/RememberMeListenerTest.php b/src/Symfony/Component/Security/Http/Tests/EventListener/RememberMeListenerTest.php index 07e1203d33cdd..89a32c3af8609 100644 --- a/src/Symfony/Component/Security/Http/Tests/EventListener/RememberMeListenerTest.php +++ b/src/Symfony/Component/Security/Http/Tests/EventListener/RememberMeListenerTest.php @@ -65,14 +65,14 @@ public function testCredentialsInvalid() $this->listener->clearCookie(); } - private function createLoginSuccessfulEvent(Passport $passport = null) + private function createLoginSuccessfulEvent(?Passport $passport = null) { $passport ??= $this->createPassport(); return new LoginSuccessEvent($this->createMock(AuthenticatorInterface::class), $passport, $this->createMock(TokenInterface::class), $this->request, $this->response, 'main_firewall'); } - private function createPassport(array $badges = null) + private function createPassport(?array $badges = null) { if (null === $badges) { $badge = new RememberMeBadge(); diff --git a/src/Symfony/Component/Security/Http/Tests/Firewall/ContextListenerTest.php b/src/Symfony/Component/Security/Http/Tests/Firewall/ContextListenerTest.php index 282dc2c4787a6..2afa9b04b55bf 100644 --- a/src/Symfony/Component/Security/Http/Tests/Firewall/ContextListenerTest.php +++ b/src/Symfony/Component/Security/Http/Tests/Firewall/ContextListenerTest.php @@ -390,7 +390,7 @@ protected function runSessionOnKernelResponse($newToken, $original = null) return $session; } - private function handleEventWithPreviousSession($userProviders, UserInterface $user = null) + private function handleEventWithPreviousSession($userProviders, ?UserInterface $user = null) { $tokenUser = $user ?? new InMemoryUser('foo', 'bar'); $session = new Session(new MockArraySessionStorage()); @@ -465,7 +465,7 @@ class SupportingUserProvider implements UserProviderInterface { private ?InMemoryUser $refreshedUser; - public function __construct(InMemoryUser $refreshedUser = null) + public function __construct(?InMemoryUser $refreshedUser = null) { $this->refreshedUser = $refreshedUser; } diff --git a/src/Symfony/Component/Security/Http/Tests/Firewall/ExceptionListenerTest.php b/src/Symfony/Component/Security/Http/Tests/Firewall/ExceptionListenerTest.php index 5b79943d24b3e..8bcc958854275 100644 --- a/src/Symfony/Component/Security/Http/Tests/Firewall/ExceptionListenerTest.php +++ b/src/Symfony/Component/Security/Http/Tests/Firewall/ExceptionListenerTest.php @@ -78,7 +78,7 @@ public static function getAuthenticationExceptionProvider() /** * @dataProvider getAccessDeniedExceptionProvider */ - public function testAccessDeniedExceptionFullFledgedAndWithoutAccessDeniedHandlerAndWithoutErrorPage(\Exception $exception, \Exception $eventException = null) + public function testAccessDeniedExceptionFullFledgedAndWithoutAccessDeniedHandlerAndWithoutErrorPage(\Exception $exception, ?\Exception $eventException = null) { $event = $this->createEvent($exception); @@ -92,7 +92,7 @@ public function testAccessDeniedExceptionFullFledgedAndWithoutAccessDeniedHandle /** * @dataProvider getAccessDeniedExceptionProvider */ - public function testAccessDeniedExceptionFullFledgedAndWithoutAccessDeniedHandlerAndWithErrorPage(\Exception $exception, \Exception $eventException = null) + public function testAccessDeniedExceptionFullFledgedAndWithoutAccessDeniedHandlerAndWithErrorPage(\Exception $exception, ?\Exception $eventException = null) { $kernel = $this->createMock(HttpKernelInterface::class); $kernel->expects($this->once())->method('handle')->willReturn(new Response('Unauthorized', 401)); @@ -115,7 +115,7 @@ public function testAccessDeniedExceptionFullFledgedAndWithoutAccessDeniedHandle /** * @dataProvider getAccessDeniedExceptionProvider */ - public function testAccessDeniedExceptionFullFledgedAndWithAccessDeniedHandlerAndWithoutErrorPage(\Exception $exception, \Exception $eventException = null) + public function testAccessDeniedExceptionFullFledgedAndWithAccessDeniedHandlerAndWithoutErrorPage(\Exception $exception, ?\Exception $eventException = null) { $event = $this->createEvent($exception); @@ -132,7 +132,7 @@ public function testAccessDeniedExceptionFullFledgedAndWithAccessDeniedHandlerAn /** * @dataProvider getAccessDeniedExceptionProvider */ - public function testAccessDeniedExceptionNotFullFledged(\Exception $exception, \Exception $eventException = null) + public function testAccessDeniedExceptionNotFullFledged(\Exception $exception, ?\Exception $eventException = null) { $event = $this->createEvent($exception); @@ -180,7 +180,7 @@ public static function getAccessDeniedExceptionProvider() ]; } - private function createEntryPoint(Response $response = null) + private function createEntryPoint(?Response $response = null) { $entryPoint = $this->createMock(AuthenticationEntryPointInterface::class); $entryPoint->expects($this->once())->method('start')->willReturn($response ?? new Response('OK')); @@ -203,7 +203,7 @@ private function createEvent(\Exception $exception, $kernel = null) return new ExceptionEvent($kernel, Request::create('/'), HttpKernelInterface::MAIN_REQUEST, $exception); } - private function createExceptionListener(TokenStorageInterface $tokenStorage = null, AuthenticationTrustResolverInterface $trustResolver = null, HttpUtils $httpUtils = null, AuthenticationEntryPointInterface $authenticationEntryPoint = null, $errorPage = null, AccessDeniedHandlerInterface $accessDeniedHandler = null) + private function createExceptionListener(?TokenStorageInterface $tokenStorage = null, ?AuthenticationTrustResolverInterface $trustResolver = null, ?HttpUtils $httpUtils = null, ?AuthenticationEntryPointInterface $authenticationEntryPoint = null, $errorPage = null, ?AccessDeniedHandlerInterface $accessDeniedHandler = null) { return new ExceptionListener( $tokenStorage ?? $this->createMock(TokenStorageInterface::class), diff --git a/src/Symfony/Component/Security/Http/Tests/LoginLink/LoginLinkHandlerTest.php b/src/Symfony/Component/Security/Http/Tests/LoginLink/LoginLinkHandlerTest.php index cde245637efea..820f0d68f3209 100644 --- a/src/Symfony/Component/Security/Http/Tests/LoginLink/LoginLinkHandlerTest.php +++ b/src/Symfony/Component/Security/Http/Tests/LoginLink/LoginLinkHandlerTest.php @@ -52,7 +52,7 @@ protected function setUp(): void * * @dataProvider provideCreateLoginLinkData */ - public function testCreateLoginLink($user, array $extraProperties, Request $request = null) + public function testCreateLoginLink($user, array $extraProperties, ?Request $request = null) { $this->router->expects($this->once()) ->method('generate') diff --git a/src/Symfony/Component/Security/Http/composer.json b/src/Symfony/Component/Security/Http/composer.json index 3f96dc20c137b..4bf955b99358c 100644 --- a/src/Symfony/Component/Security/Http/composer.json +++ b/src/Symfony/Component/Security/Http/composer.json @@ -28,6 +28,7 @@ "symfony/cache": "^6.4|^7.0", "symfony/clock": "^6.4|^7.0", "symfony/expression-language": "^6.4|^7.0", + "symfony/http-client": "^6.4|^7.0", "symfony/http-client-contracts": "^3.0", "symfony/rate-limiter": "^6.4|^7.0", "symfony/routing": "^6.4|^7.0", diff --git a/src/Symfony/Component/Semaphore/Semaphore.php b/src/Symfony/Component/Semaphore/Semaphore.php index b9494c5d7c03b..97eaa61994080 100644 --- a/src/Symfony/Component/Semaphore/Semaphore.php +++ b/src/Symfony/Component/Semaphore/Semaphore.php @@ -87,7 +87,7 @@ public function acquire(): bool } } - public function refresh(float $ttlInSecond = null): void + public function refresh(?float $ttlInSecond = null): void { if (!$ttlInSecond ??= $this->ttlInSecond) { throw new InvalidArgumentException('You have to define an expiration duration.'); diff --git a/src/Symfony/Component/Semaphore/SemaphoreInterface.php b/src/Symfony/Component/Semaphore/SemaphoreInterface.php index 66b6c4ef0ea8a..2c8874fd0572b 100644 --- a/src/Symfony/Component/Semaphore/SemaphoreInterface.php +++ b/src/Symfony/Component/Semaphore/SemaphoreInterface.php @@ -35,7 +35,7 @@ public function acquire(): bool; * * @throws SemaphoreExpiredException If the semaphore has expired */ - public function refresh(float $ttlInSecond = null): void; + public function refresh(?float $ttlInSecond = null): void; /** * Returns whether or not the semaphore is acquired. diff --git a/src/Symfony/Component/Serializer/CHANGELOG.md b/src/Symfony/Component/Serializer/CHANGELOG.md index b329cf1542334..a5cb2e777acc2 100644 --- a/src/Symfony/Component/Serializer/CHANGELOG.md +++ b/src/Symfony/Component/Serializer/CHANGELOG.md @@ -1,6 +1,12 @@ CHANGELOG ========= +7.1 +--- + + * Add `DateTimeNormalizer::CAST_KEY` context option + * Add `Default` and "class name" default groups + 7.0 --- diff --git a/src/Symfony/Component/Serializer/CacheWarmer/CompiledClassMetadataCacheWarmer.php b/src/Symfony/Component/Serializer/CacheWarmer/CompiledClassMetadataCacheWarmer.php index 64998e98fb295..379a2a38071d2 100644 --- a/src/Symfony/Component/Serializer/CacheWarmer/CompiledClassMetadataCacheWarmer.php +++ b/src/Symfony/Component/Serializer/CacheWarmer/CompiledClassMetadataCacheWarmer.php @@ -29,7 +29,7 @@ public function __construct( ) { } - public function warmUp(string $cacheDir, string $buildDir = null): array + public function warmUp(string $cacheDir, ?string $buildDir = null): array { $metadatas = []; diff --git a/src/Symfony/Component/Serializer/Context/Normalizer/DateTimeNormalizerContextBuilder.php b/src/Symfony/Component/Serializer/Context/Normalizer/DateTimeNormalizerContextBuilder.php index 99517afb1d8d4..e2d289e600ec6 100644 --- a/src/Symfony/Component/Serializer/Context/Normalizer/DateTimeNormalizerContextBuilder.php +++ b/src/Symfony/Component/Serializer/Context/Normalizer/DateTimeNormalizerContextBuilder.php @@ -61,4 +61,12 @@ public function withTimezone(\DateTimeZone|string|null $timezone): static return $this->with(DateTimeNormalizer::TIMEZONE_KEY, $timezone); } + + /** + * @param 'int'|'float'|null $cast + */ + public function withCast(?string $cast): static + { + return $this->with(DateTimeNormalizer::CAST_KEY, $cast); + } } diff --git a/src/Symfony/Component/Serializer/DataCollector/SerializerDataCollector.php b/src/Symfony/Component/Serializer/DataCollector/SerializerDataCollector.php index 9dd1752fad1e7..671239d28c7fb 100644 --- a/src/Symfony/Component/Serializer/DataCollector/SerializerDataCollector.php +++ b/src/Symfony/Component/Serializer/DataCollector/SerializerDataCollector.php @@ -33,7 +33,7 @@ public function reset(): void $this->collected = []; } - public function collect(Request $request, Response $response, \Throwable $exception = null): void + public function collect(Request $request, Response $response, ?\Throwable $exception = null): void { // Everything is collected during the request, and formatted on kernel terminate. } diff --git a/src/Symfony/Component/Serializer/Debug/TraceableNormalizer.php b/src/Symfony/Component/Serializer/Debug/TraceableNormalizer.php index 5d4c79e933cd8..50842a7b92f95 100644 --- a/src/Symfony/Component/Serializer/Debug/TraceableNormalizer.php +++ b/src/Symfony/Component/Serializer/Debug/TraceableNormalizer.php @@ -39,7 +39,7 @@ public function getSupportedTypes(?string $format): array return $this->normalizer->getSupportedTypes($format); } - public function normalize(mixed $object, string $format = null, array $context = []): array|string|int|float|bool|\ArrayObject|null + public function normalize(mixed $object, ?string $format = null, array $context = []): array|string|int|float|bool|\ArrayObject|null { if (!$this->normalizer instanceof NormalizerInterface) { throw new \BadMethodCallException(sprintf('The "%s()" method cannot be called as nested normalizer doesn\'t implements "%s".', __METHOD__, NormalizerInterface::class)); @@ -56,7 +56,7 @@ public function normalize(mixed $object, string $format = null, array $context = return $normalized; } - public function supportsNormalization(mixed $data, string $format = null, array $context = []): bool + public function supportsNormalization(mixed $data, ?string $format = null, array $context = []): bool { if (!$this->normalizer instanceof NormalizerInterface) { return false; @@ -65,7 +65,7 @@ public function supportsNormalization(mixed $data, string $format = null, array return $this->normalizer->supportsNormalization($data, $format, $context); } - public function denormalize(mixed $data, string $type, string $format = null, array $context = []): mixed + public function denormalize(mixed $data, string $type, ?string $format = null, array $context = []): mixed { if (!$this->normalizer instanceof DenormalizerInterface) { throw new \BadMethodCallException(sprintf('The "%s()" method cannot be called as nested normalizer doesn\'t implements "%s".', __METHOD__, DenormalizerInterface::class)); @@ -82,7 +82,7 @@ public function denormalize(mixed $data, string $type, string $format = null, ar return $denormalized; } - public function supportsDenormalization(mixed $data, string $type, string $format = null, array $context = []): bool + public function supportsDenormalization(mixed $data, string $type, ?string $format = null, array $context = []): bool { if (!$this->normalizer instanceof DenormalizerInterface) { return false; diff --git a/src/Symfony/Component/Serializer/Debug/TraceableSerializer.php b/src/Symfony/Component/Serializer/Debug/TraceableSerializer.php index d7673ed103728..f3d14a2b79c10 100644 --- a/src/Symfony/Component/Serializer/Debug/TraceableSerializer.php +++ b/src/Symfony/Component/Serializer/Debug/TraceableSerializer.php @@ -65,7 +65,7 @@ public function deserialize(mixed $data, string $type, string $format, array $co return $result; } - public function normalize(mixed $object, string $format = null, array $context = []): array|string|int|float|bool|\ArrayObject|null + public function normalize(mixed $object, ?string $format = null, array $context = []): array|string|int|float|bool|\ArrayObject|null { $context[self::DEBUG_TRACE_ID] = $traceId = uniqid(); @@ -80,7 +80,7 @@ public function normalize(mixed $object, string $format = null, array $context = return $result; } - public function denormalize(mixed $data, string $type, string $format = null, array $context = []): mixed + public function denormalize(mixed $data, string $type, ?string $format = null, array $context = []): mixed { $context[self::DEBUG_TRACE_ID] = $traceId = uniqid(); @@ -130,12 +130,12 @@ public function getSupportedTypes(?string $format): array return $this->serializer->getSupportedTypes($format); } - public function supportsNormalization(mixed $data, string $format = null, array $context = []): bool + public function supportsNormalization(mixed $data, ?string $format = null, array $context = []): bool { return $this->serializer->supportsNormalization($data, $format, $context); } - public function supportsDenormalization(mixed $data, string $type, string $format = null, array $context = []): bool + public function supportsDenormalization(mixed $data, string $type, ?string $format = null, array $context = []): bool { return $this->serializer->supportsDenormalization($data, $type, $format, $context); } diff --git a/src/Symfony/Component/Serializer/Encoder/JsonEncoder.php b/src/Symfony/Component/Serializer/Encoder/JsonEncoder.php index d6cf495b0b167..5727e7be0ca08 100644 --- a/src/Symfony/Component/Serializer/Encoder/JsonEncoder.php +++ b/src/Symfony/Component/Serializer/Encoder/JsonEncoder.php @@ -27,7 +27,7 @@ class JsonEncoder implements EncoderInterface, DecoderInterface JsonDecode::ASSOCIATIVE => true, ]; - public function __construct(JsonEncode $encodingImpl = null, JsonDecode $decodingImpl = null, array $defaultContext = []) + public function __construct(?JsonEncode $encodingImpl = null, ?JsonDecode $decodingImpl = null, array $defaultContext = []) { $this->defaultContext = array_merge($this->defaultContext, $defaultContext); $this->encodingImpl = $encodingImpl ?? new JsonEncode($this->defaultContext); diff --git a/src/Symfony/Component/Serializer/Encoder/XmlEncoder.php b/src/Symfony/Component/Serializer/Encoder/XmlEncoder.php index b6ceb21b2cf44..c33a3565079cb 100644 --- a/src/Symfony/Component/Serializer/Encoder/XmlEncoder.php +++ b/src/Symfony/Component/Serializer/Encoder/XmlEncoder.php @@ -338,7 +338,7 @@ private function addXmlNamespaces(array $data, \DOMNode $node, \DOMDocument $doc * * @throws NotEncodableValueException */ - private function buildXml(\DOMNode $parentNode, mixed $data, string $format, array $context, string $xmlRootNodeName = null): bool + private function buildXml(\DOMNode $parentNode, mixed $data, string $format, array $context, ?string $xmlRootNodeName = null): bool { $append = true; $removeEmptyTags = $context[self::REMOVE_EMPTY_TAGS] ?? $this->defaultContext[self::REMOVE_EMPTY_TAGS] ?? false; @@ -412,7 +412,7 @@ private function buildXml(\DOMNode $parentNode, mixed $data, string $format, arr /** * Selects the type of node to create and appends it to the parent. */ - private function appendNode(\DOMNode $parentNode, mixed $data, string $format, array $context, string $nodeName, string $key = null): bool + private function appendNode(\DOMNode $parentNode, mixed $data, string $format, array $context, string $nodeName, ?string $key = null): bool { $dom = $parentNode instanceof \DOMDocument ? $parentNode : $parentNode->ownerDocument; $node = $dom->createElement($nodeName); diff --git a/src/Symfony/Component/Serializer/Encoder/YamlEncoder.php b/src/Symfony/Component/Serializer/Encoder/YamlEncoder.php index 5ea88ef19f6ce..223cd79333f6a 100644 --- a/src/Symfony/Component/Serializer/Encoder/YamlEncoder.php +++ b/src/Symfony/Component/Serializer/Encoder/YamlEncoder.php @@ -50,7 +50,7 @@ class YamlEncoder implements EncoderInterface, DecoderInterface self::YAML_FLAGS => 0, ]; - public function __construct(Dumper $dumper = null, Parser $parser = null, array $defaultContext = []) + public function __construct(?Dumper $dumper = null, ?Parser $parser = null, array $defaultContext = []) { if (!class_exists(Dumper::class)) { throw new RuntimeException('The YamlEncoder class requires the "Yaml" component. Try running "composer require symfony/yaml".'); diff --git a/src/Symfony/Component/Serializer/Exception/ExtraAttributesException.php b/src/Symfony/Component/Serializer/Exception/ExtraAttributesException.php index d681de419cf8c..24b031ae33878 100644 --- a/src/Symfony/Component/Serializer/Exception/ExtraAttributesException.php +++ b/src/Symfony/Component/Serializer/Exception/ExtraAttributesException.php @@ -20,7 +20,7 @@ class ExtraAttributesException extends RuntimeException { public function __construct( private readonly array $extraAttributes, - \Throwable $previous = null, + ?\Throwable $previous = null, ) { $msg = sprintf('Extra attributes are not allowed ("%s" %s unknown).', implode('", "', $extraAttributes), \count($extraAttributes) > 1 ? 'are' : 'is'); diff --git a/src/Symfony/Component/Serializer/Exception/MissingConstructorArgumentsException.php b/src/Symfony/Component/Serializer/Exception/MissingConstructorArgumentsException.php index ee8fcb5dc22e5..ad3f5c82bd134 100644 --- a/src/Symfony/Component/Serializer/Exception/MissingConstructorArgumentsException.php +++ b/src/Symfony/Component/Serializer/Exception/MissingConstructorArgumentsException.php @@ -23,7 +23,7 @@ class MissingConstructorArgumentsException extends RuntimeException public function __construct( string $message, int $code = 0, - \Throwable $previous = null, + ?\Throwable $previous = null, private array $missingArguments = [], private ?string $class = null, ) { diff --git a/src/Symfony/Component/Serializer/Exception/NotNormalizableValueException.php b/src/Symfony/Component/Serializer/Exception/NotNormalizableValueException.php index 148c60ada4e1c..bf87b28dd3726 100644 --- a/src/Symfony/Component/Serializer/Exception/NotNormalizableValueException.php +++ b/src/Symfony/Component/Serializer/Exception/NotNormalizableValueException.php @@ -11,6 +11,8 @@ namespace Symfony\Component\Serializer\Exception; +use Symfony\Component\TypeInfo\Type; + /** * @author Christian Flothmann */ @@ -22,17 +24,17 @@ class NotNormalizableValueException extends UnexpectedValueException private bool $useMessageForUser = false; /** - * @param string[] $expectedTypes - * @param bool $useMessageForUser If the message passed to this exception is something that can be shown - * safely to your user. In other words, avoid catching other exceptions and - * passing their message directly to this class. + * @param list $expectedTypes + * @param bool $useMessageForUser If the message passed to this exception is something that can be shown + * safely to your user. In other words, avoid catching other exceptions and + * passing their message directly to this class. */ - public static function createForUnexpectedDataType(string $message, mixed $data, array $expectedTypes, string $path = null, bool $useMessageForUser = false, int $code = 0, \Throwable $previous = null): self + public static function createForUnexpectedDataType(string $message, mixed $data, array $expectedTypes, ?string $path = null, bool $useMessageForUser = false, int $code = 0, ?\Throwable $previous = null): self { $self = new self($message, $code, $previous); $self->currentType = get_debug_type($data); - $self->expectedTypes = $expectedTypes; + $self->expectedTypes = array_map(fn (string|Type $t): string => $t instanceof Type ? (string) $t : $t, $expectedTypes); $self->path = $path; $self->useMessageForUser = $useMessageForUser; diff --git a/src/Symfony/Component/Serializer/Extractor/ObjectPropertyListExtractor.php b/src/Symfony/Component/Serializer/Extractor/ObjectPropertyListExtractor.php index c237e13a76344..8422b078632dd 100644 --- a/src/Symfony/Component/Serializer/Extractor/ObjectPropertyListExtractor.php +++ b/src/Symfony/Component/Serializer/Extractor/ObjectPropertyListExtractor.php @@ -21,7 +21,7 @@ final class ObjectPropertyListExtractor implements ObjectPropertyListExtractorIn private PropertyListExtractorInterface $propertyListExtractor; private \Closure $objectClassResolver; - public function __construct(PropertyListExtractorInterface $propertyListExtractor, callable $objectClassResolver = null) + public function __construct(PropertyListExtractorInterface $propertyListExtractor, ?callable $objectClassResolver = null) { $this->propertyListExtractor = $propertyListExtractor; $this->objectClassResolver = ($objectClassResolver ?? 'get_class')(...); diff --git a/src/Symfony/Component/Serializer/Mapping/AttributeMetadata.php b/src/Symfony/Component/Serializer/Mapping/AttributeMetadata.php index 647d59309cd08..12d1cf6bfd59e 100644 --- a/src/Symfony/Component/Serializer/Mapping/AttributeMetadata.php +++ b/src/Symfony/Component/Serializer/Mapping/AttributeMetadata.php @@ -120,7 +120,7 @@ public function getSerializedName(): ?string return $this->serializedName; } - public function setSerializedPath(PropertyPath $serializedPath = null): void + public function setSerializedPath(?PropertyPath $serializedPath = null): void { $this->serializedPath = $serializedPath; } diff --git a/src/Symfony/Component/Serializer/Mapping/ClassMetadata.php b/src/Symfony/Component/Serializer/Mapping/ClassMetadata.php index 78b2af3751f54..e68f4c71f8ed5 100644 --- a/src/Symfony/Component/Serializer/Mapping/ClassMetadata.php +++ b/src/Symfony/Component/Serializer/Mapping/ClassMetadata.php @@ -44,7 +44,7 @@ class ClassMetadata implements ClassMetadataInterface /** * Constructs a metadata for the given class. */ - public function __construct(string $class, ClassDiscriminatorMapping $classDiscriminatorMapping = null) + public function __construct(string $class, ?ClassDiscriminatorMapping $classDiscriminatorMapping = null) { $this->name = $class; $this->classDiscriminatorMapping = $classDiscriminatorMapping; diff --git a/src/Symfony/Component/Serializer/NameConverter/AdvancedNameConverterInterface.php b/src/Symfony/Component/Serializer/NameConverter/AdvancedNameConverterInterface.php index 4e7ed504fa3df..1e74f4d20dc2c 100644 --- a/src/Symfony/Component/Serializer/NameConverter/AdvancedNameConverterInterface.php +++ b/src/Symfony/Component/Serializer/NameConverter/AdvancedNameConverterInterface.php @@ -18,7 +18,7 @@ */ interface AdvancedNameConverterInterface extends NameConverterInterface { - public function normalize(string $propertyName, string $class = null, string $format = null, array $context = []): string; + public function normalize(string $propertyName, ?string $class = null, ?string $format = null, array $context = []): string; - public function denormalize(string $propertyName, string $class = null, string $format = null, array $context = []): string; + public function denormalize(string $propertyName, ?string $class = null, ?string $format = null, array $context = []): string; } diff --git a/src/Symfony/Component/Serializer/NameConverter/MetadataAwareNameConverter.php b/src/Symfony/Component/Serializer/NameConverter/MetadataAwareNameConverter.php index 00f4c99bd0c7f..327d92dc1b1c3 100644 --- a/src/Symfony/Component/Serializer/NameConverter/MetadataAwareNameConverter.php +++ b/src/Symfony/Component/Serializer/NameConverter/MetadataAwareNameConverter.php @@ -41,7 +41,7 @@ public function __construct( ) { } - public function normalize(string $propertyName, string $class = null, string $format = null, array $context = []): string + public function normalize(string $propertyName, ?string $class = null, ?string $format = null, array $context = []): string { if (null === $class) { return $this->normalizeFallback($propertyName, $class, $format, $context); @@ -54,7 +54,7 @@ public function normalize(string $propertyName, string $class = null, string $fo return self::$normalizeCache[$class][$propertyName] ?? $this->normalizeFallback($propertyName, $class, $format, $context); } - public function denormalize(string $propertyName, string $class = null, string $format = null, array $context = []): string + public function denormalize(string $propertyName, ?string $class = null, ?string $format = null, array $context = []): string { if (null === $class) { return $this->denormalizeFallback($propertyName, $class, $format, $context); @@ -86,7 +86,7 @@ private function getCacheValueForNormalization(string $propertyName, string $cla return $attributesMetadata[$propertyName]->getSerializedName() ?? null; } - private function normalizeFallback(string $propertyName, string $class = null, string $format = null, array $context = []): string + private function normalizeFallback(string $propertyName, ?string $class = null, ?string $format = null, array $context = []): string { return $this->fallbackNameConverter ? $this->fallbackNameConverter->normalize($propertyName, $class, $format, $context) : $propertyName; } @@ -101,7 +101,7 @@ private function getCacheValueForDenormalization(string $propertyName, string $c return self::$attributesMetadataCache[$cacheKey][$propertyName] ?? null; } - private function denormalizeFallback(string $propertyName, string $class = null, string $format = null, array $context = []): string + private function denormalizeFallback(string $propertyName, ?string $class = null, ?string $format = null, array $context = []): string { return $this->fallbackNameConverter ? $this->fallbackNameConverter->denormalize($propertyName, $class, $format, $context) : $propertyName; } @@ -128,13 +128,16 @@ private function getCacheValueForAttributesMetadata(string $class, array $contex } $metadataGroups = $metadata->getGroups(); + $contextGroups = (array) ($context[AbstractNormalizer::GROUPS] ?? []); + $contextGroupsHasBeenDefined = [] !== $contextGroups; + $contextGroups = array_merge($contextGroups, ['Default', (false !== $nsSep = strrpos($class, '\\')) ? substr($class, $nsSep + 1) : $class]); - if ($contextGroups && !$metadataGroups) { + if ($contextGroupsHasBeenDefined && !$metadataGroups) { continue; } - if ($metadataGroups && !array_intersect($metadataGroups, $contextGroups) && !\in_array('*', $contextGroups, true)) { + if ($metadataGroups && !array_intersect(array_merge($metadataGroups, ['*']), $contextGroups)) { continue; } diff --git a/src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php index 0c52db27cf995..8fbc850428c09 100644 --- a/src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php @@ -135,7 +135,7 @@ abstract class AbstractNormalizer implements NormalizerInterface, DenormalizerIn /** * Sets the {@link ClassMetadataFactoryInterface} to use. */ - public function __construct(ClassMetadataFactoryInterface $classMetadataFactory = null, NameConverterInterface $nameConverter = null, array $defaultContext = []) + public function __construct(?ClassMetadataFactoryInterface $classMetadataFactory = null, ?NameConverterInterface $nameConverter = null, array $defaultContext = []) { $this->classMetadataFactory = $classMetadataFactory; $this->nameConverter = $nameConverter; @@ -183,7 +183,7 @@ protected function isCircularReference(object $object, array &$context): bool * * @throws CircularReferenceException */ - protected function handleCircularReference(object $object, string $format = null, array $context = []): mixed + protected function handleCircularReference(object $object, ?string $format = null, array $context = []): mixed { $circularReferenceHandler = $context[self::CIRCULAR_REFERENCE_HANDLER] ?? $this->defaultContext[self::CIRCULAR_REFERENCE_HANDLER]; if ($circularReferenceHandler) { @@ -213,11 +213,17 @@ protected function getAllowedAttributes(string|object $classOrObject, array $con return false; } + $classMetadata = $this->classMetadataFactory->getMetadataFor($classOrObject); + $class = $classMetadata->getName(); + $groups = $this->getGroups($context); + $groupsHasBeenDefined = [] !== $groups; + $groups = array_merge($groups, ['Default', (false !== $nsSep = strrpos($class, '\\')) ? substr($class, $nsSep + 1) : $class]); $allowedAttributes = []; $ignoreUsed = false; - foreach ($this->classMetadataFactory->getMetadataFor($classOrObject)->getAttributesMetadata() as $attributeMetadata) { + + foreach ($classMetadata->getAttributesMetadata() as $attributeMetadata) { if ($ignore = $attributeMetadata->isIgnored()) { $ignoreUsed = true; } @@ -225,14 +231,14 @@ protected function getAllowedAttributes(string|object $classOrObject, array $con // If you update this check, update accordingly the one in Symfony\Component\PropertyInfo\Extractor\SerializerExtractor::getProperties() if ( !$ignore - && ([] === $groups || array_intersect(array_merge($attributeMetadata->getGroups(), ['*']), $groups)) + && (!$groupsHasBeenDefined || array_intersect(array_merge($attributeMetadata->getGroups(), ['*']), $groups)) && $this->isAllowedAttribute($classOrObject, $name = $attributeMetadata->getName(), null, $context) ) { $allowedAttributes[] = $attributesAsString ? $name : $attributeMetadata; } } - if (!$ignoreUsed && [] === $groups && $allowExtraAttributes) { + if (!$ignoreUsed && !$groupsHasBeenDefined && $allowExtraAttributes) { // Backward Compatibility with the code using this method written before the introduction of @Ignore return false; } @@ -250,7 +256,7 @@ protected function getGroups(array $context): array /** * Is this attribute allowed? */ - protected function isAllowedAttribute(object|string $classOrObject, string $attribute, string $format = null, array $context = []): bool + protected function isAllowedAttribute(object|string $classOrObject, string $attribute, ?string $format = null, array $context = []): bool { $ignoredAttributes = $context[self::IGNORED_ATTRIBUTES] ?? $this->defaultContext[self::IGNORED_ATTRIBUTES]; if (\in_array($attribute, $ignoredAttributes, true)) { @@ -299,7 +305,7 @@ protected function getConstructor(array &$data, string $class, array &$context, * @throws RuntimeException * @throws MissingConstructorArgumentsException */ - protected function instantiateObject(array &$data, string $class, array &$context, \ReflectionClass $reflectionClass, array|bool $allowedAttributes, string $format = null): object + protected function instantiateObject(array &$data, string $class, array &$context, \ReflectionClass $reflectionClass, array|bool $allowedAttributes, ?string $format = null): object { if (null !== $object = $this->extractObjectToPopulate($class, $context, self::OBJECT_TO_POPULATE)) { unset($context[self::OBJECT_TO_POPULATE]); @@ -339,7 +345,7 @@ protected function instantiateObject(array &$data, string $class, array &$contex $variadicParameters[$parameterKey] = $this->denormalizeParameter($reflectionClass, $constructorParameter, $paramName, $parameterData, $attributeContext, $format); } - $params = array_merge($params, $variadicParameters); + $params = array_merge(array_values($params), $variadicParameters); $unsetKeys[] = $key; } } elseif ($allowed && !$ignored && (isset($data[$key]) || \array_key_exists($key, $data))) { @@ -438,7 +444,7 @@ protected function instantiateObject(array &$data, string $class, array &$contex /** * @internal */ - protected function denormalizeParameter(\ReflectionClass $class, \ReflectionParameter $parameter, string $parameterName, mixed $parameterData, array $context, string $format = null): mixed + protected function denormalizeParameter(\ReflectionClass $class, \ReflectionParameter $parameter, string $parameterName, mixed $parameterData, array $context, ?string $format = null): mixed { try { if (($parameterType = $parameter->getType()) instanceof \ReflectionNamedType && !$parameterType->isBuiltin()) { diff --git a/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php index a2503e2903014..447240d8dd156 100644 --- a/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php @@ -17,7 +17,6 @@ use Symfony\Component\PropertyAccess\Exception\UninitializedPropertyException; use Symfony\Component\PropertyAccess\PropertyAccess; use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface; -use Symfony\Component\PropertyInfo\Type; use Symfony\Component\Serializer\Encoder\CsvEncoder; use Symfony\Component\Serializer\Encoder\JsonEncoder; use Symfony\Component\Serializer\Encoder\XmlEncoder; @@ -32,6 +31,12 @@ use Symfony\Component\Serializer\Mapping\ClassMetadataInterface; use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface; use Symfony\Component\Serializer\NameConverter\NameConverterInterface; +use Symfony\Component\TypeInfo\Type; +use Symfony\Component\TypeInfo\Type\CollectionType; +use Symfony\Component\TypeInfo\Type\IntersectionType; +use Symfony\Component\TypeInfo\Type\ObjectType; +use Symfony\Component\TypeInfo\Type\UnionType; +use Symfony\Component\TypeInfo\TypeIdentifier; /** * Base class for a normalizer dealing with objects. @@ -51,7 +56,7 @@ abstract class AbstractObjectNormalizer extends AbstractNormalizer public const DEPTH_KEY_PATTERN = 'depth_%s::%s'; /** - * While denormalizing, we can verify that types match. + * While denormalizing, we can verify that type matches. * * You can disable this by setting this flag to true. */ @@ -109,16 +114,19 @@ abstract class AbstractObjectNormalizer extends AbstractNormalizer protected ?ClassDiscriminatorResolverInterface $classDiscriminatorResolver; - private array $typesCache = []; + /** + * @var array + */ + private array $typeCache = []; private array $attributesCache = []; private readonly \Closure $objectClassResolver; public function __construct( - ClassMetadataFactoryInterface $classMetadataFactory = null, - NameConverterInterface $nameConverter = null, + ?ClassMetadataFactoryInterface $classMetadataFactory = null, + ?NameConverterInterface $nameConverter = null, private ?PropertyTypeExtractorInterface $propertyTypeExtractor = null, - ClassDiscriminatorResolverInterface $classDiscriminatorResolver = null, - callable $objectClassResolver = null, + ?ClassDiscriminatorResolverInterface $classDiscriminatorResolver = null, + ?callable $objectClassResolver = null, array $defaultContext = [], ) { parent::__construct($classMetadataFactory, $nameConverter, $defaultContext); @@ -136,12 +144,12 @@ public function __construct( $this->objectClassResolver = ($objectClassResolver ?? 'get_class')(...); } - public function supportsNormalization(mixed $data, string $format = null, array $context = []): bool + public function supportsNormalization(mixed $data, ?string $format = null, array $context = []): bool { return \is_object($data) && !$data instanceof \Traversable; } - public function normalize(mixed $object, string $format = null, array $context = []): array|string|int|float|bool|\ArrayObject|null + public function normalize(mixed $object, ?string $format = null, array $context = []): array|string|int|float|bool|\ArrayObject|null { if (!isset($context['cache_key'])) { $context['cache_key'] = $this->getCacheKey($format, $context); @@ -219,7 +227,7 @@ public function normalize(mixed $object, string $format = null, array $context = return $data; } - protected function instantiateObject(array &$data, string $class, array &$context, \ReflectionClass $reflectionClass, array|bool $allowedAttributes, string $format = null): object + protected function instantiateObject(array &$data, string $class, array &$context, \ReflectionClass $reflectionClass, array|bool $allowedAttributes, ?string $format = null): object { if ($class !== $mappedClass = $this->getMappedClass($data, $class, $context)) { return $this->instantiateObject($data, $mappedClass, $context, new \ReflectionClass($mappedClass), $allowedAttributes, $format); @@ -270,19 +278,19 @@ protected function getAttributes(object $object, ?string $format, array $context * * @return string[] */ - abstract protected function extractAttributes(object $object, string $format = null, array $context = []): array; + abstract protected function extractAttributes(object $object, ?string $format = null, array $context = []): array; /** * Gets the attribute value. */ - abstract protected function getAttributeValue(object $object, string $attribute, string $format = null, array $context = []): mixed; + abstract protected function getAttributeValue(object $object, string $attribute, ?string $format = null, array $context = []): mixed; - public function supportsDenormalization(mixed $data, string $type, string $format = null, array $context = []): bool + public function supportsDenormalization(mixed $data, string $type, ?string $format = null, array $context = []): bool { return class_exists($type) || (interface_exists($type, false) && null !== $this->classDiscriminatorResolver?->getMappingForClass($type)); } - public function denormalize(mixed $data, string $type, string $format = null, array $context = []): mixed + public function denormalize(mixed $data, string $type, ?string $format = null, array $context = []): mixed { if (!isset($context['cache_key'])) { $context['cache_key'] = $this->getCacheKey($format, $context); @@ -352,11 +360,9 @@ public function denormalize(mixed $data, string $type, string $format = null, ar } } - $types = $this->getTypes($resolvedClass, $attribute); - - if (null !== $types) { + if (null !== $type = $this->getType($resolvedClass, $attribute)) { try { - $value = $this->validateAndDenormalize($types, $resolvedClass, $attribute, $value, $format, $attributeContext); + $value = $this->validateAndDenormalize($type, $resolvedClass, $attribute, $value, $format, $attributeContext); } catch (NotNormalizableValueException $exception) { if (isset($context['not_normalizable_value_exceptions'])) { $context['not_normalizable_value_exceptions'][] = $exception; @@ -372,7 +378,7 @@ public function denormalize(mixed $data, string $type, string $format = null, ar $this->setAttributeValue($object, $attribute, $value, $format, $attributeContext); } catch (PropertyAccessInvalidArgumentException $e) { $exception = NotNormalizableValueException::createForUnexpectedDataType( - sprintf('Failed to denormalize attribute "%s" value for class "%s": '.$e->getMessage(), $attribute, $type), + sprintf('Failed to denormalize attribute "%s" value for class "%s": '.$e->getMessage(), $attribute, $resolvedClass), $data, $e instanceof InvalidTypeException ? [$e->expectedType] : ['unknown'], $attributeContext['deserialization_path'] ?? null, @@ -395,36 +401,44 @@ public function denormalize(mixed $data, string $type, string $format = null, ar return $object; } - abstract protected function setAttributeValue(object $object, string $attribute, mixed $value, string $format = null, array $context = []): void; + abstract protected function setAttributeValue(object $object, string $attribute, mixed $value, ?string $format = null, array $context = []): void; /** * Validates the submitted data and denormalizes it. * - * @param Type[] $types - * * @throws NotNormalizableValueException * @throws ExtraAttributesException * @throws MissingConstructorArgumentsException * @throws LogicException */ - private function validateAndDenormalize(array $types, string $currentClass, string $attribute, mixed $data, ?string $format, array $context): mixed + private function validateAndDenormalize(Type $type, string $currentClass, string $attribute, mixed $data, ?string $format, array $context): mixed { $expectedTypes = []; - $isUnionType = \count($types) > 1; - $e = null; $extraAttributesException = null; $missingConstructorArgumentsException = null; - $isNullable = false; - foreach ($types as $type) { + + $types = match (true) { + $type instanceof IntersectionType => throw new LogicException('Unable to handle intersection type.'), + $type instanceof UnionType => $type->getTypes(), + default => [$type], + }; + + foreach ($types as $t) { if (null === $data && $type->isNullable()) { return null; } - $collectionValueType = $type->isCollection() ? $type->getCollectionValueTypes()[0] ?? null : null; + $collectionKeyType = $collectionValueType = null; + if ($t instanceof CollectionType) { + $collectionKeyType = $t->getCollectionKeyType(); + $collectionValueType = $t->getCollectionValueType(); + } + + $t = $t->getBaseType(); // Fix a collection that contains the only one element // This is special to xml format only - if ('xml' === $format && null !== $collectionValueType && (!\is_array($data) || !\is_int(key($data)))) { + if ('xml' === $format && $collectionValueType && !$collectionValueType->isA(TypeIdentifier::MIXED) && (!\is_array($data) || !\is_int(key($data)))) { $data = [$data]; } @@ -437,40 +451,32 @@ private function validateAndDenormalize(array $types, string $currentClass, stri // In XML and CSV all basic datatypes are represented as strings, it is e.g. not possible to determine, // if a value is meant to be a string, float, int or a boolean value from the serialized representation. // That's why we have to transform the values, if one of these non-string basic datatypes is expected. - $builtinType = $type->getBuiltinType(); + $typeIdentifier = $t->getTypeIdentifier(); if (\is_string($data) && (XmlEncoder::FORMAT === $format || CsvEncoder::FORMAT === $format)) { - if ('' === $data) { - if (Type::BUILTIN_TYPE_ARRAY === $builtinType) { - return []; - } - - if (Type::BUILTIN_TYPE_STRING === $builtinType) { - return ''; - } - - // Don't return null yet because Object-types that come first may accept empty-string too - $isNullable = $isNullable ?: $type->isNullable(); - } - - switch ($builtinType) { - case Type::BUILTIN_TYPE_BOOL: + switch ($typeIdentifier) { + case TypeIdentifier::ARRAY: + if ('' === $data) { + return []; + } + break; + case TypeIdentifier::BOOL: // according to https://www.w3.org/TR/xmlschema-2/#boolean, valid representations are "false", "true", "0" and "1" if ('false' === $data || '0' === $data) { $data = false; } elseif ('true' === $data || '1' === $data) { $data = true; } else { - throw NotNormalizableValueException::createForUnexpectedDataType(sprintf('The type of the "%s" attribute for class "%s" must be bool ("%s" given).', $attribute, $currentClass, $data), $data, [Type::BUILTIN_TYPE_BOOL], $context['deserialization_path'] ?? null); + throw NotNormalizableValueException::createForUnexpectedDataType(sprintf('The type of the "%s" attribute for class "%s" must be bool ("%s" given).', $attribute, $currentClass, $data), $data, [Type::bool()], $context['deserialization_path'] ?? null); } break; - case Type::BUILTIN_TYPE_INT: + case TypeIdentifier::INT: if (ctype_digit(isset($data[0]) && '-' === $data[0] ? substr($data, 1) : $data)) { $data = (int) $data; } else { - throw NotNormalizableValueException::createForUnexpectedDataType(sprintf('The type of the "%s" attribute for class "%s" must be int ("%s" given).', $attribute, $currentClass, $data), $data, [Type::BUILTIN_TYPE_INT], $context['deserialization_path'] ?? null); + throw NotNormalizableValueException::createForUnexpectedDataType(sprintf('The type of the "%s" attribute for class "%s" must be int ("%s" given).', $attribute, $currentClass, $data), $data, [Type::int()], $context['deserialization_path'] ?? null); } break; - case Type::BUILTIN_TYPE_FLOAT: + case TypeIdentifier::FLOAT: if (is_numeric($data)) { return (float) $data; } @@ -479,48 +485,58 @@ private function validateAndDenormalize(array $types, string $currentClass, stri 'NaN' => \NAN, 'INF' => \INF, '-INF' => -\INF, - default => throw NotNormalizableValueException::createForUnexpectedDataType(sprintf('The type of the "%s" attribute for class "%s" must be float ("%s" given).', $attribute, $currentClass, $data), $data, [Type::BUILTIN_TYPE_FLOAT], $context['deserialization_path'] ?? null), + default => throw NotNormalizableValueException::createForUnexpectedDataType(sprintf('The type of the "%s" attribute for class "%s" must be float ("%s" given).', $attribute, $currentClass, $data), $data, [Type::float()], $context['deserialization_path'] ?? null), }; } } - if (null !== $collectionValueType && Type::BUILTIN_TYPE_OBJECT === $collectionValueType->getBuiltinType()) { - $builtinType = Type::BUILTIN_TYPE_OBJECT; - $class = $collectionValueType->getClassName().'[]'; - - if (\count($collectionKeyType = $type->getCollectionKeyTypes()) > 0) { - $context['key_type'] = \count($collectionKeyType) > 1 ? $collectionKeyType : $collectionKeyType[0]; - } - - $context['value_type'] = $collectionValueType; - } elseif ($type->isCollection() && \count($collectionValueType = $type->getCollectionValueTypes()) > 0 && Type::BUILTIN_TYPE_ARRAY === $collectionValueType[0]->getBuiltinType()) { - // get inner type for any nested array - [$innerType] = $collectionValueType; + if ($collectionValueType) { + $collectionValueBaseType = $collectionValueType instanceof UnionType ? $collectionValueType->asNonNullable()->getBaseType() : $collectionValueType->getBaseType(); + + if ($collectionValueBaseType instanceof ObjectType) { + $typeIdentifier = TypeIdentifier::OBJECT; + $class = $collectionValueBaseType->getClassName().'[]'; + $context['key_type'] = $collectionKeyType; + $context['value_type'] = $collectionValueType; + } elseif (TypeIdentifier::ARRAY === $collectionValueType->getBaseType()->getTypeIdentifier()) { + // get inner type for any nested array + $innerType = $collectionValueType; + + // note that it will break for any other builtinType + $dimensions = '[]'; + while ($innerType instanceof CollectionType) { + $dimensions .= '[]'; + $innerType = $innerType->getCollectionValueType(); + } - // note that it will break for any other builtinType - $dimensions = '[]'; - while (\count($innerType->getCollectionValueTypes()) > 0 && Type::BUILTIN_TYPE_ARRAY === $innerType->getBuiltinType()) { - $dimensions .= '[]'; - [$innerType] = $innerType->getCollectionValueTypes(); + if ($innerType instanceof ObjectType) { + // the builtinType is the inner one and the class is the class followed by []...[] + $typeIdentifier = TypeIdentifier::OBJECT; + $class = $innerType->getClassName().$dimensions; + } else { + // default fallback (keep it as array) + if ($t instanceof ObjectType) { + $typeIdentifier = TypeIdentifier::OBJECT; + $class = $t->getClassName(); + } else { + $typeIdentifier = $t->getTypeIdentifier()->value; + $class = null; + } + } } - - if (null !== $innerType->getClassName()) { - // the builtinType is the inner one and the class is the class followed by []...[] - $builtinType = $innerType->getBuiltinType(); - $class = $innerType->getClassName().$dimensions; + } else { + if ($t instanceof ObjectType) { + $typeIdentifier = TypeIdentifier::OBJECT; + $class = $t->getClassName(); } else { - // default fallback (keep it as array) - $builtinType = $type->getBuiltinType(); - $class = $type->getClassName(); + $typeIdentifier = $t->getTypeIdentifier(); + $class = null; } - } else { - $builtinType = $type->getBuiltinType(); - $class = $type->getClassName(); } - $expectedTypes[Type::BUILTIN_TYPE_OBJECT === $builtinType && $class ? $class : $builtinType] = true; + $expectedTypes[TypeIdentifier::OBJECT === $typeIdentifier && $class ? $class : $typeIdentifier->value] = true; - if (Type::BUILTIN_TYPE_OBJECT === $builtinType && null !== $class) { + if (TypeIdentifier::OBJECT === $typeIdentifier && null !== $class) { if (!$this->serializer instanceof DenormalizerInterface) { throw new LogicException(sprintf('Cannot denormalize attribute "%s" for class "%s" because injected serializer is not a denormalizer.', $attribute, $class)); } @@ -537,29 +553,29 @@ private function validateAndDenormalize(array $types, string $currentClass, stri // PHP's json_decode automatically converts Numbers without a decimal part to integers. // To circumvent this behavior, integers are converted to floats when denormalizing JSON based formats and when // a float is expected. - if (Type::BUILTIN_TYPE_FLOAT === $builtinType && \is_int($data) && null !== $format && str_contains($format, JsonEncoder::FORMAT)) { + if (TypeIdentifier::FLOAT === $typeIdentifier && \is_int($data) && null !== $format && str_contains($format, JsonEncoder::FORMAT)) { return (float) $data; } - if ((Type::BUILTIN_TYPE_FALSE === $builtinType && false === $data) || (Type::BUILTIN_TYPE_TRUE === $builtinType && true === $data)) { + if ((TypeIdentifier::FALSE === $typeIdentifier && false === $data) || (TypeIdentifier::TRUE === $typeIdentifier && true === $data)) { return $data; } - if (('is_'.$builtinType)($data)) { + if (('is_'.$typeIdentifier->value)($data)) { return $data; } } catch (NotNormalizableValueException|InvalidArgumentException $e) { - if (!$isUnionType && !$isNullable) { + if (!$type instanceof UnionType) { throw $e; } } catch (ExtraAttributesException $e) { - if (!$isUnionType && !$isNullable) { + if (!$type instanceof UnionType) { throw $e; } $extraAttributesException ??= $e; } catch (MissingConstructorArgumentsException $e) { - if (!$isUnionType && !$isNullable) { + if (!$type instanceof UnionType) { throw $e; } @@ -567,7 +583,7 @@ private function validateAndDenormalize(array $types, string $currentClass, stri } } - if ($isNullable) { + if ('' === $data && (XmlEncoder::FORMAT === $format || CsvEncoder::FORMAT === $format) && $type->isNullable()) { return null; } @@ -579,10 +595,6 @@ private function validateAndDenormalize(array $types, string $currentClass, stri throw $missingConstructorArgumentsException; } - if (!$isUnionType && $e) { - throw $e; - } - if ($context[self::DISABLE_TYPE_ENFORCEMENT] ?? $this->defaultContext[self::DISABLE_TYPE_ENFORCEMENT] ?? false) { return $data; } @@ -593,50 +605,45 @@ private function validateAndDenormalize(array $types, string $currentClass, stri /** * @internal */ - protected function denormalizeParameter(\ReflectionClass $class, \ReflectionParameter $parameter, string $parameterName, mixed $parameterData, array $context, string $format = null): mixed + protected function denormalizeParameter(\ReflectionClass $class, \ReflectionParameter $parameter, string $parameterName, mixed $parameterData, array $context, ?string $format = null): mixed { - if ($parameter->isVariadic() || null === $this->propertyTypeExtractor || null === $types = $this->getTypes($class->getName(), $parameterName)) { + if ($parameter->isVariadic() || null === $this->propertyTypeExtractor || null === $type = $this->getType($class->getName(), $parameterName)) { return parent::denormalizeParameter($class, $parameter, $parameterName, $parameterData, $context, $format); } - $parameterData = $this->validateAndDenormalize($types, $class->getName(), $parameterName, $parameterData, $format, $context); + $parameterData = $this->validateAndDenormalize($type, $class->getName(), $parameterName, $parameterData, $format, $context); return $this->applyCallbacks($parameterData, $class->getName(), $parameterName, $format, $context); } - /** - * @return Type[]|null - */ - private function getTypes(string $currentClass, string $attribute): ?array + private function getType(string $currentClass, string $attribute): ?Type { if (null === $this->propertyTypeExtractor) { return null; } $key = $currentClass.'::'.$attribute; - if (isset($this->typesCache[$key])) { - return false === $this->typesCache[$key] ? null : $this->typesCache[$key]; + if (isset($this->typeCache[$key])) { + return false === $this->typeCache[$key] ? null : $this->typeCache[$key]; } - if (null !== $types = $this->propertyTypeExtractor->getTypes($currentClass, $attribute)) { - return $this->typesCache[$key] = $types; + if (null !== $type = $this->propertyTypeExtractor->getType($currentClass, $attribute)) { + return $this->typeCache[$key] = $type; } if ($discriminatorMapping = $this->classDiscriminatorResolver?->getMappingForClass($currentClass)) { if ($discriminatorMapping->getTypeProperty() === $attribute) { - return $this->typesCache[$key] = [ - new Type(Type::BUILTIN_TYPE_STRING), - ]; + return $this->typeCache[$key] = Type::string(); } foreach ($discriminatorMapping->getTypesMapping() as $mappedClass) { - if (null !== $types = $this->propertyTypeExtractor->getTypes($mappedClass, $attribute)) { - return $this->typesCache[$key] = $types; + if (null !== $type = $this->propertyTypeExtractor->getType($mappedClass, $attribute)) { + return $this->typeCache[$key] = $type; } } } - $this->typesCache[$key] = false; + $this->typeCache[$key] = false; return null; } @@ -708,7 +715,11 @@ private function isMaxDepthReached(array $attributesMetadata, string $class, str protected function createChildContext(array $parentContext, string $attribute, ?string $format): array { $context = parent::createChildContext($parentContext, $attribute, $format); - $context['cache_key'] = $this->getCacheKey($format, $context); + if ($context['cache_key'] ?? false) { + $context['cache_key'] .= '-'.$attribute; + } elseif (false !== ($context['cache_key'] ?? null)) { + $context['cache_key'] = $this->getCacheKey($format, $context); + } return $context; } diff --git a/src/Symfony/Component/Serializer/Normalizer/ArrayDenormalizer.php b/src/Symfony/Component/Serializer/Normalizer/ArrayDenormalizer.php index b3f3f63cf4eb4..1e4b816276bb9 100644 --- a/src/Symfony/Component/Serializer/Normalizer/ArrayDenormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/ArrayDenormalizer.php @@ -11,10 +11,12 @@ namespace Symfony\Component\Serializer\Normalizer; -use Symfony\Component\PropertyInfo\Type; use Symfony\Component\Serializer\Exception\BadMethodCallException; use Symfony\Component\Serializer\Exception\InvalidArgumentException; use Symfony\Component\Serializer\Exception\NotNormalizableValueException; +use Symfony\Component\TypeInfo\Type; +use Symfony\Component\TypeInfo\Type\UnionType; +use Symfony\Component\TypeInfo\TypeIdentifier; /** * Denormalizes arrays of objects. @@ -40,13 +42,13 @@ public function getSupportedTypes(?string $format): array /** * @throws NotNormalizableValueException */ - public function denormalize(mixed $data, string $type, string $format = null, array $context = []): array + public function denormalize(mixed $data, string $type, ?string $format = null, array $context = []): array { if (null === $this->denormalizer) { throw new BadMethodCallException('Please set a denormalizer before calling denormalize()!'); } if (!\is_array($data)) { - throw NotNormalizableValueException::createForUnexpectedDataType(sprintf('Data expected to be "%s", "%s" given.', $type, get_debug_type($data)), $data, [Type::BUILTIN_TYPE_ARRAY], $context['deserialization_path'] ?? null); + throw NotNormalizableValueException::createForUnexpectedDataType(sprintf('Data expected to be "%s", "%s" given.', $type, get_debug_type($data)), $data, [Type::array()], $context['deserialization_path'] ?? null); } if (!str_ends_with($type, '[]')) { throw new InvalidArgumentException('Unsupported class: '.$type); @@ -54,15 +56,16 @@ public function denormalize(mixed $data, string $type, string $format = null, ar $type = substr($type, 0, -2); - $builtinTypes = array_map(static function (Type $keyType) { - return $keyType->getBuiltinType(); - }, \is_array($keyType = $context['key_type'] ?? []) ? $keyType : [$keyType]); + $typeIdentifiers = []; + if (null !== $keyType = ($context['key_type'] ?? null)) { + $typeIdentifiers = array_map(fn (Type $t) => $t->getBaseType()->getTypeIdentifier(), $keyType instanceof UnionType ? $keyType->getTypes() : [$keyType]); + } foreach ($data as $key => $value) { $subContext = $context; $subContext['deserialization_path'] = ($context['deserialization_path'] ?? false) ? sprintf('%s[%s]', $context['deserialization_path'], $key) : "[$key]"; - $this->validateKeyType($builtinTypes, $key, $subContext['deserialization_path']); + $this->validateKeyType($typeIdentifiers, $key, $subContext['deserialization_path']); $data[$key] = $this->denormalizer->denormalize($value, $type, $format, $subContext); } @@ -70,7 +73,7 @@ public function denormalize(mixed $data, string $type, string $format = null, ar return $data; } - public function supportsDenormalization(mixed $data, string $type, string $format = null, array $context = []): bool + public function supportsDenormalization(mixed $data, string $type, ?string $format = null, array $context = []): bool { if (null === $this->denormalizer) { throw new BadMethodCallException(sprintf('The nested denormalizer needs to be set to allow "%s()" to be used.', __METHOD__)); @@ -80,18 +83,23 @@ public function supportsDenormalization(mixed $data, string $type, string $forma && $this->denormalizer->supportsDenormalization($data, substr($type, 0, -2), $format, $context); } - private function validateKeyType(array $builtinTypes, mixed $key, string $path): void + /** + * @param list $typeIdentifiers + */ + private function validateKeyType(array $typeIdentifiers, mixed $key, string $path): void { - if (!$builtinTypes) { + if (!$typeIdentifiers) { return; } - foreach ($builtinTypes as $builtinType) { - if (('is_'.$builtinType)($key)) { + $typeIdentifiers = array_map(fn (TypeIdentifier $t): string => $t->value, $typeIdentifiers); + + foreach ($typeIdentifiers as $typeIdentifier) { + if (('is_'.$typeIdentifier)($key)) { return; } } - throw NotNormalizableValueException::createForUnexpectedDataType(sprintf('The type of the key "%s" must be "%s" ("%s" given).', $key, implode('", "', $builtinTypes), get_debug_type($key)), $key, $builtinTypes, $path, true); + throw NotNormalizableValueException::createForUnexpectedDataType(sprintf('The type of the key "%s" must be "%s" ("%s" given).', $key, implode('", "', $typeIdentifiers), get_debug_type($key)), $key, $typeIdentifiers, $path, true); } } diff --git a/src/Symfony/Component/Serializer/Normalizer/BackedEnumNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/BackedEnumNormalizer.php index 7327b15c8c72d..38b9d35d0cddf 100644 --- a/src/Symfony/Component/Serializer/Normalizer/BackedEnumNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/BackedEnumNormalizer.php @@ -11,9 +11,9 @@ namespace Symfony\Component\Serializer\Normalizer; -use Symfony\Component\PropertyInfo\Type; use Symfony\Component\Serializer\Exception\InvalidArgumentException; use Symfony\Component\Serializer\Exception\NotNormalizableValueException; +use Symfony\Component\TypeInfo\Type; /** * Normalizes a {@see \BackedEnum} enumeration to a string or an integer. @@ -34,7 +34,7 @@ public function getSupportedTypes(?string $format): array ]; } - public function normalize(mixed $object, string $format = null, array $context = []): int|string + public function normalize(mixed $object, ?string $format = null, array $context = []): int|string { if (!$object instanceof \BackedEnum) { throw new InvalidArgumentException('The data must belong to a backed enumeration.'); @@ -43,7 +43,7 @@ public function normalize(mixed $object, string $format = null, array $context = return $object->value; } - public function supportsNormalization(mixed $data, string $format = null, array $context = []): bool + public function supportsNormalization(mixed $data, ?string $format = null, array $context = []): bool { return $data instanceof \BackedEnum; } @@ -51,7 +51,7 @@ public function supportsNormalization(mixed $data, string $format = null, array /** * @throws NotNormalizableValueException */ - public function denormalize(mixed $data, string $type, string $format = null, array $context = []): mixed + public function denormalize(mixed $data, string $type, ?string $format = null, array $context = []): mixed { if (!is_subclass_of($type, \BackedEnum::class)) { throw new InvalidArgumentException('The data must belong to a backed enumeration.'); @@ -70,7 +70,7 @@ public function denormalize(mixed $data, string $type, string $format = null, ar } if (!\is_int($data) && !\is_string($data)) { - throw NotNormalizableValueException::createForUnexpectedDataType('The data is neither an integer nor a string, you should pass an integer or a string that can be parsed as an enumeration case of type '.$type.'.', $data, [Type::BUILTIN_TYPE_INT, Type::BUILTIN_TYPE_STRING], $context['deserialization_path'] ?? null, true); + throw NotNormalizableValueException::createForUnexpectedDataType('The data is neither an integer nor a string, you should pass an integer or a string that can be parsed as an enumeration case of type '.$type.'.', $data, [Type::int(), Type::string()], $context['deserialization_path'] ?? null, true); } try { @@ -84,7 +84,7 @@ public function denormalize(mixed $data, string $type, string $format = null, ar } } - public function supportsDenormalization(mixed $data, string $type, string $format = null, array $context = []): bool + public function supportsDenormalization(mixed $data, string $type, ?string $format = null, array $context = []): bool { return is_subclass_of($type, \BackedEnum::class); } diff --git a/src/Symfony/Component/Serializer/Normalizer/ConstraintViolationListNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/ConstraintViolationListNormalizer.php index 1e042b21c03d6..eeb6ab46288ef 100644 --- a/src/Symfony/Component/Serializer/Normalizer/ConstraintViolationListNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/ConstraintViolationListNormalizer.php @@ -43,7 +43,7 @@ public function getSupportedTypes(?string $format): array ]; } - public function normalize(mixed $object, string $format = null, array $context = []): array + public function normalize(mixed $object, ?string $format = null, array $context = []): array { if (\array_key_exists(self::PAYLOAD_FIELDS, $context)) { $payloadFieldsToSerialize = $context[self::PAYLOAD_FIELDS]; @@ -106,7 +106,7 @@ public function normalize(mixed $object, string $format = null, array $context = return $result + ['violations' => $violations]; } - public function supportsNormalization(mixed $data, string $format = null, array $context = []): bool + public function supportsNormalization(mixed $data, ?string $format = null, array $context = []): bool { return $data instanceof ConstraintViolationListInterface; } diff --git a/src/Symfony/Component/Serializer/Normalizer/CustomNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/CustomNormalizer.php index fcfb6f1737049..d97108312f55c 100644 --- a/src/Symfony/Component/Serializer/Normalizer/CustomNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/CustomNormalizer.php @@ -30,12 +30,12 @@ public function getSupportedTypes(?string $format): array ]; } - public function normalize(mixed $object, string $format = null, array $context = []): array|string|int|float|bool|\ArrayObject|null + public function normalize(mixed $object, ?string $format = null, array $context = []): array|string|int|float|bool|\ArrayObject|null { return $object->normalize($this->serializer, $format, $context); } - public function denormalize(mixed $data, string $type, string $format = null, array $context = []): mixed + public function denormalize(mixed $data, string $type, ?string $format = null, array $context = []): mixed { $object = $this->extractObjectToPopulate($type, $context) ?? new $type(); $object->denormalize($this->serializer, $data, $format, $context); @@ -49,7 +49,7 @@ public function denormalize(mixed $data, string $type, string $format = null, ar * @param mixed $data Data to normalize * @param string|null $format The format being (de-)serialized from or into */ - public function supportsNormalization(mixed $data, string $format = null, array $context = []): bool + public function supportsNormalization(mixed $data, ?string $format = null, array $context = []): bool { return $data instanceof NormalizableInterface; } @@ -61,7 +61,7 @@ public function supportsNormalization(mixed $data, string $format = null, array * @param string $type The class to which the data should be denormalized * @param string|null $format The format being deserialized from */ - public function supportsDenormalization(mixed $data, string $type, string $format = null, array $context = []): bool + public function supportsDenormalization(mixed $data, string $type, ?string $format = null, array $context = []): bool { return is_subclass_of($type, DenormalizableInterface::class); } diff --git a/src/Symfony/Component/Serializer/Normalizer/DataUriNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/DataUriNormalizer.php index 3e0a6124179b7..bd1dc822b312b 100644 --- a/src/Symfony/Component/Serializer/Normalizer/DataUriNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/DataUriNormalizer.php @@ -33,7 +33,7 @@ final class DataUriNormalizer implements NormalizerInterface, DenormalizerInterf private readonly ?MimeTypeGuesserInterface $mimeTypeGuesser; - public function __construct(MimeTypeGuesserInterface $mimeTypeGuesser = null) + public function __construct(?MimeTypeGuesserInterface $mimeTypeGuesser = null) { if (!$mimeTypeGuesser && class_exists(MimeTypes::class)) { $mimeTypeGuesser = MimeTypes::getDefault(); @@ -51,7 +51,7 @@ public function getSupportedTypes(?string $format): array ]; } - public function normalize(mixed $object, string $format = null, array $context = []): string + public function normalize(mixed $object, ?string $format = null, array $context = []): string { if (!$object instanceof \SplFileInfo) { throw new InvalidArgumentException('The object must be an instance of "\SplFileInfo".'); @@ -74,7 +74,7 @@ public function normalize(mixed $object, string $format = null, array $context = return sprintf('data:%s;base64,%s', $mimeType, base64_encode($data)); } - public function supportsNormalization(mixed $data, string $format = null, array $context = []): bool + public function supportsNormalization(mixed $data, ?string $format = null, array $context = []): bool { return $data instanceof \SplFileInfo; } @@ -87,7 +87,7 @@ public function supportsNormalization(mixed $data, string $format = null, array * @throws InvalidArgumentException * @throws NotNormalizableValueException */ - public function denormalize(mixed $data, string $type, string $format = null, array $context = []): \SplFileInfo + public function denormalize(mixed $data, string $type, ?string $format = null, array $context = []): \SplFileInfo { if (null === $data || !preg_match('/^data:([a-z0-9][a-z0-9\!\#\$\&\-\^\_\+\.]{0,126}\/[a-z0-9][a-z0-9\!\#\$\&\-\^\_\+\.]{0,126}(;[a-z0-9\-]+\=[a-z0-9\-]+)?)?(;base64)?,[a-z0-9\!\$\&\\\'\,\(\)\*\+\;\=\-\.\_\~\:\@\/\?\%\s]*\s*$/i', $data)) { throw NotNormalizableValueException::createForUnexpectedDataType('The provided "data:" URI is not valid.', $data, ['string'], $context['deserialization_path'] ?? null, true); @@ -113,7 +113,7 @@ public function denormalize(mixed $data, string $type, string $format = null, ar throw new InvalidArgumentException(sprintf('The class parameter "%s" is not supported. It must be one of "SplFileInfo", "SplFileObject" or "Symfony\Component\HttpFoundation\File\File".', $type)); } - public function supportsDenormalization(mixed $data, string $type, string $format = null, array $context = []): bool + public function supportsDenormalization(mixed $data, string $type, ?string $format = null, array $context = []): bool { return isset(self::SUPPORTED_TYPES[$type]); } diff --git a/src/Symfony/Component/Serializer/Normalizer/DateIntervalNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/DateIntervalNormalizer.php index 7655fd681843d..995d4f6a31b01 100644 --- a/src/Symfony/Component/Serializer/Normalizer/DateIntervalNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/DateIntervalNormalizer.php @@ -43,7 +43,7 @@ public function getSupportedTypes(?string $format): array /** * @throws InvalidArgumentException */ - public function normalize(mixed $object, string $format = null, array $context = []): string + public function normalize(mixed $object, ?string $format = null, array $context = []): string { if (!$object instanceof \DateInterval) { throw new InvalidArgumentException('The object must be an instance of "\DateInterval".'); @@ -52,7 +52,7 @@ public function normalize(mixed $object, string $format = null, array $context = return $object->format($context[self::FORMAT_KEY] ?? $this->defaultContext[self::FORMAT_KEY]); } - public function supportsNormalization(mixed $data, string $format = null, array $context = []): bool + public function supportsNormalization(mixed $data, ?string $format = null, array $context = []): bool { return $data instanceof \DateInterval; } @@ -60,7 +60,7 @@ public function supportsNormalization(mixed $data, string $format = null, array /** * @throws NotNormalizableValueException */ - public function denormalize(mixed $data, string $type, string $format = null, array $context = []): \DateInterval + public function denormalize(mixed $data, string $type, ?string $format = null, array $context = []): \DateInterval { if (!\is_string($data)) { throw NotNormalizableValueException::createForUnexpectedDataType('Data expected to be a string.', $data, ['string'], $context['deserialization_path'] ?? null, true); @@ -106,7 +106,7 @@ public function denormalize(mixed $data, string $type, string $format = null, ar } } - public function supportsDenormalization(mixed $data, string $type, string $format = null, array $context = []): bool + public function supportsDenormalization(mixed $data, string $type, ?string $format = null, array $context = []): bool { return \DateInterval::class === $type; } diff --git a/src/Symfony/Component/Serializer/Normalizer/DateTimeNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/DateTimeNormalizer.php index 5b4df25e3ca0a..6c9389ab047c0 100644 --- a/src/Symfony/Component/Serializer/Normalizer/DateTimeNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/DateTimeNormalizer.php @@ -11,9 +11,9 @@ namespace Symfony\Component\Serializer\Normalizer; -use Symfony\Component\PropertyInfo\Type; use Symfony\Component\Serializer\Exception\InvalidArgumentException; use Symfony\Component\Serializer\Exception\NotNormalizableValueException; +use Symfony\Component\TypeInfo\Type; /** * Normalizes an object implementing the {@see \DateTimeInterface} to a date string. @@ -25,10 +25,12 @@ final class DateTimeNormalizer implements NormalizerInterface, DenormalizerInter { public const FORMAT_KEY = 'datetime_format'; public const TIMEZONE_KEY = 'datetime_timezone'; + public const CAST_KEY = 'datetime_cast'; private array $defaultContext = [ self::FORMAT_KEY => \DateTimeInterface::RFC3339, self::TIMEZONE_KEY => null, + self::CAST_KEY => null, ]; private const SUPPORTED_TYPES = [ @@ -59,7 +61,7 @@ public function getSupportedTypes(?string $format): array /** * @throws InvalidArgumentException */ - public function normalize(mixed $object, string $format = null, array $context = []): string + public function normalize(mixed $object, ?string $format = null, array $context = []): int|float|string { if (!$object instanceof \DateTimeInterface) { throw new InvalidArgumentException('The object must implement the "\DateTimeInterface".'); @@ -73,10 +75,14 @@ public function normalize(mixed $object, string $format = null, array $context = $object = $object->setTimezone($timezone); } - return $object->format($dateTimeFormat); + return match ($context[self::CAST_KEY] ?? $this->defaultContext[self::CAST_KEY] ?? false) { + 'int' => (int) $object->format($dateTimeFormat), + 'float' => (float) $object->format($dateTimeFormat), + default => $object->format($dateTimeFormat), + }; } - public function supportsNormalization(mixed $data, string $format = null, array $context = []): bool + public function supportsNormalization(mixed $data, ?string $format = null, array $context = []): bool { return $data instanceof \DateTimeInterface; } @@ -84,7 +90,7 @@ public function supportsNormalization(mixed $data, string $format = null, array /** * @throws NotNormalizableValueException */ - public function denormalize(mixed $data, string $type, string $format = null, array $context = []): \DateTimeInterface + public function denormalize(mixed $data, string $type, ?string $format = null, array $context = []): \DateTimeInterface { if (\is_int($data) || \is_float($data)) { switch ($context[self::FORMAT_KEY] ?? $this->defaultContext[self::FORMAT_KEY] ?? null) { @@ -94,7 +100,7 @@ public function denormalize(mixed $data, string $type, string $format = null, ar } if (!\is_string($data) || '' === trim($data)) { - throw NotNormalizableValueException::createForUnexpectedDataType('The data is either not an string, an empty string, or null; you should pass a string that can be parsed with the passed format or a valid DateTime string.', $data, [Type::BUILTIN_TYPE_STRING], $context['deserialization_path'] ?? null, true); + throw NotNormalizableValueException::createForUnexpectedDataType('The data is either not an string, an empty string, or null; you should pass a string that can be parsed with the passed format or a valid DateTime string.', $data, [Type::string()], $context['deserialization_path'] ?? null, true); } try { @@ -112,7 +118,7 @@ public function denormalize(mixed $data, string $type, string $format = null, ar $dateTimeErrors = $type::getLastErrors(); - throw NotNormalizableValueException::createForUnexpectedDataType(sprintf('Parsing datetime string "%s" using format "%s" resulted in %d errors: ', $data, $dateTimeFormat, $dateTimeErrors['error_count'])."\n".implode("\n", $this->formatDateTimeErrors($dateTimeErrors['errors'])), $data, [Type::BUILTIN_TYPE_STRING], $context['deserialization_path'] ?? null, true); + throw NotNormalizableValueException::createForUnexpectedDataType(sprintf('Parsing datetime string "%s" using format "%s" resulted in %d errors: ', $data, $dateTimeFormat, $dateTimeErrors['error_count'])."\n".implode("\n", $this->formatDateTimeErrors($dateTimeErrors['errors'])), $data, [Type::string()], $context['deserialization_path'] ?? null, true); } $defaultDateTimeFormat = $this->defaultContext[self::FORMAT_KEY] ?? null; @@ -127,11 +133,11 @@ public function denormalize(mixed $data, string $type, string $format = null, ar } catch (NotNormalizableValueException $e) { throw $e; } catch (\Exception $e) { - throw NotNormalizableValueException::createForUnexpectedDataType($e->getMessage(), $data, [Type::BUILTIN_TYPE_STRING], $context['deserialization_path'] ?? null, false, $e->getCode(), $e); + throw NotNormalizableValueException::createForUnexpectedDataType($e->getMessage(), $data, ['string'], $context['deserialization_path'] ?? null, false, $e->getCode(), $e); } } - public function supportsDenormalization(mixed $data, string $type, string $format = null, array $context = []): bool + public function supportsDenormalization(mixed $data, string $type, ?string $format = null, array $context = []): bool { return isset(self::SUPPORTED_TYPES[$type]); } diff --git a/src/Symfony/Component/Serializer/Normalizer/DateTimeZoneNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/DateTimeZoneNormalizer.php index f5a08e1f3322d..9da08178c3cdc 100644 --- a/src/Symfony/Component/Serializer/Normalizer/DateTimeZoneNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/DateTimeZoneNormalizer.php @@ -11,9 +11,9 @@ namespace Symfony\Component\Serializer\Normalizer; -use Symfony\Component\PropertyInfo\Type; use Symfony\Component\Serializer\Exception\InvalidArgumentException; use Symfony\Component\Serializer\Exception\NotNormalizableValueException; +use Symfony\Component\TypeInfo\Type; /** * Normalizes a {@see \DateTimeZone} object to a timezone string. @@ -32,7 +32,7 @@ public function getSupportedTypes(?string $format): array /** * @throws InvalidArgumentException */ - public function normalize(mixed $object, string $format = null, array $context = []): string + public function normalize(mixed $object, ?string $format = null, array $context = []): string { if (!$object instanceof \DateTimeZone) { throw new InvalidArgumentException('The object must be an instance of "\DateTimeZone".'); @@ -41,7 +41,7 @@ public function normalize(mixed $object, string $format = null, array $context = return $object->getName(); } - public function supportsNormalization(mixed $data, string $format = null, array $context = []): bool + public function supportsNormalization(mixed $data, ?string $format = null, array $context = []): bool { return $data instanceof \DateTimeZone; } @@ -49,20 +49,20 @@ public function supportsNormalization(mixed $data, string $format = null, array /** * @throws NotNormalizableValueException */ - public function denormalize(mixed $data, string $type, string $format = null, array $context = []): \DateTimeZone + public function denormalize(mixed $data, string $type, ?string $format = null, array $context = []): \DateTimeZone { if ('' === $data || null === $data) { - throw NotNormalizableValueException::createForUnexpectedDataType('The data is either an empty string or null, you should pass a string that can be parsed as a DateTimeZone.', $data, [Type::BUILTIN_TYPE_STRING], $context['deserialization_path'] ?? null, true); + throw NotNormalizableValueException::createForUnexpectedDataType('The data is either an empty string or null, you should pass a string that can be parsed as a DateTimeZone.', $data, [Type::string()], $context['deserialization_path'] ?? null, true); } try { return new \DateTimeZone($data); } catch (\Exception $e) { - throw NotNormalizableValueException::createForUnexpectedDataType($e->getMessage(), $data, [Type::BUILTIN_TYPE_STRING], $context['deserialization_path'] ?? null, true, $e->getCode(), $e); + throw NotNormalizableValueException::createForUnexpectedDataType($e->getMessage(), $data, [Type::string()], $context['deserialization_path'] ?? null, true, $e->getCode(), $e); } } - public function supportsDenormalization(mixed $data, string $type, string $format = null, array $context = []): bool + public function supportsDenormalization(mixed $data, string $type, ?string $format = null, array $context = []): bool { return \DateTimeZone::class === $type; } diff --git a/src/Symfony/Component/Serializer/Normalizer/DenormalizableInterface.php b/src/Symfony/Component/Serializer/Normalizer/DenormalizableInterface.php index 458175f310288..48df976242b72 100644 --- a/src/Symfony/Component/Serializer/Normalizer/DenormalizableInterface.php +++ b/src/Symfony/Component/Serializer/Normalizer/DenormalizableInterface.php @@ -34,5 +34,5 @@ interface DenormalizableInterface * differently based on different input formats * @param array $context Options for denormalizing */ - public function denormalize(DenormalizerInterface $denormalizer, array|string|int|float|bool $data, string $format = null, array $context = []): void; + public function denormalize(DenormalizerInterface $denormalizer, array|string|int|float|bool $data, ?string $format = null, array $context = []): void; } diff --git a/src/Symfony/Component/Serializer/Normalizer/DenormalizerInterface.php b/src/Symfony/Component/Serializer/Normalizer/DenormalizerInterface.php index a0ee753450704..23ee3928a7c69 100644 --- a/src/Symfony/Component/Serializer/Normalizer/DenormalizerInterface.php +++ b/src/Symfony/Component/Serializer/Normalizer/DenormalizerInterface.php @@ -42,7 +42,7 @@ interface DenormalizerInterface * @throws RuntimeException Occurs if the class cannot be instantiated * @throws ExceptionInterface Occurs for all the other cases of errors */ - public function denormalize(mixed $data, string $type, string $format = null, array $context = []): mixed; + public function denormalize(mixed $data, string $type, ?string $format = null, array $context = []): mixed; /** * Checks whether the given class is supported for denormalization by this normalizer. @@ -51,7 +51,7 @@ public function denormalize(mixed $data, string $type, string $format = null, ar * @param string $type The class to which the data should be denormalized * @param string|null $format The format being deserialized from */ - public function supportsDenormalization(mixed $data, string $type, string $format = null, array $context = []): bool; + public function supportsDenormalization(mixed $data, string $type, ?string $format = null, array $context = []): bool; /** * Returns the types potentially supported by this denormalizer. diff --git a/src/Symfony/Component/Serializer/Normalizer/FormErrorNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/FormErrorNormalizer.php index 195921b095b35..9ef13a669eba4 100644 --- a/src/Symfony/Component/Serializer/Normalizer/FormErrorNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/FormErrorNormalizer.php @@ -22,7 +22,7 @@ final class FormErrorNormalizer implements NormalizerInterface public const TYPE = 'type'; public const CODE = 'status_code'; - public function normalize(mixed $object, string $format = null, array $context = []): array + public function normalize(mixed $object, ?string $format = null, array $context = []): array { $data = [ 'title' => $context[self::TITLE] ?? 'Validation Failed', @@ -45,7 +45,7 @@ public function getSupportedTypes(?string $format): array ]; } - public function supportsNormalization(mixed $data, string $format = null, array $context = []): bool + public function supportsNormalization(mixed $data, ?string $format = null, array $context = []): bool { return $data instanceof FormInterface && $data->isSubmitted() && !$data->isValid(); } diff --git a/src/Symfony/Component/Serializer/Normalizer/GetSetMethodNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/GetSetMethodNormalizer.php index 0e45f2d99de8f..e050bc153c906 100644 --- a/src/Symfony/Component/Serializer/Normalizer/GetSetMethodNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/GetSetMethodNormalizer.php @@ -44,12 +44,12 @@ public function getSupportedTypes(?string $format): array return ['object' => true]; } - public function supportsNormalization(mixed $data, string $format = null, array $context = []): bool + public function supportsNormalization(mixed $data, ?string $format = null, array $context = []): bool { return parent::supportsNormalization($data, $format) && $this->supports($data::class); } - public function supportsDenormalization(mixed $data, string $type, string $format = null, array $context = []): bool + public function supportsDenormalization(mixed $data, string $type, ?string $format = null, array $context = []): bool { return parent::supportsDenormalization($data, $type, $format) && $this->supports($type); } @@ -87,7 +87,7 @@ private function isGetMethod(\ReflectionMethod $method): bool ); } - protected function extractAttributes(object $object, string $format = null, array $context = []): array + protected function extractAttributes(object $object, ?string $format = null, array $context = []): array { $reflectionObject = new \ReflectionObject($object); $reflectionMethods = $reflectionObject->getMethods(\ReflectionMethod::IS_PUBLIC); @@ -108,7 +108,7 @@ protected function extractAttributes(object $object, string $format = null, arra return $attributes; } - protected function getAttributeValue(object $object, string $attribute, string $format = null, array $context = []): mixed + protected function getAttributeValue(object $object, string $attribute, ?string $format = null, array $context = []): mixed { $ucfirsted = ucfirst($attribute); @@ -130,7 +130,7 @@ protected function getAttributeValue(object $object, string $attribute, string $ return null; } - protected function setAttributeValue(object $object, string $attribute, mixed $value, string $format = null, array $context = []): void + protected function setAttributeValue(object $object, string $attribute, mixed $value, ?string $format = null, array $context = []): void { $setter = 'set'.ucfirst($attribute); $key = $object::class.':'.$setter; diff --git a/src/Symfony/Component/Serializer/Normalizer/JsonSerializableNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/JsonSerializableNormalizer.php index 324487c28d21a..1287627fb5022 100644 --- a/src/Symfony/Component/Serializer/Normalizer/JsonSerializableNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/JsonSerializableNormalizer.php @@ -21,7 +21,7 @@ */ final class JsonSerializableNormalizer extends AbstractNormalizer { - public function normalize(mixed $object, string $format = null, array $context = []): array|string|int|float|bool|\ArrayObject|null + public function normalize(mixed $object, ?string $format = null, array $context = []): array|string|int|float|bool|\ArrayObject|null { if ($this->isCircularReference($object, $context)) { return $this->handleCircularReference($object, $format, $context); @@ -45,17 +45,17 @@ public function getSupportedTypes(?string $format): array ]; } - public function supportsNormalization(mixed $data, string $format = null, array $context = []): bool + public function supportsNormalization(mixed $data, ?string $format = null, array $context = []): bool { return $data instanceof \JsonSerializable; } - public function supportsDenormalization(mixed $data, string $type, string $format = null, array $context = []): bool + public function supportsDenormalization(mixed $data, string $type, ?string $format = null, array $context = []): bool { return false; } - public function denormalize(mixed $data, string $type, string $format = null, array $context = []): mixed + public function denormalize(mixed $data, string $type, ?string $format = null, array $context = []): mixed { throw new LogicException(sprintf('Cannot denormalize with "%s".', \JsonSerializable::class)); } diff --git a/src/Symfony/Component/Serializer/Normalizer/MimeMessageNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/MimeMessageNormalizer.php index 0b35a47d0487b..7006ab321876d 100644 --- a/src/Symfony/Component/Serializer/Normalizer/MimeMessageNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/MimeMessageNormalizer.php @@ -17,6 +17,7 @@ use Symfony\Component\Mime\Header\UnstructuredHeader; use Symfony\Component\Mime\Message; use Symfony\Component\Mime\Part\AbstractPart; +use Symfony\Component\Mime\RawMessage; use Symfony\Component\Serializer\Exception\LogicException; use Symfony\Component\Serializer\SerializerAwareInterface; use Symfony\Component\Serializer\SerializerInterface; @@ -61,7 +62,7 @@ public function setSerializer(SerializerInterface $serializer): void $this->normalizer->setSerializer($serializer); } - public function normalize(mixed $object, string $format = null, array $context = []): array|string|int|float|bool|\ArrayObject|null + public function normalize(mixed $object, ?string $format = null, array $context = []): array|string|int|float|bool|\ArrayObject|null { if ($object instanceof Headers) { $ret = []; @@ -72,18 +73,21 @@ public function normalize(mixed $object, string $format = null, array $context = return $ret; } + $ret = $this->normalizer->normalize($object, $format, $context); + if ($object instanceof AbstractPart) { - $ret = $this->normalizer->normalize($object, $format, $context); $ret['class'] = $object::class; unset($ret['seekable'], $ret['cid'], $ret['handle']); + } - return $ret; + if ($object instanceof RawMessage && \array_key_exists('message', $ret) && null === $ret['message']) { + unset($ret['message']); } - return $this->normalizer->normalize($object, $format, $context); + return $ret; } - public function denormalize(mixed $data, string $type, string $format = null, array $context = []): mixed + public function denormalize(mixed $data, string $type, ?string $format = null, array $context = []): mixed { if (Headers::class === $type) { $ret = []; @@ -105,12 +109,12 @@ public function denormalize(mixed $data, string $type, string $format = null, ar return $this->normalizer->denormalize($data, $type, $format, $context); } - public function supportsNormalization(mixed $data, string $format = null, array $context = []): bool + public function supportsNormalization(mixed $data, ?string $format = null, array $context = []): bool { return $data instanceof Message || $data instanceof Headers || $data instanceof HeaderInterface || $data instanceof Address || $data instanceof AbstractPart; } - public function supportsDenormalization(mixed $data, string $type, string $format = null, array $context = []): bool + public function supportsDenormalization(mixed $data, string $type, ?string $format = null, array $context = []): bool { return is_a($type, Message::class, true) || Headers::class === $type || AbstractPart::class === $type; } diff --git a/src/Symfony/Component/Serializer/Normalizer/NormalizableInterface.php b/src/Symfony/Component/Serializer/Normalizer/NormalizableInterface.php index 0bad9d183953d..7be59866879b2 100644 --- a/src/Symfony/Component/Serializer/Normalizer/NormalizableInterface.php +++ b/src/Symfony/Component/Serializer/Normalizer/NormalizableInterface.php @@ -33,5 +33,5 @@ interface NormalizableInterface * based on different output formats * @param array $context Options for normalizing this object */ - public function normalize(NormalizerInterface $normalizer, string $format = null, array $context = []): array|string|int|float|bool|\ArrayObject|null; + public function normalize(NormalizerInterface $normalizer, ?string $format = null, array $context = []): array|string|int|float|bool|\ArrayObject|null; } diff --git a/src/Symfony/Component/Serializer/Normalizer/NormalizerInterface.php b/src/Symfony/Component/Serializer/Normalizer/NormalizerInterface.php index 6759b21997d56..562f87c28758b 100644 --- a/src/Symfony/Component/Serializer/Normalizer/NormalizerInterface.php +++ b/src/Symfony/Component/Serializer/Normalizer/NormalizerInterface.php @@ -36,7 +36,7 @@ interface NormalizerInterface * @throws LogicException Occurs when the normalizer is not called in an expected context * @throws ExceptionInterface Occurs for all the other cases of errors */ - public function normalize(mixed $object, string $format = null, array $context = []): array|string|int|float|bool|\ArrayObject|null; + public function normalize(mixed $object, ?string $format = null, array $context = []): array|string|int|float|bool|\ArrayObject|null; /** * Checks whether the given class is supported for normalization by this normalizer. @@ -44,7 +44,7 @@ public function normalize(mixed $object, string $format = null, array $context = * @param mixed $data Data to normalize * @param string|null $format The format being (de-)serialized from or into */ - public function supportsNormalization(mixed $data, string $format = null, array $context = []): bool; + public function supportsNormalization(mixed $data, ?string $format = null, array $context = []): bool; /** * Returns the types potentially supported by this normalizer. diff --git a/src/Symfony/Component/Serializer/Normalizer/ObjectNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/ObjectNormalizer.php index ef7950b00cd5e..182ac1ae6e917 100644 --- a/src/Symfony/Component/Serializer/Normalizer/ObjectNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/ObjectNormalizer.php @@ -32,7 +32,7 @@ final class ObjectNormalizer extends AbstractObjectNormalizer private readonly \Closure $objectClassResolver; - public function __construct(ClassMetadataFactoryInterface $classMetadataFactory = null, NameConverterInterface $nameConverter = null, PropertyAccessorInterface $propertyAccessor = null, PropertyTypeExtractorInterface $propertyTypeExtractor = null, ClassDiscriminatorResolverInterface $classDiscriminatorResolver = null, callable $objectClassResolver = null, array $defaultContext = []) + public function __construct(?ClassMetadataFactoryInterface $classMetadataFactory = null, ?NameConverterInterface $nameConverter = null, ?PropertyAccessorInterface $propertyAccessor = null, ?PropertyTypeExtractorInterface $propertyTypeExtractor = null, ?ClassDiscriminatorResolverInterface $classDiscriminatorResolver = null, ?callable $objectClassResolver = null, array $defaultContext = []) { if (!class_exists(PropertyAccess::class)) { throw new LogicException('The ObjectNormalizer class requires the "PropertyAccess" component. Try running "composer require symfony/property-access".'); @@ -50,7 +50,7 @@ public function getSupportedTypes(?string $format): array return ['object' => true]; } - protected function extractAttributes(object $object, string $format = null, array $context = []): array + protected function extractAttributes(object $object, ?string $format = null, array $context = []): array { if (\stdClass::class === $object::class) { return array_keys((array) $object); @@ -113,7 +113,7 @@ protected function extractAttributes(object $object, string $format = null, arra return array_keys($attributes); } - protected function getAttributeValue(object $object, string $attribute, string $format = null, array $context = []): mixed + protected function getAttributeValue(object $object, string $attribute, ?string $format = null, array $context = []): mixed { $mapping = $this->classDiscriminatorResolver?->getMappingForMappedObject($object); @@ -122,7 +122,7 @@ protected function getAttributeValue(object $object, string $attribute, string $ : $this->propertyAccessor->getValue($object, $attribute); } - protected function setAttributeValue(object $object, string $attribute, mixed $value, string $format = null, array $context = []): void + protected function setAttributeValue(object $object, string $attribute, mixed $value, ?string $format = null, array $context = []): void { try { $this->propertyAccessor->setValue($object, $attribute, $value); diff --git a/src/Symfony/Component/Serializer/Normalizer/ObjectToPopulateTrait.php b/src/Symfony/Component/Serializer/Normalizer/ObjectToPopulateTrait.php index 23be4c6afdb51..21ad5f7ad0693 100644 --- a/src/Symfony/Component/Serializer/Normalizer/ObjectToPopulateTrait.php +++ b/src/Symfony/Component/Serializer/Normalizer/ObjectToPopulateTrait.php @@ -21,7 +21,7 @@ trait ObjectToPopulateTrait * @param string|null $key They in which to look for the object to populate. * Keeps backwards compatibility with `AbstractNormalizer`. */ - protected function extractObjectToPopulate(string $class, array $context, string $key = null): ?object + protected function extractObjectToPopulate(string $class, array $context, ?string $key = null): ?object { $key ??= AbstractNormalizer::OBJECT_TO_POPULATE; diff --git a/src/Symfony/Component/Serializer/Normalizer/ProblemNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/ProblemNormalizer.php index cc5759c7159a0..04c680647fe65 100644 --- a/src/Symfony/Component/Serializer/Normalizer/ProblemNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/ProblemNormalizer.php @@ -51,7 +51,7 @@ public function getSupportedTypes(?string $format): array ]; } - public function normalize(mixed $object, string $format = null, array $context = []): array + public function normalize(mixed $object, ?string $format = null, array $context = []): array { if (!$object instanceof FlattenException) { throw new InvalidArgumentException(sprintf('The object must implement "%s".', FlattenException::class)); @@ -107,7 +107,7 @@ public function normalize(mixed $object, string $format = null, array $context = return $data; } - public function supportsNormalization(mixed $data, string $format = null, array $context = []): bool + public function supportsNormalization(mixed $data, ?string $format = null, array $context = []): bool { return $data instanceof FlattenException; } diff --git a/src/Symfony/Component/Serializer/Normalizer/PropertyNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/PropertyNormalizer.php index 8dc46b6efafed..e1d893be89771 100644 --- a/src/Symfony/Component/Serializer/Normalizer/PropertyNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/PropertyNormalizer.php @@ -45,7 +45,7 @@ final class PropertyNormalizer extends AbstractObjectNormalizer */ public const NORMALIZE_VISIBILITY = 'normalize_visibility'; - public function __construct(ClassMetadataFactoryInterface $classMetadataFactory = null, NameConverterInterface $nameConverter = null, PropertyTypeExtractorInterface $propertyTypeExtractor = null, ClassDiscriminatorResolverInterface $classDiscriminatorResolver = null, callable $objectClassResolver = null, array $defaultContext = []) + public function __construct(?ClassMetadataFactoryInterface $classMetadataFactory = null, ?NameConverterInterface $nameConverter = null, ?PropertyTypeExtractorInterface $propertyTypeExtractor = null, ?ClassDiscriminatorResolverInterface $classDiscriminatorResolver = null, ?callable $objectClassResolver = null, array $defaultContext = []) { parent::__construct($classMetadataFactory, $nameConverter, $propertyTypeExtractor, $classDiscriminatorResolver, $objectClassResolver, $defaultContext); @@ -59,12 +59,12 @@ public function getSupportedTypes(?string $format): array return ['object' => true]; } - public function supportsNormalization(mixed $data, string $format = null, array $context = []): bool + public function supportsNormalization(mixed $data, ?string $format = null, array $context = []): bool { return parent::supportsNormalization($data, $format) && $this->supports($data::class); } - public function supportsDenormalization(mixed $data, string $type, string $format = null, array $context = []): bool + public function supportsDenormalization(mixed $data, string $type, ?string $format = null, array $context = []): bool { return parent::supportsDenormalization($data, $type, $format) && $this->supports($type); } @@ -92,7 +92,7 @@ private function supports(string $class): bool return false; } - protected function isAllowedAttribute(object|string $classOrObject, string $attribute, string $format = null, array $context = []): bool + protected function isAllowedAttribute(object|string $classOrObject, string $attribute, ?string $format = null, array $context = []): bool { if (!parent::isAllowedAttribute($classOrObject, $attribute, $format, $context)) { return false; @@ -125,7 +125,7 @@ protected function isAllowedAttribute(object|string $classOrObject, string $attr return false; } - protected function extractAttributes(object $object, string $format = null, array $context = []): array + protected function extractAttributes(object $object, ?string $format = null, array $context = []): array { $reflectionObject = new \ReflectionObject($object); $attributes = []; @@ -143,7 +143,7 @@ protected function extractAttributes(object $object, string $format = null, arra return array_unique($attributes); } - protected function getAttributeValue(object $object, string $attribute, string $format = null, array $context = []): mixed + protected function getAttributeValue(object $object, string $attribute, ?string $format = null, array $context = []): mixed { try { $reflectionProperty = $this->getReflectionProperty($object, $attribute); @@ -169,7 +169,7 @@ protected function getAttributeValue(object $object, string $attribute, string $ return $reflectionProperty->getValue($object); } - protected function setAttributeValue(object $object, string $attribute, mixed $value, string $format = null, array $context = []): void + protected function setAttributeValue(object $object, string $attribute, mixed $value, ?string $format = null, array $context = []): void { try { $reflectionProperty = $this->getReflectionProperty($object, $attribute); diff --git a/src/Symfony/Component/Serializer/Normalizer/TranslatableNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/TranslatableNormalizer.php index a4aef51f8cea9..398e00b79ae06 100644 --- a/src/Symfony/Component/Serializer/Normalizer/TranslatableNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/TranslatableNormalizer.php @@ -34,7 +34,7 @@ public function __construct( /** * @throws InvalidArgumentException */ - public function normalize(mixed $object, string $format = null, array $context = []): string + public function normalize(mixed $object, ?string $format = null, array $context = []): string { if (!$object instanceof TranslatableInterface) { throw NotNormalizableValueException::createForUnexpectedDataType(sprintf('The object must implement the "%s".', TranslatableInterface::class), $object, [TranslatableInterface::class]); @@ -43,7 +43,7 @@ public function normalize(mixed $object, string $format = null, array $context = return $object->trans($this->translator, $context[self::NORMALIZATION_LOCALE_KEY] ?? $this->defaultContext[self::NORMALIZATION_LOCALE_KEY]); } - public function supportsNormalization(mixed $data, string $format = null, array $context = []): bool + public function supportsNormalization(mixed $data, ?string $format = null, array $context = []): bool { return $data instanceof TranslatableInterface; } diff --git a/src/Symfony/Component/Serializer/Normalizer/UidNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/UidNormalizer.php index 104dddecef388..db57439069a55 100644 --- a/src/Symfony/Component/Serializer/Normalizer/UidNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/UidNormalizer.php @@ -11,9 +11,9 @@ namespace Symfony\Component\Serializer\Normalizer; -use Symfony\Component\PropertyInfo\Type; use Symfony\Component\Serializer\Exception\LogicException; use Symfony\Component\Serializer\Exception\NotNormalizableValueException; +use Symfony\Component\TypeInfo\Type; use Symfony\Component\Uid\AbstractUid; final class UidNormalizer implements NormalizerInterface, DenormalizerInterface @@ -50,7 +50,7 @@ public function getSupportedTypes(?string $format): array /** * @param AbstractUid $object */ - public function normalize(mixed $object, string $format = null, array $context = []): array|string|int|float|bool|\ArrayObject|null + public function normalize(mixed $object, ?string $format = null, array $context = []): array|string|int|float|bool|\ArrayObject|null { return match ($context[self::NORMALIZATION_FORMAT_KEY] ?? $this->defaultContext[self::NORMALIZATION_FORMAT_KEY]) { self::NORMALIZATION_FORMAT_CANONICAL => (string) $object, @@ -61,21 +61,21 @@ public function normalize(mixed $object, string $format = null, array $context = }; } - public function supportsNormalization(mixed $data, string $format = null, array $context = []): bool + public function supportsNormalization(mixed $data, ?string $format = null, array $context = []): bool { return $data instanceof AbstractUid; } - public function denormalize(mixed $data, string $type, string $format = null, array $context = []): mixed + public function denormalize(mixed $data, string $type, ?string $format = null, array $context = []): mixed { try { return $type::fromString($data); } catch (\InvalidArgumentException|\TypeError) { - throw NotNormalizableValueException::createForUnexpectedDataType(sprintf('The data is not a valid "%s" string representation.', $type), $data, [Type::BUILTIN_TYPE_STRING], $context['deserialization_path'] ?? null, true); + throw NotNormalizableValueException::createForUnexpectedDataType(sprintf('The data is not a valid "%s" string representation.', $type), $data, [Type::string()], $context['deserialization_path'] ?? null, true); } } - public function supportsDenormalization(mixed $data, string $type, string $format = null, array $context = []): bool + public function supportsDenormalization(mixed $data, string $type, ?string $format = null, array $context = []): bool { return is_subclass_of($type, AbstractUid::class, true); } diff --git a/src/Symfony/Component/Serializer/Normalizer/UnwrappingDenormalizer.php b/src/Symfony/Component/Serializer/Normalizer/UnwrappingDenormalizer.php index 51397961c7df6..9f01d2a71d87a 100644 --- a/src/Symfony/Component/Serializer/Normalizer/UnwrappingDenormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/UnwrappingDenormalizer.php @@ -28,7 +28,7 @@ final class UnwrappingDenormalizer implements DenormalizerInterface, SerializerA private readonly PropertyAccessorInterface $propertyAccessor; - public function __construct(PropertyAccessorInterface $propertyAccessor = null) + public function __construct(?PropertyAccessorInterface $propertyAccessor = null) { $this->propertyAccessor = $propertyAccessor ?? PropertyAccess::createPropertyAccessor(); } @@ -38,7 +38,7 @@ public function getSupportedTypes(?string $format): array return ['*' => false]; } - public function denormalize(mixed $data, string $type, string $format = null, array $context = []): mixed + public function denormalize(mixed $data, string $type, ?string $format = null, array $context = []): mixed { $propertyPath = $context[self::UNWRAP_PATH]; $context['unwrapped'] = true; @@ -58,7 +58,7 @@ public function denormalize(mixed $data, string $type, string $format = null, ar return $this->serializer->denormalize($data, $type, $format, $context); } - public function supportsDenormalization(mixed $data, string $type, string $format = null, array $context = []): bool + public function supportsDenormalization(mixed $data, string $type, ?string $format = null, array $context = []): bool { return \array_key_exists(self::UNWRAP_PATH, $context) && !isset($context['unwrapped']); } diff --git a/src/Symfony/Component/Serializer/Serializer.php b/src/Symfony/Component/Serializer/Serializer.php index 29c52da9c61e5..9fba20f144f4c 100644 --- a/src/Symfony/Component/Serializer/Serializer.php +++ b/src/Symfony/Component/Serializer/Serializer.php @@ -143,7 +143,7 @@ final public function deserialize(mixed $data, string $type, string $format, arr return $this->denormalize($data, $type, $format, $context); } - public function normalize(mixed $data, string $format = null, array $context = []): array|string|int|float|bool|\ArrayObject|null + public function normalize(mixed $data, ?string $format = null, array $context = []): array|string|int|float|bool|\ArrayObject|null { // If a normalizer supports the given data, use it if ($normalizer = $this->getNormalizer($data, $format, $context)) { @@ -186,7 +186,7 @@ public function normalize(mixed $data, string $format = null, array $context = [ * @throws NotNormalizableValueException * @throws PartialDenormalizationException Occurs when one or more properties of $type fails to denormalize */ - public function denormalize(mixed $data, string $type, string $format = null, array $context = []): mixed + public function denormalize(mixed $data, string $type, ?string $format = null, array $context = []): mixed { if (isset($context[DenormalizerInterface::COLLECT_DENORMALIZATION_ERRORS], $context['not_normalizable_value_exceptions'])) { throw new LogicException('Passing a value for "not_normalizable_value_exceptions" context key is not allowed.'); @@ -243,12 +243,12 @@ public function getSupportedTypes(?string $format): array return ['*' => false]; } - public function supportsNormalization(mixed $data, string $format = null, array $context = []): bool + public function supportsNormalization(mixed $data, ?string $format = null, array $context = []): bool { return null !== $this->getNormalizer($data, $format, $context); } - public function supportsDenormalization(mixed $data, string $type, string $format = null, array $context = []): bool + public function supportsDenormalization(mixed $data, string $type, ?string $format = null, array $context = []): bool { return isset(self::SCALAR_TYPES[$type]) || null !== $this->getDenormalizer($data, $type, $format, $context); } diff --git a/src/Symfony/Component/Serializer/Tests/Context/Normalizer/DateTimeNormalizerContextBuilderTest.php b/src/Symfony/Component/Serializer/Tests/Context/Normalizer/DateTimeNormalizerContextBuilderTest.php index 8ab41f949c3cc..ac4badc19486b 100644 --- a/src/Symfony/Component/Serializer/Tests/Context/Normalizer/DateTimeNormalizerContextBuilderTest.php +++ b/src/Symfony/Component/Serializer/Tests/Context/Normalizer/DateTimeNormalizerContextBuilderTest.php @@ -38,6 +38,7 @@ public function testWithers(array $values) $context = $this->contextBuilder ->withFormat($values[DateTimeNormalizer::FORMAT_KEY]) ->withTimezone($values[DateTimeNormalizer::TIMEZONE_KEY]) + ->withCast($values[DateTimeNormalizer::CAST_KEY]) ->toArray(); $this->assertEquals($values, $context); @@ -51,11 +52,13 @@ public static function withersDataProvider(): iterable yield 'With values' => [[ DateTimeNormalizer::FORMAT_KEY => 'format', DateTimeNormalizer::TIMEZONE_KEY => new \DateTimeZone('GMT'), + DateTimeNormalizer::CAST_KEY => 'int', ]]; yield 'With null values' => [[ DateTimeNormalizer::FORMAT_KEY => null, DateTimeNormalizer::TIMEZONE_KEY => null, + DateTimeNormalizer::CAST_KEY => null, ]]; } diff --git a/src/Symfony/Component/Serializer/Tests/Debug/TraceableSerializerTest.php b/src/Symfony/Component/Serializer/Tests/Debug/TraceableSerializerTest.php index 9cc43d40a9a0b..ea3c851c6040b 100644 --- a/src/Symfony/Component/Serializer/Tests/Debug/TraceableSerializerTest.php +++ b/src/Symfony/Component/Serializer/Tests/Debug/TraceableSerializerTest.php @@ -140,7 +140,7 @@ public function deserialize(mixed $data, string $type, string $format, array $co return 'deserialized'; } - public function normalize(mixed $object, string $format = null, array $context = []): array|string|int|float|bool|\ArrayObject|null + public function normalize(mixed $object, ?string $format = null, array $context = []): array|string|int|float|bool|\ArrayObject|null { return 'normalized'; } @@ -150,17 +150,17 @@ public function getSupportedTypes(?string $format): array return ['*' => false]; } - public function supportsNormalization(mixed $data, string $format = null, array $context = []): bool + public function supportsNormalization(mixed $data, ?string $format = null, array $context = []): bool { return true; } - public function denormalize(mixed $data, string $type, string $format = null, array $context = []): mixed + public function denormalize(mixed $data, string $type, ?string $format = null, array $context = []): mixed { return 'denormalized'; } - public function supportsDenormalization(mixed $data, string $type, string $format = null, array $context = []): bool + public function supportsDenormalization(mixed $data, string $type, ?string $format = null, array $context = []): bool { return true; } diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/Attributes/GroupDummy.php b/src/Symfony/Component/Serializer/Tests/Fixtures/Attributes/GroupDummy.php index 749e841a5c05d..5c34c95a40a69 100644 --- a/src/Symfony/Component/Serializer/Tests/Fixtures/Attributes/GroupDummy.php +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/Attributes/GroupDummy.php @@ -27,6 +27,10 @@ class GroupDummy extends GroupDummyParent implements GroupDummyInterface protected $quux; private $fooBar; private $symfony; + #[Groups(['Default'])] + private $default; + #[Groups(['GroupDummy'])] + private $className; #[Groups(['b'])] public function setBar($bar) @@ -80,4 +84,24 @@ public function setQuux($quux): void { $this->quux = $quux; } + + public function setDefault($default) + { + $this->default = $default; + } + + public function getDefault() + { + return $this->default; + } + + public function setClassName($className) + { + $this->className = $className; + } + + public function getClassName() + { + return $this->className; + } } diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/DummyWithWithVariadicParameterConstructor.php b/src/Symfony/Component/Serializer/Tests/Fixtures/DummyWithWithVariadicParameterConstructor.php new file mode 100644 index 0000000000000..7b3819ac9f034 --- /dev/null +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/DummyWithWithVariadicParameterConstructor.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Tests\Fixtures; + +class DummyWithWithVariadicParameterConstructor +{ + private $foo; + + private $bar; + + private $baz; + + public function __construct(string $foo, int $bar = 1, Dummy ...$baz) + { + $this->foo = $foo; + $this->bar = $bar; + $this->baz = $baz; + } + + public function getFoo(): string + { + return $this->foo; + } + + public function getBar(): int + { + return $this->bar; + } + + /** @return Dummy[] */ + public function getBaz(): array + { + return $this->baz; + } +} diff --git a/src/Symfony/Component/Serializer/Tests/Mapping/TestClassMetadataFactory.php b/src/Symfony/Component/Serializer/Tests/Mapping/TestClassMetadataFactory.php index 61147316a68ed..d617ffaebf86c 100644 --- a/src/Symfony/Component/Serializer/Tests/Mapping/TestClassMetadataFactory.php +++ b/src/Symfony/Component/Serializer/Tests/Mapping/TestClassMetadataFactory.php @@ -63,6 +63,14 @@ public static function createClassMetadata(string $namespace, bool $withParent = $symfony->addGroup('name_converter'); } + $default = new AttributeMetadata('default'); + $default->addGroup('Default'); + $expected->addAttributeMetadata($default); + + $className = new AttributeMetadata('className'); + $className->addGroup('GroupDummy'); + $expected->addAttributeMetadata($className); + // load reflection class so that the comparison passes $expected->getReflectionClass(); diff --git a/src/Symfony/Component/Serializer/Tests/Normalizer/AbstractNormalizerTest.php b/src/Symfony/Component/Serializer/Tests/Normalizer/AbstractNormalizerTest.php index aabcad7864d83..a325748786de0 100644 --- a/src/Symfony/Component/Serializer/Tests/Normalizer/AbstractNormalizerTest.php +++ b/src/Symfony/Component/Serializer/Tests/Normalizer/AbstractNormalizerTest.php @@ -30,6 +30,7 @@ use Symfony\Component\Serializer\Tests\Fixtures\AbstractNormalizerDummy; use Symfony\Component\Serializer\Tests\Fixtures\Attributes\IgnoreDummy; use Symfony\Component\Serializer\Tests\Fixtures\Dummy; +use Symfony\Component\Serializer\Tests\Fixtures\DummyWithWithVariadicParameterConstructor; use Symfony\Component\Serializer\Tests\Fixtures\NullableConstructorArgumentDummy; use Symfony\Component\Serializer\Tests\Fixtures\NullableOptionalConstructorArgumentDummy; use Symfony\Component\Serializer\Tests\Fixtures\StaticConstructorDummy; @@ -248,6 +249,25 @@ public static function getNormalizer() yield [new ObjectNormalizer(null, null, null, $extractor)]; } + public function testVariadicConstructorDenormalization() + { + $data = [ + 'foo' => 'woo', + 'baz' => [ + ['foo' => null, 'bar' => null, 'baz' => null, 'qux' => null], + ['foo' => null, 'bar' => null, 'baz' => null, 'qux' => null], + ], + ]; + + $normalizer = new ObjectNormalizer(); + $normalizer->setSerializer(new Serializer([$normalizer])); + + $expected = new DummyWithWithVariadicParameterConstructor('woo', 1, new Dummy(), new Dummy()); + $actual = $normalizer->denormalize($data, DummyWithWithVariadicParameterConstructor::class); + + $this->assertEquals($expected, $actual); + } + public static function getNormalizerWithCustomNameConverter() { $extractor = new PhpDocExtractor(); diff --git a/src/Symfony/Component/Serializer/Tests/Normalizer/AbstractObjectNormalizerTest.php b/src/Symfony/Component/Serializer/Tests/Normalizer/AbstractObjectNormalizerTest.php index ca3c7579be301..a7f38830366a1 100644 --- a/src/Symfony/Component/Serializer/Tests/Normalizer/AbstractObjectNormalizerTest.php +++ b/src/Symfony/Component/Serializer/Tests/Normalizer/AbstractObjectNormalizerTest.php @@ -15,7 +15,6 @@ use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor; use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor; use Symfony\Component\PropertyInfo\PropertyInfoExtractor; -use Symfony\Component\PropertyInfo\Type; use Symfony\Component\Serializer\Attribute\Context; use Symfony\Component\Serializer\Attribute\DiscriminatorMap; use Symfony\Component\Serializer\Attribute\SerializedName; @@ -56,6 +55,7 @@ use Symfony\Component\Serializer\Tests\Fixtures\DummyWithObjectOrNull; use Symfony\Component\Serializer\Tests\Fixtures\DummyWithStringObject; use Symfony\Component\Serializer\Tests\Normalizer\Features\ObjectDummyWithContextAttribute; +use Symfony\Component\TypeInfo\Type; class AbstractObjectNormalizerTest extends TestCase { @@ -433,10 +433,10 @@ public function testDenormalizeCollectionDecodedFromXmlWithTwoChildren() private function getDenormalizerForDummyCollection() { $extractor = $this->createMock(PhpDocExtractor::class); - $extractor->method('getTypes') + $extractor->method('getType') ->will($this->onConsecutiveCalls( - [new Type('array', false, null, true, new Type('int'), new Type('object', false, DummyChild::class))], - null + Type::list(Type::object(DummyChild::class)), + null, )); $denormalizer = new AbstractObjectNormalizerCollectionDummy(null, null, $extractor); @@ -488,10 +488,10 @@ public function testDenormalizeNotSerializableObjectToPopulate() private function getDenormalizerForStringCollection() { $extractor = $this->createMock(PhpDocExtractor::class); - $extractor->method('getTypes') + $extractor->method('getType') ->will($this->onConsecutiveCalls( - [new Type('array', false, null, true, new Type('int'), new Type('string'))], - null + Type::list(Type::string()), + null, )); $denormalizer = new AbstractObjectNormalizerCollectionDummy(null, null, $extractor); @@ -675,20 +675,20 @@ public function testDenormalizeBasicTypePropertiesFromXml() private function getDenormalizerForObjectWithBasicProperties() { $extractor = $this->createMock(PhpDocExtractor::class); - $extractor->method('getTypes') + $extractor->method('getType') ->will($this->onConsecutiveCalls( - [new Type('bool')], - [new Type('bool')], - [new Type('bool')], - [new Type('bool')], - [new Type('int')], - [new Type('int')], - [new Type('float')], - [new Type('float')], - [new Type('float')], - [new Type('float')], - [new Type('float')], - [new Type('float')] + Type::bool(), + Type::bool(), + Type::bool(), + Type::bool(), + Type::int(), + Type::int(), + Type::float(), + Type::float(), + Type::float(), + Type::float(), + Type::float(), + Type::float(), )); $denormalizer = new AbstractObjectNormalizerCollectionDummy(null, null, $extractor); @@ -788,7 +788,7 @@ public function testDefaultExcludeFromCacheKey() $object->bar = 'not called'; $normalizer = new class(null, null, null, null, null, [AbstractObjectNormalizer::EXCLUDE_FROM_CACHE_KEY => ['foo']]) extends AbstractObjectNormalizerDummy { - public function supportsNormalization(mixed $data, string $format = null, array $context = []): bool + public function supportsNormalization(mixed $data, ?string $format = null, array $context = []): bool { AbstractObjectNormalizerTest::assertContains('foo', $this->defaultContext[ObjectNormalizer::EXCLUDE_FROM_CACHE_KEY]); $data->bar = 'called'; @@ -875,17 +875,17 @@ protected function getAllowedAttributes($classOrObject, array $context, bool $at return ['foo']; } - protected function extractAttributes(object $object, string $format = null, array $context = []): array + protected function extractAttributes(object $object, ?string $format = null, array $context = []): array { return []; } - protected function getAttributeValue(object $object, string $attribute, string $format = null, array $context = []): mixed + protected function getAttributeValue(object $object, string $attribute, ?string $format = null, array $context = []): mixed { return $object->$attribute; } - protected function setAttributeValue(object $object, string $attribute, $value, string $format = null, array $context = []): void + protected function setAttributeValue(object $object, string $attribute, $value, ?string $format = null, array $context = []): void { } }; @@ -935,6 +935,116 @@ public function testDenormalizeUntypedStringObject() $this->assertEquals(new DummyWithStringObject(new DummyString()), $actual); $this->assertEquals('', $actual->value->value); } + + public function testProvidingContextCacheKeyGeneratesSameChildContextCacheKey() + { + $foobar = new Dummy(); + $foobar->foo = new EmptyDummy(); + $foobar->bar = 'bar'; + $foobar->baz = 'baz'; + + $normalizer = new class() extends AbstractObjectNormalizerDummy { + public $childContextCacheKey; + + protected function extractAttributes(object $object, string $format = null, array $context = []): array + { + return array_keys((array) $object); + } + + protected function getAttributeValue(object $object, string $attribute, string $format = null, array $context = []): mixed + { + return $object->{$attribute}; + } + + protected function createChildContext(array $parentContext, string $attribute, ?string $format): array + { + $childContext = parent::createChildContext($parentContext, $attribute, $format); + $this->childContextCacheKey = $childContext['cache_key']; + + return $childContext; + } + }; + + $serializer = new Serializer([$normalizer]); + + $serializer->normalize($foobar, null, ['cache_key' => 'hardcoded', 'iri' => '/dummy/1']); + $firstChildContextCacheKey = $normalizer->childContextCacheKey; + + $serializer->normalize($foobar, null, ['cache_key' => 'hardcoded', 'iri' => '/dummy/2']); + $secondChildContextCacheKey = $normalizer->childContextCacheKey; + + $this->assertSame($firstChildContextCacheKey, $secondChildContextCacheKey); + } + + public function testChildContextKeepsOriginalContextCacheKey() + { + $foobar = new Dummy(); + $foobar->foo = new EmptyDummy(); + $foobar->bar = 'bar'; + $foobar->baz = 'baz'; + + $normalizer = new class() extends AbstractObjectNormalizerDummy { + public $childContextCacheKey; + + protected function extractAttributes(object $object, string $format = null, array $context = []): array + { + return array_keys((array) $object); + } + + protected function getAttributeValue(object $object, string $attribute, string $format = null, array $context = []): mixed + { + return $object->{$attribute}; + } + + protected function createChildContext(array $parentContext, string $attribute, ?string $format): array + { + $childContext = parent::createChildContext($parentContext, $attribute, $format); + $this->childContextCacheKey = $childContext['cache_key']; + + return $childContext; + } + }; + + $serializer = new Serializer([$normalizer]); + $serializer->normalize($foobar, null, ['cache_key' => 'hardcoded', 'iri' => '/dummy/1']); + + $this->assertSame('hardcoded-foo', $normalizer->childContextCacheKey); + } + + public function testChildContextCacheKeyStaysFalseWhenOriginalCacheKeyIsFalse() + { + $foobar = new Dummy(); + $foobar->foo = new EmptyDummy(); + $foobar->bar = 'bar'; + $foobar->baz = 'baz'; + + $normalizer = new class() extends AbstractObjectNormalizerDummy { + public $childContextCacheKey; + + protected function extractAttributes(object $object, string $format = null, array $context = []): array + { + return array_keys((array) $object); + } + + protected function getAttributeValue(object $object, string $attribute, string $format = null, array $context = []): mixed + { + return $object->{$attribute}; + } + + protected function createChildContext(array $parentContext, string $attribute, ?string $format): array + { + $childContext = parent::createChildContext($parentContext, $attribute, $format); + $this->childContextCacheKey = $childContext['cache_key']; + + return $childContext; + } + }; + + $serializer = new Serializer([$normalizer]); + $serializer->normalize($foobar, null, ['cache_key' => false]); + + $this->assertFalse($normalizer->childContextCacheKey); + } } class AbstractObjectNormalizerDummy extends AbstractObjectNormalizer @@ -944,26 +1054,26 @@ public function getSupportedTypes(?string $format): array return ['*' => false]; } - protected function extractAttributes(object $object, string $format = null, array $context = []): array + protected function extractAttributes(object $object, ?string $format = null, array $context = []): array { return []; } - protected function getAttributeValue(object $object, string $attribute, string $format = null, array $context = []): mixed + protected function getAttributeValue(object $object, string $attribute, ?string $format = null, array $context = []): mixed { } - protected function setAttributeValue(object $object, string $attribute, $value, string $format = null, array $context = []): void + protected function setAttributeValue(object $object, string $attribute, $value, ?string $format = null, array $context = []): void { $object->$attribute = $value; } - protected function isAllowedAttribute($classOrObject, string $attribute, string $format = null, array $context = []): bool + protected function isAllowedAttribute($classOrObject, string $attribute, ?string $format = null, array $context = []): bool { return \in_array($attribute, ['foo', 'baz', 'quux', 'value']); } - public function instantiateObject(array &$data, string $class, array &$context, \ReflectionClass $reflectionClass, $allowedAttributes, string $format = null): object + public function instantiateObject(array &$data, string $class, array &$context, \ReflectionClass $reflectionClass, $allowedAttributes, ?string $format = null): object { return parent::instantiateObject($data, $class, $context, $reflectionClass, $allowedAttributes, $format); } @@ -1119,15 +1229,15 @@ public function getSupportedTypes(?string $format): array return ['*' => false]; } - protected function extractAttributes(object $object, string $format = null, array $context = []): array + protected function extractAttributes(object $object, ?string $format = null, array $context = []): array { } - protected function getAttributeValue(object $object, string $attribute, string $format = null, array $context = []): mixed + protected function getAttributeValue(object $object, string $attribute, ?string $format = null, array $context = []): mixed { } - protected function setAttributeValue(object $object, string $attribute, $value, string $format = null, array $context = []): void + protected function setAttributeValue(object $object, string $attribute, $value, ?string $format = null, array $context = []): void { if (property_exists($object, $attribute)) { $object->$attribute = $value; @@ -1217,7 +1327,7 @@ public function deserialize($data, string $type, string $format, array $context { } - public function denormalize($data, string $type, string $format = null, array $context = []): mixed + public function denormalize($data, string $type, ?string $format = null, array $context = []): mixed { foreach ($this->normalizers as $normalizer) { if ($normalizer instanceof DenormalizerInterface && $normalizer->supportsDenormalization($data, $type, $format, $context)) { @@ -1233,7 +1343,7 @@ public function getSupportedTypes(?string $format): array return ['*' => false]; } - public function supportsDenormalization($data, string $type, string $format = null, array $context = []): bool + public function supportsDenormalization($data, string $type, ?string $format = null, array $context = []): bool { return true; } @@ -1246,25 +1356,25 @@ public function getSupportedTypes(?string $format): array return ['*' => false]; } - protected function extractAttributes(object $object, string $format = null, array $context = []): array + protected function extractAttributes(object $object, ?string $format = null, array $context = []): array { } - protected function getAttributeValue(object $object, string $attribute, string $format = null, array $context = []): mixed + protected function getAttributeValue(object $object, string $attribute, ?string $format = null, array $context = []): mixed { } - protected function setAttributeValue(object $object, string $attribute, $value, string $format = null, array $context = []): void + protected function setAttributeValue(object $object, string $attribute, $value, ?string $format = null, array $context = []): void { $object->$attribute = $value; } - protected function isAllowedAttribute($classOrObject, string $attribute, string $format = null, array $context = []): bool + protected function isAllowedAttribute($classOrObject, string $attribute, ?string $format = null, array $context = []): bool { return true; } - public function instantiateObject(array &$data, string $class, array &$context, \ReflectionClass $reflectionClass, $allowedAttributes, string $format = null): object + public function instantiateObject(array &$data, string $class, array &$context, \ReflectionClass $reflectionClass, $allowedAttributes, ?string $format = null): object { return parent::instantiateObject($data, $class, $context, $reflectionClass, $allowedAttributes, $format); } @@ -1285,7 +1395,7 @@ class ArrayDenormalizerDummy implements DenormalizerInterface, SerializerAwareIn /** * @throws NotNormalizableValueException */ - public function denormalize($data, string $type, string $format = null, array $context = []): mixed + public function denormalize($data, string $type, ?string $format = null, array $context = []): mixed { $serializer = $this->serializer; $type = substr($type, 0, -2); @@ -1302,7 +1412,7 @@ public function getSupportedTypes(?string $format): array return $this->serializer->getSupportedTypes($format); } - public function supportsDenormalization($data, string $type, string $format = null, array $context = []): bool + public function supportsDenormalization($data, string $type, ?string $format = null, array $context = []): bool { return str_ends_with($type, '[]') && $this->serializer->supportsDenormalization($data, substr($type, 0, -2), $format, $context); diff --git a/src/Symfony/Component/Serializer/Tests/Normalizer/ConstraintViolationListNormalizerTest.php b/src/Symfony/Component/Serializer/Tests/Normalizer/ConstraintViolationListNormalizerTest.php index b7a4273f2c9c9..bb69392f58abf 100644 --- a/src/Symfony/Component/Serializer/Tests/Normalizer/ConstraintViolationListNormalizerTest.php +++ b/src/Symfony/Component/Serializer/Tests/Normalizer/ConstraintViolationListNormalizerTest.php @@ -116,7 +116,7 @@ public function testNormalizeWithNameConverter() /** * @dataProvider payloadFieldsProvider */ - public function testNormalizePayloadFields($fields, array $expected = null) + public function testNormalizePayloadFields($fields, ?array $expected = null) { $constraint = new NotNull(); $constraint->payload = ['severity' => 'warning', 'anotherField2' => 'aValue']; diff --git a/src/Symfony/Component/Serializer/Tests/Normalizer/DateTimeNormalizerTest.php b/src/Symfony/Component/Serializer/Tests/Normalizer/DateTimeNormalizerTest.php index e65b6f67dae0d..5dbf36fbe8923 100644 --- a/src/Symfony/Component/Serializer/Tests/Normalizer/DateTimeNormalizerTest.php +++ b/src/Symfony/Component/Serializer/Tests/Normalizer/DateTimeNormalizerTest.php @@ -154,6 +154,72 @@ public static function normalizeUsingTimeZonePassedInContextAndExpectedFormatWit ]; } + /** + * @dataProvider provideNormalizeUsingCastCases + */ + public function testNormalizeUsingCastPassedInConstructor(\DateTimeInterface $value, string $format, ?string $cast, string|int|float $expectedResult) + { + $normalizer = new DateTimeNormalizer([DateTimeNormalizer::CAST_KEY => $cast]); + + $this->assertSame($normalizer->normalize($value, null, [DateTimeNormalizer::FORMAT_KEY => $format]), $expectedResult); + } + + /** + * @dataProvider provideNormalizeUsingCastCases + */ + public function testNormalizeUsingCastPassedInContext(\DateTimeInterface $value, string $format, ?string $cast, string|int|float $expectedResult) + { + $this->assertSame($this->normalizer->normalize($value, null, [DateTimeNormalizer::FORMAT_KEY => $format, DateTimeNormalizer::CAST_KEY => $cast]), $expectedResult); + } + + /** + * @return iterable + */ + public static function provideNormalizeUsingCastCases(): iterable + { + yield [ + \DateTimeImmutable::createFromFormat('U', '1703071202'), + 'Y', + null, + '2023', + ]; + + yield [ + \DateTimeImmutable::createFromFormat('U', '1703071202'), + 'Y', + 'int', + 2023, + ]; + + yield [ + \DateTimeImmutable::createFromFormat('U', '1703071202'), + 'Ymd', + 'int', + 20231220, + ]; + + yield [ + \DateTimeImmutable::createFromFormat('U', '1703071202'), + 'Y', + 'int', + 2023, + ]; + + yield [ + \DateTimeImmutable::createFromFormat('U.v', '1703071202.388'), + 'U.v', + 'float', + 1703071202.388, + ]; + + yield [ + \DateTimeImmutable::createFromFormat('U.u', '1703071202.388811'), + 'U.u', + 'float', + 1703071202.388811, + ]; + } + public function testNormalizeInvalidObjectThrowsException() { $this->expectException(InvalidArgumentException::class); diff --git a/src/Symfony/Component/Serializer/Tests/Normalizer/Features/CallbacksObject.php b/src/Symfony/Component/Serializer/Tests/Normalizer/Features/CallbacksObject.php index 19ad3f547c412..d484f8b8a7c71 100644 --- a/src/Symfony/Component/Serializer/Tests/Normalizer/Features/CallbacksObject.php +++ b/src/Symfony/Component/Serializer/Tests/Normalizer/Features/CallbacksObject.php @@ -20,7 +20,7 @@ class CallbacksObject */ public $foo; - public function __construct($bar = null, string $foo = null) + public function __construct($bar = null, ?string $foo = null) { $this->bar = $bar; $this->foo = $foo; diff --git a/src/Symfony/Component/Serializer/Tests/Normalizer/Features/GroupsTestTrait.php b/src/Symfony/Component/Serializer/Tests/Normalizer/Features/GroupsTestTrait.php index 08d5e065a6440..ba4d7632385c2 100644 --- a/src/Symfony/Component/Serializer/Tests/Normalizer/Features/GroupsTestTrait.php +++ b/src/Symfony/Component/Serializer/Tests/Normalizer/Features/GroupsTestTrait.php @@ -31,13 +31,18 @@ public function testGroupsNormalize() $obj = new GroupDummy(); $obj->setFoo('foo'); $obj->setBar('bar'); + $obj->setQuux('quux'); $obj->setFooBar('fooBar'); $obj->setSymfony('symfony'); $obj->setKevin('kevin'); $obj->setCoopTilleuls('coopTilleuls'); + $obj->setDefault('default'); + $obj->setClassName('className'); $this->assertEquals([ 'bar' => 'bar', + 'default' => 'default', + 'className' => 'className', ], $normalizer->normalize($obj, null, ['groups' => ['c']])); $this->assertEquals([ @@ -47,7 +52,26 @@ public function testGroupsNormalize() 'bar' => 'bar', 'kevin' => 'kevin', 'coopTilleuls' => 'coopTilleuls', + 'default' => 'default', + 'className' => 'className', ], $normalizer->normalize($obj, null, ['groups' => ['a', 'c']])); + + $this->assertEquals([ + 'default' => 'default', + 'className' => 'className', + ], $normalizer->normalize($obj, null, ['groups' => ['unknown']])); + + $this->assertEquals([ + 'quux' => 'quux', + 'symfony' => 'symfony', + 'foo' => 'foo', + 'fooBar' => 'fooBar', + 'bar' => 'bar', + 'kevin' => 'kevin', + 'coopTilleuls' => 'coopTilleuls', + 'default' => 'default', + 'className' => 'className', + ], $normalizer->normalize($obj)); } public function testGroupsDenormalize() @@ -55,27 +79,49 @@ public function testGroupsDenormalize() $normalizer = $this->getDenormalizerForGroups(); $obj = new GroupDummy(); - $obj->setFoo('foo'); + $obj->setDefault('default'); + $obj->setClassName('className'); - $data = ['foo' => 'foo', 'bar' => 'bar']; + $data = [ + 'foo' => 'foo', + 'bar' => 'bar', + 'quux' => 'quux', + 'default' => 'default', + 'className' => 'className', + ]; - $normalized = $normalizer->denormalize( + $denormalized = $normalizer->denormalize( + $data, + GroupDummy::class, + null, + ['groups' => ['unknown']] + ); + $this->assertEquals($obj, $denormalized); + + $obj->setFoo('foo'); + + $denormalized = $normalizer->denormalize( $data, GroupDummy::class, null, ['groups' => ['a']] ); - $this->assertEquals($obj, $normalized); + $this->assertEquals($obj, $denormalized); $obj->setBar('bar'); - $normalized = $normalizer->denormalize( + $denormalized = $normalizer->denormalize( $data, GroupDummy::class, null, ['groups' => ['a', 'b']] ); - $this->assertEquals($obj, $normalized); + $this->assertEquals($obj, $denormalized); + + $obj->setQuux('quux'); + + $denormalized = $normalizer->denormalize($data, GroupDummy::class); + $this->assertEquals($obj, $denormalized); } public function testNormalizeNoPropertyInGroup() @@ -84,7 +130,12 @@ public function testNormalizeNoPropertyInGroup() $obj = new GroupDummy(); $obj->setFoo('foo'); + $obj->setDefault('default'); + $obj->setClassName('className'); - $this->assertEquals([], $normalizer->normalize($obj, null, ['groups' => ['notExist']])); + $this->assertEquals([ + 'default' => 'default', + 'className' => 'className', + ], $normalizer->normalize($obj, null, ['groups' => ['notExist']])); } } diff --git a/src/Symfony/Component/Serializer/Tests/Normalizer/GetSetMethodNormalizerTest.php b/src/Symfony/Component/Serializer/Tests/Normalizer/GetSetMethodNormalizerTest.php index 8f28999feb006..8000dea19fe0f 100644 --- a/src/Symfony/Component/Serializer/Tests/Normalizer/GetSetMethodNormalizerTest.php +++ b/src/Symfony/Component/Serializer/Tests/Normalizer/GetSetMethodNormalizerTest.php @@ -295,6 +295,8 @@ public function testGroupsNormalizeWithNameConverter() 'bar' => null, 'foo_bar' => '@dunglas', 'symfony' => '@coopTilleuls', + 'default' => null, + 'class_name' => null, ], $this->normalizer->normalize($obj, null, [GetSetMethodNormalizer::GROUPS => ['name_converter']]) ); diff --git a/src/Symfony/Component/Serializer/Tests/Normalizer/ObjectNormalizerTest.php b/src/Symfony/Component/Serializer/Tests/Normalizer/ObjectNormalizerTest.php index 05b1891f50119..fa47995da7882 100644 --- a/src/Symfony/Component/Serializer/Tests/Normalizer/ObjectNormalizerTest.php +++ b/src/Symfony/Component/Serializer/Tests/Normalizer/ObjectNormalizerTest.php @@ -88,7 +88,7 @@ protected function setUp(): void $this->createNormalizer(); } - private function createNormalizer(array $defaultContext = [], ClassMetadataFactoryInterface $classMetadataFactory = null): void + private function createNormalizer(array $defaultContext = [], ?ClassMetadataFactoryInterface $classMetadataFactory = null): void { $this->serializer = $this->createMock(ObjectSerializerNormalizer::class); $this->normalizer = new ObjectNormalizer($classMetadataFactory, null, null, null, null, null, $defaultContext); @@ -482,6 +482,8 @@ public function testGroupsNormalizeWithNameConverter() 'bar' => null, 'foo_bar' => '@dunglas', 'symfony' => '@coopTilleuls', + 'default' => null, + 'class_name' => null, ], $this->normalizer->normalize($obj, null, [ObjectNormalizer::GROUPS => ['name_converter']]) ); @@ -758,12 +760,12 @@ public function testDenormalizeFalsePseudoType() public function testAdvancedNameConverter() { $nameConverter = new class() implements AdvancedNameConverterInterface { - public function normalize(string $propertyName, string $class = null, string $format = null, array $context = []): string + public function normalize(string $propertyName, ?string $class = null, ?string $format = null, array $context = []): string { return sprintf('%s-%s-%s-%s', $propertyName, $class, $format, $context['foo']); } - public function denormalize(string $propertyName, string $class = null, string $format = null, array $context = []): string + public function denormalize(string $propertyName, ?string $class = null, ?string $format = null, array $context = []): string { return sprintf('%s-%s-%s-%s', $propertyName, $class, $format, $context['foo']); } @@ -1075,7 +1077,7 @@ class DummyWithConstructorObjectAndDefaultValue private $foo; private $inner; - public function __construct($foo = 'a', ObjectInner $inner = null) + public function __construct($foo = 'a', ?ObjectInner $inner = null) { $this->foo = $foo; $this->inner = $inner; diff --git a/src/Symfony/Component/Serializer/Tests/Normalizer/PropertyNormalizerTest.php b/src/Symfony/Component/Serializer/Tests/Normalizer/PropertyNormalizerTest.php index 7ba3d95eb46e7..04a9afaf8bde0 100644 --- a/src/Symfony/Component/Serializer/Tests/Normalizer/PropertyNormalizerTest.php +++ b/src/Symfony/Component/Serializer/Tests/Normalizer/PropertyNormalizerTest.php @@ -184,7 +184,18 @@ public function testNormalizeWithParentClass() $group->setKevin('Kevin'); $group->setCoopTilleuls('coop'); $this->assertEquals( - ['foo' => 'foo', 'bar' => 'bar', 'quux' => 'quux', 'kevin' => 'Kevin', 'coopTilleuls' => 'coop', 'fooBar' => null, 'symfony' => null, 'baz' => 'baz'], + [ + 'foo' => 'foo', + 'bar' => 'bar', + 'quux' => 'quux', + 'kevin' => 'Kevin', + 'coopTilleuls' => 'coop', + 'fooBar' => null, + 'symfony' => null, + 'baz' => 'baz', + 'default' => null, + 'className' => null, + ], $this->normalizer->normalize($group, 'any') ); } @@ -303,6 +314,8 @@ public function testGroupsNormalizeWithNameConverter() 'bar' => null, 'foo_bar' => '@dunglas', 'symfony' => '@coopTilleuls', + 'default' => null, + 'class_name' => null, ], $this->normalizer->normalize($obj, null, [PropertyNormalizer::GROUPS => ['name_converter']]) ); diff --git a/src/Symfony/Component/Serializer/Tests/Normalizer/TestDenormalizer.php b/src/Symfony/Component/Serializer/Tests/Normalizer/TestDenormalizer.php index 70518c797951d..cd51b3c19c322 100644 --- a/src/Symfony/Component/Serializer/Tests/Normalizer/TestDenormalizer.php +++ b/src/Symfony/Component/Serializer/Tests/Normalizer/TestDenormalizer.php @@ -20,7 +20,7 @@ */ class TestDenormalizer implements DenormalizerInterface { - public function denormalize($data, string $type, string $format = null, array $context = []): mixed + public function denormalize($data, string $type, ?string $format = null, array $context = []): mixed { } @@ -29,7 +29,7 @@ public function getSupportedTypes(?string $format): array return ['*' => false]; } - public function supportsDenormalization($data, string $type, string $format = null, array $context = []): bool + public function supportsDenormalization($data, string $type, ?string $format = null, array $context = []): bool { return true; } diff --git a/src/Symfony/Component/Serializer/Tests/Normalizer/TestNormalizer.php b/src/Symfony/Component/Serializer/Tests/Normalizer/TestNormalizer.php index 26e5917e72ed5..941fef42b067a 100644 --- a/src/Symfony/Component/Serializer/Tests/Normalizer/TestNormalizer.php +++ b/src/Symfony/Component/Serializer/Tests/Normalizer/TestNormalizer.php @@ -20,7 +20,7 @@ */ class TestNormalizer implements NormalizerInterface { - public function normalize($object, string $format = null, array $context = []): array|string|int|float|bool|\ArrayObject|null + public function normalize($object, ?string $format = null, array $context = []): array|string|int|float|bool|\ArrayObject|null { return null; } @@ -30,7 +30,7 @@ public function getSupportedTypes(?string $format): array return ['*' => false]; } - public function supportsNormalization($data, string $format = null, array $context = []): bool + public function supportsNormalization($data, ?string $format = null, array $context = []): bool { return true; } diff --git a/src/Symfony/Component/Serializer/Tests/Normalizer/TranslatableNormalizerTest.php b/src/Symfony/Component/Serializer/Tests/Normalizer/TranslatableNormalizerTest.php index 54338b07e55be..3499521b65c80 100644 --- a/src/Symfony/Component/Serializer/Tests/Normalizer/TranslatableNormalizerTest.php +++ b/src/Symfony/Component/Serializer/Tests/Normalizer/TranslatableNormalizerTest.php @@ -56,7 +56,7 @@ public function testNormalizeWithNormalizationLocalePassedInConstructor() class TestMessage implements TranslatableInterface { - public function trans(TranslatorInterface $translator, string $locale = null): string + public function trans(TranslatorInterface $translator, ?string $locale = null): string { return 'key_'.($locale ?? 'null'); } diff --git a/src/Symfony/Component/Serializer/composer.json b/src/Symfony/Component/Serializer/composer.json index 627bfccaf3061..cad1ab188c8f1 100644 --- a/src/Symfony/Component/Serializer/composer.json +++ b/src/Symfony/Component/Serializer/composer.json @@ -34,7 +34,7 @@ "symfony/messenger": "^6.4|^7.0", "symfony/mime": "^6.4|^7.0", "symfony/property-access": "^6.4|^7.0", - "symfony/property-info": "^6.4|^7.0", + "symfony/property-info": "^7.1", "symfony/translation-contracts": "^2.5|^3", "symfony/uid": "^6.4|^7.0", "symfony/validator": "^6.4|^7.0", @@ -47,7 +47,7 @@ "phpdocumentor/type-resolver": "<1.4.0", "symfony/dependency-injection": "<6.4", "symfony/property-access": "<6.4", - "symfony/property-info": "<6.4", + "symfony/property-info": "<7.1", "symfony/uid": "<6.4", "symfony/validator": "<6.4", "symfony/yaml": "<6.4" diff --git a/src/Symfony/Component/Stopwatch/Section.php b/src/Symfony/Component/Stopwatch/Section.php index 280036733df6f..2a34db16f3667 100644 --- a/src/Symfony/Component/Stopwatch/Section.php +++ b/src/Symfony/Component/Stopwatch/Section.php @@ -36,7 +36,7 @@ class Section * @param float|null $origin Set the origin of the events in this section, use null to set their origin to their start time * @param bool $morePrecision If true, time is stored as float to keep the original microsecond precision */ - public function __construct(float $origin = null, bool $morePrecision = false) + public function __construct(?float $origin = null, bool $morePrecision = false) { $this->origin = $origin; $this->morePrecision = $morePrecision; diff --git a/src/Symfony/Component/Stopwatch/Stopwatch.php b/src/Symfony/Component/Stopwatch/Stopwatch.php index bf65ac3fbcde4..b50ca1b3cda41 100644 --- a/src/Symfony/Component/Stopwatch/Stopwatch.php +++ b/src/Symfony/Component/Stopwatch/Stopwatch.php @@ -59,7 +59,7 @@ public function getSections(): array * * @throws \LogicException When the section to re-open is not reachable */ - public function openSection(string $id = null): void + public function openSection(?string $id = null): void { $current = end($this->activeSections); @@ -96,7 +96,7 @@ public function stopSection(string $id): void /** * Starts an event. */ - public function start(string $name, string $category = null): StopwatchEvent + public function start(string $name, ?string $category = null): StopwatchEvent { return end($this->activeSections)->startEvent($name, $category); } diff --git a/src/Symfony/Component/Stopwatch/StopwatchEvent.php b/src/Symfony/Component/Stopwatch/StopwatchEvent.php index 4492d2d138dc5..841bb3f488848 100644 --- a/src/Symfony/Component/Stopwatch/StopwatchEvent.php +++ b/src/Symfony/Component/Stopwatch/StopwatchEvent.php @@ -42,7 +42,7 @@ class StopwatchEvent * * @throws \InvalidArgumentException When the raw time is not valid */ - public function __construct(float $origin, string $category = null, bool $morePrecision = false, string $name = null) + public function __construct(float $origin, ?string $category = null, bool $morePrecision = false, ?string $name = null) { $this->origin = $this->formatTime($origin); $this->category = \is_string($category) ? $category : 'default'; diff --git a/src/Symfony/Component/Stopwatch/Tests/StopwatchTest.php b/src/Symfony/Component/Stopwatch/Tests/StopwatchTest.php index 68585d2f8e3c6..f1e2270018447 100644 --- a/src/Symfony/Component/Stopwatch/Tests/StopwatchTest.php +++ b/src/Symfony/Component/Stopwatch/Tests/StopwatchTest.php @@ -156,6 +156,19 @@ public function testReopenASection() $this->assertCount(2, $events['__section__']->getPeriods()); } + public function testLap() + { + $stopwatch = new Stopwatch(); + + $stopwatch->start('foo'); + $stopwatch->lap('foo'); + $stopwatch->stop('foo'); + + $event = $stopwatch->getEvent('foo'); + + $this->assertCount(2, $event->getPeriods()); + } + public function testReopenANewSectionShouldThrowAnException() { $this->expectException(\LogicException::class); diff --git a/src/Symfony/Component/String/AbstractString.php b/src/Symfony/Component/String/AbstractString.php index 10231a217487c..253d2dcb64d82 100644 --- a/src/Symfony/Component/String/AbstractString.php +++ b/src/Symfony/Component/String/AbstractString.php @@ -383,7 +383,7 @@ public function isEmpty(): bool return '' === $this->string; } - abstract public function join(array $strings, string $lastGlue = null): static; + abstract public function join(array $strings, ?string $lastGlue = null): static; public function jsonSerialize(): string { @@ -429,16 +429,16 @@ abstract public function replaceMatches(string $fromRegexp, string|callable $to) abstract public function reverse(): static; - abstract public function slice(int $start = 0, int $length = null): static; + abstract public function slice(int $start = 0, ?int $length = null): static; abstract public function snake(): static; - abstract public function splice(string $replacement, int $start = 0, int $length = null): static; + abstract public function splice(string $replacement, int $start = 0, ?int $length = null): static; /** * @return static[] */ - public function split(string $delimiter, int $limit = null, int $flags = null): array + public function split(string $delimiter, ?int $limit = null, ?int $flags = null): array { if (null === $flags) { throw new \TypeError('Split behavior when $flags is null must be implemented by child classes.'); @@ -495,7 +495,7 @@ public function startsWith(string|iterable $prefix): bool abstract public function title(bool $allWords = false): static; - public function toByteString(string $toEncoding = null): ByteString + public function toByteString(?string $toEncoding = null): ByteString { $b = new ByteString(); diff --git a/src/Symfony/Component/String/AbstractUnicodeString.php b/src/Symfony/Component/String/AbstractUnicodeString.php index af0532999018b..d18da29bb6f34 100644 --- a/src/Symfony/Component/String/AbstractUnicodeString.php +++ b/src/Symfony/Component/String/AbstractUnicodeString.php @@ -198,7 +198,7 @@ public function folded(bool $compat = true): static return $str; } - public function join(array $strings, string $lastGlue = null): static + public function join(array $strings, ?string $lastGlue = null): static { $str = clone $this; diff --git a/src/Symfony/Component/String/ByteString.php b/src/Symfony/Component/String/ByteString.php index f5050ddfb8ff4..3ebe43c10a64a 100644 --- a/src/Symfony/Component/String/ByteString.php +++ b/src/Symfony/Component/String/ByteString.php @@ -42,7 +42,7 @@ public function __construct(string $string = '') * Copyright (c) 2004-2020, Facebook, Inc. (https://www.facebook.com/) */ - public static function fromRandom(int $length = 16, string $alphabet = null): self + public static function fromRandom(int $length = 16, ?string $alphabet = null): self { if ($length <= 0) { throw new InvalidArgumentException(sprintf('A strictly positive length is expected, "%d" given.', $length)); @@ -205,7 +205,7 @@ public function isUtf8(): bool return '' === $this->string || preg_match('//u', $this->string); } - public function join(array $strings, string $lastGlue = null): static + public function join(array $strings, ?string $lastGlue = null): static { $str = clone $this; @@ -332,7 +332,7 @@ public function reverse(): static return $str; } - public function slice(int $start = 0, int $length = null): static + public function slice(int $start = 0, ?int $length = null): static { $str = clone $this; $str->string = (string) substr($this->string, $start, $length ?? \PHP_INT_MAX); @@ -348,7 +348,7 @@ public function snake(): static return $str; } - public function splice(string $replacement, int $start = 0, int $length = null): static + public function splice(string $replacement, int $start = 0, ?int $length = null): static { $str = clone $this; $str->string = substr_replace($this->string, $replacement, $start, $length ?? \PHP_INT_MAX); @@ -356,7 +356,7 @@ public function splice(string $replacement, int $start = 0, int $length = null): return $str; } - public function split(string $delimiter, int $limit = null, int $flags = null): array + public function split(string $delimiter, ?int $limit = null, ?int $flags = null): array { if (1 > $limit ??= \PHP_INT_MAX) { throw new InvalidArgumentException('Split limit must be a positive integer.'); @@ -402,12 +402,12 @@ public function title(bool $allWords = false): static return $str; } - public function toUnicodeString(string $fromEncoding = null): UnicodeString + public function toUnicodeString(?string $fromEncoding = null): UnicodeString { return new UnicodeString($this->toCodePointString($fromEncoding)->string); } - public function toCodePointString(string $fromEncoding = null): CodePointString + public function toCodePointString(?string $fromEncoding = null): CodePointString { $u = new CodePointString(); diff --git a/src/Symfony/Component/String/CodePointString.php b/src/Symfony/Component/String/CodePointString.php index f5c900fb2c73f..337bfc12aba5e 100644 --- a/src/Symfony/Component/String/CodePointString.php +++ b/src/Symfony/Component/String/CodePointString.php @@ -186,7 +186,7 @@ public function replace(string $from, string $to): static return $str; } - public function slice(int $start = 0, int $length = null): static + public function slice(int $start = 0, ?int $length = null): static { $str = clone $this; $str->string = mb_substr($this->string, $start, $length, 'UTF-8'); @@ -194,7 +194,7 @@ public function slice(int $start = 0, int $length = null): static return $str; } - public function splice(string $replacement, int $start = 0, int $length = null): static + public function splice(string $replacement, int $start = 0, ?int $length = null): static { if (!preg_match('//u', $replacement)) { throw new InvalidArgumentException('Invalid UTF-8 string.'); @@ -208,7 +208,7 @@ public function splice(string $replacement, int $start = 0, int $length = null): return $str; } - public function split(string $delimiter, int $limit = null, int $flags = null): array + public function split(string $delimiter, ?int $limit = null, ?int $flags = null): array { if (1 > $limit ??= \PHP_INT_MAX) { throw new InvalidArgumentException('Split limit must be a positive integer.'); diff --git a/src/Symfony/Component/String/Inflector/EnglishInflector.php b/src/Symfony/Component/String/Inflector/EnglishInflector.php index feea7f668df04..dc7b08e7ec9b5 100644 --- a/src/Symfony/Component/String/Inflector/EnglishInflector.php +++ b/src/Symfony/Component/String/Inflector/EnglishInflector.php @@ -166,6 +166,9 @@ final class EnglishInflector implements InflectorInterface // Fourth entry: Whether the suffix may succeed a consonant // Fifth entry: plural suffix, normal + // axes (axis) + ['sixa', 4, false, false, 'axes'], + // criterion (criteria) ['airetirc', 8, false, false, 'criterion'], @@ -288,6 +291,12 @@ final class EnglishInflector implements InflectorInterface // circuses (circus) ['suc', 3, true, true, 'cuses'], + // hippocampi (hippocampus) + ['supmacoppih', 11, false, false, 'hippocampi'], + + // campuses (campus) + ['sup', 3, true, true, 'puses'], + // status (status) ['sutats', 6, true, true, ['status', 'statuses']], @@ -384,6 +393,9 @@ final class EnglishInflector implements InflectorInterface // traffic 'ciffart', + + // aircraft + 'tfarcria', ]; public function singularize(string $plural): array diff --git a/src/Symfony/Component/String/LazyString.php b/src/Symfony/Component/String/LazyString.php index 4a303e19a6fe2..8f2bbbf80bd83 100644 --- a/src/Symfony/Component/String/LazyString.php +++ b/src/Symfony/Component/String/LazyString.php @@ -39,7 +39,7 @@ public static function fromCallable(callable|array $callback, mixed ...$argument $callback[1] ??= '__invoke'; } $value = $callback(...$arguments); - $callback = self::getPrettyName($callback); + $callback = !\is_scalar($value) && !$value instanceof \Stringable ? self::getPrettyName($callback) : 'callable'; $arguments = null; } diff --git a/src/Symfony/Component/String/Slugger/AsciiSlugger.php b/src/Symfony/Component/String/Slugger/AsciiSlugger.php index 4f428da9603f0..d2545329dfc2c 100644 --- a/src/Symfony/Component/String/Slugger/AsciiSlugger.php +++ b/src/Symfony/Component/String/Slugger/AsciiSlugger.php @@ -11,7 +11,7 @@ namespace Symfony\Component\String\Slugger; -use Symfony\Component\Intl\Transliterator\EmojiTransliterator; +use Symfony\Component\Emoji\EmojiTransliterator; use Symfony\Component\String\AbstractUnicodeString; use Symfony\Component\String\UnicodeString; use Symfony\Contracts\Translation\LocaleAwareInterface; @@ -69,7 +69,7 @@ class AsciiSlugger implements SluggerInterface, LocaleAwareInterface public function __construct( private ?string $defaultLocale = null, - array|\Closure $symbolsMap = null, + array|\Closure|null $symbolsMap = null, ) { $this->symbolsMap = $symbolsMap ?? $this->symbolsMap; } @@ -92,7 +92,7 @@ public function getLocale(): string public function withEmoji(bool|string $emoji = true): static { if (false !== $emoji && !class_exists(EmojiTransliterator::class)) { - throw new \LogicException(sprintf('You cannot use the "%s()" method as the "symfony/intl" package is not installed. Try running "composer require symfony/intl".', __METHOD__)); + throw new \LogicException(sprintf('You cannot use the "%s()" method as the "symfony/emoji" package is not installed. Try running "composer require symfony/emoji".', __METHOD__)); } $new = clone $this; @@ -101,7 +101,7 @@ public function withEmoji(bool|string $emoji = true): static return $new; } - public function slug(string $string, string $separator = '-', string $locale = null): AbstractUnicodeString + public function slug(string $string, string $separator = '-', ?string $locale = null): AbstractUnicodeString { $locale ??= $this->defaultLocale; diff --git a/src/Symfony/Component/String/Slugger/SluggerInterface.php b/src/Symfony/Component/String/Slugger/SluggerInterface.php index c679ed9331040..dd0d58102c6ea 100644 --- a/src/Symfony/Component/String/Slugger/SluggerInterface.php +++ b/src/Symfony/Component/String/Slugger/SluggerInterface.php @@ -23,5 +23,5 @@ interface SluggerInterface /** * Creates a slug for the given string and locale, using appropriate transliteration when needed. */ - public function slug(string $string, string $separator = '-', string $locale = null): AbstractUnicodeString; + public function slug(string $string, string $separator = '-', ?string $locale = null): AbstractUnicodeString; } diff --git a/src/Symfony/Component/String/Tests/AbstractAsciiTestCase.php b/src/Symfony/Component/String/Tests/AbstractAsciiTestCase.php index b4578c77802fa..eabe7b912bed5 100644 --- a/src/Symfony/Component/String/Tests/AbstractAsciiTestCase.php +++ b/src/Symfony/Component/String/Tests/AbstractAsciiTestCase.php @@ -46,7 +46,7 @@ public function testCreateFromEmptyString() /** * @dataProvider provideBytesAt */ - public function testBytesAt(array $expected, string $string, int $offset, int $form = null) + public function testBytesAt(array $expected, string $string, int $offset, ?int $form = null) { if (2 !== grapheme_strlen('च्छे') && 'नमस्ते' === $string) { $this->markTestSkipped('Skipping due to issue ICU-21661.'); @@ -319,7 +319,7 @@ public static function provideIndexOfLastIgnoreCase(): array /** * @dataProvider provideSplit */ - public function testSplit(string $string, string $delimiter, array $chunks, ?int $limit, int $flags = null) + public function testSplit(string $string, string $delimiter, array $chunks, ?int $limit, ?int $flags = null) { $this->assertEquals($chunks, static::createFromString($string)->split($delimiter, $limit, $flags)); } @@ -595,7 +595,7 @@ public static function provideTitle() /** * @dataProvider provideSlice */ - public function testSlice(string $expected, string $origin, int $start, int $length = null) + public function testSlice(string $expected, string $origin, int $start, ?int $length = null) { $this->assertEquals( static::createFromString($expected), @@ -623,7 +623,7 @@ public static function provideSlice() /** * @dataProvider provideSplice */ - public function testSplice(string $expected, int $start, int $length = null) + public function testSplice(string $expected, int $start, ?int $length = null) { $this->assertEquals( static::createFromString($expected), @@ -1081,7 +1081,7 @@ public static function provideSnake() /** * @dataProvider provideStartsWith */ - public function testStartsWith(bool $expected, string $origin, $prefix, int $form = null) + public function testStartsWith(bool $expected, string $origin, $prefix, ?int $form = null) { $instance = static::createFromString($origin); $instance = $form ? $instance->normalize($form) : $instance; @@ -1135,7 +1135,7 @@ public static function provideStartsWithIgnoreCase() /** * @dataProvider provideEndsWith */ - public function testEndsWith(bool $expected, string $origin, $suffix, int $form = null) + public function testEndsWith(bool $expected, string $origin, $suffix, ?int $form = null) { $instance = static::createFromString($origin); $instance = $form ? $instance->normalize($form) : $instance; diff --git a/src/Symfony/Component/String/Tests/AbstractUnicodeTestCase.php b/src/Symfony/Component/String/Tests/AbstractUnicodeTestCase.php index cefc6b1b820b9..cb69b7eb30980 100644 --- a/src/Symfony/Component/String/Tests/AbstractUnicodeTestCase.php +++ b/src/Symfony/Component/String/Tests/AbstractUnicodeTestCase.php @@ -120,7 +120,7 @@ public static function provideBytesAt(): array /** * @dataProvider provideCodePointsAt */ - public function testCodePointsAt(array $expected, string $string, int $offset, int $form = null) + public function testCodePointsAt(array $expected, string $string, int $offset, ?int $form = null) { if (2 !== grapheme_strlen('च्छे') && 'नमस्ते' === $string) { $this->markTestSkipped('Skipping due to issue ICU-21661.'); diff --git a/src/Symfony/Component/String/Tests/Inflector/EnglishInflectorTest.php b/src/Symfony/Component/String/Tests/Inflector/EnglishInflectorTest.php index cf66bf05b660c..51849fd42540a 100644 --- a/src/Symfony/Component/String/Tests/Inflector/EnglishInflectorTest.php +++ b/src/Symfony/Component/String/Tests/Inflector/EnglishInflectorTest.php @@ -178,6 +178,7 @@ public static function pluralizeProvider() ['access', 'accesses'], ['address', 'addresses'], ['agenda', 'agendas'], + ['aircraft', 'aircraft'], ['alumnus', 'alumni'], ['analysis', 'analyses'], ['antenna', 'antennas'], // antennae @@ -185,6 +186,7 @@ public static function pluralizeProvider() ['arch', 'arches'], ['atlas', 'atlases'], ['axe', 'axes'], + ['axis', 'axes'], ['baby', 'babies'], ['bacterium', 'bacteria'], ['base', 'bases'], @@ -296,6 +298,8 @@ public static function pluralizeProvider() ['waltz', 'waltzes'], ['wife', 'wives'], ['icon', 'icons'], + ['hippocampus', 'hippocampi'], + ['campus', 'campuses'], // test casing: if the first letter was uppercase, it should remain so ['Man', 'Men'], diff --git a/src/Symfony/Component/String/Tests/Slugger/AsciiSluggerTest.php b/src/Symfony/Component/String/Tests/Slugger/AsciiSluggerTest.php index 3544367b647fc..c21a3ddf250a6 100644 --- a/src/Symfony/Component/String/Tests/Slugger/AsciiSluggerTest.php +++ b/src/Symfony/Component/String/Tests/Slugger/AsciiSluggerTest.php @@ -9,7 +9,7 @@ * file that was distributed with this source code. */ -namespace Symfony\Component\String; +namespace Symfony\Component\String\Tests\Slugger; use PHPUnit\Framework\TestCase; use Symfony\Component\String\Slugger\AsciiSlugger; @@ -19,7 +19,7 @@ class AsciiSluggerTest extends TestCase /** * @dataProvider provideSlugTests */ - public function testSlug(string $expected, string $string, string $separator = '-', string $locale = null) + public function testSlug(string $expected, string $string, string $separator = '-', ?string $locale = null) { $slugger = new AsciiSlugger(); diff --git a/src/Symfony/Component/String/UnicodeString.php b/src/Symfony/Component/String/UnicodeString.php index bc09627cf5a5d..4b16caf9fc97e 100644 --- a/src/Symfony/Component/String/UnicodeString.php +++ b/src/Symfony/Component/String/UnicodeString.php @@ -185,7 +185,7 @@ public function indexOfLast(string|iterable|AbstractString $needle, int $offset return false === $i ? null : $i; } - public function join(array $strings, string $lastGlue = null): static + public function join(array $strings, ?string $lastGlue = null): static { $str = parent::join($strings, $lastGlue); normalizer_is_normalized($str->string) ?: $str->string = normalizer_normalize($str->string); @@ -272,7 +272,7 @@ public function replaceMatches(string $fromRegexp, string|callable $to): static return $str; } - public function slice(int $start = 0, int $length = null): static + public function slice(int $start = 0, ?int $length = null): static { $str = clone $this; @@ -281,7 +281,7 @@ public function slice(int $start = 0, int $length = null): static return $str; } - public function splice(string $replacement, int $start = 0, int $length = null): static + public function splice(string $replacement, int $start = 0, ?int $length = null): static { $str = clone $this; @@ -302,7 +302,7 @@ public function splice(string $replacement, int $start = 0, int $length = null): return $str; } - public function split(string $delimiter, int $limit = null, int $flags = null): array + public function split(string $delimiter, ?int $limit = null, ?int $flags = null): array { if (1 > $limit ??= 2147483647) { throw new InvalidArgumentException('Split limit must be a positive integer.'); diff --git a/src/Symfony/Component/String/composer.json b/src/Symfony/Component/String/composer.json index 26ce26da3fe47..10d0ee620e4da 100644 --- a/src/Symfony/Component/String/composer.json +++ b/src/Symfony/Component/String/composer.json @@ -24,8 +24,9 @@ }, "require-dev": { "symfony/error-handler": "^6.4|^7.0", - "symfony/intl": "^6.4|^7.0", + "symfony/emoji": "^7.1", "symfony/http-client": "^6.4|^7.0", + "symfony/intl": "^6.4|^7.0", "symfony/translation-contracts": "^2.5|^3.0", "symfony/var-exporter": "^6.4|^7.0" }, diff --git a/src/Symfony/Component/Translation/Bridge/Crowdin/Tests/CrowdinProviderTest.php b/src/Symfony/Component/Translation/Bridge/Crowdin/Tests/CrowdinProviderTest.php index 44c374d273931..55ec74cda4cda 100644 --- a/src/Symfony/Component/Translation/Bridge/Crowdin/Tests/CrowdinProviderTest.php +++ b/src/Symfony/Component/Translation/Bridge/Crowdin/Tests/CrowdinProviderTest.php @@ -31,7 +31,7 @@ class CrowdinProviderTest extends ProviderTestCase { - public static function createProvider(HttpClientInterface $client, LoaderInterface $loader, LoggerInterface $logger, string $defaultLocale, string $endpoint, TranslatorBagInterface $translatorBag = null): ProviderInterface + public static function createProvider(HttpClientInterface $client, LoaderInterface $loader, LoggerInterface $logger, string $defaultLocale, string $endpoint, ?TranslatorBagInterface $translatorBag = null): ProviderInterface { return new CrowdinProvider($client, $loader, $logger, new XliffFileDumper(), $defaultLocale, $endpoint); } diff --git a/src/Symfony/Component/Translation/Bridge/Loco/Tests/LocoProviderTest.php b/src/Symfony/Component/Translation/Bridge/Loco/Tests/LocoProviderTest.php index 9802204d2959c..5ce4cf03f3541 100644 --- a/src/Symfony/Component/Translation/Bridge/Loco/Tests/LocoProviderTest.php +++ b/src/Symfony/Component/Translation/Bridge/Loco/Tests/LocoProviderTest.php @@ -30,7 +30,7 @@ class LocoProviderTest extends ProviderTestCase { - public static function createProvider(HttpClientInterface $client, LoaderInterface $loader, LoggerInterface $logger, string $defaultLocale, string $endpoint, TranslatorBagInterface $translatorBag = null): ProviderInterface + public static function createProvider(HttpClientInterface $client, LoaderInterface $loader, LoggerInterface $logger, string $defaultLocale, string $endpoint, ?TranslatorBagInterface $translatorBag = null): ProviderInterface { return new LocoProvider($client, $loader, $logger, $defaultLocale, $endpoint, $translatorBag ?? new TranslatorBag()); } diff --git a/src/Symfony/Component/Translation/Bridge/Loco/Tests/LocoProviderWithoutTranslatorBagTest.php b/src/Symfony/Component/Translation/Bridge/Loco/Tests/LocoProviderWithoutTranslatorBagTest.php index 26867bbfd1cab..81b0da879f6d2 100644 --- a/src/Symfony/Component/Translation/Bridge/Loco/Tests/LocoProviderWithoutTranslatorBagTest.php +++ b/src/Symfony/Component/Translation/Bridge/Loco/Tests/LocoProviderWithoutTranslatorBagTest.php @@ -25,7 +25,7 @@ class LocoProviderWithoutTranslatorBagTest extends LocoProviderTest { - public static function createProvider(HttpClientInterface $client, LoaderInterface $loader, LoggerInterface $logger, string $defaultLocale, string $endpoint, TranslatorBagInterface $translatorBag = null): ProviderInterface + public static function createProvider(HttpClientInterface $client, LoaderInterface $loader, LoggerInterface $logger, string $defaultLocale, string $endpoint, ?TranslatorBagInterface $translatorBag = null): ProviderInterface { return new LocoProvider($client, $loader, $logger, $defaultLocale, $endpoint, null); } diff --git a/src/Symfony/Component/Translation/Bridge/Lokalise/Tests/LokaliseProviderTest.php b/src/Symfony/Component/Translation/Bridge/Lokalise/Tests/LokaliseProviderTest.php index 617a43adf6413..bb03df7519c95 100644 --- a/src/Symfony/Component/Translation/Bridge/Lokalise/Tests/LokaliseProviderTest.php +++ b/src/Symfony/Component/Translation/Bridge/Lokalise/Tests/LokaliseProviderTest.php @@ -31,7 +31,7 @@ class LokaliseProviderTest extends ProviderTestCase { - public static function createProvider(HttpClientInterface $client, LoaderInterface $loader, LoggerInterface $logger, string $defaultLocale, string $endpoint, TranslatorBagInterface $translatorBag = null): ProviderInterface + public static function createProvider(HttpClientInterface $client, LoaderInterface $loader, LoggerInterface $logger, string $defaultLocale, string $endpoint, ?TranslatorBagInterface $translatorBag = null): ProviderInterface { return new LokaliseProvider($client, $loader, $logger, $defaultLocale, $endpoint); } diff --git a/src/Symfony/Component/Translation/Bridge/Phrase/Tests/PhraseProviderTest.php b/src/Symfony/Component/Translation/Bridge/Phrase/Tests/PhraseProviderTest.php index fd1c220935b7f..453a5b3528786 100644 --- a/src/Symfony/Component/Translation/Bridge/Phrase/Tests/PhraseProviderTest.php +++ b/src/Symfony/Component/Translation/Bridge/Phrase/Tests/PhraseProviderTest.php @@ -1119,7 +1119,7 @@ private function getInitLocaleResponseMock(): \Closure }; } - private function createProvider(MockHttpClient $httpClient = null, string $endpoint = null, XliffFileDumper $dumper = null, bool $isFallbackLocaleEnabled = false): ProviderInterface + private function createProvider(?MockHttpClient $httpClient = null, ?string $endpoint = null, ?XliffFileDumper $dumper = null, bool $isFallbackLocaleEnabled = false): ProviderInterface { return new PhraseProvider( $httpClient ?? $this->getHttpClient(), diff --git a/src/Symfony/Component/Translation/Command/XliffLintCommand.php b/src/Symfony/Component/Translation/Command/XliffLintCommand.php index d76bc10fc1e53..439562556a2ce 100644 --- a/src/Symfony/Component/Translation/Command/XliffLintCommand.php +++ b/src/Symfony/Component/Translation/Command/XliffLintCommand.php @@ -41,7 +41,7 @@ class XliffLintCommand extends Command private ?\Closure $isReadableProvider; private bool $requireStrictFileNames; - public function __construct(string $name = null, callable $directoryIteratorProvider = null, callable $isReadableProvider = null, bool $requireStrictFileNames = true) + public function __construct(?string $name = null, ?callable $directoryIteratorProvider = null, ?callable $isReadableProvider = null, bool $requireStrictFileNames = true) { parent::__construct($name); @@ -106,7 +106,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int return $this->display($io, $filesInfo); } - private function validate(string $content, string $file = null): array + private function validate(string $content, ?string $file = null): array { $errors = []; diff --git a/src/Symfony/Component/Translation/DataCollector/TranslationDataCollector.php b/src/Symfony/Component/Translation/DataCollector/TranslationDataCollector.php index 439ebe11aec51..d4f49cc66d9a6 100644 --- a/src/Symfony/Component/Translation/DataCollector/TranslationDataCollector.php +++ b/src/Symfony/Component/Translation/DataCollector/TranslationDataCollector.php @@ -42,7 +42,7 @@ public function lateCollect(): void $this->data = $this->cloneVar($this->data); } - public function collect(Request $request, Response $response, \Throwable $exception = null): void + public function collect(Request $request, Response $response, ?\Throwable $exception = null): void { $this->data['locale'] = $this->translator->getLocale(); $this->data['fallback_locales'] = $this->translator->getFallbackLocales(); diff --git a/src/Symfony/Component/Translation/DataCollectorTranslator.php b/src/Symfony/Component/Translation/DataCollectorTranslator.php index d6e69d5613fdf..1f52088b49b34 100644 --- a/src/Symfony/Component/Translation/DataCollectorTranslator.php +++ b/src/Symfony/Component/Translation/DataCollectorTranslator.php @@ -42,7 +42,7 @@ public function __construct(TranslatorInterface $translator) $this->translator = $translator; } - public function trans(?string $id, array $parameters = [], string $domain = null, string $locale = null): string + public function trans(?string $id, array $parameters = [], ?string $domain = null, ?string $locale = null): string { $trans = $this->translator->trans($id = (string) $id, $parameters, $domain, $locale); $this->collectMessage($locale, $domain, $id, $trans, $parameters); @@ -60,7 +60,7 @@ public function getLocale(): string return $this->translator->getLocale(); } - public function getCatalogue(string $locale = null): MessageCatalogueInterface + public function getCatalogue(?string $locale = null): MessageCatalogueInterface { return $this->translator->getCatalogue($locale); } @@ -70,7 +70,7 @@ public function getCatalogues(): array return $this->translator->getCatalogues(); } - public function warmUp(string $cacheDir, string $buildDir = null): array + public function warmUp(string $cacheDir, ?string $buildDir = null): array { if ($this->translator instanceof WarmableInterface) { return (array) $this->translator->warmUp($cacheDir, $buildDir); diff --git a/src/Symfony/Component/Translation/Dumper/XliffFileDumper.php b/src/Symfony/Component/Translation/Dumper/XliffFileDumper.php index 382bb678bee11..66698c5ca14b8 100644 --- a/src/Symfony/Component/Translation/Dumper/XliffFileDumper.php +++ b/src/Symfony/Component/Translation/Dumper/XliffFileDumper.php @@ -214,7 +214,7 @@ private function dumpXliff2(string $defaultLocale, MessageCatalogue $messages, ? return $dom->saveXML(); } - private function hasMetadataArrayInfo(string $key, array $metadata = null): bool + private function hasMetadataArrayInfo(string $key, ?array $metadata = null): bool { return is_iterable($metadata[$key] ?? null); } diff --git a/src/Symfony/Component/Translation/Exception/IncompleteDsnException.php b/src/Symfony/Component/Translation/Exception/IncompleteDsnException.php index cb0ce027eb77c..b304bde018807 100644 --- a/src/Symfony/Component/Translation/Exception/IncompleteDsnException.php +++ b/src/Symfony/Component/Translation/Exception/IncompleteDsnException.php @@ -13,7 +13,7 @@ class IncompleteDsnException extends InvalidArgumentException { - public function __construct(string $message, string $dsn = null, \Throwable $previous = null) + public function __construct(string $message, ?string $dsn = null, ?\Throwable $previous = null) { if ($dsn) { $message = sprintf('Invalid "%s" provider DSN: ', $dsn).$message; diff --git a/src/Symfony/Component/Translation/Exception/MissingRequiredOptionException.php b/src/Symfony/Component/Translation/Exception/MissingRequiredOptionException.php index 2b5f808065eee..46152e254cefc 100644 --- a/src/Symfony/Component/Translation/Exception/MissingRequiredOptionException.php +++ b/src/Symfony/Component/Translation/Exception/MissingRequiredOptionException.php @@ -16,7 +16,7 @@ */ class MissingRequiredOptionException extends IncompleteDsnException { - public function __construct(string $option, string $dsn = null, \Throwable $previous = null) + public function __construct(string $option, ?string $dsn = null, ?\Throwable $previous = null) { $message = sprintf('The option "%s" is required but missing.', $option); diff --git a/src/Symfony/Component/Translation/Exception/ProviderException.php b/src/Symfony/Component/Translation/Exception/ProviderException.php index 65883f8524f39..f2981f58bf584 100644 --- a/src/Symfony/Component/Translation/Exception/ProviderException.php +++ b/src/Symfony/Component/Translation/Exception/ProviderException.php @@ -21,7 +21,7 @@ class ProviderException extends RuntimeException implements ProviderExceptionInt private ResponseInterface $response; private string $debug; - public function __construct(string $message, ResponseInterface $response, int $code = 0, \Exception $previous = null) + public function __construct(string $message, ResponseInterface $response, int $code = 0, ?\Exception $previous = null) { $this->response = $response; $this->debug = $response->getInfo('debug') ?? ''; diff --git a/src/Symfony/Component/Translation/Exception/UnsupportedSchemeException.php b/src/Symfony/Component/Translation/Exception/UnsupportedSchemeException.php index a4d838581ff61..8d329518471e8 100644 --- a/src/Symfony/Component/Translation/Exception/UnsupportedSchemeException.php +++ b/src/Symfony/Component/Translation/Exception/UnsupportedSchemeException.php @@ -35,7 +35,7 @@ class UnsupportedSchemeException extends LogicException ], ]; - public function __construct(Dsn $dsn, string $name = null, array $supported = []) + public function __construct(Dsn $dsn, ?string $name = null, array $supported = []) { $provider = $dsn->getScheme(); if (false !== $pos = strpos($provider, '+')) { diff --git a/src/Symfony/Component/Translation/Extractor/PhpAstExtractor.php b/src/Symfony/Component/Translation/Extractor/PhpAstExtractor.php index 3769fc27d91db..06fc77de3c46c 100644 --- a/src/Symfony/Component/Translation/Extractor/PhpAstExtractor.php +++ b/src/Symfony/Component/Translation/Extractor/PhpAstExtractor.php @@ -39,17 +39,18 @@ public function __construct( throw new \LogicException(sprintf('You cannot use "%s" as the "nikic/php-parser" package is not installed. Try running "composer require nikic/php-parser".', static::class)); } - if (method_exists(ParserFactory::class, 'createForHostVersion')) { - $this->parser = (new ParserFactory())->createForHostVersion(); - } else { - $this->parser = (new ParserFactory())->create(ParserFactory::PREFER_PHP7); - } + $this->parser = (new ParserFactory())->createForHostVersion(); } public function extract(iterable|string $resource, MessageCatalogue $catalogue): void { foreach ($this->extractFiles($resource) as $file) { $traverser = new NodeTraverser(); + + // This is needed to resolve namespaces in class methods/constants. + $nameResolver = new NodeVisitor\NameResolver(); + $traverser->addVisitor($nameResolver); + /** @var AbstractVisitor&NodeVisitor $visitor */ foreach ($this->visitors as $visitor) { $visitor->initialize($catalogue, $file, $this->prefix); diff --git a/src/Symfony/Component/Translation/Extractor/Visitor/AbstractVisitor.php b/src/Symfony/Component/Translation/Extractor/Visitor/AbstractVisitor.php index c341056409115..c336896169a8b 100644 --- a/src/Symfony/Component/Translation/Extractor/Visitor/AbstractVisitor.php +++ b/src/Symfony/Component/Translation/Extractor/Visitor/AbstractVisitor.php @@ -81,7 +81,7 @@ protected function nodeFirstNamedArgumentIndex(Node\Expr\CallLike|Node\Attribute return \PHP_INT_MAX; } - private function getStringNamedArguments(Node\Expr\CallLike|Node\Attribute $node, string $argumentName = null, bool $isArgumentNamePattern = false): array + private function getStringNamedArguments(Node\Expr\CallLike|Node\Attribute $node, ?string $argumentName = null, bool $isArgumentNamePattern = false): array { $args = $node instanceof Node\Expr\CallLike ? $node->getArgs() : $node->args; $argumentValues = []; @@ -119,6 +119,17 @@ private function getStringValue(Node $node): ?string return $node->expr->value; } + if ($node instanceof Node\Expr\ClassConstFetch) { + try { + $reflection = new \ReflectionClass($node->class->toString()); + $constant = $reflection->getReflectionConstant($node->name->toString()); + if (false !== $constant && \is_string($constant->getValue())) { + return $constant->getValue(); + } + } catch (\ReflectionException) { + } + } + return null; } } diff --git a/src/Symfony/Component/Translation/Extractor/Visitor/ConstraintVisitor.php b/src/Symfony/Component/Translation/Extractor/Visitor/ConstraintVisitor.php index b317f01c88ca1..45cae35369e36 100644 --- a/src/Symfony/Component/Translation/Extractor/Visitor/ConstraintVisitor.php +++ b/src/Symfony/Component/Translation/Extractor/Visitor/ConstraintVisitor.php @@ -32,6 +32,11 @@ public function beforeTraverse(array $nodes): ?Node } public function enterNode(Node $node): ?Node + { + return null; + } + + public function leaveNode(Node $node): ?Node { if (!$node instanceof Node\Expr\New_ && !$node instanceof Node\Attribute) { return null; @@ -100,11 +105,6 @@ public function enterNode(Node $node): ?Node return null; } - public function leaveNode(Node $node): ?Node - { - return null; - } - public function afterTraverse(array $nodes): ?Node { return null; diff --git a/src/Symfony/Component/Translation/Extractor/Visitor/TransMethodVisitor.php b/src/Symfony/Component/Translation/Extractor/Visitor/TransMethodVisitor.php index 0b537baa24c13..53a6981a2681a 100644 --- a/src/Symfony/Component/Translation/Extractor/Visitor/TransMethodVisitor.php +++ b/src/Symfony/Component/Translation/Extractor/Visitor/TransMethodVisitor.php @@ -25,6 +25,11 @@ public function beforeTraverse(array $nodes): ?Node } public function enterNode(Node $node): ?Node + { + return null; + } + + public function leaveNode(Node $node): ?Node { if (!$node instanceof Node\Expr\MethodCall && !$node instanceof Node\Expr\FuncCall) { return null; @@ -53,11 +58,6 @@ public function enterNode(Node $node): ?Node return null; } - public function leaveNode(Node $node): ?Node - { - return null; - } - public function afterTraverse(array $nodes): ?Node { return null; diff --git a/src/Symfony/Component/Translation/Extractor/Visitor/TranslatableMessageVisitor.php b/src/Symfony/Component/Translation/Extractor/Visitor/TranslatableMessageVisitor.php index 9849fd2b373e9..6bd8bb022306f 100644 --- a/src/Symfony/Component/Translation/Extractor/Visitor/TranslatableMessageVisitor.php +++ b/src/Symfony/Component/Translation/Extractor/Visitor/TranslatableMessageVisitor.php @@ -25,6 +25,11 @@ public function beforeTraverse(array $nodes): ?Node } public function enterNode(Node $node): ?Node + { + return null; + } + + public function leaveNode(Node $node): ?Node { if (!$node instanceof Node\Expr\New_) { return null; @@ -53,11 +58,6 @@ public function enterNode(Node $node): ?Node return null; } - public function leaveNode(Node $node): ?Node - { - return null; - } - public function afterTraverse(array $nodes): ?Node { return null; diff --git a/src/Symfony/Component/Translation/Formatter/MessageFormatter.php b/src/Symfony/Component/Translation/Formatter/MessageFormatter.php index 5e101aa438c19..d5255bdcb930e 100644 --- a/src/Symfony/Component/Translation/Formatter/MessageFormatter.php +++ b/src/Symfony/Component/Translation/Formatter/MessageFormatter.php @@ -28,7 +28,7 @@ class MessageFormatter implements MessageFormatterInterface, IntlFormatterInterf /** * @param TranslatorInterface|null $translator An identity translator to use as selector for pluralization */ - public function __construct(TranslatorInterface $translator = null, IntlFormatterInterface $intlFormatter = null) + public function __construct(?TranslatorInterface $translator = null, ?IntlFormatterInterface $intlFormatter = null) { $this->translator = $translator ?? new IdentityTranslator(); $this->intlFormatter = $intlFormatter ?? new IntlFormatter(); diff --git a/src/Symfony/Component/Translation/Loader/IcuResFileLoader.php b/src/Symfony/Component/Translation/Loader/IcuResFileLoader.php index 94d55b86189e4..949dd97925a7d 100644 --- a/src/Symfony/Component/Translation/Loader/IcuResFileLoader.php +++ b/src/Symfony/Component/Translation/Loader/IcuResFileLoader.php @@ -70,7 +70,7 @@ public function load(mixed $resource, string $locale, string $domain = 'messages * @param array $messages Used internally for recursive calls * @param string|null $path Current path being parsed, used internally for recursive calls */ - protected function flatten(\ResourceBundle $rb, array &$messages = [], string $path = null): array + protected function flatten(\ResourceBundle $rb, array &$messages = [], ?string $path = null): array { foreach ($rb as $key => $value) { $nodePath = $path ? $path.'.'.$key : $key; diff --git a/src/Symfony/Component/Translation/Loader/XliffFileLoader.php b/src/Symfony/Component/Translation/Loader/XliffFileLoader.php index 09cc3a13f15f4..31b3251baed92 100644 --- a/src/Symfony/Component/Translation/Loader/XliffFileLoader.php +++ b/src/Symfony/Component/Translation/Loader/XliffFileLoader.php @@ -195,7 +195,7 @@ private function extractXliff2(\DOMDocument $dom, MessageCatalogue $catalogue, s /** * Convert a UTF8 string to the specified encoding. */ - private function utf8ToCharset(string $content, string $encoding = null): string + private function utf8ToCharset(string $content, ?string $encoding = null): string { if ('UTF-8' !== $encoding && !empty($encoding)) { return mb_convert_encoding($content, $encoding, 'UTF-8'); @@ -204,7 +204,7 @@ private function utf8ToCharset(string $content, string $encoding = null): string return $content; } - private function parseNotesMetadata(\SimpleXMLElement $noteElement = null, string $encoding = null): array + private function parseNotesMetadata(?\SimpleXMLElement $noteElement = null, ?string $encoding = null): array { $notes = []; diff --git a/src/Symfony/Component/Translation/LoggingTranslator.php b/src/Symfony/Component/Translation/LoggingTranslator.php index 3f6f5c2354734..8c9b2646389dd 100644 --- a/src/Symfony/Component/Translation/LoggingTranslator.php +++ b/src/Symfony/Component/Translation/LoggingTranslator.php @@ -37,7 +37,7 @@ public function __construct(TranslatorInterface $translator, LoggerInterface $lo $this->logger = $logger; } - public function trans(?string $id, array $parameters = [], string $domain = null, string $locale = null): string + public function trans(?string $id, array $parameters = [], ?string $domain = null, ?string $locale = null): string { $trans = $this->translator->trans($id = (string) $id, $parameters, $domain, $locale); $this->log($id, $domain, $locale); @@ -61,7 +61,7 @@ public function getLocale(): string return $this->translator->getLocale(); } - public function getCatalogue(string $locale = null): MessageCatalogueInterface + public function getCatalogue(?string $locale = null): MessageCatalogueInterface { return $this->translator->getCatalogue($locale); } diff --git a/src/Symfony/Component/Translation/MessageCatalogue.php b/src/Symfony/Component/Translation/MessageCatalogue.php index 1ae29ff5d9113..f0cc6b321d940 100644 --- a/src/Symfony/Component/Translation/MessageCatalogue.php +++ b/src/Symfony/Component/Translation/MessageCatalogue.php @@ -55,7 +55,7 @@ public function getDomains(): array return array_values($domains); } - public function all(string $domain = null): array + public function all(?string $domain = null): array { if (null !== $domain) { // skip messages merge if intl-icu requested explicitly diff --git a/src/Symfony/Component/Translation/MessageCatalogueInterface.php b/src/Symfony/Component/Translation/MessageCatalogueInterface.php index e703c85a16248..5d6356053f7ad 100644 --- a/src/Symfony/Component/Translation/MessageCatalogueInterface.php +++ b/src/Symfony/Component/Translation/MessageCatalogueInterface.php @@ -37,7 +37,7 @@ public function getDomains(): array; * * If $domain is null, it returns all messages. */ - public function all(string $domain = null): array; + public function all(?string $domain = null): array; /** * Sets a message translation. diff --git a/src/Symfony/Component/Translation/Provider/Dsn.php b/src/Symfony/Component/Translation/Provider/Dsn.php index c0f087c7a9cae..1d90e27f994e0 100644 --- a/src/Symfony/Component/Translation/Provider/Dsn.php +++ b/src/Symfony/Component/Translation/Provider/Dsn.php @@ -74,7 +74,7 @@ public function getPassword(): ?string return $this->password; } - public function getPort(int $default = null): ?int + public function getPort(?int $default = null): ?int { return $this->port ?? $default; } diff --git a/src/Symfony/Component/Translation/PseudoLocalizationTranslator.php b/src/Symfony/Component/Translation/PseudoLocalizationTranslator.php index 0207e9997ad0e..f26909f5e1143 100644 --- a/src/Symfony/Component/Translation/PseudoLocalizationTranslator.php +++ b/src/Symfony/Component/Translation/PseudoLocalizationTranslator.php @@ -83,7 +83,7 @@ public function __construct(TranslatorInterface $translator, array $options = [] $this->localizableHTMLAttributes = $options['localizable_html_attributes'] ?? []; } - public function trans(string $id, array $parameters = [], string $domain = null, string $locale = null): string + public function trans(string $id, array $parameters = [], ?string $domain = null, ?string $locale = null): string { $trans = ''; $visibleText = ''; @@ -120,7 +120,7 @@ private function getParts(string $originalTrans): array return [[true, true, $originalTrans]]; } - $html = mb_encode_numericentity($originalTrans, [0x80, 0xFFFF, 0, 0xFFFF], mb_detect_encoding($originalTrans, null, true) ?: 'UTF-8'); + $html = mb_encode_numericentity($originalTrans, [0x80, 0x10FFFF, 0, 0x1FFFFF], mb_detect_encoding($originalTrans, null, true) ?: 'UTF-8'); $useInternalErrors = libxml_use_internal_errors(true); diff --git a/src/Symfony/Component/Translation/Resources/functions.php b/src/Symfony/Component/Translation/Resources/functions.php index 901d2f87efcdd..0d2a037a2c1ec 100644 --- a/src/Symfony/Component/Translation/Resources/functions.php +++ b/src/Symfony/Component/Translation/Resources/functions.php @@ -15,7 +15,7 @@ /** * @author Nate Wiebe */ - function t(string $message, array $parameters = [], string $domain = null): TranslatableMessage + function t(string $message, array $parameters = [], ?string $domain = null): TranslatableMessage { return new TranslatableMessage($message, $parameters, $domain); } diff --git a/src/Symfony/Component/Translation/Test/ProviderFactoryTestCase.php b/src/Symfony/Component/Translation/Test/ProviderFactoryTestCase.php index 110ea3d7a49f6..95ffcb1e58294 100644 --- a/src/Symfony/Component/Translation/Test/ProviderFactoryTestCase.php +++ b/src/Symfony/Component/Translation/Test/ProviderFactoryTestCase.php @@ -90,7 +90,7 @@ public function testCreate(string $expected, string $dsn) /** * @dataProvider unsupportedSchemeProvider */ - public function testUnsupportedSchemeException(string $dsn, string $message = null) + public function testUnsupportedSchemeException(string $dsn, ?string $message = null) { $factory = $this->createFactory(); @@ -107,7 +107,7 @@ public function testUnsupportedSchemeException(string $dsn, string $message = nu /** * @dataProvider incompleteDsnProvider */ - public function testIncompleteDsnException(string $dsn, string $message = null) + public function testIncompleteDsnException(string $dsn, ?string $message = null) { $factory = $this->createFactory(); diff --git a/src/Symfony/Component/Translation/Tests/DependencyInjection/DataCollectorTranslatorPassTest.php b/src/Symfony/Component/Translation/Tests/DependencyInjection/DataCollectorTranslatorPassTest.php index e61165047138d..43bace4721365 100644 --- a/src/Symfony/Component/Translation/Tests/DependencyInjection/DataCollectorTranslatorPassTest.php +++ b/src/Symfony/Component/Translation/Tests/DependencyInjection/DataCollectorTranslatorPassTest.php @@ -110,7 +110,7 @@ public static function getNotImplementingTranslatorBagInterfaceTranslatorClassNa class TranslatorWithoutTranslatorBag implements TranslatorInterface { - public function trans(string $id, array $parameters = [], string $domain = null, string $locale = null): string + public function trans(string $id, array $parameters = [], ?string $domain = null, ?string $locale = null): string { } diff --git a/src/Symfony/Component/Translation/Tests/Extractor/PhpAstExtractorTest.php b/src/Symfony/Component/Translation/Tests/Extractor/PhpAstExtractorTest.php index d808a365ac383..10a144b2c6cd3 100644 --- a/src/Symfony/Component/Translation/Tests/Extractor/PhpAstExtractorTest.php +++ b/src/Symfony/Component/Translation/Tests/Extractor/PhpAstExtractorTest.php @@ -20,6 +20,8 @@ final class PhpAstExtractorTest extends TestCase { + public const OTHER_DOMAIN = 'not_messages'; + /** * @dataProvider resourcesProvider */ @@ -124,6 +126,7 @@ public function testExtraction(iterable|string $resource) 'variable-assignation-inlined-with-named-arguments-in-trans-method' => 'prefixvariable-assignation-inlined-with-named-arguments-in-trans-method', 'mix-named-arguments-without-parameters' => 'prefixmix-named-arguments-without-parameters', 'mix-named-arguments-disordered' => 'prefixmix-named-arguments-disordered', + 'const-domain' => 'prefixconst-domain', ], 'validators' => [ 'message-in-constraint-attribute' => 'prefixmessage-in-constraint-attribute', diff --git a/src/Symfony/Component/Translation/Tests/Fixtures/extractor-ast/translation.html.php b/src/Symfony/Component/Translation/Tests/Fixtures/extractor-ast/translation.html.php index db27b303c5945..68d966bcdd38d 100644 --- a/src/Symfony/Component/Translation/Tests/Fixtures/extractor-ast/translation.html.php +++ b/src/Symfony/Component/Translation/Tests/Fixtures/extractor-ast/translation.html.php @@ -62,3 +62,8 @@ trans('mix-named-arguments-disordered', domain: 'not_messages', parameters: []); ?> trans(...); // should not fail ?> + +trans('const-domain', [], PhpAstExtractorTest::OTHER_DOMAIN); +?> diff --git a/src/Symfony/Component/Translation/Tests/Provider/DsnTest.php b/src/Symfony/Component/Translation/Tests/Provider/DsnTest.php index 54593be96710d..af641372e5ff3 100644 --- a/src/Symfony/Component/Translation/Tests/Provider/DsnTest.php +++ b/src/Symfony/Component/Translation/Tests/Provider/DsnTest.php @@ -21,7 +21,7 @@ final class DsnTest extends TestCase /** * @dataProvider constructProvider */ - public function testConstruct(string $dsnString, string $scheme, string $host, string $user = null, string $password = null, int $port = null, array $options = [], string $path = null) + public function testConstruct(string $dsnString, string $scheme, string $host, ?string $user = null, ?string $password = null, ?int $port = null, array $options = [], ?string $path = null) { $dsn = new Dsn($dsnString); $this->assertSame($dsnString, $dsn->getOriginalDsn()); @@ -172,7 +172,7 @@ public static function invalidDsnProvider(): iterable /** * @dataProvider getOptionProvider */ - public function testGetOption($expected, string $dsnString, string $option, string $default = null) + public function testGetOption($expected, string $dsnString, string $option, ?string $default = null) { $dsn = new Dsn($dsnString); diff --git a/src/Symfony/Component/Translation/Tests/PseudoLocalizationTranslatorTest.php b/src/Symfony/Component/Translation/Tests/PseudoLocalizationTranslatorTest.php index e69e669c205af..d8490a5554d46 100644 --- a/src/Symfony/Component/Translation/Tests/PseudoLocalizationTranslatorTest.php +++ b/src/Symfony/Component/Translation/Tests/PseudoLocalizationTranslatorTest.php @@ -44,6 +44,7 @@ public static function provideTrans(): array ['

ƀåŕ

', '

bar

', self::getIsolatedOptions(['parse_html' => true, 'accents' => true])], ['

″≤″

', '

"<"

', self::getIsolatedOptions(['parse_html' => true, 'accents' => true])], ['Symfony is an Open Source, community-driven project with thousands of contributors. ~~~~~~~ ~~ ~~~~ ~~~~~~~ ~~~~~~~ ~~ ~~~~ ~~~~~~~~~~~~~ ~~~~~~~~~~~~~ ~~~~~~~ ~~ ~~~', 'Symfony is an Open Source, community-driven project with thousands of contributors.', self::getIsolatedOptions(['expansion_factor' => 2.0])], + ['

👇👇👇👇👇👇👇

', '

👇👇👇👇👇👇👇

', self::getIsolatedOptions(['parse_html' => true])], ]; } diff --git a/src/Symfony/Component/Translation/TranslatableMessage.php b/src/Symfony/Component/Translation/TranslatableMessage.php index 91d4c194168ce..c591e68c28b67 100644 --- a/src/Symfony/Component/Translation/TranslatableMessage.php +++ b/src/Symfony/Component/Translation/TranslatableMessage.php @@ -23,7 +23,7 @@ class TranslatableMessage implements TranslatableInterface private array $parameters; private ?string $domain; - public function __construct(string $message, array $parameters = [], string $domain = null) + public function __construct(string $message, array $parameters = [], ?string $domain = null) { $this->message = $message; $this->parameters = $parameters; @@ -50,7 +50,7 @@ public function getDomain(): ?string return $this->domain; } - public function trans(TranslatorInterface $translator, string $locale = null): string + public function trans(TranslatorInterface $translator, ?string $locale = null): string { return $translator->trans($this->getMessage(), array_map( static fn ($parameter) => $parameter instanceof TranslatableInterface ? $parameter->trans($translator, $locale) : $parameter, diff --git a/src/Symfony/Component/Translation/Translator.php b/src/Symfony/Component/Translation/Translator.php index dd2b112b703f3..7dad475139dc2 100644 --- a/src/Symfony/Component/Translation/Translator.php +++ b/src/Symfony/Component/Translation/Translator.php @@ -69,7 +69,7 @@ class Translator implements TranslatorInterface, TranslatorBagInterface, LocaleA /** * @throws InvalidArgumentException If a locale contains invalid characters */ - public function __construct(string $locale, MessageFormatterInterface $formatter = null, string $cacheDir = null, bool $debug = false, array $cacheVary = []) + public function __construct(string $locale, ?MessageFormatterInterface $formatter = null, ?string $cacheDir = null, bool $debug = false, array $cacheVary = []) { $this->setLocale($locale); @@ -103,7 +103,7 @@ public function addLoader(string $format, LoaderInterface $loader): void * * @throws InvalidArgumentException If the locale contains invalid characters */ - public function addResource(string $format, mixed $resource, string $locale, string $domain = null): void + public function addResource(string $format, mixed $resource, string $locale, ?string $domain = null): void { $domain ??= 'messages'; @@ -159,7 +159,7 @@ public function getFallbackLocales(): array return $this->fallbackLocales; } - public function trans(?string $id, array $parameters = [], string $domain = null, string $locale = null): string + public function trans(?string $id, array $parameters = [], ?string $domain = null, ?string $locale = null): string { if (null === $id || '' === $id) { return ''; @@ -191,7 +191,7 @@ public function trans(?string $id, array $parameters = [], string $domain = null return $this->formatter->format($catalogue->get($id, $domain), $locale, $parameters); } - public function getCatalogue(string $locale = null): MessageCatalogueInterface + public function getCatalogue(?string $locale = null): MessageCatalogueInterface { if (!$locale) { $locale = $this->getLocale(); diff --git a/src/Symfony/Component/Translation/TranslatorBag.php b/src/Symfony/Component/Translation/TranslatorBag.php index 84275ee1403bf..3b47aecee1602 100644 --- a/src/Symfony/Component/Translation/TranslatorBag.php +++ b/src/Symfony/Component/Translation/TranslatorBag.php @@ -35,7 +35,7 @@ public function addBag(TranslatorBagInterface $bag): void } } - public function getCatalogue(string $locale = null): MessageCatalogueInterface + public function getCatalogue(?string $locale = null): MessageCatalogueInterface { if (null === $locale || !isset($this->catalogues[$locale])) { $this->catalogues[$locale] = new MessageCatalogue($locale); diff --git a/src/Symfony/Component/Translation/TranslatorBagInterface.php b/src/Symfony/Component/Translation/TranslatorBagInterface.php index a787acf12fccc..365d1f13bfc88 100644 --- a/src/Symfony/Component/Translation/TranslatorBagInterface.php +++ b/src/Symfony/Component/Translation/TranslatorBagInterface.php @@ -25,7 +25,7 @@ interface TranslatorBagInterface * * @throws InvalidArgumentException If the locale contains invalid characters */ - public function getCatalogue(string $locale = null): MessageCatalogueInterface; + public function getCatalogue(?string $locale = null): MessageCatalogueInterface; /** * Returns all catalogues of the instance. diff --git a/src/Symfony/Component/Translation/composer.json b/src/Symfony/Component/Translation/composer.json index 076af3740feeb..b793e4c98f269 100644 --- a/src/Symfony/Component/Translation/composer.json +++ b/src/Symfony/Component/Translation/composer.json @@ -21,7 +21,7 @@ "symfony/translation-contracts": "^2.5|^3.0" }, "require-dev": { - "nikic/php-parser": "^4.16|^5.0", + "nikic/php-parser": "^4.18|^5.0", "symfony/config": "^6.4|^7.0", "symfony/console": "^6.4|^7.0", "symfony/dependency-injection": "^6.4|^7.0", diff --git a/src/Symfony/Component/TypeInfo/Tests/TypeResolver/StringTypeResolverTest.php b/src/Symfony/Component/TypeInfo/Tests/TypeResolver/StringTypeResolverTest.php index f348baed5f6df..cc666f678073f 100644 --- a/src/Symfony/Component/TypeInfo/Tests/TypeResolver/StringTypeResolverTest.php +++ b/src/Symfony/Component/TypeInfo/Tests/TypeResolver/StringTypeResolverTest.php @@ -50,7 +50,7 @@ public function resolveDataProvider(): iterable yield [Type::callable(), 'callable(string, int): mixed']; // array - yield [Type::array(Type::bool()), 'bool[]']; + yield [Type::list(Type::bool()), 'bool[]']; // array shape yield [Type::array(), 'array{0: true, 1: false}']; diff --git a/src/Symfony/Component/TypeInfo/Type.php b/src/Symfony/Component/TypeInfo/Type.php index 5a81f7e2ecc29..dbf26240df5ee 100644 --- a/src/Symfony/Component/TypeInfo/Type.php +++ b/src/Symfony/Component/TypeInfo/Type.php @@ -46,6 +46,20 @@ public function getBaseType(): BuiltinType|ObjectType return $baseType; } + /** + * BC Layer for Symfony\Component\PropertyInfo\Type. + * + * @internal + */ + public ?bool $isNullable = null; + + /** + * BC Layer for Symfony\Component\PropertyInfo\Type. + * + * @internal + */ + public bool $isCollection = false; + /** * @param callable(Type): bool $callable */ @@ -65,6 +79,11 @@ public function isA(TypeIdentifier $typeIdentifier): bool public function isNullable(): bool { + // BC Layer for Symfony\Component\PropertyInfo\Type. + if (null !== $this->isNullable) { + return $this->isNullable; + } + return $this->testIdentifier(fn (TypeIdentifier $i): bool => TypeIdentifier::NULL === $i || TypeIdentifier::MIXED === $i); } @@ -86,4 +105,24 @@ private function testIdentifier(callable $test): bool return $this->is($callable); } + + /** + * BC Layer for Symfony\Component\PropertyInfo\Type. + * + * @internal + */ + public function setCollection(bool $collection): void + { + $this->isCollection = $collection; + } + + /** + * BC Layer for Symfony\Component\PropertyInfo\Type. + * + * @internal + */ + public function setNullable(bool $nullable): void + { + $this->isNullable = $nullable; + } } diff --git a/src/Symfony/Component/TypeInfo/Type/BuiltinType.php b/src/Symfony/Component/TypeInfo/Type/BuiltinType.php index 9a8cc13e25837..40e50ac2369b2 100644 --- a/src/Symfony/Component/TypeInfo/Type/BuiltinType.php +++ b/src/Symfony/Component/TypeInfo/Type/BuiltinType.php @@ -29,6 +29,10 @@ final class BuiltinType extends Type public function __construct( private readonly TypeIdentifier $typeIdentifier, ) { + // BC Layer for Symfony\Component\PropertyInfo\Type. + if (\in_array($typeIdentifier, [TypeIdentifier::MIXED, TypeIdentifier::NULL], true)) { + $this->setNullable(true); + } } /** diff --git a/src/Symfony/Component/TypeInfo/Type/CollectionType.php b/src/Symfony/Component/TypeInfo/Type/CollectionType.php index 6b99d1325eaab..828ef57ffe693 100644 --- a/src/Symfony/Component/TypeInfo/Type/CollectionType.php +++ b/src/Symfony/Component/TypeInfo/Type/CollectionType.php @@ -34,6 +34,11 @@ public function __construct( private readonly BuiltinType|ObjectType|GenericType $type, private readonly bool $isList = false, ) { + // BC layer to allow invalid list keys + if (\func_get_args()[2] ?? false) { + return; + } + if ($this->isList()) { $keyType = $this->getCollectionKeyType(); @@ -105,4 +110,26 @@ public function __call(string $method, array $arguments): mixed { return $this->type->{$method}(...$arguments); } + + /** + * BC Layer for Symfony\Component\PropertyInfo\Type. + * + * @internal + */ + public function setCollection(bool $collection): void + { + parent::setCollection($collection); + $this->type->setCollection($collection); + } + + /** + * BC Layer for Symfony\Component\PropertyInfo\Type. + * + * @internal + */ + public function setNullable(bool $nullable): void + { + parent::setNullable($nullable); + $this->type->setNullable($nullable); + } } diff --git a/src/Symfony/Component/TypeInfo/Type/CompositeTypeTrait.php b/src/Symfony/Component/TypeInfo/Type/CompositeTypeTrait.php index 9da43daab924a..7591ec0094dfc 100644 --- a/src/Symfony/Component/TypeInfo/Type/CompositeTypeTrait.php +++ b/src/Symfony/Component/TypeInfo/Type/CompositeTypeTrait.php @@ -83,4 +83,32 @@ public function everyTypeIs(callable $callable): bool return true; } + + /** + * BC Layer for Symfony\Component\PropertyInfo\Type. + * + * @internal + */ + public function setCollection(bool $collection): void + { + parent::setCollection($collection); + + foreach ($this->types as $t) { + $t->setCollection($collection); + } + } + + /** + * BC Layer for Symfony\Component\PropertyInfo\Type. + * + * @internal + */ + public function setNullable(bool $nullable): void + { + parent::setNullable($nullable); + + foreach ($this->types as $t) { + $t->setNullable($nullable); + } + } } diff --git a/src/Symfony/Component/TypeInfo/Type/GenericType.php b/src/Symfony/Component/TypeInfo/Type/GenericType.php index dc6a845278da8..9b53ccdfa09c5 100644 --- a/src/Symfony/Component/TypeInfo/Type/GenericType.php +++ b/src/Symfony/Component/TypeInfo/Type/GenericType.php @@ -85,4 +85,26 @@ public function __call(string $method, array $arguments): mixed { return $this->type->{$method}(...$arguments); } + + /** + * BC Layer for Symfony\Component\PropertyInfo\Type. + * + * @internal + */ + public function setCollection(bool $collection): void + { + parent::setCollection($collection); + $this->type->setCollection($collection); + } + + /** + * BC Layer for Symfony\Component\PropertyInfo\Type. + * + * @internal + */ + public function setNullable(bool $nullable): void + { + parent::setNullable($nullable); + $this->type->setNullable($nullable); + } } diff --git a/src/Symfony/Component/TypeInfo/Type/UnionType.php b/src/Symfony/Component/TypeInfo/Type/UnionType.php index 02ace46087d6a..39c354d2de4fc 100644 --- a/src/Symfony/Component/TypeInfo/Type/UnionType.php +++ b/src/Symfony/Component/TypeInfo/Type/UnionType.php @@ -57,4 +57,32 @@ public function __toString(): string return $string; } + + /** + * BC Layer for Symfony\Component\PropertyInfo\Type. + * + * @internal + */ + public function setCollection(bool $collection): void + { + parent::setCollection($collection); + + foreach ($this->types as $t) { + $t->setCollection($collection); + } + } + + /** + * BC Layer for Symfony\Component\PropertyInfo\Type. + * + * @internal + */ + public function setNullable(bool $nullable): void + { + parent::setNullable($nullable); + + foreach ($this->types as $t) { + $t->setNullable($nullable); + } + } } diff --git a/src/Symfony/Component/TypeInfo/TypeIdentifier.php b/src/Symfony/Component/TypeInfo/TypeIdentifier.php index 5326262a8562d..8776ef661ba83 100644 --- a/src/Symfony/Component/TypeInfo/TypeIdentifier.php +++ b/src/Symfony/Component/TypeInfo/TypeIdentifier.php @@ -34,4 +34,12 @@ enum TypeIdentifier: string case TRUE = 'true'; case NEVER = 'never'; case VOID = 'void'; + + /** + * @return list + */ + public static function values(): array + { + return array_column(self::cases(), 'value'); + } } diff --git a/src/Symfony/Component/TypeInfo/TypeResolver/StringTypeResolver.php b/src/Symfony/Component/TypeInfo/TypeResolver/StringTypeResolver.php index de90a5073fc60..5ef488cbb8165 100644 --- a/src/Symfony/Component/TypeInfo/TypeResolver/StringTypeResolver.php +++ b/src/Symfony/Component/TypeInfo/TypeResolver/StringTypeResolver.php @@ -71,6 +71,8 @@ public function __construct() public function resolve(mixed $subject, TypeContext $typeContext = null): Type { + $backwardCompatible = \func_get_args()[2] ?? false; + if (!\is_string($subject)) { throw new UnsupportedException(sprintf('Expected subject to be a "string", "%s" given.', get_debug_type($subject)), $subject); } @@ -79,20 +81,20 @@ public function resolve(mixed $subject, TypeContext $typeContext = null): Type $tokens = new TokenIterator($this->lexer->tokenize($subject)); $node = $this->parser->parse($tokens); - return $this->getTypeFromNode($node, $typeContext); + return $this->getTypeFromNode($node, $typeContext, $backwardCompatible); } catch (\DomainException $e) { throw new UnsupportedException(sprintf('Cannot resolve "%s".', $subject), $subject, previous: $e); } } - private function getTypeFromNode(TypeNode $node, ?TypeContext $typeContext): Type + private function getTypeFromNode(TypeNode $node, ?TypeContext $typeContext, bool $backwardCompatible): Type { if ($node instanceof CallableTypeNode) { return Type::callable(); } if ($node instanceof ArrayTypeNode) { - return Type::array($this->getTypeFromNode($node->type, $typeContext)); + return Type::list($this->getTypeFromNode($node->type, $typeContext, $backwardCompatible)); } if ($node instanceof ArrayShapeNode) { @@ -172,20 +174,26 @@ private function getTypeFromNode(TypeNode $node, ?TypeContext $typeContext): Typ } if ($node instanceof NullableTypeNode) { - return Type::nullable($this->getTypeFromNode($node->type, $typeContext)); + return Type::nullable($this->getTypeFromNode($node->type, $typeContext, $backwardCompatible)); } if ($node instanceof GenericTypeNode) { - $type = $this->getTypeFromNode($node->type, $typeContext); + $type = $this->getTypeFromNode($node->type, $typeContext, $backwardCompatible); // handle integer ranges as simple integers if ($type->isA(TypeIdentifier::INT)) { return $type; } - $variableTypes = array_map(fn (TypeNode $t): Type => $this->getTypeFromNode($t, $typeContext), $node->genericTypes); + // BC layer to convert class-string generics as simple strings + if ($backwardCompatible && 'class-string' === $node->type->name) { + return $type; + } + + $variableTypes = array_map(fn (TypeNode $t): Type => $this->getTypeFromNode($t, $typeContext, $backwardCompatible), $node->genericTypes); if ($type instanceof CollectionType) { + $asList = $type->isList(); $keyType = $type->getCollectionKeyType(); $type = $type->getType(); @@ -194,9 +202,12 @@ private function getTypeFromNode(TypeNode $node, ?TypeContext $typeContext): Typ } if (1 === \count($variableTypes)) { - return Type::collection($type, $variableTypes[0], $keyType); + // BC layer to keep "mixed" array key + $keyType = $backwardCompatible && 'list' !== $node->type->name ? Type::mixed() : $keyType; + + return new CollectionType(Type::generic($type, $keyType, $variableTypes[0]), $asList, true); } elseif (2 === \count($variableTypes)) { - return Type::collection($type, $variableTypes[1], $variableTypes[0]); + return Type::collection($type, $variableTypes[1], $variableTypes[0], $asList); } } @@ -212,11 +223,11 @@ private function getTypeFromNode(TypeNode $node, ?TypeContext $typeContext): Typ } if ($node instanceof UnionTypeNode) { - return Type::union(...array_map(fn (TypeNode $t): Type => $this->getTypeFromNode($t, $typeContext), $node->types)); + return Type::union(...array_map(fn (TypeNode $t): Type => $this->getTypeFromNode($t, $typeContext, $backwardCompatible), $node->types)); } if ($node instanceof IntersectionTypeNode) { - return Type::intersection(...array_map(fn (TypeNode $t): Type => $this->getTypeFromNode($t, $typeContext), $node->types)); + return Type::intersection(...array_map(fn (TypeNode $t): Type => $this->getTypeFromNode($t, $typeContext, $backwardCompatible), $node->types)); } throw new \DomainException(sprintf('Unhandled "%s" node.', $node::class)); diff --git a/src/Symfony/Component/Uid/Factory/TimeBasedUuidFactory.php b/src/Symfony/Component/Uid/Factory/TimeBasedUuidFactory.php index 5e27a27f28b1d..a24f247232b37 100644 --- a/src/Symfony/Component/Uid/Factory/TimeBasedUuidFactory.php +++ b/src/Symfony/Component/Uid/Factory/TimeBasedUuidFactory.php @@ -25,7 +25,7 @@ public function __construct( ) { } - public function create(\DateTimeInterface $time = null): Uuid&TimeBasedUidInterface + public function create(?\DateTimeInterface $time = null): Uuid&TimeBasedUidInterface { $class = $this->class; diff --git a/src/Symfony/Component/Uid/Factory/UlidFactory.php b/src/Symfony/Component/Uid/Factory/UlidFactory.php index 40cb7837178a9..9dd9d004c8207 100644 --- a/src/Symfony/Component/Uid/Factory/UlidFactory.php +++ b/src/Symfony/Component/Uid/Factory/UlidFactory.php @@ -15,7 +15,7 @@ class UlidFactory { - public function create(\DateTimeInterface $time = null): Ulid + public function create(?\DateTimeInterface $time = null): Ulid { return new Ulid(null === $time ? null : Ulid::generate($time)); } diff --git a/src/Symfony/Component/Uid/Factory/UuidFactory.php b/src/Symfony/Component/Uid/Factory/UuidFactory.php index 0255d51bf37b0..d1935c4baf642 100644 --- a/src/Symfony/Component/Uid/Factory/UuidFactory.php +++ b/src/Symfony/Component/Uid/Factory/UuidFactory.php @@ -26,7 +26,7 @@ class UuidFactory private ?Uuid $timeBasedNode; private ?Uuid $nameBasedNamespace; - public function __construct(string|int $defaultClass = UuidV6::class, string|int $timeBasedClass = UuidV6::class, string|int $nameBasedClass = UuidV5::class, string|int $randomBasedClass = UuidV4::class, Uuid|string $timeBasedNode = null, Uuid|string $nameBasedNamespace = null) + public function __construct(string|int $defaultClass = UuidV6::class, string|int $timeBasedClass = UuidV6::class, string|int $nameBasedClass = UuidV5::class, string|int $randomBasedClass = UuidV4::class, Uuid|string|null $timeBasedNode = null, Uuid|string|null $nameBasedNamespace = null) { if (null !== $timeBasedNode && !$timeBasedNode instanceof Uuid) { $timeBasedNode = Uuid::fromString($timeBasedNode); @@ -56,7 +56,7 @@ public function randomBased(): RandomBasedUuidFactory return new RandomBasedUuidFactory($this->randomBasedClass); } - public function timeBased(Uuid|string $node = null): TimeBasedUuidFactory + public function timeBased(Uuid|string|null $node = null): TimeBasedUuidFactory { $node ??= $this->timeBasedNode; @@ -67,7 +67,7 @@ public function timeBased(Uuid|string $node = null): TimeBasedUuidFactory return new TimeBasedUuidFactory($this->timeBasedClass, $node); } - public function nameBased(Uuid|string $namespace = null): NameBasedUuidFactory + public function nameBased(Uuid|string|null $namespace = null): NameBasedUuidFactory { $namespace ??= $this->nameBasedNamespace; diff --git a/src/Symfony/Component/Uid/Ulid.php b/src/Symfony/Component/Uid/Ulid.php index 7e42c451e325d..5359067afa545 100644 --- a/src/Symfony/Component/Uid/Ulid.php +++ b/src/Symfony/Component/Uid/Ulid.php @@ -26,7 +26,7 @@ class Ulid extends AbstractUid implements TimeBasedUidInterface private static string $time = ''; private static array $rand = []; - public function __construct(string $ulid = null) + public function __construct(?string $ulid = null) { if (null === $ulid) { $this->uid = static::generate(); @@ -148,7 +148,7 @@ public function getDateTime(): \DateTimeImmutable return \DateTimeImmutable::createFromFormat('U.u', substr_replace($time, '.', -3, 0)); } - public static function generate(\DateTimeInterface $time = null): string + public static function generate(?\DateTimeInterface $time = null): string { if (null === $mtime = $time) { $time = microtime(false); diff --git a/src/Symfony/Component/Uid/UuidV1.php b/src/Symfony/Component/Uid/UuidV1.php index 4bb24dacee87d..93100dd0f9b6e 100644 --- a/src/Symfony/Component/Uid/UuidV1.php +++ b/src/Symfony/Component/Uid/UuidV1.php @@ -22,7 +22,7 @@ class UuidV1 extends Uuid implements TimeBasedUidInterface private static string $clockSeq; - public function __construct(string $uuid = null) + public function __construct(?string $uuid = null) { if (null === $uuid) { $this->uid = uuid_create(static::TYPE); @@ -53,7 +53,7 @@ public function toV7(): UuidV7 return $this->toV6()->toV7(); } - public static function generate(\DateTimeInterface $time = null, Uuid $node = null): string + public static function generate(?\DateTimeInterface $time = null, ?Uuid $node = null): string { $uuid = !$time || !$node ? uuid_create(static::TYPE) : parent::NIL; diff --git a/src/Symfony/Component/Uid/UuidV4.php b/src/Symfony/Component/Uid/UuidV4.php index 9724b67de2c59..9b96ef345296c 100644 --- a/src/Symfony/Component/Uid/UuidV4.php +++ b/src/Symfony/Component/Uid/UuidV4.php @@ -20,7 +20,7 @@ class UuidV4 extends Uuid { protected const TYPE = 4; - public function __construct(string $uuid = null) + public function __construct(?string $uuid = null) { if (null === $uuid) { $uuid = random_bytes(16); diff --git a/src/Symfony/Component/Uid/UuidV6.php b/src/Symfony/Component/Uid/UuidV6.php index cb213ce774208..d106572e612cc 100644 --- a/src/Symfony/Component/Uid/UuidV6.php +++ b/src/Symfony/Component/Uid/UuidV6.php @@ -24,7 +24,7 @@ class UuidV6 extends Uuid implements TimeBasedUidInterface private static string $node; - public function __construct(string $uuid = null) + public function __construct(?string $uuid = null) { if (null === $uuid) { $this->uid = static::generate(); @@ -70,7 +70,7 @@ public function toV7(): UuidV7 ), '-', 8, 0)); } - public static function generate(\DateTimeInterface $time = null, Uuid $node = null): string + public static function generate(?\DateTimeInterface $time = null, ?Uuid $node = null): string { $uuidV1 = UuidV1::generate($time, $node); $uuid = substr($uuidV1, 15, 3).substr($uuidV1, 9, 4).$uuidV1[0].'-'.substr($uuidV1, 1, 4).'-6'.substr($uuidV1, 5, 3).substr($uuidV1, 18, 6); diff --git a/src/Symfony/Component/Uid/UuidV7.php b/src/Symfony/Component/Uid/UuidV7.php index 88797d37eda67..43740b67e08dc 100644 --- a/src/Symfony/Component/Uid/UuidV7.php +++ b/src/Symfony/Component/Uid/UuidV7.php @@ -28,7 +28,7 @@ class UuidV7 extends Uuid implements TimeBasedUidInterface private static array $seedParts; private static int $seedIndex = 0; - public function __construct(string $uuid = null) + public function __construct(?string $uuid = null) { if (null === $uuid) { $this->uid = static::generate(); @@ -49,7 +49,7 @@ public function getDateTime(): \DateTimeImmutable return \DateTimeImmutable::createFromFormat('U.v', substr_replace($time, '.', -3, 0)); } - public static function generate(\DateTimeInterface $time = null): string + public static function generate(?\DateTimeInterface $time = null): string { if (null === $mtime = $time) { $time = microtime(false); diff --git a/src/Symfony/Component/Validator/CHANGELOG.md b/src/Symfony/Component/Validator/CHANGELOG.md index 1a0a50b427bb9..72b08b710aa73 100644 --- a/src/Symfony/Component/Validator/CHANGELOG.md +++ b/src/Symfony/Component/Validator/CHANGELOG.md @@ -6,6 +6,8 @@ CHANGELOG * Add support for `Stringable` values when using the `Cidr`, `CssColor`, `ExpressionSyntax` and `PasswordStrength` constraints * Add `MacAddress` constraint + * Add `*_NO_PUBLIC`, `*_ONLY_PRIVATE` and `*_ONLY_RESERVED` versions to `Ip` constraint + * Possibility to use all `Ip` constraint versions for `Cidr` constraint * Add `list` and `associative_array` types to `Type` constraint * Add the `Charset` constraint diff --git a/src/Symfony/Component/Validator/Constraint.php b/src/Symfony/Component/Validator/Constraint.php index c8ec4041eadfe..42cc52da21b19 100644 --- a/src/Symfony/Component/Validator/Constraint.php +++ b/src/Symfony/Component/Validator/Constraint.php @@ -106,7 +106,7 @@ public static function getErrorName(string $errorCode): string * array, but getDefaultOption() returns * null */ - public function __construct(mixed $options = null, array $groups = null, mixed $payload = null) + public function __construct(mixed $options = null, ?array $groups = null, mixed $payload = null) { unset($this->groups); // enable lazy initialization diff --git a/src/Symfony/Component/Validator/ConstraintViolation.php b/src/Symfony/Component/Validator/ConstraintViolation.php index 43bab60aa87df..5129ccf952122 100644 --- a/src/Symfony/Component/Validator/ConstraintViolation.php +++ b/src/Symfony/Component/Validator/ConstraintViolation.php @@ -49,7 +49,7 @@ class ConstraintViolation implements ConstraintViolationInterface * caused the violation * @param mixed $cause The cause of the violation */ - public function __construct(string|\Stringable $message, ?string $messageTemplate, array $parameters, mixed $root, ?string $propertyPath, mixed $invalidValue, int $plural = null, string $code = null, Constraint $constraint = null, mixed $cause = null) + public function __construct(string|\Stringable $message, ?string $messageTemplate, array $parameters, mixed $root, ?string $propertyPath, mixed $invalidValue, ?int $plural = null, ?string $code = null, ?Constraint $constraint = null, mixed $cause = null) { $this->message = $message; $this->messageTemplate = $messageTemplate; diff --git a/src/Symfony/Component/Validator/Constraints/AbstractComparison.php b/src/Symfony/Component/Validator/Constraints/AbstractComparison.php index c7b87ba0ec791..5eb8c4696340f 100644 --- a/src/Symfony/Component/Validator/Constraints/AbstractComparison.php +++ b/src/Symfony/Component/Validator/Constraints/AbstractComparison.php @@ -28,7 +28,7 @@ abstract class AbstractComparison extends Constraint public mixed $value = null; public ?string $propertyPath = null; - public function __construct(mixed $value = null, string $propertyPath = null, string $message = null, array $groups = null, mixed $payload = null, array $options = []) + public function __construct(mixed $value = null, ?string $propertyPath = null, ?string $message = null, ?array $groups = null, mixed $payload = null, array $options = []) { if (\is_array($value)) { $options = array_merge($value, $options); diff --git a/src/Symfony/Component/Validator/Constraints/AbstractComparisonValidator.php b/src/Symfony/Component/Validator/Constraints/AbstractComparisonValidator.php index 2d358f47cfb43..941b1bef60429 100644 --- a/src/Symfony/Component/Validator/Constraints/AbstractComparisonValidator.php +++ b/src/Symfony/Component/Validator/Constraints/AbstractComparisonValidator.php @@ -29,7 +29,7 @@ abstract class AbstractComparisonValidator extends ConstraintValidator { private ?PropertyAccessorInterface $propertyAccessor; - public function __construct(PropertyAccessorInterface $propertyAccessor = null) + public function __construct(?PropertyAccessorInterface $propertyAccessor = null) { $this->propertyAccessor = $propertyAccessor; } diff --git a/src/Symfony/Component/Validator/Constraints/All.php b/src/Symfony/Component/Validator/Constraints/All.php index 33236dc1154f6..1da939dd5559f 100644 --- a/src/Symfony/Component/Validator/Constraints/All.php +++ b/src/Symfony/Component/Validator/Constraints/All.php @@ -28,7 +28,7 @@ class All extends Composite * @param array|array|null $constraints * @param string[]|null $groups */ - public function __construct(mixed $constraints = null, array $groups = null, mixed $payload = null) + public function __construct(mixed $constraints = null, ?array $groups = null, mixed $payload = null) { parent::__construct($constraints ?? [], $groups, $payload); } diff --git a/src/Symfony/Component/Validator/Constraints/AtLeastOneOf.php b/src/Symfony/Component/Validator/Constraints/AtLeastOneOf.php index 0b0f76dbe16d0..7fe57972d8cde 100644 --- a/src/Symfony/Component/Validator/Constraints/AtLeastOneOf.php +++ b/src/Symfony/Component/Validator/Constraints/AtLeastOneOf.php @@ -39,7 +39,7 @@ class AtLeastOneOf extends Composite * @param string|null $messageCollection Failure message for All and Collection inner constraints * @param bool|null $includeInternalMessages Whether to include inner constraint messages (defaults to true) */ - public function __construct(mixed $constraints = null, array $groups = null, mixed $payload = null, string $message = null, string $messageCollection = null, bool $includeInternalMessages = null) + public function __construct(mixed $constraints = null, ?array $groups = null, mixed $payload = null, ?string $message = null, ?string $messageCollection = null, ?bool $includeInternalMessages = null) { parent::__construct($constraints ?? [], $groups, $payload); diff --git a/src/Symfony/Component/Validator/Constraints/Bic.php b/src/Symfony/Component/Validator/Constraints/Bic.php index 653caad0ad927..37e0bd5030d67 100644 --- a/src/Symfony/Component/Validator/Constraints/Bic.php +++ b/src/Symfony/Component/Validator/Constraints/Bic.php @@ -53,7 +53,7 @@ class Bic extends Constraint * @param string|null $ibanPropertyPath Property path to the IBAN value when validating objects * @param string[]|null $groups */ - public function __construct(array $options = null, string $message = null, string $iban = null, string $ibanPropertyPath = null, string $ibanMessage = null, array $groups = null, mixed $payload = null) + public function __construct(?array $options = null, ?string $message = null, ?string $iban = null, ?string $ibanPropertyPath = null, ?string $ibanMessage = null, ?array $groups = null, mixed $payload = null) { if (!class_exists(Countries::class)) { throw new LogicException('The Intl component is required to use the Bic constraint. Try running "composer require symfony/intl".'); diff --git a/src/Symfony/Component/Validator/Constraints/BicValidator.php b/src/Symfony/Component/Validator/Constraints/BicValidator.php index b9eac610f7072..ffde49a995964 100644 --- a/src/Symfony/Component/Validator/Constraints/BicValidator.php +++ b/src/Symfony/Component/Validator/Constraints/BicValidator.php @@ -58,7 +58,7 @@ class BicValidator extends ConstraintValidator private ?PropertyAccessor $propertyAccessor; - public function __construct(PropertyAccessor $propertyAccessor = null) + public function __construct(?PropertyAccessor $propertyAccessor = null) { $this->propertyAccessor = $propertyAccessor; } diff --git a/src/Symfony/Component/Validator/Constraints/Blank.php b/src/Symfony/Component/Validator/Constraints/Blank.php index 6f13155e23611..283164fc17136 100644 --- a/src/Symfony/Component/Validator/Constraints/Blank.php +++ b/src/Symfony/Component/Validator/Constraints/Blank.php @@ -33,7 +33,7 @@ class Blank extends Constraint * @param array|null $options * @param string[]|null $groups */ - public function __construct(array $options = null, string $message = null, array $groups = null, mixed $payload = null) + public function __construct(?array $options = null, ?string $message = null, ?array $groups = null, mixed $payload = null) { parent::__construct($options ?? [], $groups, $payload); diff --git a/src/Symfony/Component/Validator/Constraints/Callback.php b/src/Symfony/Component/Validator/Constraints/Callback.php index 6d3912cc2db29..5ef48d88022be 100644 --- a/src/Symfony/Component/Validator/Constraints/Callback.php +++ b/src/Symfony/Component/Validator/Constraints/Callback.php @@ -30,7 +30,7 @@ class Callback extends Constraint * @param string|string[]|callable|array|null $callback The callback definition * @param string[]|null $groups */ - public function __construct(array|string|callable $callback = null, array $groups = null, mixed $payload = null, array $options = []) + public function __construct(array|string|callable|null $callback = null, ?array $groups = null, mixed $payload = null, array $options = []) { // Invocation through attributes with an array parameter only if (\is_array($callback) && 1 === \count($callback) && isset($callback['value'])) { diff --git a/src/Symfony/Component/Validator/Constraints/CardScheme.php b/src/Symfony/Component/Validator/Constraints/CardScheme.php index 19fdbe759f376..965ec1c01819b 100644 --- a/src/Symfony/Component/Validator/Constraints/CardScheme.php +++ b/src/Symfony/Component/Validator/Constraints/CardScheme.php @@ -51,7 +51,7 @@ class CardScheme extends Constraint * @param string[]|null $groups * @param array $options */ - public function __construct(array|string|null $schemes, string $message = null, array $groups = null, mixed $payload = null, array $options = []) + public function __construct(array|string|null $schemes, ?string $message = null, ?array $groups = null, mixed $payload = null, array $options = []) { if (\is_array($schemes) && \is_string(key($schemes))) { $options = array_merge($schemes, $options); diff --git a/src/Symfony/Component/Validator/Constraints/Cascade.php b/src/Symfony/Component/Validator/Constraints/Cascade.php index 3ef3b4bbc8c28..a1e7ccfa56379 100644 --- a/src/Symfony/Component/Validator/Constraints/Cascade.php +++ b/src/Symfony/Component/Validator/Constraints/Cascade.php @@ -28,7 +28,7 @@ class Cascade extends Constraint * @param string[]|string|array|null $exclude Properties excluded from validation * @param array|null $options */ - public function __construct(array|string $exclude = null, array $options = null) + public function __construct(array|string|null $exclude = null, ?array $options = null) { if (\is_array($exclude) && !array_is_list($exclude)) { $options = array_merge($exclude, $options ?? []); diff --git a/src/Symfony/Component/Validator/Constraints/Charset.php b/src/Symfony/Component/Validator/Constraints/Charset.php index 23d493fb62362..7afffcfa360f3 100644 --- a/src/Symfony/Component/Validator/Constraints/Charset.php +++ b/src/Symfony/Component/Validator/Constraints/Charset.php @@ -29,7 +29,7 @@ final class Charset extends Constraint public function __construct( public array|string $encodings = [], public string $message = 'The detected character encoding is invalid ({{ detected }}). Allowed encodings are {{ encodings }}.', - array $groups = null, + ?array $groups = null, mixed $payload = null, ) { parent::__construct(null, $groups, $payload); diff --git a/src/Symfony/Component/Validator/Constraints/CharsetValidator.php b/src/Symfony/Component/Validator/Constraints/CharsetValidator.php index 3e5de79d2eb48..5cde6ae90fd96 100644 --- a/src/Symfony/Component/Validator/Constraints/CharsetValidator.php +++ b/src/Symfony/Component/Validator/Constraints/CharsetValidator.php @@ -35,9 +35,9 @@ public function validate(mixed $value, Constraint $constraint): void throw new UnexpectedValueException($value, 'string'); } - if (!\in_array($detected = mb_detect_encoding($value, $constraint->encodings, true), (array) $constraint->encodings, true)) { + if (!\in_array(mb_detect_encoding($value, $constraint->encodings, true), (array) $constraint->encodings, true)) { $this->context->buildViolation($constraint->message) - ->setParameter('{{ detected }}', $detected) + ->setParameter('{{ detected }}', mb_detect_encoding($value, strict: true)) ->setParameter('{{ encodings }}', implode(', ', $constraint->encodings)) ->setCode(Charset::BAD_ENCODING_ERROR) ->addViolation(); diff --git a/src/Symfony/Component/Validator/Constraints/Choice.php b/src/Symfony/Component/Validator/Constraints/Choice.php index 36f1595a80d2d..743abaf9795a2 100644 --- a/src/Symfony/Component/Validator/Constraints/Choice.php +++ b/src/Symfony/Component/Validator/Constraints/Choice.php @@ -61,19 +61,19 @@ public function getDefaultOption(): ?string */ public function __construct( string|array $options = [], - array $choices = null, - callable|string $callback = null, - bool $multiple = null, - bool $strict = null, - int $min = null, - int $max = null, - string $message = null, - string $multipleMessage = null, - string $minMessage = null, - string $maxMessage = null, - array $groups = null, + ?array $choices = null, + callable|string|null $callback = null, + ?bool $multiple = null, + ?bool $strict = null, + ?int $min = null, + ?int $max = null, + ?string $message = null, + ?string $multipleMessage = null, + ?string $minMessage = null, + ?string $maxMessage = null, + ?array $groups = null, mixed $payload = null, - bool $match = null, + ?bool $match = null, ) { if (\is_array($options) && $options && array_is_list($options)) { $choices ??= $options; diff --git a/src/Symfony/Component/Validator/Constraints/Cidr.php b/src/Symfony/Component/Validator/Constraints/Cidr.php index 608c7ff26847d..98617c5578f29 100644 --- a/src/Symfony/Component/Validator/Constraints/Cidr.php +++ b/src/Symfony/Component/Validator/Constraints/Cidr.php @@ -13,6 +13,7 @@ use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\Exception\ConstraintDefinitionException; +use Symfony\Component\Validator\Exception\InvalidArgumentException; /** * Validates that a value is a valid CIDR notation. @@ -21,6 +22,7 @@ * * @author Sorin Pop * @author Calin Bolea + * @author Ninos Ego */ #[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] class Cidr extends Constraint @@ -34,9 +36,33 @@ class Cidr extends Constraint ]; private const NET_MAXES = [ - Ip::ALL => 128, Ip::V4 => 32, Ip::V6 => 128, + Ip::ALL => 128, + + Ip::V4_NO_PUBLIC => 32, + Ip::V6_NO_PUBLIC => 128, + Ip::ALL_NO_PUBLIC => 128, + + Ip::V4_NO_PRIVATE => 32, + Ip::V6_NO_PRIVATE => 128, + Ip::ALL_NO_PRIVATE => 128, + + Ip::V4_NO_RESERVED => 32, + Ip::V6_NO_RESERVED => 128, + Ip::ALL_NO_RESERVED => 128, + + Ip::V4_ONLY_PUBLIC => 32, + Ip::V6_ONLY_PUBLIC => 128, + Ip::ALL_ONLY_PUBLIC => 128, + + Ip::V4_ONLY_PRIVATE => 32, + Ip::V6_ONLY_PRIVATE => 128, + Ip::ALL_ONLY_PRIVATE => 128, + + Ip::V4_ONLY_RESERVED => 32, + Ip::V6_ONLY_RESERVED => 128, + Ip::ALL_ONLY_RESERVED => 128, ]; public string $version = Ip::ALL; @@ -45,21 +71,18 @@ class Cidr extends Constraint public int $netmaskMin = 0; public int $netmaskMax; - /** - * @param array|null $options - * @param string|null $version The CIDR version to validate (4, 6 or all, defaults to all) - * @param int|null $netmaskMin The lowest valid for a valid netmask (defaults to 0) - * @param int|null $netmaskMax The biggest valid for a valid netmask (defaults to 32 for IPv4, 128 for IPv6) - * @param string[]|null $groups - */ + /** @var callable|null */ + public $normalizer; + public function __construct( - array $options = null, - string $version = null, - int $netmaskMin = null, - int $netmaskMax = null, - string $message = null, - array $groups = null, + ?array $options = null, + ?string $version = null, + ?int $netmaskMin = null, + ?int $netmaskMax = null, + ?string $message = null, + ?array $groups = null, $payload = null, + ?callable $normalizer = null, ) { $this->version = $version ?? $options['version'] ?? $this->version; @@ -70,6 +93,7 @@ public function __construct( $this->netmaskMin = $netmaskMin ?? $options['netmaskMin'] ?? $this->netmaskMin; $this->netmaskMax = $netmaskMax ?? $options['netmaskMax'] ?? self::NET_MAXES[$this->version]; $this->message = $message ?? $this->message; + $this->normalizer = $normalizer ?? $this->normalizer; unset($options['netmaskMin'], $options['netmaskMax'], $options['version']); @@ -77,6 +101,10 @@ public function __construct( throw new ConstraintDefinitionException(sprintf('The netmask range must be between 0 and %d.', self::NET_MAXES[$this->version])); } + if (null !== $this->normalizer && !\is_callable($this->normalizer)) { + throw new InvalidArgumentException(sprintf('The "normalizer" option must be a valid callable ("%s" given).', get_debug_type($this->normalizer))); + } + parent::__construct($options, $groups, $payload); } } diff --git a/src/Symfony/Component/Validator/Constraints/CidrValidator.php b/src/Symfony/Component/Validator/Constraints/CidrValidator.php index 38168ebb47478..82373992f234a 100644 --- a/src/Symfony/Component/Validator/Constraints/CidrValidator.php +++ b/src/Symfony/Component/Validator/Constraints/CidrValidator.php @@ -16,6 +16,13 @@ use Symfony\Component\Validator\Exception\UnexpectedTypeException; use Symfony\Component\Validator\Exception\UnexpectedValueException; +/** + * Validates whether a value is a CIDR notation. + * + * @author Sorin Pop + * @author Calin Bolea + * @author Ninos Ego + */ class CidrValidator extends ConstraintValidator { public function validate($value, Constraint $constraint): void @@ -28,10 +35,16 @@ public function validate($value, Constraint $constraint): void return; } - if (!\is_string($value) && !$value instanceof \Stringable) { + if (!\is_scalar($value) && !$value instanceof \Stringable) { throw new UnexpectedValueException($value, 'string'); } + $value = (string) $value; + + if (null !== $constraint->normalizer) { + $value = ($constraint->normalizer)($value); + } + $cidrParts = explode('/', $value, 2); if (!isset($cidrParts[1]) @@ -49,14 +62,7 @@ public function validate($value, Constraint $constraint): void $ipAddress = $cidrParts[0]; $netmask = (int) $cidrParts[1]; - $validV4 = Ip::V6 !== $constraint->version - && filter_var($ipAddress, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV4) - && $netmask <= 32; - - $validV6 = Ip::V4 !== $constraint->version - && filter_var($ipAddress, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV6); - - if (!$validV4 && !$validV6) { + if (!IpValidator::checkIP($ipAddress, $constraint->version)) { $this->context ->buildViolation($constraint->message) ->setCode(Cidr::INVALID_CIDR_ERROR) @@ -65,6 +71,10 @@ public function validate($value, Constraint $constraint): void return; } + if (filter_var($ipAddress, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV4) && $constraint->netmaskMax > 32) { + $constraint->netmaskMax = 32; + } + if ($netmask < $constraint->netmaskMin || $netmask > $constraint->netmaskMax) { $this->context ->buildViolation($constraint->netmaskRangeViolationMessage) diff --git a/src/Symfony/Component/Validator/Constraints/Collection.php b/src/Symfony/Component/Validator/Constraints/Collection.php index dc7ed9b55eb7a..fe87938743c3c 100644 --- a/src/Symfony/Component/Validator/Constraints/Collection.php +++ b/src/Symfony/Component/Validator/Constraints/Collection.php @@ -41,7 +41,7 @@ class Collection extends Composite * @param bool|null $allowExtraFields Whether to allow additional keys not declared in the configured fields (defaults to false) * @param bool|null $allowMissingFields Whether to allow the collection to lack some fields declared in the configured fields (defaults to false) */ - public function __construct(mixed $fields = null, array $groups = null, mixed $payload = null, bool $allowExtraFields = null, bool $allowMissingFields = null, string $extraFieldsMessage = null, string $missingFieldsMessage = null) + public function __construct(mixed $fields = null, ?array $groups = null, mixed $payload = null, ?bool $allowExtraFields = null, ?bool $allowMissingFields = null, ?string $extraFieldsMessage = null, ?string $missingFieldsMessage = null) { if (\is_array($fields) && ([] === $fields || ($firstField = reset($fields)) instanceof Constraint || ($firstField[0] ?? null) instanceof Constraint)) { $fields = ['fields' => $fields]; diff --git a/src/Symfony/Component/Validator/Constraints/Composite.php b/src/Symfony/Component/Validator/Constraints/Composite.php index c77f8cf880789..b62aca91e8325 100644 --- a/src/Symfony/Component/Validator/Constraints/Composite.php +++ b/src/Symfony/Component/Validator/Constraints/Composite.php @@ -49,7 +49,7 @@ abstract class Composite extends Constraint * cached. When constraints are loaded from the cache, no more group * checks need to be done. */ - public function __construct(mixed $options = null, array $groups = null, mixed $payload = null) + public function __construct(mixed $options = null, ?array $groups = null, mixed $payload = null) { parent::__construct($options, $groups, $payload); diff --git a/src/Symfony/Component/Validator/Constraints/Count.php b/src/Symfony/Component/Validator/Constraints/Count.php index 7f9c47bcc3a25..3e1bf1e6b531f 100644 --- a/src/Symfony/Component/Validator/Constraints/Count.php +++ b/src/Symfony/Component/Validator/Constraints/Count.php @@ -51,15 +51,15 @@ class Count extends Constraint * @param array $options */ public function __construct( - int|array $exactly = null, - int $min = null, - int $max = null, - int $divisibleBy = null, - string $exactMessage = null, - string $minMessage = null, - string $maxMessage = null, - string $divisibleByMessage = null, - array $groups = null, + int|array|null $exactly = null, + ?int $min = null, + ?int $max = null, + ?int $divisibleBy = null, + ?string $exactMessage = null, + ?string $minMessage = null, + ?string $maxMessage = null, + ?string $divisibleByMessage = null, + ?array $groups = null, mixed $payload = null, array $options = [], ) { diff --git a/src/Symfony/Component/Validator/Constraints/Country.php b/src/Symfony/Component/Validator/Constraints/Country.php index ce1ece7e547f9..cb00162123691 100644 --- a/src/Symfony/Component/Validator/Constraints/Country.php +++ b/src/Symfony/Component/Validator/Constraints/Country.php @@ -42,10 +42,10 @@ class Country extends Constraint * @see https://en.wikipedia.org/wiki/ISO_3166-1_alpha-3#Current_codes */ public function __construct( - array $options = null, - string $message = null, - bool $alpha3 = null, - array $groups = null, + ?array $options = null, + ?string $message = null, + ?bool $alpha3 = null, + ?array $groups = null, mixed $payload = null, ) { if (!class_exists(Countries::class)) { diff --git a/src/Symfony/Component/Validator/Constraints/CssColor.php b/src/Symfony/Component/Validator/Constraints/CssColor.php index 47b0e7224e25a..5e67d157ba4dc 100644 --- a/src/Symfony/Component/Validator/Constraints/CssColor.php +++ b/src/Symfony/Component/Validator/Constraints/CssColor.php @@ -66,7 +66,7 @@ class CssColor extends Constraint * @param string[]|null $groups * @param array|null $options */ - public function __construct(array|string $formats = [], string $message = null, array $groups = null, $payload = null, array $options = null) + public function __construct(array|string $formats = [], ?string $message = null, ?array $groups = null, $payload = null, ?array $options = null) { $validationModesAsString = implode(', ', self::$validationModes); diff --git a/src/Symfony/Component/Validator/Constraints/Currency.php b/src/Symfony/Component/Validator/Constraints/Currency.php index ada418ab7477d..337481543a3af 100644 --- a/src/Symfony/Component/Validator/Constraints/Currency.php +++ b/src/Symfony/Component/Validator/Constraints/Currency.php @@ -38,7 +38,7 @@ class Currency extends Constraint * @param array|null $options * @param string[]|null $groups */ - public function __construct(array $options = null, string $message = null, array $groups = null, mixed $payload = null) + public function __construct(?array $options = null, ?string $message = null, ?array $groups = null, mixed $payload = null) { if (!class_exists(Currencies::class)) { throw new LogicException('The Intl component is required to use the Currency constraint. Try running "composer require symfony/intl".'); diff --git a/src/Symfony/Component/Validator/Constraints/Date.php b/src/Symfony/Component/Validator/Constraints/Date.php index 5d2be69ae7561..add1080798026 100644 --- a/src/Symfony/Component/Validator/Constraints/Date.php +++ b/src/Symfony/Component/Validator/Constraints/Date.php @@ -37,7 +37,7 @@ class Date extends Constraint * @param array|null $options * @param string[]|null $groups */ - public function __construct(array $options = null, string $message = null, array $groups = null, mixed $payload = null) + public function __construct(?array $options = null, ?string $message = null, ?array $groups = null, mixed $payload = null) { parent::__construct($options, $groups, $payload); diff --git a/src/Symfony/Component/Validator/Constraints/DateTime.php b/src/Symfony/Component/Validator/Constraints/DateTime.php index f35d6f2ff54ec..da2a2d6ba7a51 100644 --- a/src/Symfony/Component/Validator/Constraints/DateTime.php +++ b/src/Symfony/Component/Validator/Constraints/DateTime.php @@ -41,7 +41,7 @@ class DateTime extends Constraint * @param string[]|null $groups * @param array $options */ - public function __construct(string|array $format = null, string $message = null, array $groups = null, mixed $payload = null, array $options = []) + public function __construct(string|array|null $format = null, ?string $message = null, ?array $groups = null, mixed $payload = null, array $options = []) { if (\is_array($format)) { $options = array_merge($format, $options); diff --git a/src/Symfony/Component/Validator/Constraints/DisableAutoMapping.php b/src/Symfony/Component/Validator/Constraints/DisableAutoMapping.php index 29cfd19232f50..5dedddaa4f081 100644 --- a/src/Symfony/Component/Validator/Constraints/DisableAutoMapping.php +++ b/src/Symfony/Component/Validator/Constraints/DisableAutoMapping.php @@ -28,7 +28,7 @@ class DisableAutoMapping extends Constraint /** * @param array|null $options */ - public function __construct(array $options = null) + public function __construct(?array $options = null) { if (\is_array($options) && \array_key_exists('groups', $options)) { throw new ConstraintDefinitionException(sprintf('The option "groups" is not supported by the constraint "%s".', __CLASS__)); diff --git a/src/Symfony/Component/Validator/Constraints/Email.php b/src/Symfony/Component/Validator/Constraints/Email.php index 3ba5dcc50988e..01a91e50b5dc3 100644 --- a/src/Symfony/Component/Validator/Constraints/Email.php +++ b/src/Symfony/Component/Validator/Constraints/Email.php @@ -51,11 +51,11 @@ class Email extends Constraint * @param string[]|null $groups */ public function __construct( - array $options = null, - string $message = null, - string $mode = null, - callable $normalizer = null, - array $groups = null, + ?array $options = null, + ?string $message = null, + ?string $mode = null, + ?callable $normalizer = null, + ?array $groups = null, mixed $payload = null, ) { if (\is_array($options) && \array_key_exists('mode', $options) && !\in_array($options['mode'], self::VALIDATION_MODES, true)) { diff --git a/src/Symfony/Component/Validator/Constraints/EnableAutoMapping.php b/src/Symfony/Component/Validator/Constraints/EnableAutoMapping.php index 43ef30e6c2d5b..56327e3a2c178 100644 --- a/src/Symfony/Component/Validator/Constraints/EnableAutoMapping.php +++ b/src/Symfony/Component/Validator/Constraints/EnableAutoMapping.php @@ -28,7 +28,7 @@ class EnableAutoMapping extends Constraint /** * @param array|null $options */ - public function __construct(array $options = null) + public function __construct(?array $options = null) { if (\is_array($options) && \array_key_exists('groups', $options)) { throw new ConstraintDefinitionException(sprintf('The option "groups" is not supported by the constraint "%s".', __CLASS__)); diff --git a/src/Symfony/Component/Validator/Constraints/Expression.php b/src/Symfony/Component/Validator/Constraints/Expression.php index 7d3511d9c5869..aa4ee77852a88 100644 --- a/src/Symfony/Component/Validator/Constraints/Expression.php +++ b/src/Symfony/Component/Validator/Constraints/Expression.php @@ -47,12 +47,12 @@ class Expression extends Constraint */ public function __construct( string|ExpressionObject|array|null $expression, - string $message = null, - array $values = null, - array $groups = null, + ?string $message = null, + ?array $values = null, + ?array $groups = null, mixed $payload = null, array $options = [], - bool $negate = null, + ?bool $negate = null, ) { if (!class_exists(ExpressionLanguage::class)) { throw new LogicException(sprintf('The "symfony/expression-language" component is required to use the "%s" constraint. Try running "composer require symfony/expression-language".', __CLASS__)); diff --git a/src/Symfony/Component/Validator/Constraints/ExpressionSyntax.php b/src/Symfony/Component/Validator/Constraints/ExpressionSyntax.php index 082e41940a110..a03053f0242d3 100644 --- a/src/Symfony/Component/Validator/Constraints/ExpressionSyntax.php +++ b/src/Symfony/Component/Validator/Constraints/ExpressionSyntax.php @@ -37,7 +37,7 @@ class ExpressionSyntax extends Constraint * @param string[]|null $allowedVariables Restrict the available variables in the expression to these values (defaults to null that allows any variable) * @param string[]|null $groups */ - public function __construct(array $options = null, string $message = null, string $service = null, array $allowedVariables = null, array $groups = null, mixed $payload = null) + public function __construct(?array $options = null, ?string $message = null, ?string $service = null, ?array $allowedVariables = null, ?array $groups = null, mixed $payload = null) { parent::__construct($options, $groups, $payload); diff --git a/src/Symfony/Component/Validator/Constraints/ExpressionSyntaxValidator.php b/src/Symfony/Component/Validator/Constraints/ExpressionSyntaxValidator.php index c0c758d6a52ba..f377406a2209c 100644 --- a/src/Symfony/Component/Validator/Constraints/ExpressionSyntaxValidator.php +++ b/src/Symfony/Component/Validator/Constraints/ExpressionSyntaxValidator.php @@ -25,7 +25,7 @@ class ExpressionSyntaxValidator extends ConstraintValidator { private ?ExpressionLanguage $expressionLanguage; - public function __construct(ExpressionLanguage $expressionLanguage = null) + public function __construct(?ExpressionLanguage $expressionLanguage = null) { $this->expressionLanguage = $expressionLanguage; } diff --git a/src/Symfony/Component/Validator/Constraints/ExpressionValidator.php b/src/Symfony/Component/Validator/Constraints/ExpressionValidator.php index a119910788299..17ee72499fd77 100644 --- a/src/Symfony/Component/Validator/Constraints/ExpressionValidator.php +++ b/src/Symfony/Component/Validator/Constraints/ExpressionValidator.php @@ -24,7 +24,7 @@ class ExpressionValidator extends ConstraintValidator { private ExpressionLanguage $expressionLanguage; - public function __construct(ExpressionLanguage $expressionLanguage = null) + public function __construct(?ExpressionLanguage $expressionLanguage = null) { if ($expressionLanguage) { $this->expressionLanguage = $expressionLanguage; diff --git a/src/Symfony/Component/Validator/Constraints/File.php b/src/Symfony/Component/Validator/Constraints/File.php index c6be695223cee..fbc76e214c2d2 100644 --- a/src/Symfony/Component/Validator/Constraints/File.php +++ b/src/Symfony/Component/Validator/Constraints/File.php @@ -89,31 +89,31 @@ class File extends Constraint * @see https://www.iana.org/assignments/media-types/media-types.xhtml Existing media types */ public function __construct( - array $options = null, - int|string $maxSize = null, - bool $binaryFormat = null, - array|string $mimeTypes = null, - int $filenameMaxLength = null, - string $notFoundMessage = null, - string $notReadableMessage = null, - string $maxSizeMessage = null, - string $mimeTypesMessage = null, - string $disallowEmptyMessage = null, - string $filenameTooLongMessage = null, - - string $uploadIniSizeErrorMessage = null, - string $uploadFormSizeErrorMessage = null, - string $uploadPartialErrorMessage = null, - string $uploadNoFileErrorMessage = null, - string $uploadNoTmpDirErrorMessage = null, - string $uploadCantWriteErrorMessage = null, - string $uploadExtensionErrorMessage = null, - string $uploadErrorMessage = null, - array $groups = null, + ?array $options = null, + int|string|null $maxSize = null, + ?bool $binaryFormat = null, + array|string|null $mimeTypes = null, + ?int $filenameMaxLength = null, + ?string $notFoundMessage = null, + ?string $notReadableMessage = null, + ?string $maxSizeMessage = null, + ?string $mimeTypesMessage = null, + ?string $disallowEmptyMessage = null, + ?string $filenameTooLongMessage = null, + + ?string $uploadIniSizeErrorMessage = null, + ?string $uploadFormSizeErrorMessage = null, + ?string $uploadPartialErrorMessage = null, + ?string $uploadNoFileErrorMessage = null, + ?string $uploadNoTmpDirErrorMessage = null, + ?string $uploadCantWriteErrorMessage = null, + ?string $uploadExtensionErrorMessage = null, + ?string $uploadErrorMessage = null, + ?array $groups = null, mixed $payload = null, - array|string $extensions = null, - string $extensionsMessage = null, + array|string|null $extensions = null, + ?string $extensionsMessage = null, ) { parent::__construct($options, $groups, $payload); diff --git a/src/Symfony/Component/Validator/Constraints/Hostname.php b/src/Symfony/Component/Validator/Constraints/Hostname.php index 3ed4642064fc0..3090f1ecc54aa 100644 --- a/src/Symfony/Component/Validator/Constraints/Hostname.php +++ b/src/Symfony/Component/Validator/Constraints/Hostname.php @@ -36,10 +36,10 @@ class Hostname extends Constraint * @param string[]|null $groups */ public function __construct( - array $options = null, - string $message = null, - bool $requireTld = null, - array $groups = null, + ?array $options = null, + ?string $message = null, + ?bool $requireTld = null, + ?array $groups = null, mixed $payload = null, ) { parent::__construct($options, $groups, $payload); diff --git a/src/Symfony/Component/Validator/Constraints/Iban.php b/src/Symfony/Component/Validator/Constraints/Iban.php index b652d235466df..71b6d18c723df 100644 --- a/src/Symfony/Component/Validator/Constraints/Iban.php +++ b/src/Symfony/Component/Validator/Constraints/Iban.php @@ -45,7 +45,7 @@ class Iban extends Constraint * @param array|null $options * @param string[]|null $groups */ - public function __construct(array $options = null, string $message = null, array $groups = null, mixed $payload = null) + public function __construct(?array $options = null, ?string $message = null, ?array $groups = null, mixed $payload = null) { parent::__construct($options, $groups, $payload); diff --git a/src/Symfony/Component/Validator/Constraints/Image.php b/src/Symfony/Component/Validator/Constraints/Image.php index b49fc9c09d78c..158d4b2cb85ed 100644 --- a/src/Symfony/Component/Validator/Constraints/Image.php +++ b/src/Symfony/Component/Validator/Constraints/Image.php @@ -42,6 +42,7 @@ class Image extends File self::EMPTY_ERROR => 'EMPTY_ERROR', self::TOO_LARGE_ERROR => 'TOO_LARGE_ERROR', self::INVALID_MIME_TYPE_ERROR => 'INVALID_MIME_TYPE_ERROR', + self::FILENAME_TOO_LONG => 'FILENAME_TOO_LONG', self::SIZE_NOT_DETECTED_ERROR => 'SIZE_NOT_DETECTED_ERROR', self::TOO_WIDE_ERROR => 'TOO_WIDE_ERROR', self::TOO_NARROW_ERROR => 'TOO_NARROW_ERROR', @@ -118,51 +119,51 @@ class Image extends File * @see https://www.iana.org/assignments/media-types/media-types.xhtml Existing media types */ public function __construct( - array $options = null, - int|string $maxSize = null, - bool $binaryFormat = null, - array $mimeTypes = null, - int $filenameMaxLength = null, - int $minWidth = null, - int $maxWidth = null, - int $maxHeight = null, - int $minHeight = null, - int|float $maxRatio = null, - int|float $minRatio = null, - int|float $minPixels = null, - int|float $maxPixels = null, - bool $allowSquare = null, - bool $allowLandscape = null, - bool $allowPortrait = null, - bool $detectCorrupted = null, - string $notFoundMessage = null, - string $notReadableMessage = null, - string $maxSizeMessage = null, - string $mimeTypesMessage = null, - string $disallowEmptyMessage = null, - string $filenameTooLongMessage = null, - string $uploadIniSizeErrorMessage = null, - string $uploadFormSizeErrorMessage = null, - string $uploadPartialErrorMessage = null, - string $uploadNoFileErrorMessage = null, - string $uploadNoTmpDirErrorMessage = null, - string $uploadCantWriteErrorMessage = null, - string $uploadExtensionErrorMessage = null, - string $uploadErrorMessage = null, - string $sizeNotDetectedMessage = null, - string $maxWidthMessage = null, - string $minWidthMessage = null, - string $maxHeightMessage = null, - string $minHeightMessage = null, - string $minPixelsMessage = null, - string $maxPixelsMessage = null, - string $maxRatioMessage = null, - string $minRatioMessage = null, - string $allowSquareMessage = null, - string $allowLandscapeMessage = null, - string $allowPortraitMessage = null, - string $corruptedMessage = null, - array $groups = null, + ?array $options = null, + int|string|null $maxSize = null, + ?bool $binaryFormat = null, + ?array $mimeTypes = null, + ?int $filenameMaxLength = null, + ?int $minWidth = null, + ?int $maxWidth = null, + ?int $maxHeight = null, + ?int $minHeight = null, + int|float|null $maxRatio = null, + int|float|null $minRatio = null, + int|float|null $minPixels = null, + int|float|null $maxPixels = null, + ?bool $allowSquare = null, + ?bool $allowLandscape = null, + ?bool $allowPortrait = null, + ?bool $detectCorrupted = null, + ?string $notFoundMessage = null, + ?string $notReadableMessage = null, + ?string $maxSizeMessage = null, + ?string $mimeTypesMessage = null, + ?string $disallowEmptyMessage = null, + ?string $filenameTooLongMessage = null, + ?string $uploadIniSizeErrorMessage = null, + ?string $uploadFormSizeErrorMessage = null, + ?string $uploadPartialErrorMessage = null, + ?string $uploadNoFileErrorMessage = null, + ?string $uploadNoTmpDirErrorMessage = null, + ?string $uploadCantWriteErrorMessage = null, + ?string $uploadExtensionErrorMessage = null, + ?string $uploadErrorMessage = null, + ?string $sizeNotDetectedMessage = null, + ?string $maxWidthMessage = null, + ?string $minWidthMessage = null, + ?string $maxHeightMessage = null, + ?string $minHeightMessage = null, + ?string $minPixelsMessage = null, + ?string $maxPixelsMessage = null, + ?string $maxRatioMessage = null, + ?string $minRatioMessage = null, + ?string $allowSquareMessage = null, + ?string $allowLandscapeMessage = null, + ?string $allowPortraitMessage = null, + ?string $corruptedMessage = null, + ?array $groups = null, mixed $payload = null, ) { parent::__construct( diff --git a/src/Symfony/Component/Validator/Constraints/Ip.php b/src/Symfony/Component/Validator/Constraints/Ip.php index b828d51b74e37..4a8b980216a6d 100644 --- a/src/Symfony/Component/Validator/Constraints/Ip.php +++ b/src/Symfony/Component/Validator/Constraints/Ip.php @@ -20,6 +20,7 @@ * * @author Bernhard Schussek * @author Joseph Bielawski + * @author Ninos Ego */ #[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] class Ip extends Constraint @@ -28,21 +29,42 @@ class Ip extends Constraint public const V6 = '6'; public const ALL = 'all'; + // adds inverse FILTER_FLAG_NO_RES_RANGE and FILTER_FLAG_NO_PRIV_RANGE flags (skip both) + public const V4_NO_PUBLIC = '4_no_public'; + public const V6_NO_PUBLIC = '6_no_public'; + public const ALL_NO_PUBLIC = 'all_no_public'; + // adds FILTER_FLAG_NO_PRIV_RANGE flag (skip private ranges) - public const V4_NO_PRIV = '4_no_priv'; - public const V6_NO_PRIV = '6_no_priv'; - public const ALL_NO_PRIV = 'all_no_priv'; + public const V4_NO_PRIVATE = '4_no_priv'; + public const V4_NO_PRIV = self::V4_NO_PRIVATE; // BC: Alias + public const V6_NO_PRIVATE = '6_no_priv'; + public const V6_NO_PRIV = self::V6_NO_PRIVATE; // BC: Alias + public const ALL_NO_PRIVATE = 'all_no_priv'; + public const ALL_NO_PRIV = self::ALL_NO_PRIVATE; // BC: Alias // adds FILTER_FLAG_NO_RES_RANGE flag (skip reserved ranges) - public const V4_NO_RES = '4_no_res'; - public const V6_NO_RES = '6_no_res'; - public const ALL_NO_RES = 'all_no_res'; + public const V4_NO_RESERVED = '4_no_res'; + public const V4_NO_RES = self::V4_NO_RESERVED; // BC: Alias + public const V6_NO_RESERVED = '6_no_res'; + public const V6_NO_RES = self::V6_NO_RESERVED; // BC: Alias + public const ALL_NO_RESERVED = 'all_no_res'; + public const ALL_NO_RES = self::ALL_NO_RESERVED; // BC: Alias // adds FILTER_FLAG_NO_PRIV_RANGE and FILTER_FLAG_NO_RES_RANGE flags (skip both) public const V4_ONLY_PUBLIC = '4_public'; public const V6_ONLY_PUBLIC = '6_public'; public const ALL_ONLY_PUBLIC = 'all_public'; + // adds inverse FILTER_FLAG_NO_PRIV_RANGE + public const V4_ONLY_PRIVATE = '4_private'; + public const V6_ONLY_PRIVATE = '6_private'; + public const ALL_ONLY_PRIVATE = 'all_private'; + + // adds inverse FILTER_FLAG_NO_RES_RANGE + public const V4_ONLY_RESERVED = '4_reserved'; + public const V6_ONLY_RESERVED = '6_reserved'; + public const ALL_ONLY_RESERVED = 'all_reserved'; + public const INVALID_IP_ERROR = 'b1b427ae-9f6f-41b0-aa9b-84511fbb3c5b'; protected const VERSIONS = [ @@ -50,17 +72,29 @@ class Ip extends Constraint self::V6, self::ALL, - self::V4_NO_PRIV, - self::V6_NO_PRIV, - self::ALL_NO_PRIV, + self::V4_NO_PUBLIC, + self::V6_NO_PUBLIC, + self::ALL_NO_PUBLIC, - self::V4_NO_RES, - self::V6_NO_RES, - self::ALL_NO_RES, + self::V4_NO_PRIVATE, + self::V6_NO_PRIVATE, + self::ALL_NO_PRIVATE, + + self::V4_NO_RESERVED, + self::V6_NO_RESERVED, + self::ALL_NO_RESERVED, self::V4_ONLY_PUBLIC, self::V6_ONLY_PUBLIC, self::ALL_ONLY_PUBLIC, + + self::V4_ONLY_PRIVATE, + self::V6_ONLY_PRIVATE, + self::ALL_ONLY_PRIVATE, + + self::V4_ONLY_RESERVED, + self::V6_ONLY_RESERVED, + self::ALL_ONLY_RESERVED, ]; protected const ERROR_NAMES = [ @@ -78,11 +112,11 @@ class Ip extends Constraint * @param string[]|null $groups */ public function __construct( - array $options = null, - string $version = null, - string $message = null, - callable $normalizer = null, - array $groups = null, + ?array $options = null, + ?string $version = null, + ?string $message = null, + ?callable $normalizer = null, + ?array $groups = null, mixed $payload = null, ) { parent::__construct($options, $groups, $payload); diff --git a/src/Symfony/Component/Validator/Constraints/IpValidator.php b/src/Symfony/Component/Validator/Constraints/IpValidator.php index 8adba4bc95273..e2bf0d29afdfa 100644 --- a/src/Symfony/Component/Validator/Constraints/IpValidator.php +++ b/src/Symfony/Component/Validator/Constraints/IpValidator.php @@ -21,9 +21,50 @@ * * @author Bernhard Schussek * @author Joseph Bielawski + * @author Ninos Ego */ class IpValidator extends ConstraintValidator { + /** + * Checks whether an IP address is valid. + * + * @internal + */ + public static function checkIp(string $ip, mixed $version): bool + { + $flag = match ($version) { + Ip::V4, Ip::V4_NO_PUBLIC, Ip::V4_ONLY_PRIVATE, Ip::V4_ONLY_RESERVED => \FILTER_FLAG_IPV4, + Ip::V6, Ip::V6_NO_PUBLIC, Ip::V6_ONLY_PRIVATE, Ip::V6_ONLY_RESERVED => \FILTER_FLAG_IPV6, + Ip::V4_NO_PRIVATE => \FILTER_FLAG_IPV4 | \FILTER_FLAG_NO_PRIV_RANGE, + Ip::V6_NO_PRIVATE => \FILTER_FLAG_IPV6 | \FILTER_FLAG_NO_PRIV_RANGE, + Ip::ALL_NO_PRIVATE => \FILTER_FLAG_NO_PRIV_RANGE, + Ip::V4_NO_RESERVED => \FILTER_FLAG_IPV4 | \FILTER_FLAG_NO_RES_RANGE, + Ip::V6_NO_RESERVED => \FILTER_FLAG_IPV6 | \FILTER_FLAG_NO_RES_RANGE, + Ip::ALL_NO_RESERVED => \FILTER_FLAG_NO_RES_RANGE, + Ip::V4_ONLY_PUBLIC => \FILTER_FLAG_IPV4 | \FILTER_FLAG_NO_PRIV_RANGE | \FILTER_FLAG_NO_RES_RANGE, + Ip::V6_ONLY_PUBLIC => \FILTER_FLAG_IPV6 | \FILTER_FLAG_NO_PRIV_RANGE | \FILTER_FLAG_NO_RES_RANGE, + Ip::ALL_ONLY_PUBLIC => \FILTER_FLAG_NO_PRIV_RANGE | \FILTER_FLAG_NO_RES_RANGE, + default => 0, + }; + + if (!filter_var($ip, \FILTER_VALIDATE_IP, $flag)) { + return false; + } + + $inverseFlag = match ($version) { + Ip::V4_NO_PUBLIC, Ip::V6_NO_PUBLIC, Ip::ALL_NO_PUBLIC => \FILTER_FLAG_NO_PRIV_RANGE | \FILTER_FLAG_NO_RES_RANGE, + Ip::V4_ONLY_PRIVATE, Ip::V6_ONLY_PRIVATE, Ip::ALL_ONLY_PRIVATE => \FILTER_FLAG_NO_PRIV_RANGE, + Ip::V4_ONLY_RESERVED, Ip::V6_ONLY_RESERVED, Ip::ALL_ONLY_RESERVED => \FILTER_FLAG_NO_RES_RANGE, + default => 0, + }; + + if ($inverseFlag && filter_var($ip, \FILTER_VALIDATE_IP, $inverseFlag)) { + return false; + } + + return true; + } + public function validate(mixed $value, Constraint $constraint): void { if (!$constraint instanceof Ip) { @@ -44,22 +85,7 @@ public function validate(mixed $value, Constraint $constraint): void $value = ($constraint->normalizer)($value); } - $flag = match ($constraint->version) { - Ip::V4 => \FILTER_FLAG_IPV4, - Ip::V6 => \FILTER_FLAG_IPV6, - Ip::V4_NO_PRIV => \FILTER_FLAG_IPV4 | \FILTER_FLAG_NO_PRIV_RANGE, - Ip::V6_NO_PRIV => \FILTER_FLAG_IPV6 | \FILTER_FLAG_NO_PRIV_RANGE, - Ip::ALL_NO_PRIV => \FILTER_FLAG_NO_PRIV_RANGE, - Ip::V4_NO_RES => \FILTER_FLAG_IPV4 | \FILTER_FLAG_NO_RES_RANGE, - Ip::V6_NO_RES => \FILTER_FLAG_IPV6 | \FILTER_FLAG_NO_RES_RANGE, - Ip::ALL_NO_RES => \FILTER_FLAG_NO_RES_RANGE, - Ip::V4_ONLY_PUBLIC => \FILTER_FLAG_IPV4 | \FILTER_FLAG_NO_PRIV_RANGE | \FILTER_FLAG_NO_RES_RANGE, - Ip::V6_ONLY_PUBLIC => \FILTER_FLAG_IPV6 | \FILTER_FLAG_NO_PRIV_RANGE | \FILTER_FLAG_NO_RES_RANGE, - Ip::ALL_ONLY_PUBLIC => \FILTER_FLAG_NO_PRIV_RANGE | \FILTER_FLAG_NO_RES_RANGE, - default => 0, - }; - - if (!filter_var($value, \FILTER_VALIDATE_IP, $flag)) { + if (!self::checkIp($value, $constraint->version)) { $this->context->buildViolation($constraint->message) ->setParameter('{{ value }}', $this->formatValue($value)) ->setCode(Ip::INVALID_IP_ERROR) diff --git a/src/Symfony/Component/Validator/Constraints/IsFalse.php b/src/Symfony/Component/Validator/Constraints/IsFalse.php index 9a9d36ed0af66..a46b071c99950 100644 --- a/src/Symfony/Component/Validator/Constraints/IsFalse.php +++ b/src/Symfony/Component/Validator/Constraints/IsFalse.php @@ -33,7 +33,7 @@ class IsFalse extends Constraint * @param array|null $options * @param string[]|null $groups */ - public function __construct(array $options = null, string $message = null, array $groups = null, mixed $payload = null) + public function __construct(?array $options = null, ?string $message = null, ?array $groups = null, mixed $payload = null) { parent::__construct($options ?? [], $groups, $payload); diff --git a/src/Symfony/Component/Validator/Constraints/IsNull.php b/src/Symfony/Component/Validator/Constraints/IsNull.php index d0de1a1d8cf73..9f86e856030b5 100644 --- a/src/Symfony/Component/Validator/Constraints/IsNull.php +++ b/src/Symfony/Component/Validator/Constraints/IsNull.php @@ -33,7 +33,7 @@ class IsNull extends Constraint * @param array|null $options * @param string[]|null $groups */ - public function __construct(array $options = null, string $message = null, array $groups = null, mixed $payload = null) + public function __construct(?array $options = null, ?string $message = null, ?array $groups = null, mixed $payload = null) { parent::__construct($options ?? [], $groups, $payload); diff --git a/src/Symfony/Component/Validator/Constraints/IsTrue.php b/src/Symfony/Component/Validator/Constraints/IsTrue.php index 2366f279a7e83..c8418a2803dec 100644 --- a/src/Symfony/Component/Validator/Constraints/IsTrue.php +++ b/src/Symfony/Component/Validator/Constraints/IsTrue.php @@ -33,7 +33,7 @@ class IsTrue extends Constraint * @param array|null $options * @param string[]|null $groups */ - public function __construct(array $options = null, string $message = null, array $groups = null, mixed $payload = null) + public function __construct(?array $options = null, ?string $message = null, ?array $groups = null, mixed $payload = null) { parent::__construct($options ?? [], $groups, $payload); diff --git a/src/Symfony/Component/Validator/Constraints/Isbn.php b/src/Symfony/Component/Validator/Constraints/Isbn.php index b06c4a7a8fceb..c1bc83a344021 100644 --- a/src/Symfony/Component/Validator/Constraints/Isbn.php +++ b/src/Symfony/Component/Validator/Constraints/Isbn.php @@ -55,12 +55,12 @@ class Isbn extends Constraint * @param array $options */ public function __construct( - string|array $type = null, - string $message = null, - string $isbn10Message = null, - string $isbn13Message = null, - string $bothIsbnMessage = null, - array $groups = null, + string|array|null $type = null, + ?string $message = null, + ?string $isbn10Message = null, + ?string $isbn13Message = null, + ?string $bothIsbnMessage = null, + ?array $groups = null, mixed $payload = null, array $options = [], ) { diff --git a/src/Symfony/Component/Validator/Constraints/IsbnValidator.php b/src/Symfony/Component/Validator/Constraints/IsbnValidator.php index e0a390c65451f..de5f4437b8d3f 100644 --- a/src/Symfony/Component/Validator/Constraints/IsbnValidator.php +++ b/src/Symfony/Component/Validator/Constraints/IsbnValidator.php @@ -166,7 +166,7 @@ protected function validateIsbn13(string $isbn): string|bool return 0 === $checkSum % 10 ? true : Isbn::CHECKSUM_FAILED_ERROR; } - protected function getMessage(Isbn $constraint, string $type = null): string + protected function getMessage(Isbn $constraint, ?string $type = null): string { if (null !== $constraint->message) { return $constraint->message; diff --git a/src/Symfony/Component/Validator/Constraints/Isin.php b/src/Symfony/Component/Validator/Constraints/Isin.php index dfe1ab65e11d9..3f722d21af0cb 100644 --- a/src/Symfony/Component/Validator/Constraints/Isin.php +++ b/src/Symfony/Component/Validator/Constraints/Isin.php @@ -42,7 +42,7 @@ class Isin extends Constraint * @param array|null $options * @param string[]|null $groups */ - public function __construct(array $options = null, string $message = null, array $groups = null, mixed $payload = null) + public function __construct(?array $options = null, ?string $message = null, ?array $groups = null, mixed $payload = null) { parent::__construct($options, $groups, $payload); diff --git a/src/Symfony/Component/Validator/Constraints/Issn.php b/src/Symfony/Component/Validator/Constraints/Issn.php index 1f2c8d3a4421f..7d0e5b51580bd 100644 --- a/src/Symfony/Component/Validator/Constraints/Issn.php +++ b/src/Symfony/Component/Validator/Constraints/Issn.php @@ -51,11 +51,11 @@ class Issn extends Constraint * @param string[]|null $groups */ public function __construct( - array $options = null, - string $message = null, - bool $caseSensitive = null, - bool $requireHyphen = null, - array $groups = null, + ?array $options = null, + ?string $message = null, + ?bool $caseSensitive = null, + ?bool $requireHyphen = null, + ?array $groups = null, mixed $payload = null, ) { parent::__construct($options, $groups, $payload); diff --git a/src/Symfony/Component/Validator/Constraints/Json.php b/src/Symfony/Component/Validator/Constraints/Json.php index c97a5ca81bf0b..3b85a347c5c64 100644 --- a/src/Symfony/Component/Validator/Constraints/Json.php +++ b/src/Symfony/Component/Validator/Constraints/Json.php @@ -33,7 +33,7 @@ class Json extends Constraint * @param array|null $options * @param string[]|null $groups */ - public function __construct(array $options = null, string $message = null, array $groups = null, mixed $payload = null) + public function __construct(?array $options = null, ?string $message = null, ?array $groups = null, mixed $payload = null) { parent::__construct($options, $groups, $payload); diff --git a/src/Symfony/Component/Validator/Constraints/Language.php b/src/Symfony/Component/Validator/Constraints/Language.php index 71fcc2b356cd8..67c228448c11b 100644 --- a/src/Symfony/Component/Validator/Constraints/Language.php +++ b/src/Symfony/Component/Validator/Constraints/Language.php @@ -40,10 +40,10 @@ class Language extends Constraint * @param string[]|null $groups */ public function __construct( - array $options = null, - string $message = null, - bool $alpha3 = null, - array $groups = null, + ?array $options = null, + ?string $message = null, + ?bool $alpha3 = null, + ?array $groups = null, mixed $payload = null, ) { if (!class_exists(Languages::class)) { diff --git a/src/Symfony/Component/Validator/Constraints/Length.php b/src/Symfony/Component/Validator/Constraints/Length.php index 14b42535ba023..a80928bb3b96b 100644 --- a/src/Symfony/Component/Validator/Constraints/Length.php +++ b/src/Symfony/Component/Validator/Constraints/Length.php @@ -69,17 +69,17 @@ class Length extends Constraint * */ public function __construct( - int|array $exactly = null, - int $min = null, - int $max = null, - string $charset = null, - callable $normalizer = null, - string $countUnit = null, - string $exactMessage = null, - string $minMessage = null, - string $maxMessage = null, - string $charsetMessage = null, - array $groups = null, + int|array|null $exactly = null, + ?int $min = null, + ?int $max = null, + ?string $charset = null, + ?callable $normalizer = null, + ?string $countUnit = null, + ?string $exactMessage = null, + ?string $minMessage = null, + ?string $maxMessage = null, + ?string $charsetMessage = null, + ?array $groups = null, mixed $payload = null, array $options = [], ) { diff --git a/src/Symfony/Component/Validator/Constraints/Locale.php b/src/Symfony/Component/Validator/Constraints/Locale.php index dc6d00a54b2d9..fa31fbac41b10 100644 --- a/src/Symfony/Component/Validator/Constraints/Locale.php +++ b/src/Symfony/Component/Validator/Constraints/Locale.php @@ -40,10 +40,10 @@ class Locale extends Constraint * @param string[]|null $groups */ public function __construct( - array $options = null, - string $message = null, - bool $canonicalize = null, - array $groups = null, + ?array $options = null, + ?string $message = null, + ?bool $canonicalize = null, + ?array $groups = null, mixed $payload = null, ) { if (!class_exists(Locales::class)) { diff --git a/src/Symfony/Component/Validator/Constraints/Luhn.php b/src/Symfony/Component/Validator/Constraints/Luhn.php index c486bbd129678..df26b283e6696 100644 --- a/src/Symfony/Component/Validator/Constraints/Luhn.php +++ b/src/Symfony/Component/Validator/Constraints/Luhn.php @@ -40,9 +40,9 @@ class Luhn extends Constraint * @param string[]|null $groups */ public function __construct( - array $options = null, - string $message = null, - array $groups = null, + ?array $options = null, + ?string $message = null, + ?array $groups = null, mixed $payload = null, ) { parent::__construct($options, $groups, $payload); diff --git a/src/Symfony/Component/Validator/Constraints/MacAddress.php b/src/Symfony/Component/Validator/Constraints/MacAddress.php index 474f1d96d41ef..7f1f6325465c7 100644 --- a/src/Symfony/Component/Validator/Constraints/MacAddress.php +++ b/src/Symfony/Component/Validator/Constraints/MacAddress.php @@ -31,8 +31,8 @@ class MacAddress extends Constraint public function __construct( public string $message = 'This value is not a valid MAC address.', - callable $normalizer = null, - array $groups = null, + ?callable $normalizer = null, + ?array $groups = null, mixed $payload = null, ) { parent::__construct(null, $groups, $payload); diff --git a/src/Symfony/Component/Validator/Constraints/NoSuspiciousCharacters.php b/src/Symfony/Component/Validator/Constraints/NoSuspiciousCharacters.php index b201357e8e410..2dd6fb8f5a025 100644 --- a/src/Symfony/Component/Validator/Constraints/NoSuspiciousCharacters.php +++ b/src/Symfony/Component/Validator/Constraints/NoSuspiciousCharacters.php @@ -90,15 +90,15 @@ class NoSuspiciousCharacters extends Constraint * @param string[]|null $groups */ public function __construct( - array $options = null, - string $restrictionLevelMessage = null, - string $invisibleMessage = null, - string $mixedNumbersMessage = null, - string $hiddenOverlayMessage = null, - int $checks = null, - int $restrictionLevel = null, - array $locales = null, - array $groups = null, + ?array $options = null, + ?string $restrictionLevelMessage = null, + ?string $invisibleMessage = null, + ?string $mixedNumbersMessage = null, + ?string $hiddenOverlayMessage = null, + ?int $checks = null, + ?int $restrictionLevel = null, + ?array $locales = null, + ?array $groups = null, mixed $payload = null, ) { if (!class_exists(\Spoofchecker::class)) { diff --git a/src/Symfony/Component/Validator/Constraints/NotBlank.php b/src/Symfony/Component/Validator/Constraints/NotBlank.php index 2fe2d53086a05..96b49a302a592 100644 --- a/src/Symfony/Component/Validator/Constraints/NotBlank.php +++ b/src/Symfony/Component/Validator/Constraints/NotBlank.php @@ -39,7 +39,7 @@ class NotBlank extends Constraint * @param bool|null $allowNull Whether to allow null values (defaults to false) * @param string[]|null $groups */ - public function __construct(array $options = null, string $message = null, bool $allowNull = null, callable $normalizer = null, array $groups = null, mixed $payload = null) + public function __construct(?array $options = null, ?string $message = null, ?bool $allowNull = null, ?callable $normalizer = null, ?array $groups = null, mixed $payload = null) { parent::__construct($options ?? [], $groups, $payload); diff --git a/src/Symfony/Component/Validator/Constraints/NotCompromisedPassword.php b/src/Symfony/Component/Validator/Constraints/NotCompromisedPassword.php index f4d383770d7cb..a122089af97b0 100644 --- a/src/Symfony/Component/Validator/Constraints/NotCompromisedPassword.php +++ b/src/Symfony/Component/Validator/Constraints/NotCompromisedPassword.php @@ -38,11 +38,11 @@ class NotCompromisedPassword extends Constraint * @param string[]|null $groups */ public function __construct( - array $options = null, - string $message = null, - int $threshold = null, - bool $skipOnError = null, - array $groups = null, + ?array $options = null, + ?string $message = null, + ?int $threshold = null, + ?bool $skipOnError = null, + ?array $groups = null, mixed $payload = null, ) { parent::__construct($options, $groups, $payload); diff --git a/src/Symfony/Component/Validator/Constraints/NotCompromisedPasswordValidator.php b/src/Symfony/Component/Validator/Constraints/NotCompromisedPasswordValidator.php index f5cf41a032e1d..b16e97ce56e59 100644 --- a/src/Symfony/Component/Validator/Constraints/NotCompromisedPasswordValidator.php +++ b/src/Symfony/Component/Validator/Constraints/NotCompromisedPasswordValidator.php @@ -37,7 +37,7 @@ class NotCompromisedPasswordValidator extends ConstraintValidator private bool $enabled; private string $endpoint; - public function __construct(HttpClientInterface $httpClient = null, string $charset = 'UTF-8', bool $enabled = true, string $endpoint = null) + public function __construct(?HttpClientInterface $httpClient = null, string $charset = 'UTF-8', bool $enabled = true, ?string $endpoint = null) { if (null === $httpClient && !class_exists(HttpClient::class)) { throw new LogicException(sprintf('The "%s" class requires the "HttpClient" component. Try running "composer require symfony/http-client".', self::class)); diff --git a/src/Symfony/Component/Validator/Constraints/NotNull.php b/src/Symfony/Component/Validator/Constraints/NotNull.php index cd771a8d2a004..32a327a57c491 100644 --- a/src/Symfony/Component/Validator/Constraints/NotNull.php +++ b/src/Symfony/Component/Validator/Constraints/NotNull.php @@ -33,7 +33,7 @@ class NotNull extends Constraint * @param array|null $options * @param string[]|null $groups */ - public function __construct(array $options = null, string $message = null, array $groups = null, mixed $payload = null) + public function __construct(?array $options = null, ?string $message = null, ?array $groups = null, mixed $payload = null) { parent::__construct($options ?? [], $groups, $payload); diff --git a/src/Symfony/Component/Validator/Constraints/PasswordStrength.php b/src/Symfony/Component/Validator/Constraints/PasswordStrength.php index 662e89d17cefb..1827c5fb4f70f 100644 --- a/src/Symfony/Component/Validator/Constraints/PasswordStrength.php +++ b/src/Symfony/Component/Validator/Constraints/PasswordStrength.php @@ -43,7 +43,7 @@ final class PasswordStrength extends Constraint * @param self::STRENGTH_*|null $minScore The minimum required strength of the password (defaults to {@see PasswordStrength::STRENGTH_MEDIUM}) * @param string[]|null $groups */ - public function __construct(array $options = null, int $minScore = null, array $groups = null, mixed $payload = null, string $message = null) + public function __construct(?array $options = null, ?int $minScore = null, ?array $groups = null, mixed $payload = null, ?string $message = null) { $options['minScore'] ??= self::STRENGTH_MEDIUM; diff --git a/src/Symfony/Component/Validator/Constraints/Range.php b/src/Symfony/Component/Validator/Constraints/Range.php index bbf8e22bdde22..e91aaf98f1066 100644 --- a/src/Symfony/Component/Validator/Constraints/Range.php +++ b/src/Symfony/Component/Validator/Constraints/Range.php @@ -58,17 +58,17 @@ class Range extends Constraint * @param string[]|null $groups */ public function __construct( - array $options = null, - string $notInRangeMessage = null, - string $minMessage = null, - string $maxMessage = null, - string $invalidMessage = null, - string $invalidDateTimeMessage = null, + ?array $options = null, + ?string $notInRangeMessage = null, + ?string $minMessage = null, + ?string $maxMessage = null, + ?string $invalidMessage = null, + ?string $invalidDateTimeMessage = null, mixed $min = null, - string $minPropertyPath = null, + ?string $minPropertyPath = null, mixed $max = null, - string $maxPropertyPath = null, - array $groups = null, + ?string $maxPropertyPath = null, + ?array $groups = null, mixed $payload = null, ) { parent::__construct($options, $groups, $payload); diff --git a/src/Symfony/Component/Validator/Constraints/RangeValidator.php b/src/Symfony/Component/Validator/Constraints/RangeValidator.php index ee6d6acaa21f1..717c5d7f3b0fc 100644 --- a/src/Symfony/Component/Validator/Constraints/RangeValidator.php +++ b/src/Symfony/Component/Validator/Constraints/RangeValidator.php @@ -26,7 +26,7 @@ class RangeValidator extends ConstraintValidator { private ?PropertyAccessorInterface $propertyAccessor; - public function __construct(PropertyAccessorInterface $propertyAccessor = null) + public function __construct(?PropertyAccessorInterface $propertyAccessor = null) { $this->propertyAccessor = $propertyAccessor; } diff --git a/src/Symfony/Component/Validator/Constraints/Regex.php b/src/Symfony/Component/Validator/Constraints/Regex.php index add759670cdc4..d5342e7cba101 100644 --- a/src/Symfony/Component/Validator/Constraints/Regex.php +++ b/src/Symfony/Component/Validator/Constraints/Regex.php @@ -44,11 +44,11 @@ class Regex extends Constraint */ public function __construct( string|array|null $pattern, - string $message = null, - string $htmlPattern = null, - bool $match = null, - callable $normalizer = null, - array $groups = null, + ?string $message = null, + ?string $htmlPattern = null, + ?bool $match = null, + ?callable $normalizer = null, + ?array $groups = null, mixed $payload = null, array $options = [], ) { diff --git a/src/Symfony/Component/Validator/Constraints/Sequentially.php b/src/Symfony/Component/Validator/Constraints/Sequentially.php index 1ddb8b356c46c..d2b45e4bbea21 100644 --- a/src/Symfony/Component/Validator/Constraints/Sequentially.php +++ b/src/Symfony/Component/Validator/Constraints/Sequentially.php @@ -28,7 +28,7 @@ class Sequentially extends Composite * @param Constraint[]|array|null $constraints An array of validation constraints * @param string[]|null $groups */ - public function __construct(mixed $constraints = null, array $groups = null, mixed $payload = null) + public function __construct(mixed $constraints = null, ?array $groups = null, mixed $payload = null) { parent::__construct($constraints ?? [], $groups, $payload); } diff --git a/src/Symfony/Component/Validator/Constraints/Time.php b/src/Symfony/Component/Validator/Constraints/Time.php index 0e9056ae7d30f..dca9537cf68d9 100644 --- a/src/Symfony/Component/Validator/Constraints/Time.php +++ b/src/Symfony/Component/Validator/Constraints/Time.php @@ -38,11 +38,11 @@ class Time extends Constraint * @param bool|null $withSeconds Whether to allow seconds in the given value (defaults to true) */ public function __construct( - array $options = null, - string $message = null, - array $groups = null, + ?array $options = null, + ?string $message = null, + ?array $groups = null, mixed $payload = null, - bool $withSeconds = null, + ?bool $withSeconds = null, ) { parent::__construct($options, $groups, $payload); diff --git a/src/Symfony/Component/Validator/Constraints/Timezone.php b/src/Symfony/Component/Validator/Constraints/Timezone.php index e43f3f4ede794..de10e280353a5 100644 --- a/src/Symfony/Component/Validator/Constraints/Timezone.php +++ b/src/Symfony/Component/Validator/Constraints/Timezone.php @@ -50,11 +50,11 @@ class Timezone extends Constraint * @see \DateTimeZone */ public function __construct( - int|array $zone = null, - string $message = null, - string $countryCode = null, - bool $intlCompatible = null, - array $groups = null, + int|array|null $zone = null, + ?string $message = null, + ?string $countryCode = null, + ?bool $intlCompatible = null, + ?array $groups = null, mixed $payload = null, array $options = [], ) { diff --git a/src/Symfony/Component/Validator/Constraints/TimezoneValidator.php b/src/Symfony/Component/Validator/Constraints/TimezoneValidator.php index eee55d6e4d369..c6c7cbbfca18a 100644 --- a/src/Symfony/Component/Validator/Constraints/TimezoneValidator.php +++ b/src/Symfony/Component/Validator/Constraints/TimezoneValidator.php @@ -72,7 +72,7 @@ public function validate(mixed $value, Constraint $constraint): void ->addViolation(); } - private static function getPhpTimezones(int $zone, string $countryCode = null): array + private static function getPhpTimezones(int $zone, ?string $countryCode = null): array { if (null !== $countryCode) { try { @@ -85,7 +85,7 @@ private static function getPhpTimezones(int $zone, string $countryCode = null): return \DateTimeZone::listIdentifiers($zone); } - private static function getIntlTimezones(int $zone, string $countryCode = null): array + private static function getIntlTimezones(int $zone, ?string $countryCode = null): array { if (!class_exists(Timezones::class)) { return []; diff --git a/src/Symfony/Component/Validator/Constraints/Traverse.php b/src/Symfony/Component/Validator/Constraints/Traverse.php index 89c6e124eda41..8f999adee16a7 100644 --- a/src/Symfony/Component/Validator/Constraints/Traverse.php +++ b/src/Symfony/Component/Validator/Constraints/Traverse.php @@ -27,7 +27,7 @@ class Traverse extends Constraint /** * @param bool|array|null $traverse Whether to traverse the given object or not (defaults to true). Pass an associative array to configure the constraint's options (e.g. payload). */ - public function __construct(bool|array $traverse = null) + public function __construct(bool|array|null $traverse = null) { if (\is_array($traverse) && \array_key_exists('groups', $traverse)) { throw new ConstraintDefinitionException(sprintf('The option "groups" is not supported by the constraint "%s".', __CLASS__)); diff --git a/src/Symfony/Component/Validator/Constraints/Type.php b/src/Symfony/Component/Validator/Constraints/Type.php index ebedd39fa13b0..0482ff253d423 100644 --- a/src/Symfony/Component/Validator/Constraints/Type.php +++ b/src/Symfony/Component/Validator/Constraints/Type.php @@ -35,7 +35,7 @@ class Type extends Constraint * @param string[]|null $groups * @param array $options */ - public function __construct(string|array|null $type, string $message = null, array $groups = null, mixed $payload = null, array $options = []) + public function __construct(string|array|null $type, ?string $message = null, ?array $groups = null, mixed $payload = null, array $options = []) { if (\is_array($type) && \is_string(key($type))) { $options = array_merge($type, $options); diff --git a/src/Symfony/Component/Validator/Constraints/Ulid.php b/src/Symfony/Component/Validator/Constraints/Ulid.php index 719f57b1d197e..a5e2a47b9face 100644 --- a/src/Symfony/Component/Validator/Constraints/Ulid.php +++ b/src/Symfony/Component/Validator/Constraints/Ulid.php @@ -42,9 +42,9 @@ class Ulid extends Constraint * @param string[]|null $groups */ public function __construct( - array $options = null, - string $message = null, - array $groups = null, + ?array $options = null, + ?string $message = null, + ?array $groups = null, mixed $payload = null, ) { parent::__construct($options, $groups, $payload); diff --git a/src/Symfony/Component/Validator/Constraints/Unique.php b/src/Symfony/Component/Validator/Constraints/Unique.php index b1d08a10d600d..c759fac634349 100644 --- a/src/Symfony/Component/Validator/Constraints/Unique.php +++ b/src/Symfony/Component/Validator/Constraints/Unique.php @@ -40,12 +40,12 @@ class Unique extends Constraint * @param string[]|string|null $fields Defines the key or keys in the collection that should be checked for uniqueness (defaults to null, which ensure uniqueness for all keys) */ public function __construct( - array $options = null, - string $message = null, - callable $normalizer = null, - array $groups = null, + ?array $options = null, + ?string $message = null, + ?callable $normalizer = null, + ?array $groups = null, mixed $payload = null, - array|string $fields = null, + array|string|null $fields = null, ) { parent::__construct($options, $groups, $payload); diff --git a/src/Symfony/Component/Validator/Constraints/Url.php b/src/Symfony/Component/Validator/Constraints/Url.php index e1230e8619285..17d72d5236829 100644 --- a/src/Symfony/Component/Validator/Constraints/Url.php +++ b/src/Symfony/Component/Validator/Constraints/Url.php @@ -41,12 +41,12 @@ class Url extends Constraint * @param string[]|null $groups */ public function __construct( - array $options = null, - string $message = null, - array $protocols = null, - bool $relativeProtocol = null, - callable $normalizer = null, - array $groups = null, + ?array $options = null, + ?string $message = null, + ?array $protocols = null, + ?bool $relativeProtocol = null, + ?callable $normalizer = null, + ?array $groups = null, mixed $payload = null, ) { parent::__construct($options, $groups, $payload); diff --git a/src/Symfony/Component/Validator/Constraints/Uuid.php b/src/Symfony/Component/Validator/Constraints/Uuid.php index 565110428cef8..1b028f1a9ddfa 100644 --- a/src/Symfony/Component/Validator/Constraints/Uuid.php +++ b/src/Symfony/Component/Validator/Constraints/Uuid.php @@ -101,12 +101,12 @@ class Uuid extends Constraint * @param string[]|null $groups */ public function __construct( - array $options = null, - string $message = null, - array|int $versions = null, - bool $strict = null, - callable $normalizer = null, - array $groups = null, + ?array $options = null, + ?string $message = null, + array|int|null $versions = null, + ?bool $strict = null, + ?callable $normalizer = null, + ?array $groups = null, mixed $payload = null, ) { parent::__construct($options, $groups, $payload); diff --git a/src/Symfony/Component/Validator/Constraints/Valid.php b/src/Symfony/Component/Validator/Constraints/Valid.php index 91e59f56077c5..f94d959a3ce26 100644 --- a/src/Symfony/Component/Validator/Constraints/Valid.php +++ b/src/Symfony/Component/Validator/Constraints/Valid.php @@ -28,7 +28,7 @@ class Valid extends Constraint * @param string[]|null $groups * @param bool|null $traverse Whether to validate {@see \Traversable} objects (defaults to true) */ - public function __construct(array $options = null, array $groups = null, $payload = null, bool $traverse = null) + public function __construct(?array $options = null, ?array $groups = null, $payload = null, ?bool $traverse = null) { parent::__construct($options ?? [], $groups, $payload); diff --git a/src/Symfony/Component/Validator/Constraints/When.php b/src/Symfony/Component/Validator/Constraints/When.php index e5c5629c2783e..52121c41c03ce 100644 --- a/src/Symfony/Component/Validator/Constraints/When.php +++ b/src/Symfony/Component/Validator/Constraints/When.php @@ -35,7 +35,7 @@ class When extends Composite * @param string[]|null $groups * @param array $options */ - public function __construct(string|Expression|array $expression, array|Constraint $constraints = null, array $values = null, array $groups = null, $payload = null, array $options = []) + public function __construct(string|Expression|array $expression, array|Constraint|null $constraints = null, ?array $values = null, ?array $groups = null, $payload = null, array $options = []) { if (!class_exists(ExpressionLanguage::class)) { throw new LogicException(sprintf('The "symfony/expression-language" component is required to use the "%s" constraint. Try running "composer require symfony/expression-language".', __CLASS__)); diff --git a/src/Symfony/Component/Validator/Constraints/ZeroComparisonConstraintTrait.php b/src/Symfony/Component/Validator/Constraints/ZeroComparisonConstraintTrait.php index 79f6ab10d7c3b..4c816474f9701 100644 --- a/src/Symfony/Component/Validator/Constraints/ZeroComparisonConstraintTrait.php +++ b/src/Symfony/Component/Validator/Constraints/ZeroComparisonConstraintTrait.php @@ -21,7 +21,7 @@ */ trait ZeroComparisonConstraintTrait { - public function __construct(array $options = null, string $message = null, array $groups = null, mixed $payload = null) + public function __construct(?array $options = null, ?string $message = null, ?array $groups = null, mixed $payload = null) { $options ??= []; diff --git a/src/Symfony/Component/Validator/Context/ExecutionContext.php b/src/Symfony/Component/Validator/Context/ExecutionContext.php index 93b7166b892e8..f21ed90fbd40e 100644 --- a/src/Symfony/Component/Validator/Context/ExecutionContext.php +++ b/src/Symfony/Component/Validator/Context/ExecutionContext.php @@ -111,7 +111,7 @@ class ExecutionContext implements ExecutionContextInterface /** * @internal Called by {@link ExecutionContextFactory}. Should not be used in user code. */ - public function __construct(ValidatorInterface $validator, mixed $root, TranslatorInterface $translator, string $translationDomain = null) + public function __construct(ValidatorInterface $validator, mixed $root, TranslatorInterface $translator, ?string $translationDomain = null) { $this->validator = $validator; $this->root = $root; diff --git a/src/Symfony/Component/Validator/Context/ExecutionContextFactory.php b/src/Symfony/Component/Validator/Context/ExecutionContextFactory.php index bcad7c20eac92..9979059b6d1b4 100644 --- a/src/Symfony/Component/Validator/Context/ExecutionContextFactory.php +++ b/src/Symfony/Component/Validator/Context/ExecutionContextFactory.php @@ -26,7 +26,7 @@ class ExecutionContextFactory implements ExecutionContextFactoryInterface private TranslatorInterface $translator; private ?string $translationDomain; - public function __construct(TranslatorInterface $translator, string $translationDomain = null) + public function __construct(TranslatorInterface $translator, ?string $translationDomain = null) { $this->translator = $translator; $this->translationDomain = $translationDomain; diff --git a/src/Symfony/Component/Validator/DataCollector/ValidatorDataCollector.php b/src/Symfony/Component/Validator/DataCollector/ValidatorDataCollector.php index 25734da878ce2..a50b16687ad54 100644 --- a/src/Symfony/Component/Validator/DataCollector/ValidatorDataCollector.php +++ b/src/Symfony/Component/Validator/DataCollector/ValidatorDataCollector.php @@ -37,7 +37,7 @@ public function __construct(TraceableValidator $validator) $this->reset(); } - public function collect(Request $request, Response $response, \Throwable $exception = null): void + public function collect(Request $request, Response $response, ?\Throwable $exception = null): void { // Everything is collected once, on kernel terminate. } diff --git a/src/Symfony/Component/Validator/Mapping/Factory/LazyLoadingMetadataFactory.php b/src/Symfony/Component/Validator/Mapping/Factory/LazyLoadingMetadataFactory.php index 4c2998116056e..e8651417ccaec 100644 --- a/src/Symfony/Component/Validator/Mapping/Factory/LazyLoadingMetadataFactory.php +++ b/src/Symfony/Component/Validator/Mapping/Factory/LazyLoadingMetadataFactory.php @@ -49,7 +49,7 @@ class LazyLoadingMetadataFactory implements MetadataFactoryInterface */ protected array $loadedClasses = []; - public function __construct(LoaderInterface $loader = null, CacheItemPoolInterface $cache = null) + public function __construct(?LoaderInterface $loader = null, ?CacheItemPoolInterface $cache = null) { $this->loader = $loader; $this->cache = $cache; diff --git a/src/Symfony/Component/Validator/Mapping/GetterMetadata.php b/src/Symfony/Component/Validator/Mapping/GetterMetadata.php index 6964c5130b95f..8b8d2e15f7829 100644 --- a/src/Symfony/Component/Validator/Mapping/GetterMetadata.php +++ b/src/Symfony/Component/Validator/Mapping/GetterMetadata.php @@ -39,7 +39,7 @@ class GetterMetadata extends MemberMetadata * * @throws ValidatorException */ - public function __construct(string $class, string $property, string $method = null) + public function __construct(string $class, string $property, ?string $method = null) { if (null === $method) { $getMethod = 'get'.ucfirst($property); diff --git a/src/Symfony/Component/Validator/Mapping/Loader/AutoMappingTrait.php b/src/Symfony/Component/Validator/Mapping/Loader/AutoMappingTrait.php index f76442f06c05e..4661ce657cd7c 100644 --- a/src/Symfony/Component/Validator/Mapping/Loader/AutoMappingTrait.php +++ b/src/Symfony/Component/Validator/Mapping/Loader/AutoMappingTrait.php @@ -21,7 +21,7 @@ */ trait AutoMappingTrait { - private function isAutoMappingEnabledForClass(ClassMetadata $metadata, string $classValidatorRegexp = null): bool + private function isAutoMappingEnabledForClass(ClassMetadata $metadata, ?string $classValidatorRegexp = null): bool { // Check if AutoMapping constraint is set first if (AutoMappingStrategy::NONE !== $strategy = $metadata->getAutoMappingStrategy()) { diff --git a/src/Symfony/Component/Validator/Mapping/Loader/PropertyInfoLoader.php b/src/Symfony/Component/Validator/Mapping/Loader/PropertyInfoLoader.php index 89929bd43df24..d775e244535cf 100644 --- a/src/Symfony/Component/Validator/Mapping/Loader/PropertyInfoLoader.php +++ b/src/Symfony/Component/Validator/Mapping/Loader/PropertyInfoLoader.php @@ -14,7 +14,12 @@ use Symfony\Component\PropertyInfo\PropertyAccessExtractorInterface; use Symfony\Component\PropertyInfo\PropertyListExtractorInterface; use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface; -use Symfony\Component\PropertyInfo\Type as PropertyInfoType; +use Symfony\Component\TypeInfo\Type as TypeInfoType; +use Symfony\Component\TypeInfo\Type\CollectionType; +use Symfony\Component\TypeInfo\Type\IntersectionType; +use Symfony\Component\TypeInfo\Type\ObjectType; +use Symfony\Component\TypeInfo\Type\UnionType; +use Symfony\Component\TypeInfo\TypeIdentifier; use Symfony\Component\Validator\Constraints\All; use Symfony\Component\Validator\Constraints\NotBlank; use Symfony\Component\Validator\Constraints\NotNull; @@ -36,7 +41,7 @@ final class PropertyInfoLoader implements LoaderInterface private PropertyAccessExtractorInterface $accessExtractor; private ?string $classValidatorRegexp; - public function __construct(PropertyListExtractorInterface $listExtractor, PropertyTypeExtractorInterface $typeExtractor, PropertyAccessExtractorInterface $accessExtractor, string $classValidatorRegexp = null) + public function __construct(PropertyListExtractorInterface $listExtractor, PropertyTypeExtractorInterface $typeExtractor, PropertyAccessExtractorInterface $accessExtractor, ?string $classValidatorRegexp = null) { $this->listExtractor = $listExtractor; $this->typeExtractor = $typeExtractor; @@ -62,11 +67,6 @@ public function loadClassMetadata(ClassMetadata $metadata): bool continue; } - $types = $this->typeExtractor->getTypes($className, $property); - if (null === $types) { - continue; - } - $enabledForProperty = $enabledForClass; $hasTypeConstraint = false; $hasNotNullConstraint = false; @@ -100,31 +100,28 @@ public function loadClassMetadata(ClassMetadata $metadata): bool } $loaded = true; - $builtinTypes = []; + + if ($hasTypeConstraint) { + continue; + } + + if (null === $type = $this->typeExtractor->getType($className, $property)) { + continue; + } + $nullable = false; - $scalar = true; - foreach ($types as $type) { - $builtinTypes[] = $type->getBuiltinType(); - if ($scalar && !\in_array($type->getBuiltinType(), [PropertyInfoType::BUILTIN_TYPE_INT, PropertyInfoType::BUILTIN_TYPE_FLOAT, PropertyInfoType::BUILTIN_TYPE_STRING, PropertyInfoType::BUILTIN_TYPE_BOOL], true)) { - $scalar = false; - } + if ($type instanceof UnionType && $type->isNullable()) { + $nullable = true; + $type = $type->asNonNullable(); + } - if (!$nullable && $type->isNullable()) { - $nullable = true; - } + if ($type instanceof CollectionType) { + $this->handleAllConstraint($property, $allConstraint, $type->getCollectionValueType(), $metadata); } - if (!$hasTypeConstraint) { - if (1 === \count($builtinTypes)) { - if ($types[0]->isCollection() && \count($collectionValueType = $types[0]->getCollectionValueTypes()) > 0) { - [$collectionValueType] = $collectionValueType; - $this->handleAllConstraint($property, $allConstraint, $collectionValueType, $metadata); - } - $metadata->addPropertyConstraint($property, $this->getTypeConstraint($builtinTypes[0], $types[0])); - } elseif ($scalar) { - $metadata->addPropertyConstraint($property, new Type(['type' => 'scalar'])); - } + if (null !== $typeConstraint = $this->getTypeConstraint($type)) { + $metadata->addPropertyConstraint($property, $typeConstraint); } if (!$nullable && !$hasNotBlankConstraint && !$hasNotNullConstraint) { @@ -135,16 +132,26 @@ public function loadClassMetadata(ClassMetadata $metadata): bool return $loaded; } - private function getTypeConstraint(string $builtinType, PropertyInfoType $type): Type + private function getTypeConstraint(TypeInfoType $type): ?Type { - if (PropertyInfoType::BUILTIN_TYPE_OBJECT === $builtinType && null !== $className = $type->getClassName()) { - return new Type(['type' => $className]); + if ($type instanceof UnionType || $type instanceof IntersectionType) { + return ($type->isA(TypeIdentifier::INT) || $type->isA(TypeIdentifier::FLOAT) || $type->isA(TypeIdentifier::STRING) || $type->isA(TypeIdentifier::BOOL)) ? new Type(['type' => 'scalar']) : null; } - return new Type(['type' => $builtinType]); + $baseType = $type->getBaseType(); + + if ($baseType instanceof ObjectType) { + return new Type(['type' => $baseType->getClassName()]); + } + + if (TypeIdentifier::MIXED !== $baseType->getTypeIdentifier()) { + return new Type(['type' => $baseType->getTypeIdentifier()->value]); + } + + return null; } - private function handleAllConstraint(string $property, ?All $allConstraint, PropertyInfoType $propertyInfoType, ClassMetadata $metadata): void + private function handleAllConstraint(string $property, ?All $allConstraint, TypeInfoType $typeInfoType, ClassMetadata $metadata): void { $containsTypeConstraint = false; $containsNotNullConstraint = false; @@ -159,12 +166,16 @@ private function handleAllConstraint(string $property, ?All $allConstraint, Prop } $constraints = []; - if (!$containsNotNullConstraint && !$propertyInfoType->isNullable()) { + if (!$containsNotNullConstraint && !$typeInfoType->isNullable()) { $constraints[] = new NotNull(); } - if (!$containsTypeConstraint) { - $constraints[] = $this->getTypeConstraint($propertyInfoType->getBuiltinType(), $propertyInfoType); + if (!$containsTypeConstraint && null !== $typeConstraint = $this->getTypeConstraint($typeInfoType)) { + $constraints[] = $typeConstraint; + } + + if ([] === $constraints) { + return; } if (null === $allConstraint) { diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.af.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.af.xlf index 2b436c1dce25b..387fb9a649711 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.af.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.af.xlf @@ -134,8 +134,8 @@ This file is not a valid image. Hierdie lêer is nie 'n geldige beeld nie. - - This is not a valid IP address. + + This value is not a valid IP address. Hierdie waarde is nie 'n geldige IP-adres nie. @@ -190,9 +190,9 @@ No file was uploaded. Geen lêer is opgelaai nie. - - No temporary folder was configured in php.ini. - Geen tydelike lêer is ingestel in php.ini nie. + + No temporary folder was configured in php.ini, or the configured folder does not exist. + Geen tydelike gids is in php.ini opgestel nie, of die opgestelde gids bestaan nie. Cannot write temporary file to disk. @@ -222,8 +222,8 @@ Unsupported card type or invalid card number. Nie-ondersteunde tipe kaart of ongeldige kredietkaart nommer. - - This is not a valid International Bank Account Number (IBAN). + + This value is not a valid International Bank Account Number (IBAN). Hierdie waarde is nie 'n geldige Internasionale Bankrekeningnommer (IBAN) nie. @@ -310,16 +310,16 @@ This value does not match the expected {{ charset }} charset. Die waarde stem nie ooreen met die verwagte {{ charset }} karakterstel nie. - - This is not a valid Business Identifier Code (BIC). + + This value is not a valid Business Identifier Code (BIC). Hierdie waarde is nie 'n geldige Besigheid Identifiseerder Kode (BIC) nie. Error Fout - - This is not a valid UUID. + + This value is not a valid UUID. Hierdie waarde is nie 'n geldige UUID nie. diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.ar.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.ar.xlf index 0903a9249cf01..6ac303a778fa9 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.ar.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.ar.xlf @@ -134,8 +134,8 @@ This file is not a valid image. هذا الملف ليس صورة صحيحة. - - This is not a valid IP address. + + This value is not a valid IP address. هذه القيمة ليست عنوان IP صالحًا. @@ -190,9 +190,9 @@ No file was uploaded. لم يتم ارسال اى ملف. - - No temporary folder was configured in php.ini. - لم يتم تهيئة حافظة مؤقتة فى ملف php.ini. + + No temporary folder was configured in php.ini, or the configured folder does not exist. + لم يتم تكوين مجلد مؤقت في ملف php.ini، أو المجلد المعد لا يوجد. Cannot write temporary file to disk. @@ -222,8 +222,8 @@ Unsupported card type or invalid card number. نوع البطاقه غير مدعوم او الرقم غير صحيح. - - This is not a valid International Bank Account Number (IBAN). + + This value is not a valid International Bank Account Number (IBAN). هذه القيمة ليست رقم حساب بنكي دولي (IBAN) صالحًا. @@ -310,16 +310,16 @@ This value does not match the expected {{ charset }} charset. هذه القيمة غير متطابقة مع صيغة التحويل {{ charset }}. - - This is not a valid Business Identifier Code (BIC). + + This value is not a valid Business Identifier Code (BIC). هذه القيمة ليست رمز معرف الأعمال (BIC) صالحًا. Error خطأ - - This is not a valid UUID. + + This value is not a valid UUID. هذه القيمة ليست UUID صالحًا. diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.az.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.az.xlf index 6030add7a5b5b..b6152e99dabc0 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.az.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.az.xlf @@ -134,8 +134,8 @@ This file is not a valid image. Bu fayl düzgün bir şəkil deyil. - - This is not a valid IP address. + + This value is not a valid IP address. Bu dəyər etibarlı bir IP ünvanı deyil. @@ -190,9 +190,9 @@ No file was uploaded. Fayl yüklənmədi. - - No temporary folder was configured in php.ini. - php.ini'də müvəqqəti qovluq quraşdırılmayıb. + + No temporary folder was configured in php.ini, or the configured folder does not exist. + php.ini-də müvəqqəti qovluq quraşdırılmayıb, və ya quraşdırılmış qovluq mövcud deyil. Cannot write temporary file to disk. @@ -222,8 +222,8 @@ Unsupported card type or invalid card number. Dəstəklənməyən kart tipi və ya yanlış kart nömrəsi. - - This is not a valid International Bank Account Number (IBAN). + + This value is not a valid International Bank Account Number (IBAN). Bu dəyər etibarlı bir Beynəlxalq Bank Hesab Nömrəsi (IBAN) deyil. @@ -310,16 +310,16 @@ This value does not match the expected {{ charset }} charset. Bu dəyər gözlənilən {{ charset }} simvol cədvəli ilə uyğun gəlmir. - - This is not a valid Business Identifier Code (BIC). + + This value is not a valid Business Identifier Code (BIC). Bu dəyər etibarlı bir Biznes Təyinat Kodu (BIC) deyil. Error Xəta - - This is not a valid UUID. + + This value is not a valid UUID. Bu dəyər etibarlı bir UUID deyil. diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.be.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.be.xlf index cc479bf6fbf92..ea7001957572b 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.be.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.be.xlf @@ -134,8 +134,8 @@ This file is not a valid image. Гэты файл не з'яўляецца сапраўднай выявай. - - This is not a valid IP address. + + This value is not a valid IP address. Гэта значэнне не з'яўляецца сапраўдным IP-адрасам. @@ -190,8 +190,8 @@ No file was uploaded. Файл не быў запампаваны. - - No temporary folder was configured in php.ini. + + No temporary folder was configured in php.ini, or the configured folder does not exist. У php.ini не была налажана часовая папка, або часовая папка не існуе. @@ -222,8 +222,8 @@ Unsupported card type or invalid card number. Тып карты не падтрымліваецца або несапраўдны нумар карты. - - This is not a valid International Bank Account Number (IBAN). + + This value is not a valid International Bank Account Number (IBAN). Гэта значэнне не з'яўляецца сапраўдным міжнародным нумарам банкаўскага рахунку (IBAN). @@ -310,16 +310,16 @@ This value does not match the expected {{ charset }} charset. Гэта значэнне не супадае з чаканай {{ charset }} кадыроўкай. - - This is not a valid Business Identifier Code (BIC). + + This value is not a valid Business Identifier Code (BIC). Гэта значэнне не з'яўляецца сапраўдным кодам ідэнтыфікацыі бізнесу (BIC). Error Памылка - - This is not a valid UUID. + + This value is not a valid UUID. Гэта значэнне не з'яўляецца сапраўдным UUID. diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.bg.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.bg.xlf index a6872c34ac6ea..5705364f80f84 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.bg.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.bg.xlf @@ -134,8 +134,8 @@ This file is not a valid image. Файлът не е валидно изображение. - - This is not a valid IP address. + + This value is not a valid IP address. Тази стойност не е валиден IP адрес. @@ -190,9 +190,9 @@ No file was uploaded. Файлът не беше качен. - - No temporary folder was configured in php.ini. - Не е посочена директория за временни файлове в php.ini. + + No temporary folder was configured in php.ini, or the configured folder does not exist. + В php.ini не е конфигурирана временна директория, или конфигурираната директория не съществува. Cannot write temporary file to disk. @@ -222,8 +222,8 @@ Unsupported card type or invalid card number. Неподдържан тип карта или невалиден номер на карта. - - This is not a valid International Bank Account Number (IBAN). + + This value is not a valid International Bank Account Number (IBAN). Тази стойност не е валиден международен банков сметка номер (IBAN). @@ -310,16 +310,16 @@ This value does not match the expected {{ charset }} charset. Стойността не съвпада с очакваната {{ charset }} кодировка. - - This is not a valid Business Identifier Code (BIC). + + This value is not a valid Business Identifier Code (BIC). Тази стойност не е валиден код за идентификация на бизнеса (BIC). Error Грешка - - This is not a valid UUID. + + This value is not a valid UUID. Тази стойност не е валиден UUID. diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.bs.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.bs.xlf index a984c794af2bb..bff1c8b441e2c 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.bs.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.bs.xlf @@ -134,8 +134,8 @@ This file is not a valid image. Ova datoteka nije validna slika. - - This is not a valid IP address. + + This value is not a valid IP address. Ova vrijednost nije valjana IP adresa. @@ -190,9 +190,9 @@ No file was uploaded. Nijedna datoteka nije prenijeta (uploaded). - - No temporary folder was configured in php.ini. - Privremeni direktorijum nije konfigurisan u datoteci php.ini. + + No temporary folder was configured in php.ini, or the configured folder does not exist. + Privremeni direktorij nije konfiguriran u php.ini, ili konfigurirani direktorij ne postoji. Cannot write temporary file to disk. @@ -222,8 +222,8 @@ Unsupported card type or invalid card number. Tip kartice nije podržan ili je broj kartice neispravan. - - This is not a valid International Bank Account Number (IBAN). + + This value is not a valid International Bank Account Number (IBAN). Ova vrijednost nije valjan Međunarodni broj bankovnog računa (IBAN). @@ -310,16 +310,16 @@ This value does not match the expected {{ charset }} charset. Ova vrijednost ne odgovara očekivanom {{ charset }} setu karaktera (charset). - - This is not a valid Business Identifier Code (BIC). + + This value is not a valid Business Identifier Code (BIC). Ova vrijednost nije valjan Poslovni identifikacijski kod (BIC). Error Greška - - This is not a valid UUID. + + This value is not a valid UUID. Ova vrijednost nije valjan UUID. diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.ca.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.ca.xlf index 1712a54e57bb7..2f301e784ac03 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.ca.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.ca.xlf @@ -134,8 +134,8 @@ This file is not a valid image. L'arxiu no és una imatge vàlida. - - This is not a valid IP address. + + This value is not a valid IP address. Aquest valor no és una adreça IP vàlida. @@ -190,9 +190,9 @@ No file was uploaded. Cap arxiu va ser pujat. - - No temporary folder was configured in php.ini. - Cap carpeta temporal va ser configurada en php.ini. + + No temporary folder was configured in php.ini, or the configured folder does not exist. + No s'ha configurat cap carpeta temporal en php.ini, o la carpeta configurada no existeix. Cannot write temporary file to disk. @@ -222,8 +222,8 @@ Unsupported card type or invalid card number. Tipus de targeta no suportada o número de targeta invàlid. - - This is not a valid International Bank Account Number (IBAN). + + This value is not a valid International Bank Account Number (IBAN). Aquest valor no és un Número de Compte Bancari Internacional (IBAN) vàlid. @@ -310,16 +310,16 @@ This value does not match the expected {{ charset }} charset. Aquest valor no coincideix amb l'esperat {{ charset }} joc de caràcters. - - This is not a valid Business Identifier Code (BIC). + + This value is not a valid Business Identifier Code (BIC). Aquest valor no és un Codi d'Identificador de Negocis (BIC) vàlid. Error Error - - This is not a valid UUID. + + This value is not a valid UUID. Aquest valor no és un UUID vàlid. diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.cs.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.cs.xlf index 1e06806a0c210..fcae07cec0e79 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.cs.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.cs.xlf @@ -134,8 +134,8 @@ This file is not a valid image. Tento soubor není obrázek. - - This is not a valid IP address. + + This value is not a valid IP address. Tato hodnota není platnou IP adresou. @@ -190,9 +190,9 @@ No file was uploaded. Žádný soubor nebyl nahrán. - - No temporary folder was configured in php.ini. - V php.ini není nastavena cesta k adresáři pro dočasné soubory. + + No temporary folder was configured in php.ini, or the configured folder does not exist. + V php.ini nebyla nastavena cesta k dočasnému adresáři, nebo nastavený adresář neexistuje. Cannot write temporary file to disk. @@ -222,8 +222,8 @@ Unsupported card type or invalid card number. Nepodporovaný typ karty nebo neplatné číslo karty. - - This is not a valid International Bank Account Number (IBAN). + + This value is not a valid International Bank Account Number (IBAN). Tato hodnota není platným Mezinárodním bankovním číslem účtu (IBAN). @@ -310,16 +310,16 @@ This value does not match the expected {{ charset }} charset. Tato hodnota neodpovídá očekávané znakové sadě {{ charset }}. - - This is not a valid Business Identifier Code (BIC). + + This value is not a valid Business Identifier Code (BIC). Tato hodnota není platným Kódem obchodního identifikátoru (BIC). Error Chyba - - This is not a valid UUID. + + This value is not a valid UUID. Tato hodnota není platným UUID. diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.cy.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.cy.xlf index eeafa9afc6ce3..fd984989e3597 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.cy.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.cy.xlf @@ -134,8 +134,8 @@ This file is not a valid image. Nid yw'r ffeil hon yn ddelwedd dilys. - - This is not a valid IP address. + + This value is not a valid IP address. Nid yw'r gwerth hwn yn gyfeiriad IP dilys. @@ -190,9 +190,9 @@ No file was uploaded. Ni uwchlwythwyd unrhyw ffeil. - - No temporary folder was configured in php.ini. - Nid oes ffolder dros-dro wedi'i gosod yn php.ini. + + No temporary folder was configured in php.ini, or the configured folder does not exist. + Nid oedd ffolder dros dro wedi'i ffurfweddu yn php.ini, neu nid yw'r ffolder a ffurfweddiwyd yn bodoli. Cannot write temporary file to disk. @@ -222,8 +222,8 @@ Unsupported card type or invalid card number. Unai ni dderbynir y math yna o gerdyn, neu nid yw rhif y cerdyn yn ddilys. - - This is not a valid International Bank Account Number (IBAN). + + This value is not a valid International Bank Account Number (IBAN). Nid yw'r gwerth hwn yn Rhif Cyfrif Banc Rhyngwladol (IBAN) dilys. @@ -310,16 +310,16 @@ This value does not match the expected {{ charset }} charset. Nid yw'r gwerth hwn yn cyfateb â'r {{ charset }} set nodau ddisgwyliedig. - - This is not a valid Business Identifier Code (BIC). + + This value is not a valid Business Identifier Code (BIC). Nid yw'r gwerth hwn yn God Adnabod Busnes (BIC) dilys. Error Gwall - - This is not a valid UUID. + + This value is not a valid UUID. Nid yw'r gwerth hwn yn UUID dilys. diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.da.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.da.xlf index 9639e99f6226f..826ef10c955db 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.da.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.da.xlf @@ -134,8 +134,8 @@ This file is not a valid image. Filen er ikke gyldigt billede. - - This is not a valid IP address. + + This value is not a valid IP address. Denne værdi er ikke en gyldig IP-adresse. @@ -190,9 +190,9 @@ No file was uploaded. Ingen fil blev uploadet. - - No temporary folder was configured in php.ini. - Ingen midlertidig mappe er konfigureret i php.ini. + + No temporary folder was configured in php.ini, or the configured folder does not exist. + Der blev ikke konfigureret en midlertidig mappe i php.ini, eller den konfigurerede mappe eksisterer ikke. Cannot write temporary file to disk. @@ -222,8 +222,8 @@ Unsupported card type or invalid card number. Ikke-understøttet korttype eller ugyldigt kortnummer. - - This is not a valid International Bank Account Number (IBAN). + + This value is not a valid International Bank Account Number (IBAN). Denne værdi er ikke et gyldigt internationalt bankkontonummer (IBAN). @@ -310,16 +310,16 @@ This value does not match the expected {{ charset }} charset. Denne værdi stemmer ikke overens med den forventede {{ charset }} charset. - - This is not a valid Business Identifier Code (BIC). + + This value is not a valid Business Identifier Code (BIC). Denne værdi er ikke en gyldig forretningsidentifikationskode (BIC). Error Fejl - - This is not a valid UUID. + + This value is not a valid UUID. Denne værdi er ikke en gyldig UUID. diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.de.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.de.xlf index 15192f4cb2749..9f145fdeed911 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.de.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.de.xlf @@ -134,8 +134,8 @@ This file is not a valid image. Diese Datei ist kein gültiges Bild. - - This is not a valid IP address. + + This value is not a valid IP address. Dieser Wert ist keine gültige IP-Adresse. @@ -190,8 +190,8 @@ No file was uploaded. Es wurde keine Datei hochgeladen. - - No temporary folder was configured in php.ini. + + No temporary folder was configured in php.ini, or the configured folder does not exist. Es wurde kein temporärer Ordner in der php.ini konfiguriert oder der temporäre Ordner existiert nicht. @@ -222,8 +222,8 @@ Unsupported card type or invalid card number. Nicht unterstützter Kartentyp oder ungültige Kartennummer. - - This is not a valid International Bank Account Number (IBAN). + + This value is not a valid International Bank Account Number (IBAN). Dieser Wert ist keine gültige Internationale Bankkontonummer (IBAN). @@ -310,16 +310,16 @@ This value does not match the expected {{ charset }} charset. Dieser Wert entspricht nicht dem erwarteten Zeichensatz {{ charset }}. - - This is not a valid Business Identifier Code (BIC). + + This value is not a valid Business Identifier Code (BIC). Dieser Wert ist keine gültige internationale Bankleitzahl (BIC). Error Fehler - - This is not a valid UUID. + + This value is not a valid UUID. Dieser Wert ist keine gültige UUID. diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.el.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.el.xlf index c2f0f0f97e2ca..f7677fabfb89a 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.el.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.el.xlf @@ -134,8 +134,8 @@ This file is not a valid image. Το αρχείο δεν αποτελεί έγκυρη εικόνα. - - This is not a valid IP address. + + This value is not a valid IP address. Αυτή η τιμή δεν είναι έγκυρη διεύθυνση IP. @@ -190,9 +190,9 @@ No file was uploaded. Δεν ανέβηκε κανένα αρχείο. - - No temporary folder was configured in php.ini. - Κανένας προσωρινός φάκελος δεν έχει ρυθμιστεί στο php.ini. + + No temporary folder was configured in php.ini, or the configured folder does not exist. + Δεν ρυθμίστηκε προσωρινός φάκελος στο php.ini, ή ο ρυθμισμένος φάκελος δεν υπάρχει. Cannot write temporary file to disk. @@ -222,8 +222,8 @@ Unsupported card type or invalid card number. Μη υποστηριζόμενος τύπος κάρτας ή μη έγκυρος αριθμός κάρτας. - - This is not a valid International Bank Account Number (IBAN). + + This value is not a valid International Bank Account Number (IBAN). Αυτή η τιμή δεν είναι έγκυρος Διεθνής Αριθμός Τραπεζικού Λογαριασμού (IBAN). @@ -310,16 +310,16 @@ This value does not match the expected {{ charset }} charset. Αυτή η τιμή δεν ταιριάζει στο αναμενόμενο {{ charset }} σύνολο χαρακτήρων. - - This is not a valid Business Identifier Code (BIC). + + This value is not a valid Business Identifier Code (BIC). Αυτή η τιμή δεν είναι έγκυρος Κωδικός Ταυτοποίησης Επιχείρησης (BIC). Error Σφάλμα - - This is not a valid UUID. + + This value is not a valid UUID. Αυτή η τιμή δεν είναι έγκυρη UUID. diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.es.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.es.xlf index 68b9a2d8ab945..4c1fa82e2f471 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.es.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.es.xlf @@ -134,8 +134,8 @@ This file is not a valid image. El archivo no es una imagen válida. - - This is not a valid IP address. + + This value is not a valid IP address. Este valor no es una dirección IP válida. @@ -190,8 +190,8 @@ No file was uploaded. No se subió ningún archivo. - - No temporary folder was configured in php.ini. + + No temporary folder was configured in php.ini, or the configured folder does not exist. Ninguna carpeta temporal fue configurada en php.ini o la carpeta configurada no existe. @@ -222,8 +222,8 @@ Unsupported card type or invalid card number. Tipo de tarjeta no soportado o número de tarjeta inválido. - - This is not a valid International Bank Account Number (IBAN). + + This value is not a valid International Bank Account Number (IBAN). Este valor no es un Número de Cuenta Bancaria Internacional (IBAN) válido. @@ -310,16 +310,16 @@ This value does not match the expected {{ charset }} charset. La codificación de caracteres para este valor debería ser {{ charset }}. - - This is not a valid Business Identifier Code (BIC). + + This value is not a valid Business Identifier Code (BIC). Este valor no es un Código de Identificación de Negocios (BIC) válido. Error Error - - This is not a valid UUID. + + This value is not a valid UUID. Este valor no es un UUID válido. diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.et.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.et.xlf index a3e10203082b8..853d7a09954ef 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.et.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.et.xlf @@ -134,8 +134,8 @@ This file is not a valid image. Fail ei ole korrektne pilt. - - This is not a valid IP address. + + This value is not a valid IP address. See väärtus ei ole kehtiv IP-aadress. @@ -190,9 +190,9 @@ No file was uploaded. Ühtegi faili ei laetud üles. - - No temporary folder was configured in php.ini. - Ühtegi ajutist kausta polnud php.ini-s seadistatud. + + No temporary folder was configured in php.ini, or the configured folder does not exist. + php.ini-s ei olnud seadistatud ajutist kausta, või seadistatud kaust ei eksisteeri. Cannot write temporary file to disk. @@ -222,8 +222,8 @@ Unsupported card type or invalid card number. Kaardi tüüpi ei toetata või kaardi number on vigane. - - This is not a valid International Bank Account Number (IBAN). + + This value is not a valid International Bank Account Number (IBAN). See väärtus ei ole kehtiv Rahvusvaheline Pangakonto Number (IBAN). @@ -310,16 +310,16 @@ This value does not match the expected {{ charset }} charset. See väärtus ei ühti eeldatava tähemärgiga {{ charset }}. - - This is not a valid Business Identifier Code (BIC). + + This value is not a valid Business Identifier Code (BIC). See väärtus ei ole kehtiv Äriühingu Tuvastuskood (BIC). Error Viga - - This is not a valid UUID. + + This value is not a valid UUID. See väärtus ei ole kehtiv UUID. diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.eu.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.eu.xlf index 87e6a52ef19e2..6094d1cbca575 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.eu.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.eu.xlf @@ -134,8 +134,8 @@ This file is not a valid image. Fitxategi hau ez da irudi egoki bat. - - This is not a valid IP address. + + This value is not a valid IP address. Balio hau ez da IP helbide baliozko bat. @@ -190,9 +190,9 @@ No file was uploaded. Ez da fitxategirik igo. - - No temporary folder was configured in php.ini. - Ez da aldi baterako karpetarik konfiguratu php.ini fitxategian. + + No temporary folder was configured in php.ini, or the configured folder does not exist. + Ez da aldi baterako karpetarik konfiguratu php.ini-n, edo konfiguratutako karpeta ez da existitzen. Cannot write temporary file to disk. @@ -222,8 +222,8 @@ Unsupported card type or invalid card number. Txartel mota onartezina edo txartel zenbaki baliogabea. - - This is not a valid International Bank Account Number (IBAN). + + This value is not a valid International Bank Account Number (IBAN). Balio hau ez da Nazioarteko Banku Kontu Zenbaki (IBAN) baliozko bat. @@ -310,16 +310,16 @@ This value does not match the expected {{ charset }} charset. Balio honen karaktere kodea ez da esperotakoa {{ charset }}. - - This is not a valid Business Identifier Code (BIC). + + This value is not a valid Business Identifier Code (BIC). Balio hau ez da Negozioaren Identifikazio Kode (BIC) baliozko bat. Error Errore - - This is not a valid UUID. + + This value is not a valid UUID. Balio hau ez da UUID baliozko bat. diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.fa.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.fa.xlf index f0348b1111db7..1db553c9ffb18 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.fa.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.fa.xlf @@ -134,8 +134,8 @@ This file is not a valid image. این فایل یک تصویر معتبر نمی‌باشد. - - This is not a valid IP address. + + This value is not a valid IP address. این مقدار آدرس IP معتبری نیست. @@ -190,9 +190,9 @@ No file was uploaded. هیچ فایلی بارگذاری نشد. - - No temporary folder was configured in php.ini. - پوشه موقتی در php.ini پیکربندی نگردیده است. + + No temporary folder was configured in php.ini, or the configured folder does not exist. + هیچ پوشه موقتی در php.ini پیکربندی نشده است، یا پوشه پیکربندی شده وجود ندارد. Cannot write temporary file to disk. @@ -222,8 +222,8 @@ Unsupported card type or invalid card number. نوع کارت پشتیبانی نمی‌شود و یا شماره کارت نامعتبر می‌باشد. - - This is not a valid International Bank Account Number (IBAN). + + This value is not a valid International Bank Account Number (IBAN). این مقدار یک شماره حساب بانکی بین‌المللی (IBAN) معتبر نیست. @@ -310,16 +310,16 @@ This value does not match the expected {{ charset }} charset. این مقدار مطابق charset مورد انتظار {{ charset }} نمی باشد. - - This is not a valid Business Identifier Code (BIC). + + This value is not a valid Business Identifier Code (BIC). این مقدار یک کد شناسه کسب‌وکار (BIC) معتبر نیست. Error خطا - - This is not a valid UUID. + + This value is not a valid UUID. این مقدار یک UUID معتبر نیست. diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.fi.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.fi.xlf index bd4f296b8ba5b..324df2c276b79 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.fi.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.fi.xlf @@ -134,8 +134,8 @@ This file is not a valid image. Tiedosto ei ole kelvollinen kuva. - - This is not a valid IP address. + + This value is not a valid IP address. Tämä arvo ei ole kelvollinen IP-osoite. @@ -190,9 +190,9 @@ No file was uploaded. Tiedostoa ei ladattu. - - No temporary folder was configured in php.ini. - Väliaikaishakemistoa ei ole asetettu php.ini-tiedostossa. + + No temporary folder was configured in php.ini, or the configured folder does not exist. + Väliaikaista kansiota ei ole määritetty php.ini:ssä, tai määritetty kansio ei ole olemassa. Cannot write temporary file to disk. @@ -222,8 +222,8 @@ Unsupported card type or invalid card number. Tätä korttityyppiä ei tueta tai korttinumero on virheellinen. - - This is not a valid International Bank Account Number (IBAN). + + This value is not a valid International Bank Account Number (IBAN). Tämä arvo ei ole kelvollinen kansainvälinen pankkitilinumero (IBAN). @@ -310,16 +310,16 @@ This value does not match the expected {{ charset }} charset. Arvo ei vastaa odotettua merkistöä {{ charset }}. - - This is not a valid Business Identifier Code (BIC). + + This value is not a valid Business Identifier Code (BIC). Tämä arvo ei ole kelvollinen liiketoiminnan tunnistekoodi (BIC). Error Virhe - - This is not a valid UUID. + + This value is not a valid UUID. Tämä arvo ei ole kelvollinen UUID. diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.fr.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.fr.xlf index 54e3e842c3345..e2d747a49bffe 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.fr.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.fr.xlf @@ -134,8 +134,8 @@ This file is not a valid image. Ce fichier n'est pas une image valide. - - This is not a valid IP address. + + This value is not a valid IP address. Cette valeur n'est pas une adresse IP valide. @@ -190,8 +190,8 @@ No file was uploaded. Aucun fichier n'a été transféré. - - No temporary folder was configured in php.ini. + + No temporary folder was configured in php.ini, or the configured folder does not exist. Aucun répertoire temporaire n'a été configuré dans le php.ini, ou le répertoire configuré n'existe pas. @@ -222,8 +222,8 @@ Unsupported card type or invalid card number. Type de carte non supporté ou numéro invalide. - - This is not a valid International Bank Account Number (IBAN). + + This value is not a valid International Bank Account Number (IBAN). Cette valeur n'est pas un Numéro de Compte Bancaire International (IBAN) valide. @@ -310,16 +310,16 @@ This value does not match the expected {{ charset }} charset. Cette valeur ne correspond pas au jeu de caractères {{ charset }} attendu. - - This is not a valid Business Identifier Code (BIC). + + This value is not a valid Business Identifier Code (BIC). Cette valeur n'est pas un Code Identifiant de Business (BIC) valide. Error Erreur - - This is not a valid UUID. + + This value is not a valid UUID. Cette valeur n'est pas un UUID valide. diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.gl.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.gl.xlf index fb726e6532350..2723983c5a99d 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.gl.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.gl.xlf @@ -134,8 +134,8 @@ This file is not a valid image. O arquivo non é unha imaxe válida. - - This is not a valid IP address. + + This value is not a valid IP address. Este valor non é un enderezo IP válido. @@ -190,8 +190,8 @@ No file was uploaded. Non se subiu ningún arquivo. - - No temporary folder was configured in php.ini. + + No temporary folder was configured in php.ini, or the configured folder does not exist. Ningunha carpeta temporal foi configurada en php.ini, ou a carpeta non existe. @@ -222,8 +222,8 @@ Unsupported card type or invalid card number. Tipo de tarxeta non soportado ou número de tarxeta non válido. - - This is not a valid International Bank Account Number (IBAN). + + This value is not a valid International Bank Account Number (IBAN). Este valor non é un Número de Conta Bancaria Internacional (IBAN) válido. @@ -310,16 +310,16 @@ This value does not match the expected {{ charset }} charset. A codificación de caracteres para este valor debería ser {{ charset }}. - - This is not a valid Business Identifier Code (BIC). + + This value is not a valid Business Identifier Code (BIC). Este valor non é un Código de Identificación de Negocios (BIC) válido. Error Erro - - This is not a valid UUID. + + This value is not a valid UUID. Este valor non é un UUID válido. diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.he.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.he.xlf index 29755aa069625..b1bb894a7fe4b 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.he.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.he.xlf @@ -134,8 +134,8 @@ This file is not a valid image. הקובץ הזה אינו תמונה תקינה. - - This is not a valid IP address. + + This value is not a valid IP address. ערך זה אינו כתובת IP תקפה. @@ -190,9 +190,9 @@ No file was uploaded. הקובץ לא הועלה. - - No temporary folder was configured in php.ini. - לא הוגדרה תיקייה זמנית ב php.ini. + + No temporary folder was configured in php.ini, or the configured folder does not exist. + לא הוגדרה תיקייה זמנית ב-php.ini, או שהתיקייה המוגדרת אינה קיימת. Cannot write temporary file to disk. @@ -222,8 +222,8 @@ Unsupported card type or invalid card number. סוג הכרטיס אינו נתמך או לא חוקי. - - This is not a valid International Bank Account Number (IBAN). + + This value is not a valid International Bank Account Number (IBAN). ערך זה אינו מספר חשבון בנק בינלאומי (IBAN) תקף. @@ -310,16 +310,16 @@ This value does not match the expected {{ charset }} charset. הערך אינו תואם למערך התווים {{ charset }} הצפוי. - - This is not a valid Business Identifier Code (BIC). + + This value is not a valid Business Identifier Code (BIC). ערך זה אינו קוד מזהה עסקי (BIC) תקף. Error שגיאה - - This is not a valid UUID. + + This value is not a valid UUID. ערך זה אינו UUID תקף. diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.hr.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.hr.xlf index 7d269969643b5..9186d8b3bae84 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.hr.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.hr.xlf @@ -134,8 +134,8 @@ This file is not a valid image. Ova datoteka nije ispravna slika. - - This is not a valid IP address. + + This value is not a valid IP address. Ova vrijednost nije valjana IP adresa. @@ -190,9 +190,9 @@ No file was uploaded. Niti jedna datoteka nije prenesena. - - No temporary folder was configured in php.ini. - U php.ini datoteci nije konfiguriran privremeni direktorij. + + No temporary folder was configured in php.ini, or the configured folder does not exist. + Privremena mapa nije konfigurirana u php.ini, ili konfigurirana mapa ne postoji. Cannot write temporary file to disk. @@ -222,8 +222,8 @@ Unsupported card type or invalid card number. Tip kartice nije podržan ili je broj kartice neispravan. - - This is not a valid International Bank Account Number (IBAN). + + This value is not a valid International Bank Account Number (IBAN). Ova vrijednost nije valjani međunarodni bankovni broj računa (IBAN). @@ -310,16 +310,16 @@ This value does not match the expected {{ charset }} charset. Ova vrijednost ne odgovara očekivanom {{ charset }} znakovnom skupu. - - This is not a valid Business Identifier Code (BIC). + + This value is not a valid Business Identifier Code (BIC). Ova vrijednost nije valjani poslovni identifikacijski kod (BIC). Error Greška - - This is not a valid UUID. + + This value is not a valid UUID. Ova vrijednost nije valjani UUID. diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.hu.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.hu.xlf index 3e846cbc4638d..56591ac0fa729 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.hu.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.hu.xlf @@ -134,8 +134,8 @@ This file is not a valid image. Ez a fájl nem egy érvényes kép. - - This is not a valid IP address. + + This value is not a valid IP address. Ez az érték nem érvényes IP-cím. @@ -190,9 +190,9 @@ No file was uploaded. Nem lett fájl feltöltve. - - No temporary folder was configured in php.ini. - Nincs ideiglenes könyvtár beállítva a php.ini-ben. + + No temporary folder was configured in php.ini, or the configured folder does not exist. + Nem lett ideiglenes mappa beállítva a php.ini-ben, vagy a beállított mappa nem létezik. Cannot write temporary file to disk. @@ -222,8 +222,8 @@ Unsupported card type or invalid card number. Nem támogatott kártyatípus vagy érvénytelen kártyaszám. - - This is not a valid International Bank Account Number (IBAN). + + This value is not a valid International Bank Account Number (IBAN). Ez az érték nem érvényes Nemzetközi Bankszámlaszám (IBAN). @@ -310,16 +310,16 @@ This value does not match the expected {{ charset }} charset. Ez az érték nem az elvárt {{ charset }} karakterkódolást használja. - - This is not a valid Business Identifier Code (BIC). + + This value is not a valid Business Identifier Code (BIC). Ez az érték nem érvényes Üzleti Azonosító Kód (BIC). Error Hiba - - This is not a valid UUID. + + This value is not a valid UUID. Ez az érték nem érvényes UUID. diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.hy.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.hy.xlf index b0ad9d93ab6be..707301d18c037 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.hy.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.hy.xlf @@ -134,8 +134,8 @@ This file is not a valid image. Նիշքը նկարի վավեր ֆորմատ չէ։ - - This is not a valid IP address. + + This value is not a valid IP address. Այս արժեքը վավեր IP հասցե չէ։ @@ -190,9 +190,9 @@ No file was uploaded. Նիշքը չի բեռնվել։ - - No temporary folder was configured in php.ini. - php.ini նիշքում ժամանակավոր պանակ նշված չէ։ + + No temporary folder was configured in php.ini, or the configured folder does not exist. + php.ini-ում չի կարգավորվել ժամանակավոր թղթապանակ, կամ կարգավորված թղթապանակը չկա։ Cannot write temporary file to disk. @@ -222,8 +222,8 @@ Unsupported card type or invalid card number. Չսպասարկվող կամ սխալ քարտի համար: - - This is not a valid International Bank Account Number (IBAN). + + This value is not a valid International Bank Account Number (IBAN). Այս արժեքը վավեր միջազգային բանկային հաշվի համար (IBAN) չէ։ @@ -310,16 +310,16 @@ This value does not match the expected {{ charset }} charset. Արժեքը չի համընկնում {{ charset }} կոդավորման հետ։ - - This is not a valid Business Identifier Code (BIC). + + This value is not a valid Business Identifier Code (BIC). Այս արժեքը վավեր բիզնեսի նորմատիվ կոդ (BIC) չէ։ Error Սխալ - - This is not a valid UUID. + + This value is not a valid UUID. Այս արժեքը վավեր UUID չէ։ diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.id.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.id.xlf index 4271794353285..1cc60c4d8f9a2 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.id.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.id.xlf @@ -134,8 +134,8 @@ This file is not a valid image. Berkas ini tidak termasuk citra. - - This is not a valid IP address. + + This value is not a valid IP address. Nilai ini bukan alamat IP yang valid. @@ -190,9 +190,9 @@ No file was uploaded. Tidak ada berkas terunggah. - - No temporary folder was configured in php.ini. - Direktori sementara tidak dikonfiguasi pada php.ini. + + No temporary folder was configured in php.ini, or the configured folder does not exist. + Tidak ada folder sementara yang dikonfigurasi di php.ini, atau folder yang dikonfigurasi tidak ada. Cannot write temporary file to disk. @@ -222,8 +222,8 @@ Unsupported card type or invalid card number. Jenis kartu tidak didukung atau nomor kartu tidak sah. - - This is not a valid International Bank Account Number (IBAN). + + This value is not a valid International Bank Account Number (IBAN). Nilai ini bukan Nomor Rekening Bank Internasional (IBAN) yang valid. @@ -310,16 +310,16 @@ This value does not match the expected {{ charset }} charset. Nilai ini tidak memenuhi set karakter {{ charset }} yang diharapkan. - - This is not a valid Business Identifier Code (BIC). + + This value is not a valid Business Identifier Code (BIC). Nilai ini bukan Kode Identifikasi Bisnis (BIC) yang valid. Error Galat - - This is not a valid UUID. + + This value is not a valid UUID. Nilai ini bukan UUID yang valid. diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.it.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.it.xlf index 7c85986f1f022..8fe556696d7cc 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.it.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.it.xlf @@ -134,8 +134,8 @@ This file is not a valid image. Questo file non è una immagine valida. - - This is not a valid IP address. + + This value is not a valid IP address. Questo valore non è un indirizzo IP valido. @@ -190,9 +190,9 @@ No file was uploaded. Nessun file è stato caricato. - - No temporary folder was configured in php.ini. - Nessuna cartella temporanea è stata configurata nel php.ini. + + No temporary folder was configured in php.ini, or the configured folder does not exist. + Nessuna cartella temporanea è stata configurata in php.ini, o la cartella configurata non esiste. Cannot write temporary file to disk. @@ -222,8 +222,8 @@ Unsupported card type or invalid card number. Tipo di carta non supportato o numero non valido. - - This is not a valid International Bank Account Number (IBAN). + + This value is not a valid International Bank Account Number (IBAN). Questo valore non è un Numero di Conto Bancario Internazionale (IBAN) valido. @@ -310,16 +310,16 @@ This value does not match the expected {{ charset }} charset. Questo valore non corrisponde al charset {{ charset }}. - - This is not a valid Business Identifier Code (BIC). + + This value is not a valid Business Identifier Code (BIC). Questo valore non è un Codice Identificativo di Business (BIC) valido. Error Errore - - This is not a valid UUID. + + This value is not a valid UUID. Questo valore non è un UUID valido. diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.ja.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.ja.xlf index e39478092bf0b..0524cd0511e15 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.ja.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.ja.xlf @@ -134,8 +134,8 @@ This file is not a valid image. ファイルが画像ではありません。 - - This is not a valid IP address. + + This value is not a valid IP address. この値は有効なIPアドレスではありません。 @@ -190,9 +190,9 @@ No file was uploaded. ファイルがアップロードされていません。 - - No temporary folder was configured in php.ini. - php.iniで一時フォルダが設定されていません。 + + No temporary folder was configured in php.ini, or the configured folder does not exist. + php.iniに一時フォルダが設定されていないか、設定されたフォルダが存在しません。 Cannot write temporary file to disk. @@ -222,8 +222,8 @@ Unsupported card type or invalid card number. 未対応のカード種類又は無効なカード番号です。 - - This is not a valid International Bank Account Number (IBAN). + + This value is not a valid International Bank Account Number (IBAN). この値は有効な国際銀行口座番号(IBAN)ではありません。 @@ -310,16 +310,16 @@ This value does not match the expected {{ charset }} charset. この値は予期される文字コード({{ charset }})と異なります。 - - This is not a valid Business Identifier Code (BIC). + + This value is not a valid Business Identifier Code (BIC). この値は有効なビジネス識別コード(BIC)ではありません。 Error エラー - - This is not a valid UUID. + + This value is not a valid UUID. この値は有効なUUIDではありません。 diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.lb.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.lb.xlf index 60edd282f1c42..548d82da41683 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.lb.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.lb.xlf @@ -134,8 +134,8 @@ This file is not a valid image. Dëse Fichier ass kee gëltegt Bild. - - This is not a valid IP address. + + This value is not a valid IP address. Dëse Wäert ass keng gülteg IP-Adress. @@ -190,8 +190,8 @@ No file was uploaded. Et gouf kee Fichier eropgelueden. - - No temporary folder was configured in php.ini. + + No temporary folder was configured in php.ini, or the configured folder does not exist. Et gouf keen temporären Dossier an der php.ini konfiguréiert oder den temporären Dossier existéiert net. @@ -222,8 +222,8 @@ Unsupported card type or invalid card number. Net ënnerstëtzte Kaartentyp oder ongëlteg Kaartennummer. - - This is not a valid International Bank Account Number (IBAN). + + This value is not a valid International Bank Account Number (IBAN). Dëse Wäert ass keng gülteg International Bankkontonummer (IBAN). @@ -310,16 +310,16 @@ This value does not match the expected {{ charset }} charset. Dëse Wäert entsprécht net dem erwaarten Zeechesaz {{ charset }}. - - This is not a valid Business Identifier Code (BIC). + + This value is not a valid Business Identifier Code (BIC). Dëse Wäert ass kee gültege Business Identifier Code (BIC). Error Feeler - - This is not a valid UUID. + + This value is not a valid UUID. Dëse Wäert ass keng gülteg UUID. diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.lt.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.lt.xlf index 78ed992933ac5..b3ee199fe4c73 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.lt.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.lt.xlf @@ -134,8 +134,8 @@ This file is not a valid image. Byla nėra paveikslėlis. - - This is not a valid IP address. + + This value is not a valid IP address. Ši vertė nėra galiojantis IP adresas. @@ -190,9 +190,9 @@ No file was uploaded. Nebuvo įkelta jokių failų. - - No temporary folder was configured in php.ini. - Nėra sukonfiguruoto jokio laikino katalogo php.ini faile. + + No temporary folder was configured in php.ini, or the configured folder does not exist. + php.ini nesukonfigūruotas laikinas aplankas, arba sukonfigūruotas aplankas neegzistuoja. Cannot write temporary file to disk. @@ -222,8 +222,8 @@ Unsupported card type or invalid card number. Kortelės tipas nepalaikomas arba klaidingas kortelės numeris. - - This is not a valid International Bank Account Number (IBAN). + + This value is not a valid International Bank Account Number (IBAN). Ši vertė nėra galiojantis Tarptautinis Banko Sąskaitos Numeris (IBAN). @@ -310,16 +310,16 @@ This value does not match the expected {{ charset }} charset. Ši reikšmė neatitinka {{ charset }} koduotės. - - This is not a valid Business Identifier Code (BIC). + + This value is not a valid Business Identifier Code (BIC). Ši vertė nėra galiojantis Verslo Identifikavimo Kodas (BIC). Error Klaida - - This is not a valid UUID. + + This value is not a valid UUID. Ši vertė nėra galiojantis UUID. diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.lv.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.lv.xlf index e95c9631ae1d5..d1222f02b72a5 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.lv.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.lv.xlf @@ -20,7 +20,7 @@ The value you selected is not a valid choice. - Vērtība, kuru jūs izvēlējāties nav derīga izvēle. + Vērtība, kuru jūs izvēlējāties, nav derīga izvēle. You must select at least {{ limit }} choice.|You must select at least {{ limit }} choices. @@ -76,7 +76,7 @@ This value is too long. It should have {{ limit }} character or less.|This value is too long. It should have {{ limit }} characters or less. - Šīs vērtības garums ir 0 rakstzīmju.|Šī vērtība ir pārāk gara. Tai būtu jābūt ne vairāk kā {{ limit }} rakstzīmei.|Šī vērtība ir pārāk gara. Tai būtu jābūt ne vairāk kā {{ limit }} rakstzīmēm. + Šīs vērtības garums ir 0 rakstzīmes.|Šī vērtība ir pārāk gara. Tai būtu jābūt ne vairāk kā {{ limit }} rakstzīmi garai.|Šī vērtība ir pārāk gara. Tai būtu jābūt ne vairāk kā {{ limit }} rakstzīmes garai. This value should be {{ limit }} or more. @@ -84,7 +84,7 @@ This value is too short. It should have {{ limit }} character or more.|This value is too short. It should have {{ limit }} characters or more. - Šīs vērtības garums ir 0 rakstzīmju.|Šī vērtība ir pārāk īsa. Tai būtu jābūt ne mazāk kā {{ limit }} rakstzīmei.|Šī vērtība ir pārāk īsa. Tai būtu jābūt ne mazāk kā {{ limit }} rakstzīmēm. + Šīs vērtības garums ir 0 rakstzīmes.|Šī vērtība ir pārāk īsa. Tai būtu jābūt ne mazāk kā {{ limit }} rakstzīmi garai.|Šī vērtība ir pārāk īsa. Tai būtu jābūt ne mazāk kā {{ limit }} rakstzīmes garai. This value should not be blank. @@ -112,7 +112,7 @@ The two values should be equal. - Abām vērtībām jābūt vienādam. + Abām vērtībām jābūt vienādām. The file is too large. Allowed maximum size is {{ limit }} {{ suffix }}. @@ -134,9 +134,9 @@ This file is not a valid image. Šis fails nav derīgs attēls. - - This is not a valid IP address. - Šī vērtība nav derīga IP adrese. + + This value is not a valid IP address. + Šī vērtība nav derīga IP adrese. This value is not a valid language. @@ -180,7 +180,7 @@ This value should have exactly {{ limit }} character.|This value should have exactly {{ limit }} characters. - Šīs vērtības garums ir 0 rakstzīmju.|Šai vērtībai ir jābūt tieši {{ limit }} rakstzīmei.|Šai vērtībai ir jābūt tieši {{ limit }} rakstzīmēm. + Šīs vērtības garums ir 0 rakstzīmes.|Šai vērtībai ir jābūt tieši {{ limit }} rakstzīmi garai.|Šai vērtībai ir jābūt tieši {{ limit }} rakstzīmes garai. The file was only partially uploaded. @@ -190,13 +190,13 @@ No file was uploaded. Fails netika augšupielādēts. - - No temporary folder was configured in php.ini. - Pagaidu mape php.ini failā nav nokonfigurēta. + + No temporary folder was configured in php.ini, or the configured folder does not exist. + php.ini nav konfigurēta pagaidu mape vai arī konfigurētā mape neeksistē. Cannot write temporary file to disk. - Nevar ierakstīt pagaidu failu uz diska. + Nevar ierakstīt pagaidu failu diskā. A PHP extension caused the upload to fail. @@ -204,15 +204,15 @@ This collection should contain {{ limit }} element or more.|This collection should contain {{ limit }} elements or more. - Šis krājums satur 0 elementu.|Šim krājumam jāsatur vismaz {{ limit }} elementu.|Šim krājumam jāsatur vismaz {{ limit }} elementus. + Šis krājums satur 0 elementu.|Šim krājumam jāsatur vismaz {{ limit }} elements.|Šim krājumam jāsatur vismaz {{ limit }} elementi. This collection should contain {{ limit }} element or less.|This collection should contain {{ limit }} elements or less. - Šis krājums satur 0 elementu.|Šim krājumam jāsatur ne vairāk kā {{ limit }} elementu.|Šim krājumam jāsatur ne vairāk kā {{ limit }} elementus. + Šis krājums satur 0 elementu.|Šim krājumam jāsatur ne vairāk kā {{ limit }} elements.|Šim krājumam jāsatur ne vairāk kā {{ limit }} elementi. This collection should contain exactly {{ limit }} element.|This collection should contain exactly {{ limit }} elements. - Šis krājums satur 0 elementu.|Šim krājumam jāsatur tieši {{ limit }} elementu.|Šim krājumam jāsatur tieši {{ limit }} elementus. + Šis krājums satur 0 elementu.|Šim krājumam jāsatur tieši {{ limit }} elements.|Šim krājumam jāsatur tieši {{ limit }} elementi. Invalid card number. @@ -222,9 +222,9 @@ Unsupported card type or invalid card number. Neatbalstīts kartes tips vai nederīgs kartes numurs. - - This is not a valid International Bank Account Number (IBAN). - Šī vērtība nav derīgs Starptautiskais Bankas Konta Numurs (IBAN). + + This value is not a valid International Bank Account Number (IBAN). + Šī vērtība nav derīgs Starptautiskais Bankas Konta Numurs (IBAN). This value is not a valid ISBN-10. @@ -232,7 +232,7 @@ This value is not a valid ISBN-13. - Šī vērtība nav derīgs ISBN-13 numurs + Šī vērtība nav derīgs ISBN-13 numurs. This value is neither a valid ISBN-10 nor a valid ISBN-13. @@ -240,11 +240,11 @@ This value is not a valid ISSN. - Šī vērtība nav derīgs ISSN numurs + Šī vērtība nav derīgs ISSN numurs. This value is not a valid currency. - Šī vērtība nav derīga valūta + Šī vērtība nav derīga valūta. This value should be equal to {{ compared_value }}. @@ -292,11 +292,11 @@ The image is landscape oriented ({{ width }}x{{ height }}px). Landscape oriented images are not allowed. - Attēls ir orientēts kā ainava ({{ width }}x{{ height }}px). Attēli, kas ir orientēti kā ainavas nav atļauti. + Attēls ir orientēts kā ainava ({{ width }}x{{ height }}px). Attēli, kas ir orientēti kā ainavas, nav atļauti. The image is portrait oriented ({{ width }}x{{ height }}px). Portrait oriented images are not allowed. - Attēls ir orientēts kā portrets ({{ width }}x{{ height }}px). Attēli, kas ir orientēti kā portreti nav atļauti. + Attēls ir orientēts kā portrets ({{ width }}x{{ height }}px). Attēli, kas ir orientēti kā portreti, nav atļauti. An empty file is not allowed. @@ -310,17 +310,17 @@ This value does not match the expected {{ charset }} charset. Šī vērtība neatbilst sagaidāmajai rakstzīmju kopai {{ charset }}. - - This is not a valid Business Identifier Code (BIC). - Šī vērtība nav derīgs Uzņēmuma Identifikācijas Kods (BIC). + + This value is not a valid Business Identifier Code (BIC). + Šī vērtība nav derīgs Uzņēmuma Identifikācijas Kods (BIC). Error Kļūda - - This is not a valid UUID. - Šī vērtība nav derīgs UUID. + + This value is not a valid UUID. + Šī vērtība nav derīgs UUID. This value should be a multiple of {{ compared_value }}. @@ -360,7 +360,7 @@ This password has been leaked in a data breach, it must not be used. Please use another password. - Šī parole tika publicēta datu noplūdē, viņu nedrīkst izmantot. Lūdzu, izvēlieties citu paroli. + Šī parole tika publicēta datu noplūdē, to nedrīkst izmantot. Lūdzu, izvēlieties citu paroli. This value should be between {{ min }} and {{ max }}. @@ -404,7 +404,7 @@ The filename is too long. It should have {{ filename_max_length }} character or less.|The filename is too long. It should have {{ filename_max_length }} characters or less. - Faila nosaukums ir pārāk garš. Tas var būt {{ filename_max_length }} rakstzīme vai īsāks.|Faila nosaukums ir pārāk garš. Tas var būt {{ filename_max_length }} rakstzīmes vai īsāks. + Faila nosaukums ir pārāk garš. Tas var būt {{ filename_max_length }} rakstzīmi garš vai īsāks.|Faila nosaukums ir pārāk garš. Tas var būt {{ filename_max_length }} rakstzīmes garš vai īsāks. The password strength is too low. Please use a stronger password. @@ -436,7 +436,7 @@ This value is not a valid MAC address. - Šī vērtība nav derīga MAC adrese. + Šī vērtība nav derīga MAC adrese. diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.mk.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.mk.xlf index 8267b40b19d7a..9d6dec6288b8a 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.mk.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.mk.xlf @@ -134,8 +134,8 @@ This file is not a valid image. Оваа датотека не е валидна слика. - - This is not a valid IP address. + + This value is not a valid IP address. Оваа вредност не е валидна IP адреса. @@ -190,9 +190,9 @@ No file was uploaded. Датотеката не е подигната. - - No temporary folder was configured in php.ini. - Ниту една привремена папка не е конфигурирана во php.ini. + + No temporary folder was configured in php.ini, or the configured folder does not exist. + Не е конфигурирана привремена папка во php.ini, или конфигурираната папка не постои. Cannot write temporary file to disk. @@ -222,8 +222,8 @@ Unsupported card type or invalid card number. Неподдржан тип на картичка или бројот на картичката не е валиден. - - This is not a valid International Bank Account Number (IBAN). + + This value is not a valid International Bank Account Number (IBAN). Оваа вредност не е валиден Меѓународен Банкарски Сметка Број (IBAN). @@ -310,16 +310,16 @@ This value does not match the expected {{ charset }} charset. Оваа вредност не се совпаѓа со очекуваниот {{ charset }} сет на карактери (charset). - - This is not a valid Business Identifier Code (BIC). + + This value is not a valid Business Identifier Code (BIC). Оваа вредност не е валиден Бизнис Идентификациски Код (BIC). Error Грешка - - This is not a valid UUID. + + This value is not a valid UUID. Оваа вредност не е валиден UUID. diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.mn.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.mn.xlf index 7cc3d6f9021b8..4984bb127cab1 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.mn.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.mn.xlf @@ -134,8 +134,8 @@ This file is not a valid image. Файл зураг биш байна. - - This is not a valid IP address. + + This value is not a valid IP address. Энэ утга хүчинтэй IP хаяг биш юм. @@ -190,8 +190,8 @@ No file was uploaded. Ямар ч файл upload хийгдсэнгүй. - - No temporary folder was configured in php.ini. + + No temporary folder was configured in php.ini, or the configured folder does not exist. php.ini дээр түр зуурын хавтсыг тохируулаагүй байна, эсвэл тохируулсан хавтас байхгүй байна. @@ -222,8 +222,8 @@ Unsupported card type or invalid card number. Дэмжигдээгүй картын төрөл эсвэл картын дугаар буруу байна. - - This is not a valid International Bank Account Number (IBAN). + + This value is not a valid International Bank Account Number (IBAN). Энэ утга хүчинтэй Олон улсын Банкны Дансны Дугаар (IBAN) биш юм. @@ -310,16 +310,16 @@ This value does not match the expected {{ charset }} charset. Энэ утга тооцоолсон {{ charset }} тэмдэгттэй таарахгүй байна. - - This is not a valid Business Identifier Code (BIC). + + This value is not a valid Business Identifier Code (BIC). Энэ утга хүчинтэй Бизнес Таних Код (BIC) биш юм. Error Алдаа - - This is not a valid UUID. + + This value is not a valid UUID. Энэ утга хүчинтэй UUID биш юм. diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.my.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.my.xlf index 9328d6184bb89..e4858336c1c7d 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.my.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.my.xlf @@ -134,8 +134,8 @@ This file is not a valid image. ဤဖိုင်သည်မှန်ကန်သော ဓါတ်ပုံမဟုတ်ပါ။ - - This is not a valid IP address. + + This value is not a valid IP address. ဤတန်ဖိုးသည် မှန်ကန်သော IP လိပ်စာ မဟုတ်ပါ။ @@ -190,9 +190,9 @@ No file was uploaded. မည်သည့် ဖိုင်မျှ upload မလုပ်ခဲ့ပါ။ - - No temporary folder was configured in php.ini. - php.ini တွင်ယာယီဖိုင်တွဲကိုပြင်ဆင်ထားခြင်းမရှိပါ၊ + + No temporary folder was configured in php.ini, or the configured folder does not exist. + php.ini တွင်ယာယီဖိုင်တွဲကိုပြင်ဆင်ထားခြင်းမရှိပါ၊ သို့မဟုတ် ပြင်ဆင်ထားသောဖိုင်တွဲမရှိပါ။ Cannot write temporary file to disk. @@ -222,8 +222,8 @@ Unsupported card type or invalid card number. ကဒ်အမျိုးအစားမမှန်ပါ (သို့မဟုတ်) ကဒ်နံပါတ်မမှန်ပါ။ - - This is not a valid International Bank Account Number (IBAN). + + This value is not a valid International Bank Account Number (IBAN). ဤတန်ဖိုးသည် မှန်ကန်သော နိုင်ငံတကာ ဘဏ်စာရင်းနံပါတ် (IBAN) မဟုတ်ပါ။ @@ -310,16 +310,16 @@ This value does not match the expected {{ charset }} charset. ဤတန်ဖိုးသည် မျှော်မှန်းထားသော {{ charset }} စားလုံးနှင့် ကိုက်ညီမှုမရှိပါ။ - - This is not a valid Business Identifier Code (BIC). + + This value is not a valid Business Identifier Code (BIC). ဤတန်ဖိုးသည် မှန်ကန်သော စီးပွားရေး မှတ်ပုံတင်ကုဒ် (BIC) မဟုတ်ပါ။ Error အမှား - - This is not a valid UUID. + + This value is not a valid UUID. ဤတန်ဖိုးသည် မှန်ကန်သော UUID မဟုတ်ပါ။ diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.nb.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.nb.xlf index fa17ed61a5a49..8b317449ef4e8 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.nb.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.nb.xlf @@ -134,8 +134,8 @@ This file is not a valid image. Denne filen er ikke et gyldig bilde. - - This is not a valid IP address. + + This value is not a valid IP address. Denne verdien er ikke en gyldig IP-adresse. @@ -190,9 +190,9 @@ No file was uploaded. Ingen fil var lastet opp. - - No temporary folder was configured in php.ini. - Den midlertidige mappen (tmp) er ikke konfigurert i php.ini. + + No temporary folder was configured in php.ini, or the configured folder does not exist. + Ingen midlertidig mappe ble konfigurert i php.ini, eller den konfigurerte mappen eksisterer ikke. Cannot write temporary file to disk. @@ -222,8 +222,8 @@ Unsupported card type or invalid card number. Korttypen er ikke støttet eller kortnummeret er ugyldig. - - This is not a valid International Bank Account Number (IBAN). + + This value is not a valid International Bank Account Number (IBAN). Denne verdien er ikke et gyldig internasjonalt bankkontonummer (IBAN). @@ -310,16 +310,16 @@ This value does not match the expected {{ charset }} charset. Verdien samsvarer ikke med forventet tegnsett {{ charset }}. - - This is not a valid Business Identifier Code (BIC). + + This value is not a valid Business Identifier Code (BIC). Denne verdien er ikke en gyldig forretningsidentifikasjonskode (BIC). Error Feil - - This is not a valid UUID. + + This value is not a valid UUID. Denne verdien er ikke en gyldig UUID. diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.nl.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.nl.xlf index 911af770b3ef6..b8c00c26cc61f 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.nl.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.nl.xlf @@ -134,8 +134,8 @@ This file is not a valid image. Dit bestand is geen geldige afbeelding. - - This is not a valid IP address. + + This value is not a valid IP address. Deze waarde is geen geldig IP-adres. @@ -190,8 +190,8 @@ No file was uploaded. Er is geen bestand geüpload. - - No temporary folder was configured in php.ini. + + No temporary folder was configured in php.ini, or the configured folder does not exist. Er is geen tijdelijke map geconfigureerd in php.ini, of de gespecificeerde map bestaat niet. @@ -222,8 +222,8 @@ Unsupported card type or invalid card number. Niet-ondersteund type creditcard of ongeldig nummer. - - This is not a valid International Bank Account Number (IBAN). + + This value is not a valid International Bank Account Number (IBAN). Deze waarde is geen geldig internationaal bankrekeningnummer (IBAN). @@ -310,16 +310,16 @@ This value does not match the expected {{ charset }} charset. Deze waarde is niet in de verwachte tekencodering {{ charset }}. - - This is not a valid Business Identifier Code (BIC). + + This value is not a valid Business Identifier Code (BIC). Deze waarde is geen geldige zakelijke identificatiecode (BIC). Error Fout - - This is not a valid UUID. + + This value is not a valid UUID. Deze waarde is geen geldige UUID. diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.nn.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.nn.xlf index f2faa21f9aecc..4e1a41dab84d7 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.nn.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.nn.xlf @@ -134,8 +134,8 @@ This file is not a valid image. Fila er ikkje eit gyldig bilete. - - This is not a valid IP address. + + This value is not a valid IP address. Denne verdien er ikkje ein gyldig IP-adresse. @@ -190,9 +190,9 @@ No file was uploaded. Inga fil vart lasta opp. - - No temporary folder was configured in php.ini. - Førebels mappe (tmp) er ikkje konfigurert i php.ini. + + No temporary folder was configured in php.ini, or the configured folder does not exist. + Ingen midlertidig mappe var konfigurert i php.ini, eller den konfigurerte mappa eksisterer ikkje. Cannot write temporary file to disk. @@ -222,8 +222,8 @@ Unsupported card type or invalid card number. Korttypen er ikkje støtta, eller kortnummeret er ugyldig. - - This is not a valid International Bank Account Number (IBAN). + + This value is not a valid International Bank Account Number (IBAN). Denne verdien er ikkje eit gyldig internasjonalt bankkontonummer (IBAN). @@ -310,16 +310,16 @@ This value does not match the expected {{ charset }} charset. Verdien stemmer ikkje med forventa {{ charset }} charset. - - This is not a valid Business Identifier Code (BIC). + + This value is not a valid Business Identifier Code (BIC). Denne verdien er ikkje ein gyldig forretningsidentifikasjonskode (BIC). Error Feil - - This is not a valid UUID. + + This value is not a valid UUID. Denne verdien er ikkje ein gyldig UUID. diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.no.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.no.xlf index fa17ed61a5a49..8b317449ef4e8 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.no.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.no.xlf @@ -134,8 +134,8 @@ This file is not a valid image. Denne filen er ikke et gyldig bilde. - - This is not a valid IP address. + + This value is not a valid IP address. Denne verdien er ikke en gyldig IP-adresse. @@ -190,9 +190,9 @@ No file was uploaded. Ingen fil var lastet opp. - - No temporary folder was configured in php.ini. - Den midlertidige mappen (tmp) er ikke konfigurert i php.ini. + + No temporary folder was configured in php.ini, or the configured folder does not exist. + Ingen midlertidig mappe ble konfigurert i php.ini, eller den konfigurerte mappen eksisterer ikke. Cannot write temporary file to disk. @@ -222,8 +222,8 @@ Unsupported card type or invalid card number. Korttypen er ikke støttet eller kortnummeret er ugyldig. - - This is not a valid International Bank Account Number (IBAN). + + This value is not a valid International Bank Account Number (IBAN). Denne verdien er ikke et gyldig internasjonalt bankkontonummer (IBAN). @@ -310,16 +310,16 @@ This value does not match the expected {{ charset }} charset. Verdien samsvarer ikke med forventet tegnsett {{ charset }}. - - This is not a valid Business Identifier Code (BIC). + + This value is not a valid Business Identifier Code (BIC). Denne verdien er ikke en gyldig forretningsidentifikasjonskode (BIC). Error Feil - - This is not a valid UUID. + + This value is not a valid UUID. Denne verdien er ikke en gyldig UUID. diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.pl.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.pl.xlf index 3fcce79a67032..29180984e6dfb 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.pl.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.pl.xlf @@ -134,8 +134,8 @@ This file is not a valid image. Ten plik nie jest obrazem. - - This is not a valid IP address. + + This value is not a valid IP address. Ta wartość nie jest prawidłowym adresem IP. @@ -190,8 +190,8 @@ No file was uploaded. Żaden plik nie został wgrany. - - No temporary folder was configured in php.ini. + + No temporary folder was configured in php.ini, or the configured folder does not exist. Nie skonfigurowano folderu tymczasowego w php.ini lub skonfigurowany folder nie istnieje. @@ -222,8 +222,8 @@ Unsupported card type or invalid card number. Nieobsługiwany rodzaj karty lub nieprawidłowy numer karty. - - This is not a valid International Bank Account Number (IBAN). + + This value is not a valid International Bank Account Number (IBAN). Ta wartość nie jest prawidłowym Międzynarodowym Numerem Rachunku Bankowego (IBAN). @@ -310,16 +310,16 @@ This value does not match the expected {{ charset }} charset. Ta wartość nie pasuje do oczekiwanego zestawu znaków {{ charset }}. - - This is not a valid Business Identifier Code (BIC). + + This value is not a valid Business Identifier Code (BIC). Ta wartość nie jest prawidłowym Kodem Identyfikującym Bank (BIC). Error Błąd - - This is not a valid UUID. + + This value is not a valid UUID. Ta wartość nie jest prawidłowym UUID. diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.pt.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.pt.xlf index d0941ef470a9f..b380f686eeaa0 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.pt.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.pt.xlf @@ -134,8 +134,8 @@ This file is not a valid image. Este ficheiro não é uma imagem. - - This is not a valid IP address. + + This value is not a valid IP address. Este valor não é um endereço IP válido. @@ -190,9 +190,9 @@ No file was uploaded. Nenhum arquivo foi enviado. - - No temporary folder was configured in php.ini. - Não existe uma pasta temporária configurada no arquivo php.ini. + + No temporary folder was configured in php.ini, or the configured folder does not exist. + Nenhuma pasta temporária foi configurada no php.ini, ou a pasta configurada não existe. Cannot write temporary file to disk. @@ -222,8 +222,8 @@ Unsupported card type or invalid card number. Tipo de cartão não suportado ou número de cartão inválido. - - This is not a valid International Bank Account Number (IBAN). + + This value is not a valid International Bank Account Number (IBAN). Este valor não é um Número de Conta Bancária Internacional (IBAN) válido. @@ -310,16 +310,16 @@ This value does not match the expected {{ charset }} charset. O valor não corresponde ao conjunto de caracteres {{ charset }} esperado. - - This is not a valid Business Identifier Code (BIC). + + This value is not a valid Business Identifier Code (BIC). Este valor não é um Código de Identificação de Negócio (BIC) válido. Error Erro - - This is not a valid UUID. + + This value is not a valid UUID. Este valor não é um UUID válido. diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.pt_BR.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.pt_BR.xlf index 7c40ffcc1ee59..4372885085282 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.pt_BR.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.pt_BR.xlf @@ -134,9 +134,9 @@ This file is not a valid image. Este arquivo não é uma imagem válida. - - This is not a valid IP address. - Este valor não é um endereço IP válido. + + This value is not a valid IP address. + Este valor não é um endereço IP válido. This value is not a valid language. @@ -190,9 +190,9 @@ No file was uploaded. Nenhum arquivo foi enviado. - - No temporary folder was configured in php.ini. - Nenhum diretório temporário foi configurado no php.ini. + + No temporary folder was configured in php.ini, or the configured folder does not exist. + Nenhum diretório temporário foi configurado no php.ini, ou o diretório configurado não existe. Cannot write temporary file to disk. @@ -222,9 +222,9 @@ Unsupported card type or invalid card number. Tipo de cartão não suportado ou número de cartão inválido. - - This is not a valid International Bank Account Number (IBAN). - Este valor não é um Número de Conta Bancária Internacional (IBAN) válido. + + This value is not a valid International Bank Account Number (IBAN). + Este valor não é um Número de Conta Bancária Internacional (IBAN) válido. This value is not a valid ISBN-10. @@ -310,17 +310,17 @@ This value does not match the expected {{ charset }} charset. Este valor não corresponde ao charset {{ charset }} esperado. - - This is not a valid Business Identifier Code (BIC). - Este valor não é um Código de Identificação de Negócios (BIC) válido. + + This value is not a valid Business Identifier Code (BIC). + Este valor não é um Código de Identificação de Negócios (BIC) válido. Error Erro - - This is not a valid UUID. - Este valor não é um UUID válido. + + This value is not a valid UUID. + Este valor não é um UUID válido. This value should be a multiple of {{ compared_value }}. @@ -428,15 +428,15 @@ The extension of the file is invalid ({{ extension }}). Allowed extensions are {{ extensions }}. - A extensão do arquivo é inválida ({{ extension }}). As extensões permitidas são {{ extensions }}. + A extensão do arquivo é inválida ({{ extension }}). As extensões permitidas são {{ extensions }}. The detected character encoding is invalid ({{ detected }}). Allowed encodings are {{ encodings }}. - A codificação de caracteres detectada é inválida ({{ detected }}). As codificações permitidas são {{ encodings }}. + A codificação de caracteres detectada é inválida ({{ detected }}). As codificações permitidas são {{ encodings }}. This value is not a valid MAC address. - Este valor não é um endereço MAC válido. + Este valor não é um endereço MAC válido. diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.ro.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.ro.xlf index 17d9e596faaa1..5c4a4cd308cfb 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.ro.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.ro.xlf @@ -134,8 +134,8 @@ This file is not a valid image. Acest fișier nu este o imagine validă. - - This is not a valid IP address. + + This value is not a valid IP address. Această valoare nu este o adresă IP validă. @@ -190,9 +190,9 @@ No file was uploaded. Nu a fost încărcat nici un fișier. - - No temporary folder was configured in php.ini. - Nu este configurat nici un director temporar in php.ini. + + No temporary folder was configured in php.ini, or the configured folder does not exist. + Nu a fost configurat niciun folder temporar în php.ini, sau folderul configurat nu există. Cannot write temporary file to disk. @@ -222,8 +222,8 @@ Unsupported card type or invalid card number. Tipul sau numărul cardului nu sunt valide. - - This is not a valid International Bank Account Number (IBAN). + + This value is not a valid International Bank Account Number (IBAN). Această valoare nu este un Număr de Cont Bancar Internațional (IBAN) valid. @@ -310,16 +310,16 @@ This value does not match the expected {{ charset }} charset. Această valoare nu corespunde setului de caractere {{ charset }} așteptat. - - This is not a valid Business Identifier Code (BIC). + + This value is not a valid Business Identifier Code (BIC). Această valoare nu este un Cod de Identificare a Afacerilor (BIC) valid. Error Eroare - - This is not a valid UUID. + + This value is not a valid UUID. Această valoare nu este un UUID valid. diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.ru.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.ru.xlf index 9b29aaac726c4..2e96883727892 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.ru.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.ru.xlf @@ -134,8 +134,8 @@ This file is not a valid image. Файл не является допустимым форматом изображения. - - This is not a valid IP address. + + This value is not a valid IP address. Это значение не является действительным IP-адресом. @@ -190,9 +190,9 @@ No file was uploaded. Файл не был загружен. - - No temporary folder was configured in php.ini. - Не настроена временная директория в php.ini. + + No temporary folder was configured in php.ini, or the configured folder does not exist. + В php.ini не была настроена временная папка, или настроенная папка не существует. Cannot write temporary file to disk. @@ -222,8 +222,8 @@ Unsupported card type or invalid card number. Неподдерживаемый тип или неверный номер карты. - - This is not a valid International Bank Account Number (IBAN). + + This value is not a valid International Bank Account Number (IBAN). Это значение не является действительным Международным банковским счетом (IBAN). @@ -310,16 +310,16 @@ This value does not match the expected {{ charset }} charset. Значение не совпадает с ожидаемой {{ charset }} кодировкой. - - This is not a valid Business Identifier Code (BIC). + + This value is not a valid Business Identifier Code (BIC). Это значение не является действительным Бизнес-идентификатором (BIC). Error Ошибка - - This is not a valid UUID. + + This value is not a valid UUID. Это значение не является действительным UUID. diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.sk.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.sk.xlf index 598866b1754c9..9b06bdfb8c12e 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.sk.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.sk.xlf @@ -134,8 +134,8 @@ This file is not a valid image. Tento súbor nie je obrázok. - - This is not a valid IP address. + + This value is not a valid IP address. Táto hodnota nie je platná IP adresa. @@ -190,9 +190,9 @@ No file was uploaded. Žiadny súbor nebol nahraný. - - No temporary folder was configured in php.ini. - V php.ini nie je nastavená cesta k addressáru pre dočasné súbory. + + No temporary folder was configured in php.ini, or the configured folder does not exist. + V php.ini nie je nastavený žiadny dočasný adresár, alebo nastavený adresár neexistuje. Cannot write temporary file to disk. @@ -222,8 +222,8 @@ Unsupported card type or invalid card number. Nepodporovaný typ karty alebo neplatné číslo karty. - - This is not a valid International Bank Account Number (IBAN). + + This value is not a valid International Bank Account Number (IBAN). Táto hodnota nie je platným Medzinárodným bankovým číslom účtu (IBAN). @@ -310,16 +310,16 @@ This value does not match the expected {{ charset }} charset. Táto hodnota nezodpovedá očakávanej znakovej sade {{ charset }}. - - This is not a valid Business Identifier Code (BIC). + + This value is not a valid Business Identifier Code (BIC). Táto hodnota nie je platným Obchodným identifikačným kódom (BIC). Error Chyba - - This is not a valid UUID. + + This value is not a valid UUID. Táto hodnota nie je platným UUID. diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.sl.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.sl.xlf index 0334eb43dca87..252a51969a78d 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.sl.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.sl.xlf @@ -134,8 +134,8 @@ This file is not a valid image. Ta datoteka ni veljavna slika. - - This is not a valid IP address. + + This value is not a valid IP address. Ta vrednost ni veljaven IP naslov. @@ -190,9 +190,9 @@ No file was uploaded. Nobena datoteka ni bila naložena. - - No temporary folder was configured in php.ini. - Začasna mapa ni nastavljena v php.ini. + + No temporary folder was configured in php.ini, or the configured folder does not exist. + V php.ini ni bila nastavljena začasna mapa, ali nastavljena mapa ne obstaja. Cannot write temporary file to disk. @@ -222,8 +222,8 @@ Unsupported card type or invalid card number. Nepodprti tip kartice ali neveljavna številka kartice. - - This is not a valid International Bank Account Number (IBAN). + + This value is not a valid International Bank Account Number (IBAN). Ta vrednost ni veljavna Mednarodna številka bančnega računa (IBAN). @@ -310,16 +310,16 @@ This value does not match the expected {{ charset }} charset. Ta vrednost se ne ujema s pričakovanim naborom znakov {{ charset }}. - - This is not a valid Business Identifier Code (BIC). + + This value is not a valid Business Identifier Code (BIC). Ta vrednost ni veljavna Poslovna identifikacijska koda (BIC). Error Napaka - - This is not a valid UUID. + + This value is not a valid UUID. Ta vrednost ni veljaven UUID. diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.sq.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.sq.xlf index 50f50347b90fa..d04b4af8561d8 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.sq.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.sq.xlf @@ -134,8 +134,8 @@ This file is not a valid image. Ky file nuk është imazh i vlefshëm. - - This is not a valid IP address. + + This value is not a valid IP address. Kjo vlerë nuk është një adresë IP e vlefshme. @@ -190,9 +190,9 @@ No file was uploaded. Nuk është ngarkuar ndonjë file. - - No temporary folder was configured in php.ini. - Asnjë folder i përkohshëm nuk është konfiguruar në php.ini. + + No temporary folder was configured in php.ini, or the configured folder does not exist. + Asnjë dosje e përkohshme nuk është konfiguruar në php.ini, ose dosja e konfiguruar nuk ekziston. Cannot write temporary file to disk. @@ -222,8 +222,8 @@ Unsupported card type or invalid card number. Lloj karte i papranuar ose numër karte i pavlefshëm. - - This is not a valid International Bank Account Number (IBAN). + + This value is not a valid International Bank Account Number (IBAN). Kjo vlerë nuk është një Numër i Llogarisë Bankare Ndërkombëtare (IBAN) i vlefshëm. @@ -310,16 +310,16 @@ This value does not match the expected {{ charset }} charset. Kjo vlerë nuk përputhet me kodifikimin e karaktereve {{ charset }} që pritej. - - This is not a valid Business Identifier Code (BIC). + + This value is not a valid Business Identifier Code (BIC). Kjo vlerë nuk është një Kod Identifikues i Biznesit (BIC) i vlefshëm. Error Gabim - - This is not a valid UUID. + + This value is not a valid UUID. Kjo vlerë nuk është një UUID i vlefshëm. diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.sr_Cyrl.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.sr_Cyrl.xlf index bcf7a00336b35..b73cde9bac4a9 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.sr_Cyrl.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.sr_Cyrl.xlf @@ -134,8 +134,8 @@ This file is not a valid image. Ова датотека није валидна слика. - - This is not a valid IP address. + + This value is not a valid IP address. Ова вредност није валидна IP адреса. @@ -190,9 +190,9 @@ No file was uploaded. Датотека није отпремљена. - - No temporary folder was configured in php.ini. - Привремени директоријум није конфигурисан у php.ini. + + No temporary folder was configured in php.ini, or the configured folder does not exist. + Привремени директоријум није конфигурисан у php.ini, или конфигурисани директоријум не постоји. Cannot write temporary file to disk. @@ -222,8 +222,8 @@ Unsupported card type or invalid card number. Невалидан број картице или тип картице није подржан. - - This is not a valid International Bank Account Number (IBAN). + + This value is not a valid International Bank Account Number (IBAN). Ова вредност није валидан Међународни број банкарског рачуна (IBAN). @@ -310,16 +310,16 @@ This value does not match the expected {{ charset }} charset. Вредност се не поклапа са очекиваним {{ charset }} сетом карактера. - - This is not a valid Business Identifier Code (BIC). + + This value is not a valid Business Identifier Code (BIC). Ова вредност није валидан Код за идентификацију бизниса (BIC). Error Грешка - - This is not a valid UUID. + + This value is not a valid UUID. Ова вредност није валидан UUID. diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.sr_Latn.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.sr_Latn.xlf index eae79734565b8..cd4ccfb3f3c03 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.sr_Latn.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.sr_Latn.xlf @@ -134,8 +134,8 @@ This file is not a valid image. Ovaj fajl nije validan kao slika. - - This is not a valid IP address. + + This value is not a valid IP address. Ova vrednost nije validna IP adresa. @@ -190,9 +190,9 @@ No file was uploaded. Fajl nije otpremljen. - - No temporary folder was configured in php.ini. - Privremeni direktorijum nije konfigurisan u php.ini. + + No temporary folder was configured in php.ini, or the configured folder does not exist. + Privremeni direktorijum nije konfigurisan u php.ini, ili konfigurisani direktorijum ne postoji. Cannot write temporary file to disk. @@ -222,8 +222,8 @@ Unsupported card type or invalid card number. Nevalidan broj kartice ili nepodržan tip kartice. - - This is not a valid International Bank Account Number (IBAN). + + This value is not a valid International Bank Account Number (IBAN). Ova vrednost nije validan Međunarodni broj bankovnog računa (IBAN). @@ -310,16 +310,16 @@ This value does not match the expected {{ charset }} charset. Vrednost se ne poklapa sa očekivanim {{ charset }} setom karaktera. - - This is not a valid Business Identifier Code (BIC). + + This value is not a valid Business Identifier Code (BIC). Ova vrednost nije validan Kod za identifikaciju biznisa (BIC). Error Greška - - This is not a valid UUID. + + This value is not a valid UUID. Ova vrednost nije validan UUID. diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.sv.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.sv.xlf index 4c72b69af4c48..2ec539cc5b5ee 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.sv.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.sv.xlf @@ -134,8 +134,8 @@ This file is not a valid image. Filen är ingen giltig bild. - - This is not a valid IP address. + + This value is not a valid IP address. Detta värde är inte en giltig IP-adress. @@ -190,9 +190,9 @@ No file was uploaded. Ingen fil laddades upp. - - No temporary folder was configured in php.ini. - Det finns ingen temporär mapp konfigurerad i php.ini. + + No temporary folder was configured in php.ini, or the configured folder does not exist. + Ingen tillfällig mapp konfigurerades i php.ini, eller den konfigurerade mappen finns inte. Cannot write temporary file to disk. @@ -222,8 +222,8 @@ Unsupported card type or invalid card number. Okänd korttyp eller ogiltigt kortnummer. - - This is not a valid International Bank Account Number (IBAN). + + This value is not a valid International Bank Account Number (IBAN). Detta värde är inte ett giltigt Internationellt Bankkontonummer (IBAN). @@ -310,16 +310,16 @@ This value does not match the expected {{ charset }} charset. Detta värde har inte den förväntade teckenkodningen {{ charset }}. - - This is not a valid Business Identifier Code (BIC). + + This value is not a valid Business Identifier Code (BIC). Detta värde är inte en giltig Företagsidentifieringskod (BIC). Error Fel - - This is not a valid UUID. + + This value is not a valid UUID. Detta värde är inte en giltig UUID. diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.th.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.th.xlf index 3c9229643f8d6..f109024bfeaf3 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.th.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.th.xlf @@ -134,8 +134,8 @@ This file is not a valid image. ไฟล์นี้ไม่ใช่ไฟล์รูปภาพ - - This is not a valid IP address. + + This value is not a valid IP address. ค่านี้ไม่ใช่ที่อยู่ IP ที่ถูกต้อง @@ -190,9 +190,9 @@ No file was uploaded. ไม่มีไฟล์ใดถูกอัปโหลด - - No temporary folder was configured in php.ini. - ไม่พบการตั้งค่าโฟลเดอร์ชั่วคราว (temporary folder) ใน php.ini + + No temporary folder was configured in php.ini, or the configured folder does not exist. + ไม่มีการกำหนดโฟลเดอร์ชั่วคราวใน php.ini หรือโฟลเดอร์ที่กำหนดไม่มีอยู่จริง Cannot write temporary file to disk. @@ -222,8 +222,8 @@ Unsupported card type or invalid card number. ไม่รู้จักประเภทของบัตร หรือหมายเลขบัตรไม่ถูกต้อง - - This is not a valid International Bank Account Number (IBAN). + + This value is not a valid International Bank Account Number (IBAN). ค่านี้ไม่ใช่หมายเลขบัญชีธนาคารระหว่างประเทศ (IBAN) ที่ถูกต้อง @@ -310,16 +310,16 @@ This value does not match the expected {{ charset }} charset. ค่านี้ไม่ตรงกับการเข้ารหัส {{ charset }} - - This is not a valid Business Identifier Code (BIC). + + This value is not a valid Business Identifier Code (BIC). ค่านี้ไม่ใช่รหัสประจำตัวธุรกิจ (BIC) ที่ถูกต้อง Error เกิดข้อผิดพลาด - - This is not a valid UUID. + + This value is not a valid UUID. ค่านี้ไม่ใช่ UUID ที่ถูกต้อง diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.tl.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.tl.xlf index 9ea22dea573f6..632efbc3f3f95 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.tl.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.tl.xlf @@ -134,8 +134,8 @@ This file is not a valid image. Ang file na ito ay hindi wastong imahe. - - This is not a valid IP address. + + This value is not a valid IP address. Ang halagang ito ay hindi isang wastong IP address. @@ -190,9 +190,9 @@ No file was uploaded. Walang na upload na file. - - No temporary folder was configured in php.ini. - Walang temporaryong folder ang naayos sa php.ini. + + No temporary folder was configured in php.ini, or the configured folder does not exist. + Walang pansamantalang folder na na-configure sa php.ini, o ang naka-configure na folder ay hindi umiiral. Cannot write temporary file to disk. @@ -222,8 +222,8 @@ Unsupported card type or invalid card number. Hindi supportadong uri ng kard o hindi wastong numero ng kard. - - This is not a valid International Bank Account Number (IBAN). + + This value is not a valid International Bank Account Number (IBAN). Ang halagang ito ay hindi isang wastong International Bank Account Number (IBAN). @@ -310,16 +310,16 @@ This value does not match the expected {{ charset }} charset. Ang halaga ay hindi kapareha sa inaasahang {{ charset }} set ng karater. - - This is not a valid Business Identifier Code (BIC). + + This value is not a valid Business Identifier Code (BIC). Ang halagang ito ay hindi isang wastong Business Identifier Code (BIC). Error Error - - This is not a valid UUID. + + This value is not a valid UUID. Ang halagang ito ay hindi isang wastong UUID. diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.tr.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.tr.xlf index 0e1ac94c78427..4d66ce8bcbc58 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.tr.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.tr.xlf @@ -134,8 +134,8 @@ This file is not a valid image. Bu dosya geçerli bir resim değildir. - - This is not a valid IP address. + + This value is not a valid IP address. Bu değer geçerli bir IP adresi değil. @@ -190,9 +190,9 @@ No file was uploaded. Hiçbir dosya yüklenmedi. - - No temporary folder was configured in php.ini. - php.ini içerisinde geçici dizin tanımlanmadı. + + No temporary folder was configured in php.ini, or the configured folder does not exist. + php.ini'de geçici bir klasör yapılandırılmadı, veya yapılandırılan klasör mevcut değil. Cannot write temporary file to disk. @@ -222,8 +222,8 @@ Unsupported card type or invalid card number. Desteklenmeyen kart tipi veya geçersiz kart numarası. - - This is not a valid International Bank Account Number (IBAN). + + This value is not a valid International Bank Account Number (IBAN). Bu değer geçerli bir Uluslararası Banka Hesap Numarası (IBAN) değil. @@ -310,16 +310,16 @@ This value does not match the expected {{ charset }} charset. Bu değer beklenen {{ charset }} karakter kümesiyle eşleşmiyor. - - This is not a valid Business Identifier Code (BIC). + + This value is not a valid Business Identifier Code (BIC). Bu değer geçerli bir İşletme Tanımlama Kodu (BIC) değil. Error Hata - - This is not a valid UUID. + + This value is not a valid UUID. Bu değer geçerli bir UUID değil. diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.uk.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.uk.xlf index 3889ce962bd75..fcf63e0f675f3 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.uk.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.uk.xlf @@ -134,8 +134,8 @@ This file is not a valid image. Цей файл не є допустимим форматом зображення. - - This is not a valid IP address. + + This value is not a valid IP address. Це значення не є дійсною IP-адресою. @@ -190,9 +190,9 @@ No file was uploaded. Файл не був завантажений. - - No temporary folder was configured in php.ini. - Не налаштована тимчасова директорія в php.ini. + + No temporary folder was configured in php.ini, or the configured folder does not exist. + У php.ini не було налаштовано тимчасової теки, або налаштована тека не існує. Cannot write temporary file to disk. @@ -222,8 +222,8 @@ Unsupported card type or invalid card number. Непідтримуваний тип карти або невірний номер карти. - - This is not a valid International Bank Account Number (IBAN). + + This value is not a valid International Bank Account Number (IBAN). Це значення не є дійсним Міжнародним банківським рахунком (IBAN). @@ -310,16 +310,16 @@ This value does not match the expected {{ charset }} charset. Значення не збігається з очікуваним {{ charset }} кодуванням. - - This is not a valid Business Identifier Code (BIC). + + This value is not a valid Business Identifier Code (BIC). Це значення не є дійсним Кодом ідентифікації бізнесу (BIC). Error Помилка - - This is not a valid UUID. + + This value is not a valid UUID. Це значення не є дійсним UUID. diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.ur.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.ur.xlf index b62eef2a3ee2d..65719c64ebc4a 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.ur.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.ur.xlf @@ -134,8 +134,8 @@ This file is not a valid image. یہ فائل درست تصویر نہیں ہے - - This is not a valid IP address. + + This value is not a valid IP address. یہ قیمت کوئی درست IP پتہ نہیں ہے۔ @@ -190,9 +190,9 @@ No file was uploaded. کوئی فائل اپ لوڈ نہیں کی گئی - - No temporary folder was configured in php.ini. - میں کوئی عارضی فولڈر کنفیگر نہیں کیا گیا، یا کنفیگرڈ فولڈر موجود نہیں ہے php.ini + + No temporary folder was configured in php.ini, or the configured folder does not exist. + php.ini میں کوئی عارضی فولڈر ترتیب نہیں دیا گیا تھا، یا ترتیب دیا گیا فولڈر موجود نہیں ہے۔ Cannot write temporary file to disk. @@ -222,8 +222,8 @@ Unsupported card type or invalid card number. غیر تعاون یافتہ کارڈ کی قسم یا غلط کارڈ نمبر - - This is not a valid International Bank Account Number (IBAN). + + This value is not a valid International Bank Account Number (IBAN). یہ قیمت کوئی درست بین الاقوامی بینک اکاؤنٹ نمبر (IBAN) نہیں ہے۔ @@ -310,16 +310,16 @@ This value does not match the expected {{ charset }} charset. کے جيسي نہیں ہے charset {{ charset }} یہ ويليو متوقع - - This is not a valid Business Identifier Code (BIC). + + This value is not a valid Business Identifier Code (BIC). یہ قیمت کوئی درست بزنس شناختی کوڈ (BIC) نہیں ہے۔ Error خرابی - - This is not a valid UUID. + + This value is not a valid UUID. یہ قیمت کوئی درست UUID نہیں ہے۔ diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.uz.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.uz.xlf index d7dfb174a3086..bf5a2d5f4d9de 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.uz.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.uz.xlf @@ -134,8 +134,8 @@ This file is not a valid image. Fayl yaroqli rasm formati emas. - - This is not a valid IP address. + + This value is not a valid IP address. Bu qiymat haqiqiy IP manzil emas. @@ -190,9 +190,9 @@ No file was uploaded. Fayl yuklanmagan. - - No temporary folder was configured in php.ini. - php.ini da vaqtinchalik katalog sozlanmagan. + + No temporary folder was configured in php.ini, or the configured folder does not exist. + php.ini da vaqtinchalik katalog sozlanmagan, yoki sozlangan katalog mavjud emas. Cannot write temporary file to disk. @@ -222,8 +222,8 @@ Unsupported card type or invalid card number. Qo'llab-quvvatlanmaydigan karta turi yoki yaroqsiz karta raqami. - - This is not a valid International Bank Account Number (IBAN). + + This value is not a valid International Bank Account Number (IBAN). Bu qiymat haqiqiy Xalqaro Bank Hisob Raqami (IBAN) emas. @@ -310,16 +310,16 @@ This value does not match the expected {{ charset }} charset. Qiymat kutilgan {{ charset }} kodlashiga mos kelmaydi. - - This is not a valid Business Identifier Code (BIC). + + This value is not a valid Business Identifier Code (BIC). Bu qiymat haqiqiy Biznes Identifikatsiya Kodi (BIC) emas. Error Xatolik - - This is not a valid UUID. + + This value is not a valid UUID. Bu qiymat haqiqiy UUID emas. diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.vi.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.vi.xlf index 5c0a4fe4313ef..eadf61467c8bc 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.vi.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.vi.xlf @@ -134,8 +134,8 @@ This file is not a valid image. Tập tin không phải là hình ảnh hợp lệ. - - This is not a valid IP address. + + This value is not a valid IP address. Giá trị này không phải là địa chỉ IP hợp lệ. @@ -190,9 +190,9 @@ No file was uploaded. Tập tin không được tải lên. - - No temporary folder was configured in php.ini. - Thư mục tạm không được định nghĩa trong php.ini. + + No temporary folder was configured in php.ini, or the configured folder does not exist. + Không có thư mục tạm được cấu hình trong php.ini, hoặc thư mục đã cấu hình không tồn tại. Cannot write temporary file to disk. @@ -222,8 +222,8 @@ Unsupported card type or invalid card number. Thẻ không được hỗ trợ hoặc số thẻ không hợp lệ. - - This is not a valid International Bank Account Number (IBAN). + + This value is not a valid International Bank Account Number (IBAN). Giá trị này không phải là Số Tài Khoản Ngân Hàng Quốc Tế (IBAN) hợp lệ. @@ -310,16 +310,16 @@ This value does not match the expected {{ charset }} charset. Giá trị này không đúng định dạng bộ ký tự mong muốn {{ charset }}. - - This is not a valid Business Identifier Code (BIC). + + This value is not a valid Business Identifier Code (BIC). Giá trị này không phải là Mã Định Danh Doanh Nghiệp (BIC) hợp lệ. Error Lỗi - - This is not a valid UUID. + + This value is not a valid UUID. Giá trị này không phải là UUID hợp lệ. diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.zh_CN.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.zh_CN.xlf index 61d196edeb30b..155871cd38df4 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.zh_CN.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.zh_CN.xlf @@ -134,8 +134,8 @@ This file is not a valid image. 该文件不是有效的图片。 - - This is not a valid IP address. + + This value is not a valid IP address. 该值不是有效的IP地址。 @@ -190,9 +190,9 @@ No file was uploaded. 没有上传任何文件。 - - No temporary folder was configured in php.ini. - php.ini 里没有配置临时文件目录。 + + No temporary folder was configured in php.ini, or the configured folder does not exist. + php.ini 中没有配置临时文件夹,或配置的文件夹不存在。 Cannot write temporary file to disk. @@ -222,8 +222,8 @@ Unsupported card type or invalid card number. 不支持的信用卡类型或无效的信用卡号。 - - This is not a valid International Bank Account Number (IBAN). + + This value is not a valid International Bank Account Number (IBAN). 该值不是有效的国际银行账号(IBAN)。 @@ -310,16 +310,16 @@ This value does not match the expected {{ charset }} charset. 该值不符合 {{ charset }} 编码。 - - This is not a valid Business Identifier Code (BIC). + + This value is not a valid Business Identifier Code (BIC). 该值不是有效的业务标识符代码(BIC)。 Error 错误 - - This is not a valid UUID. + + This value is not a valid UUID. 该值不是有效的UUID。 diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.zh_TW.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.zh_TW.xlf index a520e470343fd..1a90678627e97 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.zh_TW.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.zh_TW.xlf @@ -134,8 +134,8 @@ This file is not a valid image. 該檔案不是有效的圖片。 - - This is not a valid IP address. + + This value is not a valid IP address. 此值不是有效的IP地址。 @@ -190,9 +190,9 @@ No file was uploaded. 沒有上傳任何檔案。 - - No temporary folder was configured in php.ini. - php.ini 裡沒有配置臨時目錄。 + + No temporary folder was configured in php.ini, or the configured folder does not exist. + php.ini 中沒有配置臨時文件夾,或配置的文件夾不存在。 Cannot write temporary file to disk. @@ -222,8 +222,8 @@ Unsupported card type or invalid card number. 不支援的信用卡類型或無效的信用卡號。 - - This is not a valid International Bank Account Number (IBAN). + + This value is not a valid International Bank Account Number (IBAN). 此值不是有效的國際銀行帳戶號碼(IBAN)。 @@ -310,16 +310,16 @@ This value does not match the expected {{ charset }} charset. 該數值不符合預期 {{ charset }} 符號編碼。 - - This is not a valid Business Identifier Code (BIC). + + This value is not a valid Business Identifier Code (BIC). 此值不是有效的業務識別碼(BIC)。 Error 錯誤。 - - This is not a valid UUID. + + This value is not a valid UUID. 此值不是有效的UUID。 diff --git a/src/Symfony/Component/Validator/Test/ConstraintValidatorTestCase.php b/src/Symfony/Component/Validator/Test/ConstraintValidatorTestCase.php index f808e49ba26e9..56a3dafb92232 100644 --- a/src/Symfony/Component/Validator/Test/ConstraintValidatorTestCase.php +++ b/src/Symfony/Component/Validator/Test/ConstraintValidatorTestCase.php @@ -227,7 +227,7 @@ protected function expectValidateAt(int $i, string $propertyPath, mixed $value, }); } - protected function expectValidateValue(int $i, mixed $value, array $constraints = [], string|GroupSequence|array $group = null) + protected function expectValidateValue(int $i, mixed $value, array $constraints = [], string|GroupSequence|array|null $group = null) { $contextualValidator = $this->context->getValidator()->inContext($this->context); $contextualValidator->expectValidation($i, null, $value, $group, function ($passedConstraints) use ($constraints) { @@ -251,7 +251,7 @@ protected function expectFailingValueValidation(int $i, mixed $value, array $con }, $violation); } - protected function expectValidateValueAt(int $i, string $propertyPath, mixed $value, Constraint|array $constraints, string|GroupSequence|array $group = null) + protected function expectValidateValueAt(int $i, string $propertyPath, mixed $value, Constraint|array $constraints, string|GroupSequence|array|null $group = null) { $contextualValidator = $this->context->getValidator()->inContext($this->context); $contextualValidator->expectValidation($i, $propertyPath, $value, $group, function ($passedConstraints) use ($constraints) { @@ -311,7 +311,7 @@ final class ConstraintViolationAssertion /** * @internal */ - public function __construct(ExecutionContextInterface $context, string $message, Constraint $constraint = null, array $assertions = []) + public function __construct(ExecutionContextInterface $context, string $message, ?Constraint $constraint = null, array $assertions = []) { $this->context = $context; $this->message = $message; @@ -496,7 +496,7 @@ public function doAtPath(string $path): static return $this; } - public function validate(mixed $value, Constraint|array $constraints = null, string|GroupSequence|array $groups = null): static + public function validate(mixed $value, Constraint|array|null $constraints = null, string|GroupSequence|array|null $groups = null): static { throw new \BadMethodCallException(); } @@ -504,7 +504,7 @@ public function validate(mixed $value, Constraint|array $constraints = null, str /** * @return $this */ - public function doValidate(mixed $value, Constraint|array $constraints = null, string|GroupSequence|array $groups = null): static + public function doValidate(mixed $value, Constraint|array|null $constraints = null, string|GroupSequence|array|null $groups = null): static { Assert::assertFalse($this->expectNoValidate, 'No validation calls have been expected.'); @@ -526,7 +526,7 @@ public function doValidate(mixed $value, Constraint|array $constraints = null, s return $this; } - public function validateProperty(object $object, string $propertyName, string|GroupSequence|array $groups = null): static + public function validateProperty(object $object, string $propertyName, string|GroupSequence|array|null $groups = null): static { throw new \BadMethodCallException(); } @@ -534,12 +534,12 @@ public function validateProperty(object $object, string $propertyName, string|Gr /** * @return $this */ - public function doValidateProperty(object $object, string $propertyName, string|GroupSequence|array $groups = null): static + public function doValidateProperty(object $object, string $propertyName, string|GroupSequence|array|null $groups = null): static { return $this; } - public function validatePropertyValue(object|string $objectOrClass, string $propertyName, mixed $value, string|GroupSequence|array $groups = null): static + public function validatePropertyValue(object|string $objectOrClass, string $propertyName, mixed $value, string|GroupSequence|array|null $groups = null): static { throw new \BadMethodCallException(); } @@ -547,7 +547,7 @@ public function validatePropertyValue(object|string $objectOrClass, string $prop /** * @return $this */ - public function doValidatePropertyValue(object|string $objectOrClass, string $propertyName, mixed $value, string|GroupSequence|array $groups = null): static + public function doValidatePropertyValue(object|string $objectOrClass, string $propertyName, mixed $value, string|GroupSequence|array|null $groups = null): static { return $this; } @@ -567,7 +567,7 @@ public function expectNoValidate(): void $this->expectNoValidate = true; } - public function expectValidation(string $call, ?string $propertyPath, mixed $value, string|GroupSequence|array|null $group, callable $constraints, ConstraintViolationInterface $violation = null): void + public function expectValidation(string $call, ?string $propertyPath, mixed $value, string|GroupSequence|array|null $group, callable $constraints, ?ConstraintViolationInterface $violation = null): void { if (null !== $propertyPath) { $this->expectedAtPath[$call] = $propertyPath; diff --git a/src/Symfony/Component/Validator/Tests/Constraints/AbstractComparisonValidatorTestCase.php b/src/Symfony/Component/Validator/Tests/Constraints/AbstractComparisonValidatorTestCase.php index 0b3a3897dbebb..8ac430bb04701 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/AbstractComparisonValidatorTestCase.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/AbstractComparisonValidatorTestCase.php @@ -281,7 +281,7 @@ abstract public static function provideInvalidComparisons(): array; abstract public static function provideComparisonsToNullValueAtPropertyPath(); - abstract protected static function createConstraint(array $options = null): Constraint; + abstract protected static function createConstraint(?array $options = null): Constraint; protected function getErrorCode(): ?string { diff --git a/src/Symfony/Component/Validator/Tests/Constraints/AtLeastOneOfValidatorTest.php b/src/Symfony/Component/Validator/Tests/Constraints/AtLeastOneOfValidatorTest.php index 6c4fe6f9ea754..9e2da70f7f00f 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/AtLeastOneOfValidatorTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/AtLeastOneOfValidatorTest.php @@ -268,7 +268,7 @@ public function testTranslatorIsCalledOnConstraintBaseMessageAndViolations() $translator = new class() implements TranslatorInterface, LocaleAwareInterface { use TranslatorTrait; - public function trans(?string $id, array $parameters = [], string $domain = null, string $locale = null): string + public function trans(?string $id, array $parameters = [], ?string $domain = null, ?string $locale = null): string { if ('This value should satisfy at least one of the following constraints:' === $id) { return 'Dummy translation:'; diff --git a/src/Symfony/Component/Validator/Tests/Constraints/CharsetValidatorTest.php b/src/Symfony/Component/Validator/Tests/Constraints/CharsetValidatorTest.php index 76470b9370dee..88998dcdbcad0 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/CharsetValidatorTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/CharsetValidatorTest.php @@ -42,7 +42,7 @@ public function testInvalidValues(string $value, array $encodings) $this->validator->validate($value, new Charset(encodings: $encodings)); $this->buildViolation('The detected character encoding is invalid ({{ detected }}). Allowed encodings are {{ encodings }}.') - ->setParameter('{{ detected }}', mb_detect_encoding($value, $encodings, true)) + ->setParameter('{{ detected }}', 'UTF-8') ->setParameter('{{ encodings }}', implode(', ', $encodings)) ->setCode(Charset::BAD_ENCODING_ERROR) ->assertRaised(); diff --git a/src/Symfony/Component/Validator/Tests/Constraints/CidrTest.php b/src/Symfony/Component/Validator/Tests/Constraints/CidrTest.php index c9239de4d2a5b..c2536a7b5d418 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/CidrTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/CidrTest.php @@ -49,7 +49,15 @@ public function testForV6() public function testWithInvalidVersion() { - $availableVersions = [Ip::ALL, Ip::V4, Ip::V6]; + $availableVersions = [ + Ip::V4, Ip::V6, Ip::ALL, + Ip::V4_NO_PUBLIC, Ip::V6_NO_PUBLIC, Ip::ALL_NO_PUBLIC, + Ip::V4_NO_PRIVATE, Ip::V6_NO_PRIVATE, Ip::ALL_NO_PRIVATE, + Ip::V4_NO_RESERVED, Ip::V6_NO_RESERVED, Ip::ALL_NO_RESERVED, + Ip::V4_ONLY_PUBLIC, Ip::V6_ONLY_PUBLIC, Ip::ALL_ONLY_PUBLIC, + Ip::V4_ONLY_PRIVATE, Ip::V6_ONLY_PRIVATE, Ip::ALL_ONLY_PRIVATE, + Ip::V4_ONLY_RESERVED, Ip::V6_ONLY_RESERVED, Ip::ALL_ONLY_RESERVED, + ]; self::expectException(ConstraintDefinitionException::class); self::expectExceptionMessage(sprintf('The option "version" must be one of "%s".', implode('", "', $availableVersions))); diff --git a/src/Symfony/Component/Validator/Tests/Constraints/CidrValidatorTest.php b/src/Symfony/Component/Validator/Tests/Constraints/CidrValidatorTest.php index 59caf3872c79a..75b2c8ade9acc 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/CidrValidatorTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/CidrValidatorTest.php @@ -52,7 +52,7 @@ public function testExpectsStringCompatibleType() { $this->expectException(UnexpectedValueException::class); - $this->validator->validate(123456, new Cidr()); + $this->validator->validate([123456], new Cidr()); } /** @@ -106,7 +106,7 @@ public function testInvalidIpAddressAndNetmask(string|\Stringable $cidr) /** * @dataProvider getOutOfRangeNetmask */ - public function testOutOfRangeNetmask(string $cidr, string $version = null, int $min = null, int $max = null) + public function testOutOfRangeNetmask(string $cidr, ?string $version = null, ?int $min = null, ?int $max = null) { $cidrConstraint = new Cidr([ 'version' => $version, @@ -205,7 +205,6 @@ public static function getWithInvalidNetmask(): array return [ ['192.168.1.0/-1'], ['0.0.0.0/foobar'], - ['10.0.0.0/128'], ['123.45.67.178/aaa'], ['172.16.0.0//'], ['255.255.255.255/1/4'], @@ -223,7 +222,6 @@ public static function getWithInvalidMasksAndIps(): array { return [ ['0.0.0.0/foobar'], - ['10.0.0.0/128'], ['123.45.67.178/aaa'], ['172.16.0.0//'], ['172.16.0.0/a/'], @@ -243,6 +241,7 @@ public static function getOutOfRangeNetmask(): array { return [ ['10.0.0.0/24', Ip::V4, 10, 20], + ['10.0.0.0/128'], ['2001:0DB8:85A3:0000:0000:8A2E:0370:7334/24', Ip::V6, 10, 20], ]; } diff --git a/src/Symfony/Component/Validator/Tests/Constraints/DivisibleByValidatorTest.php b/src/Symfony/Component/Validator/Tests/Constraints/DivisibleByValidatorTest.php index 06e0607965eb3..30950d94b6532 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/DivisibleByValidatorTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/DivisibleByValidatorTest.php @@ -26,7 +26,7 @@ protected function createValidator(): DivisibleByValidator return new DivisibleByValidator(); } - protected static function createConstraint(array $options = null): Constraint + protected static function createConstraint(?array $options = null): Constraint { return new DivisibleBy($options); } diff --git a/src/Symfony/Component/Validator/Tests/Constraints/EqualToValidatorTest.php b/src/Symfony/Component/Validator/Tests/Constraints/EqualToValidatorTest.php index 06c0edfbf1a50..0a57fa8d34058 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/EqualToValidatorTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/EqualToValidatorTest.php @@ -28,7 +28,7 @@ protected function createValidator(): EqualToValidator return new EqualToValidator(); } - protected static function createConstraint(array $options = null): Constraint + protected static function createConstraint(?array $options = null): Constraint { return new EqualTo($options); } diff --git a/src/Symfony/Component/Validator/Tests/Constraints/GreaterThanOrEqualValidatorTest.php b/src/Symfony/Component/Validator/Tests/Constraints/GreaterThanOrEqualValidatorTest.php index aaeb98d8bfd3f..aeaf46f566d61 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/GreaterThanOrEqualValidatorTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/GreaterThanOrEqualValidatorTest.php @@ -28,7 +28,7 @@ protected function createValidator(): GreaterThanOrEqualValidator return new GreaterThanOrEqualValidator(); } - protected static function createConstraint(array $options = null): Constraint + protected static function createConstraint(?array $options = null): Constraint { return new GreaterThanOrEqual($options); } diff --git a/src/Symfony/Component/Validator/Tests/Constraints/GreaterThanOrEqualValidatorWithPositiveOrZeroConstraintTest.php b/src/Symfony/Component/Validator/Tests/Constraints/GreaterThanOrEqualValidatorWithPositiveOrZeroConstraintTest.php index 11b092c6df484..e21f6b8e909e3 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/GreaterThanOrEqualValidatorWithPositiveOrZeroConstraintTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/GreaterThanOrEqualValidatorWithPositiveOrZeroConstraintTest.php @@ -21,7 +21,7 @@ */ class GreaterThanOrEqualValidatorWithPositiveOrZeroConstraintTest extends GreaterThanOrEqualValidatorTest { - protected static function createConstraint(array $options = null): Constraint + protected static function createConstraint(?array $options = null): Constraint { return new PositiveOrZero(); } diff --git a/src/Symfony/Component/Validator/Tests/Constraints/GreaterThanValidatorTest.php b/src/Symfony/Component/Validator/Tests/Constraints/GreaterThanValidatorTest.php index ab8a0ac10f51d..709e01c26a59e 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/GreaterThanValidatorTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/GreaterThanValidatorTest.php @@ -28,7 +28,7 @@ protected function createValidator(): GreaterThanValidator return new GreaterThanValidator(); } - protected static function createConstraint(array $options = null): Constraint + protected static function createConstraint(?array $options = null): Constraint { return new GreaterThan($options); } diff --git a/src/Symfony/Component/Validator/Tests/Constraints/GreaterThanValidatorWithPositiveConstraintTest.php b/src/Symfony/Component/Validator/Tests/Constraints/GreaterThanValidatorWithPositiveConstraintTest.php index 18a503bf237d5..0b5f2e2228750 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/GreaterThanValidatorWithPositiveConstraintTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/GreaterThanValidatorWithPositiveConstraintTest.php @@ -21,7 +21,7 @@ */ class GreaterThanValidatorWithPositiveConstraintTest extends GreaterThanValidatorTest { - protected static function createConstraint(array $options = null): Constraint + protected static function createConstraint(?array $options = null): Constraint { return new Positive(); } diff --git a/src/Symfony/Component/Validator/Tests/Constraints/IdenticalToValidatorTest.php b/src/Symfony/Component/Validator/Tests/Constraints/IdenticalToValidatorTest.php index 424c028c0a808..2855e83f0b50c 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/IdenticalToValidatorTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/IdenticalToValidatorTest.php @@ -28,7 +28,7 @@ protected function createValidator(): IdenticalToValidator return new IdenticalToValidator(); } - protected static function createConstraint(array $options = null): Constraint + protected static function createConstraint(?array $options = null): Constraint { return new IdenticalTo($options); } diff --git a/src/Symfony/Component/Validator/Tests/Constraints/IpValidatorTest.php b/src/Symfony/Component/Validator/Tests/Constraints/IpValidatorTest.php index 82068184d9647..a2277a3d8ff86 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/IpValidatorTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/IpValidatorTest.php @@ -185,6 +185,33 @@ public function testInvalidIpsV4($ip) ->assertRaised(); } + /** + * @dataProvider getValidPublicIpsV4 + */ + public function testInvalidNoPublicIpsV4($ip) + { + $constraint = new Ip([ + 'version' => Ip::V4_NO_PUBLIC, + 'message' => 'myMessage', + ]); + + $this->validator->validate($ip, $constraint); + + $this->buildViolation('myMessage') + ->setParameter('{{ value }}', '"'.$ip.'"') + ->setCode(Ip::INVALID_IP_ERROR) + ->assertRaised(); + } + + public static function getValidPublicIpsV4() + { + return [ + ['8.0.0.0'], + ['90.0.0.0'], + ['110.0.0.110'], + ]; + } + public static function getInvalidIpsV4() { return [ @@ -201,12 +228,24 @@ public static function getInvalidIpsV4() } /** - * @dataProvider getInvalidPrivateIpsV4 + * @dataProvider getValidPrivateIpsV4 + */ + public function testValidPrivateIpsV4($ip) + { + $this->validator->validate($ip, new Ip([ + 'version' => Ip::V4_ONLY_PRIVATE, + ])); + + $this->assertNoViolation(); + } + + /** + * @dataProvider getValidPrivateIpsV4 */ public function testInvalidPrivateIpsV4($ip) { $constraint = new Ip([ - 'version' => Ip::V4_NO_PRIV, + 'version' => Ip::V4_NO_PRIVATE, 'message' => 'myMessage', ]); @@ -218,7 +257,25 @@ public function testInvalidPrivateIpsV4($ip) ->assertRaised(); } - public static function getInvalidPrivateIpsV4() + /** + * @dataProvider getInvalidPrivateIpsV4 + */ + public function testInvalidOnlyPrivateIpsV4($ip) + { + $constraint = new Ip([ + 'version' => Ip::V4_ONLY_PRIVATE, + 'message' => 'myMessage', + ]); + + $this->validator->validate($ip, $constraint); + + $this->buildViolation('myMessage') + ->setParameter('{{ value }}', '"'.$ip.'"') + ->setCode(Ip::INVALID_IP_ERROR) + ->assertRaised(); + } + + public static function getValidPrivateIpsV4() { return [ ['10.0.0.0'], @@ -227,13 +284,30 @@ public static function getInvalidPrivateIpsV4() ]; } + public static function getInvalidPrivateIpsV4() + { + return array_merge(self::getValidPublicIpsV4(), self::getValidReservedIpsV4()); + } + /** - * @dataProvider getInvalidReservedIpsV4 + * @dataProvider getValidReservedIpsV4 + */ + public function testValidReservedIpsV4($ip) + { + $this->validator->validate($ip, new Ip([ + 'version' => Ip::V4_ONLY_RESERVED, + ])); + + $this->assertNoViolation(); + } + + /** + * @dataProvider getValidReservedIpsV4 */ public function testInvalidReservedIpsV4($ip) { $constraint = new Ip([ - 'version' => Ip::V4_NO_RES, + 'version' => Ip::V4_NO_RESERVED, 'message' => 'myMessage', ]); @@ -245,7 +319,25 @@ public function testInvalidReservedIpsV4($ip) ->assertRaised(); } - public static function getInvalidReservedIpsV4() + /** + * @dataProvider getInvalidReservedIpsV4 + */ + public function testInvalidOnlyReservedIpsV4($ip) + { + $constraint = new Ip([ + 'version' => Ip::V4_ONLY_RESERVED, + 'message' => 'myMessage', + ]); + + $this->validator->validate($ip, $constraint); + + $this->buildViolation('myMessage') + ->setParameter('{{ value }}', '"'.$ip.'"') + ->setCode(Ip::INVALID_IP_ERROR) + ->assertRaised(); + } + + public static function getValidReservedIpsV4() { return [ ['0.0.0.0'], @@ -254,6 +346,11 @@ public static function getInvalidReservedIpsV4() ]; } + public static function getInvalidReservedIpsV4() + { + return array_merge(self::getValidPublicIpsV4(), self::getValidPrivateIpsV4()); + } + /** * @dataProvider getInvalidPublicIpsV4 */ @@ -274,7 +371,7 @@ public function testInvalidPublicIpsV4($ip) public static function getInvalidPublicIpsV4() { - return array_merge(self::getInvalidPrivateIpsV4(), self::getInvalidReservedIpsV4()); + return array_merge(self::getValidPrivateIpsV4(), self::getValidReservedIpsV4()); } /** @@ -320,7 +417,7 @@ public static function getInvalidIpsV6() public function testInvalidPrivateIpsV6($ip) { $constraint = new Ip([ - 'version' => Ip::V6_NO_PRIV, + 'version' => Ip::V6_NO_PRIVATE, 'message' => 'myMessage', ]); @@ -347,7 +444,7 @@ public static function getInvalidPrivateIpsV6() public function testInvalidReservedIpsV6($ip) { $constraint = new Ip([ - 'version' => Ip::V6_NO_RES, + 'version' => Ip::V6_NO_RESERVED, 'message' => 'myMessage', ]); @@ -419,7 +516,7 @@ public static function getInvalidIpsAll() public function testInvalidPrivateIpsAll($ip) { $constraint = new Ip([ - 'version' => Ip::ALL_NO_PRIV, + 'version' => Ip::ALL_NO_PRIVATE, 'message' => 'myMessage', ]); @@ -433,7 +530,7 @@ public function testInvalidPrivateIpsAll($ip) public static function getInvalidPrivateIpsAll() { - return array_merge(self::getInvalidPrivateIpsV4(), self::getInvalidPrivateIpsV6()); + return array_merge(self::getValidPrivateIpsV4(), self::getInvalidPrivateIpsV6()); } /** @@ -442,7 +539,7 @@ public static function getInvalidPrivateIpsAll() public function testInvalidReservedIpsAll($ip) { $constraint = new Ip([ - 'version' => Ip::ALL_NO_RES, + 'version' => Ip::ALL_NO_RESERVED, 'message' => 'myMessage', ]); @@ -456,7 +553,7 @@ public function testInvalidReservedIpsAll($ip) public static function getInvalidReservedIpsAll() { - return array_merge(self::getInvalidReservedIpsV4(), self::getInvalidReservedIpsV6()); + return array_merge(self::getValidReservedIpsV4(), self::getInvalidReservedIpsV6()); } /** diff --git a/src/Symfony/Component/Validator/Tests/Constraints/LessThanOrEqualValidatorTest.php b/src/Symfony/Component/Validator/Tests/Constraints/LessThanOrEqualValidatorTest.php index d4bcc346fdabf..ba81bbd554ae9 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/LessThanOrEqualValidatorTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/LessThanOrEqualValidatorTest.php @@ -28,7 +28,7 @@ protected function createValidator(): LessThanOrEqualValidator return new LessThanOrEqualValidator(); } - protected static function createConstraint(array $options = null): Constraint + protected static function createConstraint(?array $options = null): Constraint { return new LessThanOrEqual($options); } diff --git a/src/Symfony/Component/Validator/Tests/Constraints/LessThanOrEqualValidatorWithNegativeOrZeroConstraintTest.php b/src/Symfony/Component/Validator/Tests/Constraints/LessThanOrEqualValidatorWithNegativeOrZeroConstraintTest.php index 946bf93c7826b..ea8e245f61674 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/LessThanOrEqualValidatorWithNegativeOrZeroConstraintTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/LessThanOrEqualValidatorWithNegativeOrZeroConstraintTest.php @@ -21,7 +21,7 @@ */ class LessThanOrEqualValidatorWithNegativeOrZeroConstraintTest extends LessThanOrEqualValidatorTest { - protected static function createConstraint(array $options = null): Constraint + protected static function createConstraint(?array $options = null): Constraint { return new NegativeOrZero(); } diff --git a/src/Symfony/Component/Validator/Tests/Constraints/LessThanValidatorTest.php b/src/Symfony/Component/Validator/Tests/Constraints/LessThanValidatorTest.php index 14064962a26d7..0c4afb7b51cfb 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/LessThanValidatorTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/LessThanValidatorTest.php @@ -28,7 +28,7 @@ protected function createValidator(): LessThanValidator return new LessThanValidator(); } - protected static function createConstraint(array $options = null): Constraint + protected static function createConstraint(?array $options = null): Constraint { return new LessThan($options); } diff --git a/src/Symfony/Component/Validator/Tests/Constraints/LessThanValidatorWithNegativeConstraintTest.php b/src/Symfony/Component/Validator/Tests/Constraints/LessThanValidatorWithNegativeConstraintTest.php index 35fac73ecab34..efe2d6d211e70 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/LessThanValidatorWithNegativeConstraintTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/LessThanValidatorWithNegativeConstraintTest.php @@ -21,7 +21,7 @@ */ class LessThanValidatorWithNegativeConstraintTest extends LessThanValidatorTest { - protected static function createConstraint(array $options = null): Constraint + protected static function createConstraint(?array $options = null): Constraint { return new Negative(); } diff --git a/src/Symfony/Component/Validator/Tests/Constraints/NotCompromisedPasswordValidatorTest.php b/src/Symfony/Component/Validator/Tests/Constraints/NotCompromisedPasswordValidatorTest.php index a7d7fd394ecb4..253529444d237 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/NotCompromisedPasswordValidatorTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/NotCompromisedPasswordValidatorTest.php @@ -223,7 +223,7 @@ public static function provideErrorSkippingConstraints(): iterable yield 'named arguments' => [new NotCompromisedPassword(skipOnError: true)]; } - private function createHttpClientStub(string $returnValue = null): HttpClientInterface + private function createHttpClientStub(?string $returnValue = null): HttpClientInterface { $httpClientStub = $this->createMock(HttpClientInterface::class); $httpClientStub->method('request')->willReturnCallback( diff --git a/src/Symfony/Component/Validator/Tests/Constraints/NotEqualToValidatorTest.php b/src/Symfony/Component/Validator/Tests/Constraints/NotEqualToValidatorTest.php index a297ae37fba3f..fc5275e3ca5c8 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/NotEqualToValidatorTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/NotEqualToValidatorTest.php @@ -28,7 +28,7 @@ protected function createValidator(): NotEqualToValidator return new NotEqualToValidator(); } - protected static function createConstraint(array $options = null): Constraint + protected static function createConstraint(?array $options = null): Constraint { return new NotEqualTo($options); } diff --git a/src/Symfony/Component/Validator/Tests/Constraints/NotIdenticalToValidatorTest.php b/src/Symfony/Component/Validator/Tests/Constraints/NotIdenticalToValidatorTest.php index eab5395bcd808..0376403e235cb 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/NotIdenticalToValidatorTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/NotIdenticalToValidatorTest.php @@ -28,7 +28,7 @@ protected function createValidator(): NotIdenticalToValidator return new NotIdenticalToValidator(); } - protected static function createConstraint(array $options = null): Constraint + protected static function createConstraint(?array $options = null): Constraint { return new NotIdenticalTo($options); } diff --git a/src/Symfony/Component/Validator/Tests/Mapping/Loader/PropertyInfoLoaderTest.php b/src/Symfony/Component/Validator/Tests/Mapping/Loader/PropertyInfoLoaderTest.php index d2f205d5177ac..ffd365afbaa23 100644 --- a/src/Symfony/Component/Validator/Tests/Mapping/Loader/PropertyInfoLoaderTest.php +++ b/src/Symfony/Component/Validator/Tests/Mapping/Loader/PropertyInfoLoaderTest.php @@ -12,8 +12,10 @@ namespace Symfony\Component\Validator\Tests\Mapping\Loader; use PHPUnit\Framework\TestCase; -use Symfony\Component\PropertyInfo\PropertyInfoExtractorInterface; -use Symfony\Component\PropertyInfo\Type; +use Symfony\Component\PropertyInfo\PropertyAccessExtractorInterface; +use Symfony\Component\PropertyInfo\PropertyListExtractorInterface; +use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface; +use Symfony\Component\TypeInfo\Type; use Symfony\Component\Validator\Constraints\All; use Symfony\Component\Validator\Constraints\Iban; use Symfony\Component\Validator\Constraints\NotBlank; @@ -35,8 +37,8 @@ class PropertyInfoLoaderTest extends TestCase { public function testLoadClassMetadata() { - $propertyInfoStub = $this->createMock(PropertyInfoExtractorInterface::class); - $propertyInfoStub + $propertyListExtractor = $this->createMock(PropertyListExtractorInterface::class); + $propertyListExtractor ->method('getProperties') ->willReturn([ 'nullableString', @@ -54,24 +56,45 @@ public function testLoadClassMetadata() 'noAutoMapping', ]) ; - $propertyInfoStub - ->method('getTypes') - ->will($this->onConsecutiveCalls( - [new Type(Type::BUILTIN_TYPE_STRING, true)], - [new Type(Type::BUILTIN_TYPE_STRING)], - [new Type(Type::BUILTIN_TYPE_STRING, true), new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_BOOL)], - [new Type(Type::BUILTIN_TYPE_OBJECT, true, Entity::class)], - [new Type(Type::BUILTIN_TYPE_ARRAY, true, null, true, null, new Type(Type::BUILTIN_TYPE_OBJECT, false, Entity::class))], - [new Type(Type::BUILTIN_TYPE_ARRAY, true, null, true)], - [new Type(Type::BUILTIN_TYPE_FLOAT, true)], // The existing constraint is float - [new Type(Type::BUILTIN_TYPE_STRING, true)], - [new Type(Type::BUILTIN_TYPE_STRING, true)], - [new Type(Type::BUILTIN_TYPE_ARRAY, true, null, true, null, new Type(Type::BUILTIN_TYPE_FLOAT))], - [new Type(Type::BUILTIN_TYPE_STRING)], - [new Type(Type::BUILTIN_TYPE_STRING)] - )) - ; - $propertyInfoStub + + $propertyTypeExtractor = new class() implements PropertyTypeExtractorInterface { + private int $i = 0; + private array $types; + + public function __construct() + { + $this->types = [ + Type::nullable(Type::string()), + Type::string(), + Type::union(Type::string(), Type::int(), Type::bool(), Type::null()), + Type::nullable(Type::object(Entity::class)), + Type::nullable(Type::array(Type::object(Entity::class))), + Type::nullable(Type::array()), + Type::nullable(Type::float()), // The existing constraint is float + Type::nullable(Type::string()), + Type::nullable(Type::string()), + Type::nullable(Type::array(Type::float())), + Type::string(), + Type::string(), + ]; + } + + public function getType(string $class, string $property, array $context = []): ?Type + { + $type = $this->types[$this->i]; + ++$this->i; + + return $type; + } + + public function getTypes(string $class, string $property, array $context = []): ?array + { + return []; + } + }; + + $propertyAccessExtractor = $this->createMock(PropertyAccessExtractorInterface::class); + $propertyAccessExtractor ->method('isWritable') ->will($this->onConsecutiveCalls( true, @@ -89,7 +112,7 @@ public function testLoadClassMetadata() )) ; - $propertyInfoLoader = new PropertyInfoLoader($propertyInfoStub, $propertyInfoStub, $propertyInfoStub, '{.*}'); + $propertyInfoLoader = new PropertyInfoLoader($propertyListExtractor, $propertyTypeExtractor, $propertyAccessExtractor, '{.*}'); $validator = Validation::createValidatorBuilder() ->enableAttributeMapping() @@ -170,7 +193,6 @@ public function testLoadClassMetadata() $this->assertInstanceOf(TypeConstraint::class, $alreadyPartiallyMappedCollectionConstraints[0]->constraints[0]); $this->assertSame('string', $alreadyPartiallyMappedCollectionConstraints[0]->constraints[0]->type); $this->assertInstanceOf(Iban::class, $alreadyPartiallyMappedCollectionConstraints[0]->constraints[1]); - $this->assertInstanceOf(NotNull::class, $alreadyPartiallyMappedCollectionConstraints[0]->constraints[2]); $readOnlyMetadata = $classMetadata->getPropertyMetadata('readOnly'); $this->assertEmpty($readOnlyMetadata); @@ -186,19 +208,29 @@ public function testLoadClassMetadata() /** * @dataProvider regexpProvider */ - public function testClassValidator(bool $expected, string $classValidatorRegexp = null) + public function testClassValidator(bool $expected, ?string $classValidatorRegexp = null) { - $propertyInfoStub = $this->createMock(PropertyInfoExtractorInterface::class); - $propertyInfoStub + $propertyListExtractor = $this->createMock(PropertyListExtractorInterface::class); + $propertyListExtractor ->method('getProperties') ->willReturn(['string']) ; - $propertyInfoStub - ->method('getTypes') - ->willReturn([new Type(Type::BUILTIN_TYPE_STRING)]) - ; - $propertyInfoLoader = new PropertyInfoLoader($propertyInfoStub, $propertyInfoStub, $propertyInfoStub, $classValidatorRegexp); + $propertyTypeExtractor = new class() implements PropertyTypeExtractorInterface { + public function getType(string $class, string $property, array $context = []): ?Type + { + return Type::string(); + } + + public function getTypes(string $class, string $property, array $context = []): ?array + { + return []; + } + }; + + $propertyAccessExtractor = $this->createMock(PropertyAccessExtractorInterface::class); + + $propertyInfoLoader = new PropertyInfoLoader($propertyListExtractor, $propertyTypeExtractor, $propertyAccessExtractor, $classValidatorRegexp); $classMetadata = new ClassMetadata(PropertyInfoLoaderEntity::class); $this->assertSame($expected, $propertyInfoLoader->loadClassMetadata($classMetadata)); @@ -216,19 +248,27 @@ public static function regexpProvider(): array public function testClassNoAutoMapping() { - $propertyInfoStub = $this->createMock(PropertyInfoExtractorInterface::class); - $propertyInfoStub + $propertyListExtractor = $this->createMock(PropertyListExtractorInterface::class); + $propertyListExtractor ->method('getProperties') ->willReturn(['string', 'autoMappingExplicitlyEnabled']) ; - $propertyInfoStub - ->method('getTypes') - ->willReturnOnConsecutiveCalls( - [new Type(Type::BUILTIN_TYPE_STRING)], - [new Type(Type::BUILTIN_TYPE_BOOL)] - ); - - $propertyInfoLoader = new PropertyInfoLoader($propertyInfoStub, $propertyInfoStub, $propertyInfoStub, '{.*}'); + + $propertyTypeExtractor = new class() implements PropertyTypeExtractorInterface { + public function getType(string $class, string $property, array $context = []): ?Type + { + return Type::string(); + } + + public function getTypes(string $class, string $property, array $context = []): ?array + { + return []; + } + }; + + $propertyAccessExtractor = $this->createMock(PropertyAccessExtractorInterface::class); + + $propertyInfoLoader = new PropertyInfoLoader($propertyListExtractor, $propertyTypeExtractor, $propertyAccessExtractor, '{.*}'); $validator = Validation::createValidatorBuilder() ->enableAttributeMapping() ->addLoader($propertyInfoLoader) diff --git a/src/Symfony/Component/Validator/Validation.php b/src/Symfony/Component/Validator/Validation.php index 3df02e118c4b9..45c5930030bf9 100644 --- a/src/Symfony/Component/Validator/Validation.php +++ b/src/Symfony/Component/Validator/Validation.php @@ -24,7 +24,7 @@ final class Validation /** * Creates a callable chain of constraints. */ - public static function createCallable(Constraint|ValidatorInterface $constraintOrValidator = null, Constraint ...$constraints): callable + public static function createCallable(Constraint|ValidatorInterface|null $constraintOrValidator = null, Constraint ...$constraints): callable { $validator = self::createIsValidCallable($constraintOrValidator, ...$constraints); @@ -42,7 +42,7 @@ public static function createCallable(Constraint|ValidatorInterface $constraintO * * @return callable(mixed $value, ConstraintViolationListInterface &$violations = null): bool */ - public static function createIsValidCallable(Constraint|ValidatorInterface $constraintOrValidator = null, Constraint ...$constraints): callable + public static function createIsValidCallable(Constraint|ValidatorInterface|null $constraintOrValidator = null, Constraint ...$constraints): callable { $validator = $constraintOrValidator; @@ -53,7 +53,7 @@ public static function createIsValidCallable(Constraint|ValidatorInterface $cons $validator ??= self::createValidator(); - return static function (mixed $value, ConstraintViolationListInterface &$violations = null) use ($constraints, $validator): bool { + return static function (mixed $value, ?ConstraintViolationListInterface &$violations = null) use ($constraints, $validator): bool { $violations = $validator->validate($value, $constraints); return 0 === $violations->count(); diff --git a/src/Symfony/Component/Validator/Validator/ContextualValidatorInterface.php b/src/Symfony/Component/Validator/Validator/ContextualValidatorInterface.php index 5478660ef8c57..8acdf30763a2c 100644 --- a/src/Symfony/Component/Validator/Validator/ContextualValidatorInterface.php +++ b/src/Symfony/Component/Validator/Validator/ContextualValidatorInterface.php @@ -44,7 +44,7 @@ public function atPath(string $path): static; * * @return $this */ - public function validate(mixed $value, Constraint|array $constraints = null, string|GroupSequence|array $groups = null): static; + public function validate(mixed $value, Constraint|array|null $constraints = null, string|GroupSequence|array|null $groups = null): static; /** * Validates a property of an object against the constraints specified @@ -55,7 +55,7 @@ public function validate(mixed $value, Constraint|array $constraints = null, str * * @return $this */ - public function validateProperty(object $object, string $propertyName, string|GroupSequence|array $groups = null): static; + public function validateProperty(object $object, string $propertyName, string|GroupSequence|array|null $groups = null): static; /** * Validates a value against the constraints specified for an object's @@ -68,7 +68,7 @@ public function validateProperty(object $object, string $propertyName, string|Gr * * @return $this */ - public function validatePropertyValue(object|string $objectOrClass, string $propertyName, mixed $value, string|GroupSequence|array $groups = null): static; + public function validatePropertyValue(object|string $objectOrClass, string $propertyName, mixed $value, string|GroupSequence|array|null $groups = null): static; /** * Returns the violations that have been generated so far in the context diff --git a/src/Symfony/Component/Validator/Validator/RecursiveContextualValidator.php b/src/Symfony/Component/Validator/Validator/RecursiveContextualValidator.php index 30f9f6e92f6bf..ebdae79b61392 100644 --- a/src/Symfony/Component/Validator/Validator/RecursiveContextualValidator.php +++ b/src/Symfony/Component/Validator/Validator/RecursiveContextualValidator.php @@ -58,7 +58,7 @@ class RecursiveContextualValidator implements ContextualValidatorInterface * * @param ObjectInitializerInterface[] $objectInitializers The object initializers */ - public function __construct(ExecutionContextInterface $context, MetadataFactoryInterface $metadataFactory, ConstraintValidatorFactoryInterface $validatorFactory, array $objectInitializers = [], ContainerInterface $groupProviderLocator = null) + public function __construct(ExecutionContextInterface $context, MetadataFactoryInterface $metadataFactory, ConstraintValidatorFactoryInterface $validatorFactory, array $objectInitializers = [], ?ContainerInterface $groupProviderLocator = null) { $this->context = $context; $this->defaultPropertyPath = $context->getPropertyPath(); @@ -76,7 +76,7 @@ public function atPath(string $path): static return $this; } - public function validate(mixed $value, Constraint|array $constraints = null, string|GroupSequence|array $groups = null): static + public function validate(mixed $value, Constraint|array|null $constraints = null, string|GroupSequence|array|null $groups = null): static { $groups = $groups ? $this->normalizeGroups($groups) : $this->defaultGroups; @@ -161,7 +161,7 @@ public function validate(mixed $value, Constraint|array $constraints = null, str throw new RuntimeException(sprintf('Cannot validate values of type "%s" automatically. Please provide a constraint.', get_debug_type($value))); } - public function validateProperty(object $object, string $propertyName, string|GroupSequence|array $groups = null): static + public function validateProperty(object $object, string $propertyName, string|GroupSequence|array|null $groups = null): static { $classMetadata = $this->metadataFactory->getMetadataFor($object); @@ -202,7 +202,7 @@ public function validateProperty(object $object, string $propertyName, string|Gr return $this; } - public function validatePropertyValue(object|string $objectOrClass, string $propertyName, mixed $value, string|GroupSequence|array $groups = null): static + public function validatePropertyValue(object|string $objectOrClass, string $propertyName, mixed $value, string|GroupSequence|array|null $groups = null): static { $classMetadata = $this->metadataFactory->getMetadataFor($objectOrClass); diff --git a/src/Symfony/Component/Validator/Validator/RecursiveValidator.php b/src/Symfony/Component/Validator/Validator/RecursiveValidator.php index 369ed88e89fa4..0f39e19847939 100644 --- a/src/Symfony/Component/Validator/Validator/RecursiveValidator.php +++ b/src/Symfony/Component/Validator/Validator/RecursiveValidator.php @@ -40,7 +40,7 @@ class RecursiveValidator implements ValidatorInterface * * @param ObjectInitializerInterface[] $objectInitializers The object initializers */ - public function __construct(ExecutionContextFactoryInterface $contextFactory, MetadataFactoryInterface $metadataFactory, ConstraintValidatorFactoryInterface $validatorFactory, array $objectInitializers = [], ContainerInterface $groupProviderLocator = null) + public function __construct(ExecutionContextFactoryInterface $contextFactory, MetadataFactoryInterface $metadataFactory, ConstraintValidatorFactoryInterface $validatorFactory, array $objectInitializers = [], ?ContainerInterface $groupProviderLocator = null) { $this->contextFactory = $contextFactory; $this->metadataFactory = $metadataFactory; @@ -81,21 +81,21 @@ public function hasMetadataFor(mixed $object): bool return $this->metadataFactory->hasMetadataFor($object); } - public function validate(mixed $value, Constraint|array $constraints = null, string|GroupSequence|array $groups = null): ConstraintViolationListInterface + public function validate(mixed $value, Constraint|array|null $constraints = null, string|GroupSequence|array|null $groups = null): ConstraintViolationListInterface { return $this->startContext($value) ->validate($value, $constraints, $groups) ->getViolations(); } - public function validateProperty(object $object, string $propertyName, string|GroupSequence|array $groups = null): ConstraintViolationListInterface + public function validateProperty(object $object, string $propertyName, string|GroupSequence|array|null $groups = null): ConstraintViolationListInterface { return $this->startContext($object) ->validateProperty($object, $propertyName, $groups) ->getViolations(); } - public function validatePropertyValue(object|string $objectOrClass, string $propertyName, mixed $value, string|GroupSequence|array $groups = null): ConstraintViolationListInterface + public function validatePropertyValue(object|string $objectOrClass, string $propertyName, mixed $value, string|GroupSequence|array|null $groups = null): ConstraintViolationListInterface { // If a class name is passed, take $value as root return $this->startContext(\is_object($objectOrClass) ? $objectOrClass : $value) diff --git a/src/Symfony/Component/Validator/Validator/TraceableValidator.php b/src/Symfony/Component/Validator/Validator/TraceableValidator.php index a717a0d4159f8..5235e3ec41d2a 100644 --- a/src/Symfony/Component/Validator/Validator/TraceableValidator.php +++ b/src/Symfony/Component/Validator/Validator/TraceableValidator.php @@ -53,7 +53,7 @@ public function hasMetadataFor(mixed $value): bool return $this->validator->hasMetadataFor($value); } - public function validate(mixed $value, Constraint|array $constraints = null, string|GroupSequence|array $groups = null): ConstraintViolationListInterface + public function validate(mixed $value, Constraint|array|null $constraints = null, string|GroupSequence|array|null $groups = null): ConstraintViolationListInterface { $violations = $this->validator->validate($value, $constraints, $groups); @@ -94,12 +94,12 @@ public function validate(mixed $value, Constraint|array $constraints = null, str return $violations; } - public function validateProperty(object $object, string $propertyName, string|GroupSequence|array $groups = null): ConstraintViolationListInterface + public function validateProperty(object $object, string $propertyName, string|GroupSequence|array|null $groups = null): ConstraintViolationListInterface { return $this->validator->validateProperty($object, $propertyName, $groups); } - public function validatePropertyValue(object|string $objectOrClass, string $propertyName, mixed $value, string|GroupSequence|array $groups = null): ConstraintViolationListInterface + public function validatePropertyValue(object|string $objectOrClass, string $propertyName, mixed $value, string|GroupSequence|array|null $groups = null): ConstraintViolationListInterface { return $this->validator->validatePropertyValue($objectOrClass, $propertyName, $value, $groups); } diff --git a/src/Symfony/Component/Validator/Validator/ValidatorInterface.php b/src/Symfony/Component/Validator/Validator/ValidatorInterface.php index 076dc30349d25..f26a9b99a03e5 100644 --- a/src/Symfony/Component/Validator/Validator/ValidatorInterface.php +++ b/src/Symfony/Component/Validator/Validator/ValidatorInterface.php @@ -37,7 +37,7 @@ interface ValidatorInterface extends MetadataFactoryInterface * If the list is empty, validation * succeeded */ - public function validate(mixed $value, Constraint|array $constraints = null, string|GroupSequence|array $groups = null): ConstraintViolationListInterface; + public function validate(mixed $value, Constraint|array|null $constraints = null, string|GroupSequence|array|null $groups = null): ConstraintViolationListInterface; /** * Validates a property of an object against the constraints specified @@ -50,7 +50,7 @@ public function validate(mixed $value, Constraint|array $constraints = null, str * If the list is empty, validation * succeeded */ - public function validateProperty(object $object, string $propertyName, string|GroupSequence|array $groups = null): ConstraintViolationListInterface; + public function validateProperty(object $object, string $propertyName, string|GroupSequence|array|null $groups = null): ConstraintViolationListInterface; /** * Validates a value against the constraints specified for an object's @@ -65,7 +65,7 @@ public function validateProperty(object $object, string $propertyName, string|Gr * If the list is empty, validation * succeeded */ - public function validatePropertyValue(object|string $objectOrClass, string $propertyName, mixed $value, string|GroupSequence|array $groups = null): ConstraintViolationListInterface; + public function validatePropertyValue(object|string $objectOrClass, string $propertyName, mixed $value, string|GroupSequence|array|null $groups = null): ConstraintViolationListInterface; /** * Starts a new validation context and returns a validator for that context. diff --git a/src/Symfony/Component/Validator/Violation/ConstraintViolationBuilder.php b/src/Symfony/Component/Validator/Violation/ConstraintViolationBuilder.php index 476cfacd88d0e..e6ce597df5501 100644 --- a/src/Symfony/Component/Validator/Violation/ConstraintViolationBuilder.php +++ b/src/Symfony/Component/Validator/Violation/ConstraintViolationBuilder.php @@ -39,7 +39,7 @@ class ConstraintViolationBuilder implements ConstraintViolationBuilderInterface private ?string $code = null; private mixed $cause = null; - public function __construct(ConstraintViolationList $violations, ?Constraint $constraint, string|\Stringable $message, array $parameters, mixed $root, ?string $propertyPath, mixed $invalidValue, TranslatorInterface $translator, string|false $translationDomain = null) + public function __construct(ConstraintViolationList $violations, ?Constraint $constraint, string|\Stringable $message, array $parameters, mixed $root, ?string $propertyPath, mixed $invalidValue, TranslatorInterface $translator, string|false|null $translationDomain = null) { $this->violations = $violations; $this->message = $message; diff --git a/src/Symfony/Component/Validator/composer.json b/src/Symfony/Component/Validator/composer.json index 4f55dd2d1f827..fddf5a0764d85 100644 --- a/src/Symfony/Component/Validator/composer.json +++ b/src/Symfony/Component/Validator/composer.json @@ -36,7 +36,7 @@ "symfony/cache": "^6.4|^7.0", "symfony/mime": "^6.4|^7.0", "symfony/property-access": "^6.4|^7.0", - "symfony/property-info": "^6.4|^7.0", + "symfony/property-info": "^7.1", "symfony/translation": "^6.4.3|^7.0.3", "egulias/email-validator": "^2.1.10|^3|^4" }, @@ -47,7 +47,7 @@ "symfony/expression-language": "<6.4", "symfony/http-kernel": "<6.4", "symfony/intl": "<6.4", - "symfony/property-info": "<6.4", + "symfony/property-info": "<7.1", "symfony/translation": "<6.4.3|>=7.0,<7.0.3", "symfony/yaml": "<6.4" }, diff --git a/src/Symfony/Component/VarDumper/Caster/Caster.php b/src/Symfony/Component/VarDumper/Caster/Caster.php index d50a3dfadfdaf..d9577e7ae52fc 100644 --- a/src/Symfony/Component/VarDumper/Caster/Caster.php +++ b/src/Symfony/Component/VarDumper/Caster/Caster.php @@ -47,7 +47,7 @@ class Caster * * @param bool $hasDebugInfo Whether the __debugInfo method exists on $obj or not */ - public static function castObject(object $obj, string $class, bool $hasDebugInfo = false, string $debugClass = null): array + public static function castObject(object $obj, string $class, bool $hasDebugInfo = false, ?string $debugClass = null): array { if ($hasDebugInfo) { try { diff --git a/src/Symfony/Component/VarDumper/Caster/ClassStub.php b/src/Symfony/Component/VarDumper/Caster/ClassStub.php index 28d9ba7df19b7..5b9ce64071334 100644 --- a/src/Symfony/Component/VarDumper/Caster/ClassStub.php +++ b/src/Symfony/Component/VarDumper/Caster/ClassStub.php @@ -24,7 +24,7 @@ class ClassStub extends ConstStub * @param string $identifier A PHP identifier, e.g. a class, method, interface, etc. name * @param callable $callable The callable targeted by the identifier when it is ambiguous or not a real PHP identifier */ - public function __construct(string $identifier, callable|array|string $callable = null) + public function __construct(string $identifier, callable|array|string|null $callable = null) { $this->value = $identifier; diff --git a/src/Symfony/Component/VarDumper/Caster/ConstStub.php b/src/Symfony/Component/VarDumper/Caster/ConstStub.php index d7d1812bd0ea0..587c6c39867a6 100644 --- a/src/Symfony/Component/VarDumper/Caster/ConstStub.php +++ b/src/Symfony/Component/VarDumper/Caster/ConstStub.php @@ -20,7 +20,7 @@ */ class ConstStub extends Stub { - public function __construct(string $name, string|int|float $value = null) + public function __construct(string $name, string|int|float|null $value = null) { $this->class = $name; $this->value = 1 < \func_num_args() ? $value : $name; diff --git a/src/Symfony/Component/VarDumper/Caster/FFICaster.php b/src/Symfony/Component/VarDumper/Caster/FFICaster.php index bc586c6a49329..8c3ad02bbcb38 100644 --- a/src/Symfony/Component/VarDumper/Caster/FFICaster.php +++ b/src/Symfony/Component/VarDumper/Caster/FFICaster.php @@ -97,7 +97,7 @@ private static function castFFIFunction(Stub $stub, CType $type): array return [Caster::PREFIX_VIRTUAL.'returnType' => $returnType]; } - private static function castFFIPointer(Stub $stub, CType $type, CData $data = null): array + private static function castFFIPointer(Stub $stub, CType $type, ?CData $data = null): array { $ptr = $type->getPointerType(); @@ -132,7 +132,7 @@ private static function castFFIStringValue(CData $data): string|CutStub return $stub; } - private static function castFFIStructLike(CType $type, CData $data = null): array + private static function castFFIStructLike(CType $type, ?CData $data = null): array { $isUnion = ($type->getAttributes() & CType::ATTR_UNION) === CType::ATTR_UNION; diff --git a/src/Symfony/Component/VarDumper/Caster/LinkStub.php b/src/Symfony/Component/VarDumper/Caster/LinkStub.php index b65038551255b..3acd4fd67fba3 100644 --- a/src/Symfony/Component/VarDumper/Caster/LinkStub.php +++ b/src/Symfony/Component/VarDumper/Caster/LinkStub.php @@ -23,7 +23,7 @@ class LinkStub extends ConstStub private static array $vendorRoots; private static array $composerRoots = []; - public function __construct(string $label, int $line = 0, string $href = null) + public function __construct(string $label, int $line = 0, ?string $href = null) { $this->value = $label; diff --git a/src/Symfony/Component/VarDumper/Caster/TraceStub.php b/src/Symfony/Component/VarDumper/Caster/TraceStub.php index f28561fb5f275..5766e51692cc0 100644 --- a/src/Symfony/Component/VarDumper/Caster/TraceStub.php +++ b/src/Symfony/Component/VarDumper/Caster/TraceStub.php @@ -25,7 +25,7 @@ class TraceStub extends Stub public ?int $sliceLength; public int $numberingOffset; - public function __construct(array $trace, bool $keepArgs = true, int $sliceOffset = 0, int $sliceLength = null, int $numberingOffset = 0) + public function __construct(array $trace, bool $keepArgs = true, int $sliceOffset = 0, ?int $sliceLength = null, int $numberingOffset = 0) { $this->value = $trace; $this->keepArgs = $keepArgs; diff --git a/src/Symfony/Component/VarDumper/Cloner/AbstractCloner.php b/src/Symfony/Component/VarDumper/Cloner/AbstractCloner.php index 4fbcc6228b661..e7cb39469a58f 100644 --- a/src/Symfony/Component/VarDumper/Cloner/AbstractCloner.php +++ b/src/Symfony/Component/VarDumper/Cloner/AbstractCloner.php @@ -219,7 +219,7 @@ abstract class AbstractCloner implements ClonerInterface * * @see addCasters */ - public function __construct(array $casters = null) + public function __construct(?array $casters = null) { $this->addCasters($casters ?? static::$defaultCasters); } diff --git a/src/Symfony/Component/VarDumper/Dumper/AbstractDumper.php b/src/Symfony/Component/VarDumper/Dumper/AbstractDumper.php index 99c78f2806bfa..20a1fcd3c6186 100644 --- a/src/Symfony/Component/VarDumper/Dumper/AbstractDumper.php +++ b/src/Symfony/Component/VarDumper/Dumper/AbstractDumper.php @@ -45,7 +45,7 @@ abstract class AbstractDumper implements DataDumperInterface, DumperInterface * @param string|null $charset The default character encoding to use for non-UTF8 strings * @param int $flags A bit field of static::DUMP_* constants to fine tune dumps representation */ - public function __construct($output = null, string $charset = null, int $flags = 0) + public function __construct($output = null, ?string $charset = null, int $flags = 0) { $this->flags = $flags; $this->setCharset($charset ?: \ini_get('php.output_encoding') ?: \ini_get('default_charset') ?: 'UTF-8'); diff --git a/src/Symfony/Component/VarDumper/Dumper/CliDumper.php b/src/Symfony/Component/VarDumper/Dumper/CliDumper.php index c66cdcd412e9d..b1a7e53a78a7d 100644 --- a/src/Symfony/Component/VarDumper/Dumper/CliDumper.php +++ b/src/Symfony/Component/VarDumper/Dumper/CliDumper.php @@ -63,7 +63,7 @@ class CliDumper extends AbstractDumper private bool $handlesHrefGracefully; - public function __construct($output = null, string $charset = null, int $flags = 0) + public function __construct($output = null, ?string $charset = null, int $flags = 0) { parent::__construct($output, $charset, $flags); @@ -550,7 +550,7 @@ protected function supportsColors(): bool protected function dumpLine(int $depth, bool $endOfValue = false): void { - if ($this->colors) { + if ($this->colors ??= $this->supportsColors()) { $this->line = sprintf("\033[%sm%s\033[m", $this->styles['default'], $this->line); } parent::dumpLine($depth); diff --git a/src/Symfony/Component/VarDumper/Dumper/ContextProvider/SourceContextProvider.php b/src/Symfony/Component/VarDumper/Dumper/ContextProvider/SourceContextProvider.php index 5937ae0a36689..9477e5312c26f 100644 --- a/src/Symfony/Component/VarDumper/Dumper/ContextProvider/SourceContextProvider.php +++ b/src/Symfony/Component/VarDumper/Dumper/ContextProvider/SourceContextProvider.php @@ -30,7 +30,7 @@ final class SourceContextProvider implements ContextProviderInterface private ?string $projectDir; private ?FileLinkFormatter $fileLinkFormatter; - public function __construct(string $charset = null, string $projectDir = null, FileLinkFormatter $fileLinkFormatter = null, int $limit = 9) + public function __construct(?string $charset = null, ?string $projectDir = null, ?FileLinkFormatter $fileLinkFormatter = null, int $limit = 9) { $this->charset = $charset; $this->projectDir = $projectDir; diff --git a/src/Symfony/Component/VarDumper/Dumper/HtmlDumper.php b/src/Symfony/Component/VarDumper/Dumper/HtmlDumper.php index abbb94ef7d881..74468fff12865 100644 --- a/src/Symfony/Component/VarDumper/Dumper/HtmlDumper.php +++ b/src/Symfony/Component/VarDumper/Dumper/HtmlDumper.php @@ -74,7 +74,7 @@ class HtmlDumper extends CliDumper ]; private array $extraDisplayOptions = []; - public function __construct($output = null, string $charset = null, int $flags = 0) + public function __construct($output = null, ?string $charset = null, int $flags = 0) { AbstractDumper::__construct($output, $charset, $flags); $this->dumpId = 'sf-dump-'.mt_rand(); diff --git a/src/Symfony/Component/VarDumper/Dumper/ServerDumper.php b/src/Symfony/Component/VarDumper/Dumper/ServerDumper.php index c464475eace1f..0d3552607f1d5 100644 --- a/src/Symfony/Component/VarDumper/Dumper/ServerDumper.php +++ b/src/Symfony/Component/VarDumper/Dumper/ServerDumper.php @@ -30,7 +30,7 @@ class ServerDumper implements DataDumperInterface * @param DataDumperInterface|null $wrappedDumper A wrapped instance used whenever we failed contacting the server * @param ContextProviderInterface[] $contextProviders Context providers indexed by context name */ - public function __construct(string $host, DataDumperInterface $wrappedDumper = null, array $contextProviders = []) + public function __construct(string $host, ?DataDumperInterface $wrappedDumper = null, array $contextProviders = []) { $this->connection = new Connection($host, $contextProviders); $this->wrappedDumper = $wrappedDumper; diff --git a/src/Symfony/Component/VarDumper/Server/DumpServer.php b/src/Symfony/Component/VarDumper/Server/DumpServer.php index 8df05a150ac72..a9228a2ef5bcc 100644 --- a/src/Symfony/Component/VarDumper/Server/DumpServer.php +++ b/src/Symfony/Component/VarDumper/Server/DumpServer.php @@ -32,7 +32,7 @@ class DumpServer */ private $socket; - public function __construct(string $host, LoggerInterface $logger = null) + public function __construct(string $host, ?LoggerInterface $logger = null) { if (!str_contains($host, '://')) { $host = 'tcp://'.$host; diff --git a/src/Symfony/Component/VarDumper/Test/VarDumperTestTrait.php b/src/Symfony/Component/VarDumper/Test/VarDumperTestTrait.php index a202185e586be..4475efd12478b 100644 --- a/src/Symfony/Component/VarDumper/Test/VarDumperTestTrait.php +++ b/src/Symfony/Component/VarDumper/Test/VarDumperTestTrait.php @@ -27,7 +27,7 @@ trait VarDumperTestTrait 'flags' => null, ]; - protected function setUpVarDumper(array $casters, int $flags = null): void + protected function setUpVarDumper(array $casters, ?int $flags = null): void { $this->varDumperConfig['casters'] = $casters; $this->varDumperConfig['flags'] = $flags; @@ -52,7 +52,7 @@ public function assertDumpMatchesFormat(mixed $expected, mixed $data, int $filte $this->assertStringMatchesFormat($this->prepareExpectation($expected, $filter), $this->getDump($data, null, $filter), $message); } - protected function getDump(mixed $data, string|int $key = null, int $filter = 0): ?string + protected function getDump(mixed $data, string|int|null $key = null, int $filter = 0): ?string { if (null === $flags = $this->varDumperConfig['flags']) { $flags = getenv('DUMP_LIGHT_ARRAY') ? CliDumper::DUMP_LIGHT_ARRAY : 0; diff --git a/src/Symfony/Component/VarDumper/Tests/Caster/DoctrineCasterTest.php b/src/Symfony/Component/VarDumper/Tests/Caster/DoctrineCasterTest.php index 992c6c546dbbd..b0b0c90cd02e8 100644 --- a/src/Symfony/Component/VarDumper/Tests/Caster/DoctrineCasterTest.php +++ b/src/Symfony/Component/VarDumper/Tests/Caster/DoctrineCasterTest.php @@ -36,9 +36,9 @@ public function testCastPersistentCollection() $expected = <<assertSame('NATURAL', $attr['CASE']->class); $this->assertSame('BOTH', $attr['DEFAULT_FETCH_MODE']->class); - $xDump = <<<'EODUMP' + if (\PHP_VERSION_ID >= 80215 && \PHP_VERSION_ID < 80300 || \PHP_VERSION_ID >= 80302) { + $xDump = <<<'EODUMP' +array:2 [ + "\x00~\x00inTransaction" => false + "\x00~\x00attributes" => array:10 [ + "CASE" => NATURAL + "ERRMODE" => EXCEPTION + "PERSISTENT" => false + "DRIVER_NAME" => "sqlite" + "ORACLE_NULLS" => NATURAL + "CLIENT_VERSION" => "%s" + "SERVER_VERSION" => "%s" + "STATEMENT_CLASS" => array:%d [ + 0 => "PDOStatement"%A + ] + "STRINGIFY_FETCHES" => false + "DEFAULT_FETCH_MODE" => BOTH + ] +] +EODUMP; + } else { + $xDump = <<<'EODUMP' array:2 [ "\x00~\x00inTransaction" => false "\x00~\x00attributes" => array:9 [ @@ -61,6 +82,7 @@ public function testCastPdo() ] ] EODUMP; + } $this->assertDumpMatchesFormat($xDump, $cast); } diff --git a/src/Symfony/Component/VarDumper/Tests/Caster/ReflectionCasterTest.php b/src/Symfony/Component/VarDumper/Tests/Caster/ReflectionCasterTest.php index c6d7520c100ba..5581144cd2f76 100644 --- a/src/Symfony/Component/VarDumper/Tests/Caster/ReflectionCasterTest.php +++ b/src/Symfony/Component/VarDumper/Tests/Caster/ReflectionCasterTest.php @@ -647,6 +647,6 @@ public static function stub(): void } } -function reflectionParameterFixture(NotLoadableClass $arg1 = null, $arg2) +function reflectionParameterFixture(?NotLoadableClass $arg1, $arg2) { } diff --git a/src/Symfony/Component/VarDumper/VarDumper.php b/src/Symfony/Component/VarDumper/VarDumper.php index e3ba240c8854d..423ffed266bd7 100644 --- a/src/Symfony/Component/VarDumper/VarDumper.php +++ b/src/Symfony/Component/VarDumper/VarDumper.php @@ -37,7 +37,7 @@ class VarDumper */ private static $handler; - public static function dump(mixed $var, string $label = null): mixed + public static function dump(mixed $var, ?string $label = null): mixed { if (null === self::$handler) { self::register(); @@ -87,7 +87,7 @@ private static function register(): void $dumper = new ContextualizedDumper($dumper, [new SourceContextProvider()]); } - self::$handler = function ($var, string $label = null) use ($cloner, $dumper) { + self::$handler = function ($var, ?string $label = null) use ($cloner, $dumper) { $var = $cloner->cloneVar($var); if (null !== $label) { diff --git a/src/Symfony/Component/VarExporter/Exception/ClassNotFoundException.php b/src/Symfony/Component/VarExporter/Exception/ClassNotFoundException.php index 4cebe44b0fe49..379a76517226b 100644 --- a/src/Symfony/Component/VarExporter/Exception/ClassNotFoundException.php +++ b/src/Symfony/Component/VarExporter/Exception/ClassNotFoundException.php @@ -13,7 +13,7 @@ class ClassNotFoundException extends \Exception implements ExceptionInterface { - public function __construct(string $class, \Throwable $previous = null) + public function __construct(string $class, ?\Throwable $previous = null) { parent::__construct(sprintf('Class "%s" not found.', $class), 0, $previous); } diff --git a/src/Symfony/Component/VarExporter/Exception/NotInstantiableTypeException.php b/src/Symfony/Component/VarExporter/Exception/NotInstantiableTypeException.php index 771ee612dbc37..b9ba225d8469d 100644 --- a/src/Symfony/Component/VarExporter/Exception/NotInstantiableTypeException.php +++ b/src/Symfony/Component/VarExporter/Exception/NotInstantiableTypeException.php @@ -13,7 +13,7 @@ class NotInstantiableTypeException extends \Exception implements ExceptionInterface { - public function __construct(string $type, \Throwable $previous = null) + public function __construct(string $type, ?\Throwable $previous = null) { parent::__construct(sprintf('Type "%s" is not instantiable.', $type), 0, $previous); } diff --git a/src/Symfony/Component/VarExporter/LazyGhostTrait.php b/src/Symfony/Component/VarExporter/LazyGhostTrait.php index 539cf3e59e606..b9b77e73dc203 100644 --- a/src/Symfony/Component/VarExporter/LazyGhostTrait.php +++ b/src/Symfony/Component/VarExporter/LazyGhostTrait.php @@ -31,7 +31,7 @@ trait LazyGhostTrait * that the initializer doesn't initialize, if any * @param static|null $instance */ - public static function createLazyGhost(\Closure $initializer, array $skippedProperties = null, object $instance = null): static + public static function createLazyGhost(\Closure $initializer, ?array $skippedProperties = null, ?object $instance = null): static { if (self::class !== $class = $instance ? $instance::class : static::class) { $skippedProperties["\0".self::class."\0lazyObjectState"] = true; diff --git a/src/Symfony/Component/VarExporter/LazyProxyTrait.php b/src/Symfony/Component/VarExporter/LazyProxyTrait.php index d683ec3f1259d..4dd435bcda633 100644 --- a/src/Symfony/Component/VarExporter/LazyProxyTrait.php +++ b/src/Symfony/Component/VarExporter/LazyProxyTrait.php @@ -27,7 +27,7 @@ trait LazyProxyTrait * @param \Closure():object $initializer Returns the proxied object * @param static|null $instance */ - public static function createLazyProxy(\Closure $initializer, object $instance = null): static + public static function createLazyProxy(\Closure $initializer, ?object $instance = null): static { if (self::class !== $class = $instance ? $instance::class : static::class) { $skippedProperties = ["\0".self::class."\0lazyObjectState" => true]; diff --git a/src/Symfony/Component/VarExporter/ProxyHelper.php b/src/Symfony/Component/VarExporter/ProxyHelper.php index 9cfd7b92b8f65..b6792ad1d38b0 100644 --- a/src/Symfony/Component/VarExporter/ProxyHelper.php +++ b/src/Symfony/Component/VarExporter/ProxyHelper.php @@ -213,7 +213,7 @@ class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectState::class); EOPHP; } - public static function exportSignature(\ReflectionFunctionAbstract $function, bool $withParameterTypes = true, string &$args = null): string + public static function exportSignature(\ReflectionFunctionAbstract $function, bool $withParameterTypes = true, ?string &$args = null): string { $byRefIndex = 0; $args = ''; @@ -270,7 +270,7 @@ public static function exportSignature(\ReflectionFunctionAbstract $function, bo return $signature; } - public static function exportType(\ReflectionFunctionAbstract|\ReflectionProperty|\ReflectionParameter $owner, bool $noBuiltin = false, \ReflectionType $type = null): ?string + public static function exportType(\ReflectionFunctionAbstract|\ReflectionProperty|\ReflectionParameter $owner, bool $noBuiltin = false, ?\ReflectionType $type = null): ?string { if (!$type ??= $owner instanceof \ReflectionFunctionAbstract ? $owner->getReturnType() : $owner->getType()) { return null; diff --git a/src/Symfony/Component/VarExporter/Tests/LazyGhostTraitTest.php b/src/Symfony/Component/VarExporter/Tests/LazyGhostTraitTest.php index dfc98ffcb6b7a..7920ac1f045cb 100644 --- a/src/Symfony/Component/VarExporter/Tests/LazyGhostTraitTest.php +++ b/src/Symfony/Component/VarExporter/Tests/LazyGhostTraitTest.php @@ -247,7 +247,7 @@ public function testReadOnlyClass() * * @return T */ - private function createLazyGhost(string $class, \Closure $initializer, array $skippedProperties = null): object + private function createLazyGhost(string $class, \Closure $initializer, ?array $skippedProperties = null): object { $r = new \ReflectionClass($class); diff --git a/src/Symfony/Component/VarExporter/VarExporter.php b/src/Symfony/Component/VarExporter/VarExporter.php index b5ce3ae9e4633..22e9b51529e24 100644 --- a/src/Symfony/Component/VarExporter/VarExporter.php +++ b/src/Symfony/Component/VarExporter/VarExporter.php @@ -37,7 +37,7 @@ final class VarExporter * * @throws ExceptionInterface When the provided value cannot be serialized */ - public static function export(mixed $value, bool &$isStaticValue = null, array &$foundClasses = []): string + public static function export(mixed $value, ?bool &$isStaticValue = null, array &$foundClasses = []): string { $isStaticValue = true; diff --git a/src/Symfony/Component/WebLink/Link.php b/src/Symfony/Component/WebLink/Link.php index 6e893f0a232fe..5eab61346e925 100644 --- a/src/Symfony/Component/WebLink/Link.php +++ b/src/Symfony/Component/WebLink/Link.php @@ -153,7 +153,7 @@ class Link implements EvolvableLinkInterface */ private array $attributes = []; - public function __construct(string $rel = null, string $href = '') + public function __construct(?string $rel = null, string $href = '') { if (null !== $rel) { $this->rel[$rel] = $rel; diff --git a/src/Symfony/Component/Webhook/Exception/RejectWebhookException.php b/src/Symfony/Component/Webhook/Exception/RejectWebhookException.php index 22c28f6782723..74b30b30925ba 100644 --- a/src/Symfony/Component/Webhook/Exception/RejectWebhookException.php +++ b/src/Symfony/Component/Webhook/Exception/RejectWebhookException.php @@ -18,7 +18,7 @@ */ class RejectWebhookException extends HttpException { - public function __construct(int $statusCode = 406, string $message = '', \Throwable $previous = null, array $headers = [], int $code = 0) + public function __construct(int $statusCode = 406, string $message = '', ?\Throwable $previous = null, array $headers = [], int $code = 0) { parent::__construct($statusCode, $message, $previous, $headers, $code); } diff --git a/src/Symfony/Component/Workflow/Attribute/AsAnnounceListener.php b/src/Symfony/Component/Workflow/Attribute/AsAnnounceListener.php index 7881fc759321f..8afa4ca646c92 100644 --- a/src/Symfony/Component/Workflow/Attribute/AsAnnounceListener.php +++ b/src/Symfony/Component/Workflow/Attribute/AsAnnounceListener.php @@ -31,11 +31,11 @@ final class AsAnnounceListener extends AsEventListener * @param string|null $dispatcher The service id of the event dispatcher to listen to */ public function __construct( - string $workflow = null, - string $transition = null, - string $method = null, + ?string $workflow = null, + ?string $transition = null, + ?string $method = null, int $priority = 0, - string $dispatcher = null, + ?string $dispatcher = null, ) { parent::__construct($this->buildEventName('announce', 'transition', $workflow, $transition), $method, $priority, $dispatcher); } diff --git a/src/Symfony/Component/Workflow/Attribute/AsCompletedListener.php b/src/Symfony/Component/Workflow/Attribute/AsCompletedListener.php index dd0f024f87a30..82bfe9db75c24 100644 --- a/src/Symfony/Component/Workflow/Attribute/AsCompletedListener.php +++ b/src/Symfony/Component/Workflow/Attribute/AsCompletedListener.php @@ -31,11 +31,11 @@ final class AsCompletedListener extends AsEventListener * @param string|null $dispatcher The service id of the event dispatcher to listen to */ public function __construct( - string $workflow = null, - string $transition = null, - string $method = null, + ?string $workflow = null, + ?string $transition = null, + ?string $method = null, int $priority = 0, - string $dispatcher = null, + ?string $dispatcher = null, ) { parent::__construct($this->buildEventName('completed', 'transition', $workflow, $transition), $method, $priority, $dispatcher); } diff --git a/src/Symfony/Component/Workflow/Attribute/AsEnterListener.php b/src/Symfony/Component/Workflow/Attribute/AsEnterListener.php index 0897f7d89254e..97e791720b924 100644 --- a/src/Symfony/Component/Workflow/Attribute/AsEnterListener.php +++ b/src/Symfony/Component/Workflow/Attribute/AsEnterListener.php @@ -31,11 +31,11 @@ final class AsEnterListener extends AsEventListener * @param string|null $dispatcher The service id of the event dispatcher to listen to */ public function __construct( - string $workflow = null, - string $place = null, - string $method = null, + ?string $workflow = null, + ?string $place = null, + ?string $method = null, int $priority = 0, - string $dispatcher = null, + ?string $dispatcher = null, ) { parent::__construct($this->buildEventName('enter', 'place', $workflow, $place), $method, $priority, $dispatcher); } diff --git a/src/Symfony/Component/Workflow/Attribute/AsEnteredListener.php b/src/Symfony/Component/Workflow/Attribute/AsEnteredListener.php index 8ab6a946eed7a..0824628f54bcd 100644 --- a/src/Symfony/Component/Workflow/Attribute/AsEnteredListener.php +++ b/src/Symfony/Component/Workflow/Attribute/AsEnteredListener.php @@ -31,11 +31,11 @@ final class AsEnteredListener extends AsEventListener * @param string|null $dispatcher The service id of the event dispatcher to listen to */ public function __construct( - string $workflow = null, - string $place = null, - string $method = null, + ?string $workflow = null, + ?string $place = null, + ?string $method = null, int $priority = 0, - string $dispatcher = null, + ?string $dispatcher = null, ) { parent::__construct($this->buildEventName('entered', 'place', $workflow, $place), $method, $priority, $dispatcher); } diff --git a/src/Symfony/Component/Workflow/Attribute/AsGuardListener.php b/src/Symfony/Component/Workflow/Attribute/AsGuardListener.php index f9c17f453c3b9..e2e783fe5bc2f 100644 --- a/src/Symfony/Component/Workflow/Attribute/AsGuardListener.php +++ b/src/Symfony/Component/Workflow/Attribute/AsGuardListener.php @@ -31,11 +31,11 @@ final class AsGuardListener extends AsEventListener * @param string|null $dispatcher The service id of the event dispatcher to listen to */ public function __construct( - string $workflow = null, - string $transition = null, - string $method = null, + ?string $workflow = null, + ?string $transition = null, + ?string $method = null, int $priority = 0, - string $dispatcher = null, + ?string $dispatcher = null, ) { parent::__construct($this->buildEventName('guard', 'transition', $workflow, $transition), $method, $priority, $dispatcher); } diff --git a/src/Symfony/Component/Workflow/Attribute/AsLeaveListener.php b/src/Symfony/Component/Workflow/Attribute/AsLeaveListener.php index 2e68da10b12ae..3ef6b4d5ccdb8 100644 --- a/src/Symfony/Component/Workflow/Attribute/AsLeaveListener.php +++ b/src/Symfony/Component/Workflow/Attribute/AsLeaveListener.php @@ -31,11 +31,11 @@ final class AsLeaveListener extends AsEventListener * @param string|null $dispatcher The service id of the event dispatcher to listen to */ public function __construct( - string $workflow = null, - string $place = null, - string $method = null, + ?string $workflow = null, + ?string $place = null, + ?string $method = null, int $priority = 0, - string $dispatcher = null, + ?string $dispatcher = null, ) { parent::__construct($this->buildEventName('leave', 'place', $workflow, $place), $method, $priority, $dispatcher); } diff --git a/src/Symfony/Component/Workflow/Attribute/AsTransitionListener.php b/src/Symfony/Component/Workflow/Attribute/AsTransitionListener.php index f40131284b213..dc49749968384 100644 --- a/src/Symfony/Component/Workflow/Attribute/AsTransitionListener.php +++ b/src/Symfony/Component/Workflow/Attribute/AsTransitionListener.php @@ -31,11 +31,11 @@ final class AsTransitionListener extends AsEventListener * @param string|null $dispatcher The service id of the event dispatcher to listen to */ public function __construct( - string $workflow = null, - string $transition = null, - string $method = null, + ?string $workflow = null, + ?string $transition = null, + ?string $method = null, int $priority = 0, - string $dispatcher = null, + ?string $dispatcher = null, ) { parent::__construct($this->buildEventName('transition', 'transition', $workflow, $transition), $method, $priority, $dispatcher); } diff --git a/src/Symfony/Component/Workflow/Attribute/BuildEventNameTrait.php b/src/Symfony/Component/Workflow/Attribute/BuildEventNameTrait.php index 0ca7a09fed1a7..93eeee70c95a0 100644 --- a/src/Symfony/Component/Workflow/Attribute/BuildEventNameTrait.php +++ b/src/Symfony/Component/Workflow/Attribute/BuildEventNameTrait.php @@ -20,7 +20,7 @@ */ trait BuildEventNameTrait { - private static function buildEventName(string $keyword, string $argument, string $workflow = null, string $node = null): string + private static function buildEventName(string $keyword, string $argument, ?string $workflow = null, ?string $node = null): string { if (null === $workflow) { if (null !== $node) { diff --git a/src/Symfony/Component/Workflow/DataCollector/WorkflowDataCollector.php b/src/Symfony/Component/Workflow/DataCollector/WorkflowDataCollector.php index 821b071222fe3..ef09b98522762 100644 --- a/src/Symfony/Component/Workflow/DataCollector/WorkflowDataCollector.php +++ b/src/Symfony/Component/Workflow/DataCollector/WorkflowDataCollector.php @@ -40,7 +40,7 @@ public function __construct( ) { } - public function collect(Request $request, Response $response, \Throwable $exception = null): void + public function collect(Request $request, Response $response, ?\Throwable $exception = null): void { } @@ -165,7 +165,7 @@ private function getEventListeners(WorkflowInterface $workflow): array return $listeners; } - private function summarizeListener(callable $callable, string $eventName = null, Transition $transition = null): array + private function summarizeListener(callable $callable, ?string $eventName = null, ?Transition $transition = null): array { $extra = []; diff --git a/src/Symfony/Component/Workflow/Definition.php b/src/Symfony/Component/Workflow/Definition.php index 91172dcebce82..58456bbf14eed 100644 --- a/src/Symfony/Component/Workflow/Definition.php +++ b/src/Symfony/Component/Workflow/Definition.php @@ -32,7 +32,7 @@ final class Definition * @param Transition[] $transitions * @param string|string[]|null $initialPlaces */ - public function __construct(array $places, array $transitions, string|array $initialPlaces = null, MetadataStoreInterface $metadataStore = null) + public function __construct(array $places, array $transitions, string|array|null $initialPlaces = null, ?MetadataStoreInterface $metadataStore = null) { foreach ($places as $place) { $this->addPlace($place); diff --git a/src/Symfony/Component/Workflow/Dumper/DumperInterface.php b/src/Symfony/Component/Workflow/Dumper/DumperInterface.php index 49ddd4dc36c29..b39e0e9ae4eee 100644 --- a/src/Symfony/Component/Workflow/Dumper/DumperInterface.php +++ b/src/Symfony/Component/Workflow/Dumper/DumperInterface.php @@ -25,5 +25,5 @@ interface DumperInterface /** * Dumps a workflow definition. */ - public function dump(Definition $definition, Marking $marking = null, array $options = []): string; + public function dump(Definition $definition, ?Marking $marking = null, array $options = []): string; } diff --git a/src/Symfony/Component/Workflow/Dumper/GraphvizDumper.php b/src/Symfony/Component/Workflow/Dumper/GraphvizDumper.php index 6a36ad8cb8eec..7e6ffa4839fb1 100644 --- a/src/Symfony/Component/Workflow/Dumper/GraphvizDumper.php +++ b/src/Symfony/Component/Workflow/Dumper/GraphvizDumper.php @@ -42,7 +42,7 @@ class GraphvizDumper implements DumperInterface * * node: The default options for nodes (places + transitions) * * edge: The default options for edges */ - public function dump(Definition $definition, Marking $marking = null, array $options = []): string + public function dump(Definition $definition, ?Marking $marking = null, array $options = []): string { $withMetadata = $options['with-metadata'] ?? false; @@ -64,7 +64,7 @@ public function dump(Definition $definition, Marking $marking = null, array $opt /** * @internal */ - protected function findPlaces(Definition $definition, bool $withMetadata, Marking $marking = null): array + protected function findPlaces(Definition $definition, bool $withMetadata, ?Marking $marking = null): array { $workflowMetadata = $definition->getMetadataStore(); diff --git a/src/Symfony/Component/Workflow/Dumper/MermaidDumper.php b/src/Symfony/Component/Workflow/Dumper/MermaidDumper.php index 53436a1eca181..ed67c6f873549 100644 --- a/src/Symfony/Component/Workflow/Dumper/MermaidDumper.php +++ b/src/Symfony/Component/Workflow/Dumper/MermaidDumper.php @@ -57,7 +57,7 @@ public function __construct(string $transitionType, string $direction = self::DI $this->transitionType = $transitionType; } - public function dump(Definition $definition, Marking $marking = null, array $options = []): string + public function dump(Definition $definition, ?Marking $marking = null, array $options = []): string { $this->linkCount = 0; $placeNameMap = []; diff --git a/src/Symfony/Component/Workflow/Dumper/PlantUmlDumper.php b/src/Symfony/Component/Workflow/Dumper/PlantUmlDumper.php index ad8cdac6b5057..2a232d4f22637 100644 --- a/src/Symfony/Component/Workflow/Dumper/PlantUmlDumper.php +++ b/src/Symfony/Component/Workflow/Dumper/PlantUmlDumper.php @@ -61,7 +61,7 @@ public function __construct(string $transitionType) $this->transitionType = $transitionType; } - public function dump(Definition $definition, Marking $marking = null, array $options = []): string + public function dump(Definition $definition, ?Marking $marking = null, array $options = []): string { $options = array_replace_recursive(self::DEFAULT_OPTIONS, $options); @@ -191,7 +191,7 @@ private function escape(string $string): string return '"'.str_replace('"', '', $string).'"'; } - private function getState(string $place, Definition $definition, Marking $marking = null): string + private function getState(string $place, Definition $definition, ?Marking $marking = null): string { $workflowMetadata = $definition->getMetadataStore(); diff --git a/src/Symfony/Component/Workflow/Dumper/StateMachineGraphvizDumper.php b/src/Symfony/Component/Workflow/Dumper/StateMachineGraphvizDumper.php index a7fda868f7af6..e054cb468e748 100644 --- a/src/Symfony/Component/Workflow/Dumper/StateMachineGraphvizDumper.php +++ b/src/Symfony/Component/Workflow/Dumper/StateMachineGraphvizDumper.php @@ -25,7 +25,7 @@ class StateMachineGraphvizDumper extends GraphvizDumper * * node: The default options for nodes (places) * * edge: The default options for edges */ - public function dump(Definition $definition, Marking $marking = null, array $options = []): string + public function dump(Definition $definition, ?Marking $marking = null, array $options = []): string { $withMetadata = $options['with-metadata'] ?? false; diff --git a/src/Symfony/Component/Workflow/Event/AnnounceEvent.php b/src/Symfony/Component/Workflow/Event/AnnounceEvent.php index ff0cfe59ac44f..0c675a4854dcd 100644 --- a/src/Symfony/Component/Workflow/Event/AnnounceEvent.php +++ b/src/Symfony/Component/Workflow/Event/AnnounceEvent.php @@ -19,7 +19,7 @@ final class AnnounceEvent extends Event { use HasContextTrait; - public function __construct(object $subject, Marking $marking, Transition $transition = null, WorkflowInterface $workflow = null, array $context = []) + public function __construct(object $subject, Marking $marking, ?Transition $transition = null, ?WorkflowInterface $workflow = null, array $context = []) { parent::__construct($subject, $marking, $transition, $workflow); diff --git a/src/Symfony/Component/Workflow/Event/CompletedEvent.php b/src/Symfony/Component/Workflow/Event/CompletedEvent.php index 9643d42fd2dd1..63a5e44836b2c 100644 --- a/src/Symfony/Component/Workflow/Event/CompletedEvent.php +++ b/src/Symfony/Component/Workflow/Event/CompletedEvent.php @@ -19,7 +19,7 @@ final class CompletedEvent extends Event { use HasContextTrait; - public function __construct(object $subject, Marking $marking, Transition $transition = null, WorkflowInterface $workflow = null, array $context = []) + public function __construct(object $subject, Marking $marking, ?Transition $transition = null, ?WorkflowInterface $workflow = null, array $context = []) { parent::__construct($subject, $marking, $transition, $workflow); diff --git a/src/Symfony/Component/Workflow/Event/EnterEvent.php b/src/Symfony/Component/Workflow/Event/EnterEvent.php index 3a64cfa391038..46213d48ff466 100644 --- a/src/Symfony/Component/Workflow/Event/EnterEvent.php +++ b/src/Symfony/Component/Workflow/Event/EnterEvent.php @@ -19,7 +19,7 @@ final class EnterEvent extends Event { use HasContextTrait; - public function __construct(object $subject, Marking $marking, Transition $transition = null, WorkflowInterface $workflow = null, array $context = []) + public function __construct(object $subject, Marking $marking, ?Transition $transition = null, ?WorkflowInterface $workflow = null, array $context = []) { parent::__construct($subject, $marking, $transition, $workflow); diff --git a/src/Symfony/Component/Workflow/Event/EnteredEvent.php b/src/Symfony/Component/Workflow/Event/EnteredEvent.php index 041324287e054..17529b8a4d801 100644 --- a/src/Symfony/Component/Workflow/Event/EnteredEvent.php +++ b/src/Symfony/Component/Workflow/Event/EnteredEvent.php @@ -19,7 +19,7 @@ final class EnteredEvent extends Event { use HasContextTrait; - public function __construct(object $subject, Marking $marking, Transition $transition = null, WorkflowInterface $workflow = null, array $context = []) + public function __construct(object $subject, Marking $marking, ?Transition $transition = null, ?WorkflowInterface $workflow = null, array $context = []) { parent::__construct($subject, $marking, $transition, $workflow); diff --git a/src/Symfony/Component/Workflow/Event/Event.php b/src/Symfony/Component/Workflow/Event/Event.php index 66eada47b6ecb..1efbc7956546c 100644 --- a/src/Symfony/Component/Workflow/Event/Event.php +++ b/src/Symfony/Component/Workflow/Event/Event.php @@ -28,7 +28,7 @@ class Event extends BaseEvent private ?Transition $transition; private ?WorkflowInterface $workflow; - public function __construct(object $subject, Marking $marking, Transition $transition = null, WorkflowInterface $workflow = null) + public function __construct(object $subject, Marking $marking, ?Transition $transition = null, ?WorkflowInterface $workflow = null) { $this->subject = $subject; $this->marking = $marking; diff --git a/src/Symfony/Component/Workflow/Event/GuardEvent.php b/src/Symfony/Component/Workflow/Event/GuardEvent.php index 9409da2059664..68a57a979b503 100644 --- a/src/Symfony/Component/Workflow/Event/GuardEvent.php +++ b/src/Symfony/Component/Workflow/Event/GuardEvent.php @@ -25,7 +25,7 @@ final class GuardEvent extends Event { private TransitionBlockerList $transitionBlockerList; - public function __construct(object $subject, Marking $marking, Transition $transition, WorkflowInterface $workflow = null) + public function __construct(object $subject, Marking $marking, Transition $transition, ?WorkflowInterface $workflow = null) { parent::__construct($subject, $marking, $transition, $workflow); @@ -42,7 +42,7 @@ public function isBlocked(): bool return !$this->transitionBlockerList->isEmpty(); } - public function setBlocked(bool $blocked, string $message = null): void + public function setBlocked(bool $blocked, ?string $message = null): void { if (!$blocked) { $this->transitionBlockerList->clear(); diff --git a/src/Symfony/Component/Workflow/Event/LeaveEvent.php b/src/Symfony/Component/Workflow/Event/LeaveEvent.php index a50d7b3a0de6f..ca7ff1aa1af0a 100644 --- a/src/Symfony/Component/Workflow/Event/LeaveEvent.php +++ b/src/Symfony/Component/Workflow/Event/LeaveEvent.php @@ -19,7 +19,7 @@ final class LeaveEvent extends Event { use HasContextTrait; - public function __construct(object $subject, Marking $marking, Transition $transition = null, WorkflowInterface $workflow = null, array $context = []) + public function __construct(object $subject, Marking $marking, ?Transition $transition = null, ?WorkflowInterface $workflow = null, array $context = []) { parent::__construct($subject, $marking, $transition, $workflow); diff --git a/src/Symfony/Component/Workflow/Event/TransitionEvent.php b/src/Symfony/Component/Workflow/Event/TransitionEvent.php index e9a82a042c440..6bae1595c86f3 100644 --- a/src/Symfony/Component/Workflow/Event/TransitionEvent.php +++ b/src/Symfony/Component/Workflow/Event/TransitionEvent.php @@ -19,7 +19,7 @@ final class TransitionEvent extends Event { use HasContextTrait; - public function __construct(object $subject, Marking $marking, Transition $transition = null, WorkflowInterface $workflow = null, array $context = []) + public function __construct(object $subject, Marking $marking, ?Transition $transition = null, ?WorkflowInterface $workflow = null, array $context = []) { parent::__construct($subject, $marking, $transition, $workflow); diff --git a/src/Symfony/Component/Workflow/EventListener/GuardListener.php b/src/Symfony/Component/Workflow/EventListener/GuardListener.php index 1c30ac115fdd2..6972a896d14ce 100644 --- a/src/Symfony/Component/Workflow/EventListener/GuardListener.php +++ b/src/Symfony/Component/Workflow/EventListener/GuardListener.php @@ -32,7 +32,7 @@ class GuardListener private ?RoleHierarchyInterface $roleHierarchy; private ?ValidatorInterface $validator; - public function __construct(array $configuration, ExpressionLanguage $expressionLanguage, TokenStorageInterface $tokenStorage, AuthorizationCheckerInterface $authorizationChecker, AuthenticationTrustResolverInterface $trustResolver, RoleHierarchyInterface $roleHierarchy = null, ValidatorInterface $validator = null) + public function __construct(array $configuration, ExpressionLanguage $expressionLanguage, TokenStorageInterface $tokenStorage, AuthorizationCheckerInterface $authorizationChecker, AuthenticationTrustResolverInterface $trustResolver, ?RoleHierarchyInterface $roleHierarchy = null, ?ValidatorInterface $validator = null) { $this->configuration = $configuration; $this->expressionLanguage = $expressionLanguage; diff --git a/src/Symfony/Component/Workflow/Metadata/GetMetadataTrait.php b/src/Symfony/Component/Workflow/Metadata/GetMetadataTrait.php index b3b2f2996e2b6..83b57f786ec48 100644 --- a/src/Symfony/Component/Workflow/Metadata/GetMetadataTrait.php +++ b/src/Symfony/Component/Workflow/Metadata/GetMetadataTrait.php @@ -18,7 +18,7 @@ */ trait GetMetadataTrait { - public function getMetadata(string $key, string|Transition $subject = null): mixed + public function getMetadata(string $key, string|Transition|null $subject = null): mixed { if (null === $subject) { return $this->getWorkflowMetadata()[$key] ?? null; diff --git a/src/Symfony/Component/Workflow/Metadata/InMemoryMetadataStore.php b/src/Symfony/Component/Workflow/Metadata/InMemoryMetadataStore.php index d78b046651352..d13f9564df71f 100644 --- a/src/Symfony/Component/Workflow/Metadata/InMemoryMetadataStore.php +++ b/src/Symfony/Component/Workflow/Metadata/InMemoryMetadataStore.php @@ -27,7 +27,7 @@ final class InMemoryMetadataStore implements MetadataStoreInterface /** * @param \SplObjectStorage|null $transitionsMetadata */ - public function __construct(array $workflowMetadata = [], array $placesMetadata = [], \SplObjectStorage $transitionsMetadata = null) + public function __construct(array $workflowMetadata = [], array $placesMetadata = [], ?\SplObjectStorage $transitionsMetadata = null) { $this->workflowMetadata = $workflowMetadata; $this->placesMetadata = $placesMetadata; diff --git a/src/Symfony/Component/Workflow/Metadata/MetadataStoreInterface.php b/src/Symfony/Component/Workflow/Metadata/MetadataStoreInterface.php index ff1b31e9c00ac..e8f6b21d874cc 100644 --- a/src/Symfony/Component/Workflow/Metadata/MetadataStoreInterface.php +++ b/src/Symfony/Component/Workflow/Metadata/MetadataStoreInterface.php @@ -35,5 +35,5 @@ public function getTransitionMetadata(Transition $transition): array; * Use a string (the place name) to get place metadata * Use a Transition instance to get transition metadata */ - public function getMetadata(string $key, string|Transition $subject = null): mixed; + public function getMetadata(string $key, string|Transition|null $subject = null): mixed; } diff --git a/src/Symfony/Component/Workflow/Registry.php b/src/Symfony/Component/Workflow/Registry.php index bd024263fdabc..787bc21f91183 100644 --- a/src/Symfony/Component/Workflow/Registry.php +++ b/src/Symfony/Component/Workflow/Registry.php @@ -27,7 +27,7 @@ public function addWorkflow(WorkflowInterface $workflow, WorkflowSupportStrategy $this->workflows[] = [$workflow, $supportStrategy]; } - public function has(object $subject, string $workflowName = null): bool + public function has(object $subject, ?string $workflowName = null): bool { foreach ($this->workflows as [$workflow, $supportStrategy]) { if ($this->supports($workflow, $supportStrategy, $subject, $workflowName)) { @@ -38,7 +38,7 @@ public function has(object $subject, string $workflowName = null): bool return false; } - public function get(object $subject, string $workflowName = null): WorkflowInterface + public function get(object $subject, ?string $workflowName = null): WorkflowInterface { $matched = []; diff --git a/src/Symfony/Component/Workflow/StateMachine.php b/src/Symfony/Component/Workflow/StateMachine.php index 8fb4d3b8ff57e..0946307af3308 100644 --- a/src/Symfony/Component/Workflow/StateMachine.php +++ b/src/Symfony/Component/Workflow/StateMachine.php @@ -20,7 +20,7 @@ */ class StateMachine extends Workflow { - public function __construct(Definition $definition, MarkingStoreInterface $markingStore = null, EventDispatcherInterface $dispatcher = null, string $name = 'unnamed', array $eventsToDispatch = null) + public function __construct(Definition $definition, ?MarkingStoreInterface $markingStore = null, ?EventDispatcherInterface $dispatcher = null, string $name = 'unnamed', ?array $eventsToDispatch = null) { parent::__construct($definition, $markingStore ?? new MethodMarkingStore(true), $dispatcher, $name, $eventsToDispatch); } diff --git a/src/Symfony/Component/Workflow/Tests/Attribute/AsListenerTest.php b/src/Symfony/Component/Workflow/Tests/Attribute/AsListenerTest.php index 78de4e0d6d638..a85862624c2f5 100644 --- a/src/Symfony/Component/Workflow/Tests/Attribute/AsListenerTest.php +++ b/src/Symfony/Component/Workflow/Tests/Attribute/AsListenerTest.php @@ -20,7 +20,7 @@ class AsListenerTest extends TestCase /** * @dataProvider provideOkTests */ - public function testOk(string $class, string $expectedEvent, string $workflow = null, string $node = null) + public function testOk(string $class, string $expectedEvent, ?string $workflow = null, ?string $node = null) { $attribute = new $class($workflow, $node); diff --git a/src/Symfony/Component/Workflow/Tests/EventListener/GuardListenerTest.php b/src/Symfony/Component/Workflow/Tests/EventListener/GuardListenerTest.php index 776a3ee8470a5..9880b8550b9c7 100644 --- a/src/Symfony/Component/Workflow/Tests/EventListener/GuardListenerTest.php +++ b/src/Symfony/Component/Workflow/Tests/EventListener/GuardListenerTest.php @@ -137,7 +137,7 @@ public function testGuardExpressionBlocks() $this->assertTrue($event->isBlocked()); } - private function createEvent(Transition $transition = null): GuardEvent + private function createEvent(?Transition $transition = null): GuardEvent { $subject = new Subject(); $transition ??= new Transition('name', 'from', 'to'); diff --git a/src/Symfony/Component/Workflow/Tests/WorkflowTest.php b/src/Symfony/Component/Workflow/Tests/WorkflowTest.php index f7cb3c7f61a71..8e112df60dce5 100644 --- a/src/Symfony/Component/Workflow/Tests/WorkflowTest.php +++ b/src/Symfony/Component/Workflow/Tests/WorkflowTest.php @@ -782,7 +782,7 @@ class EventDispatcherMock implements \Symfony\Contracts\EventDispatcher\EventDis { public array $dispatchedEvents = []; - public function dispatch($event, string $eventName = null): object + public function dispatch($event, ?string $eventName = null): object { $this->dispatchedEvents[] = $eventName; diff --git a/src/Symfony/Component/Workflow/TransitionBlocker.php b/src/Symfony/Component/Workflow/TransitionBlocker.php index 59a1adefc8437..4864598fffd92 100644 --- a/src/Symfony/Component/Workflow/TransitionBlocker.php +++ b/src/Symfony/Component/Workflow/TransitionBlocker.php @@ -67,7 +67,7 @@ public static function createBlockedByExpressionGuardListener(string $expression * Creates a blocker that says the transition cannot be made because of an * unknown reason. */ - public static function createUnknown(string $message = null, int $backtraceFrame = 2): self + public static function createUnknown(?string $message = null, int $backtraceFrame = 2): self { if (null !== $message) { return new static($message, self::UNKNOWN); diff --git a/src/Symfony/Component/Workflow/Workflow.php b/src/Symfony/Component/Workflow/Workflow.php index 6ab02dbb0ff16..cfad75fb0389a 100644 --- a/src/Symfony/Component/Workflow/Workflow.php +++ b/src/Symfony/Component/Workflow/Workflow.php @@ -67,7 +67,7 @@ class Workflow implements WorkflowInterface */ private ?array $eventsToDispatch = null; - public function __construct(Definition $definition, MarkingStoreInterface $markingStore = null, EventDispatcherInterface $dispatcher = null, string $name = 'unnamed', array $eventsToDispatch = null) + public function __construct(Definition $definition, ?MarkingStoreInterface $markingStore = null, ?EventDispatcherInterface $dispatcher = null, string $name = 'unnamed', ?array $eventsToDispatch = null) { $this->definition = $definition; $this->markingStore = $markingStore ?? new MethodMarkingStore(); diff --git a/src/Symfony/Component/Yaml/CHANGELOG.md b/src/Symfony/Component/Yaml/CHANGELOG.md index 4342bb3cd490c..74b0a71466611 100644 --- a/src/Symfony/Component/Yaml/CHANGELOG.md +++ b/src/Symfony/Component/Yaml/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +7.1 +--- + + * Add support for getting all the enum cases with `!php/enum Foo` + 7.0 --- diff --git a/src/Symfony/Component/Yaml/Command/LintCommand.php b/src/Symfony/Component/Yaml/Command/LintCommand.php index d9a09092a7abc..75c09f51edf03 100644 --- a/src/Symfony/Component/Yaml/Command/LintCommand.php +++ b/src/Symfony/Component/Yaml/Command/LintCommand.php @@ -42,7 +42,7 @@ class LintCommand extends Command private ?\Closure $directoryIteratorProvider; private ?\Closure $isReadableProvider; - public function __construct(string $name = null, callable $directoryIteratorProvider = null, callable $isReadableProvider = null) + public function __construct(?string $name = null, ?callable $directoryIteratorProvider = null, ?callable $isReadableProvider = null) { parent::__construct($name); @@ -124,7 +124,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int return $this->display($io, $filesInfo); } - private function validate(string $content, int $flags, string $file = null): array + private function validate(string $content, int $flags, ?string $file = null): array { $prevErrorHandler = set_error_handler(function ($level, $message, $file, $line) use (&$prevErrorHandler) { if (\E_USER_DEPRECATED === $level) { diff --git a/src/Symfony/Component/Yaml/Exception/ParseException.php b/src/Symfony/Component/Yaml/Exception/ParseException.php index cb77f4313624d..246478f89488e 100644 --- a/src/Symfony/Component/Yaml/Exception/ParseException.php +++ b/src/Symfony/Component/Yaml/Exception/ParseException.php @@ -29,7 +29,7 @@ class ParseException extends RuntimeException * @param string|null $snippet The snippet of code near the problem * @param string|null $parsedFile The file name where the error occurred */ - public function __construct(string $message, int $parsedLine = -1, string $snippet = null, string $parsedFile = null, \Throwable $previous = null) + public function __construct(string $message, int $parsedLine = -1, ?string $snippet = null, ?string $parsedFile = null, ?\Throwable $previous = null) { $this->parsedFile = $parsedFile; $this->parsedLine = $parsedLine; diff --git a/src/Symfony/Component/Yaml/Inline.php b/src/Symfony/Component/Yaml/Inline.php index 382fa51c24a73..77aa98ff3a28f 100644 --- a/src/Symfony/Component/Yaml/Inline.php +++ b/src/Symfony/Component/Yaml/Inline.php @@ -34,7 +34,7 @@ class Inline private static bool $objectForMap = false; private static bool $constantSupport = false; - public static function initialize(int $flags, int $parsedLineNumber = null, string $parsedFilename = null): void + public static function initialize(int $flags, ?int $parsedLineNumber = null, ?string $parsedFilename = null): void { self::$exceptionOnInvalidType = (bool) (Yaml::PARSE_EXCEPTION_ON_INVALID_TYPE & $flags); self::$objectSupport = (bool) (Yaml::PARSE_OBJECT & $flags); @@ -116,7 +116,7 @@ public static function dump(mixed $value, int $flags = 0): string default => 'Y-m-d\TH:i:s.uP', }); case $value instanceof \UnitEnum: - return sprintf('!php/const %s::%s', $value::class, $value->name); + return sprintf('!php/enum %s::%s', $value::class, $value->name); case \is_object($value): if ($value instanceof TaggedValue) { return '!'.$value->getTag().' '.self::dump($value->getValue(), $flags); @@ -267,7 +267,7 @@ private static function dumpNull(int $flags): string * * @throws ParseException When malformed inline YAML string is parsed */ - public static function parseScalar(string $scalar, int $flags = 0, array $delimiters = null, int &$i = 0, bool $evaluate = true, array &$references = [], bool &$isQuoted = null): mixed + public static function parseScalar(string $scalar, int $flags = 0, ?array $delimiters = null, int &$i = 0, bool $evaluate = true, array &$references = [], ?bool &$isQuoted = null): mixed { if (\in_array($scalar[$i], ['"', "'"], true)) { // quoted scalar @@ -556,7 +556,7 @@ private static function parseMapping(string $mapping, int $flags, int &$i = 0, a * * @throws ParseException when object parsing support was disabled and the parser detected a PHP object or when a reference could not be resolved */ - private static function evaluateScalar(string $scalar, int $flags, array &$references = [], bool &$isQuotedString = null): mixed + private static function evaluateScalar(string $scalar, int $flags, array &$references = [], ?bool &$isQuotedString = null): mixed { $isQuotedString = false; $scalar = trim($scalar); @@ -643,24 +643,31 @@ private static function evaluateScalar(string $scalar, int $flags, array &$refer } $i = 0; - $enum = self::parseScalar(substr($scalar, 10), 0, null, $i, false); - if ($useValue = str_ends_with($enum, '->value')) { - $enum = substr($enum, 0, -7); - } - if (!\defined($enum)) { + $enumName = self::parseScalar(substr($scalar, 10), 0, null, $i, false); + $useName = str_contains($enumName, '::'); + $enum = $useName ? strstr($enumName, '::', true) : $enumName; + + if (!enum_exists($enum)) { throw new ParseException(sprintf('The enum "%s" is not defined.', $enum), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); } + if (!$useName) { + return $enum::cases(); + } + if ($useValue = str_ends_with($enumName, '->value')) { + $enumName = substr($enumName, 0, -7); + } - $value = \constant($enum); - - if (!$value instanceof \UnitEnum) { - throw new ParseException(sprintf('The string "%s" is not the name of a valid enum.', $enum), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); + if (!\defined($enumName)) { + throw new ParseException(sprintf('The string "%s" is not the name of a valid enum.', $enumName), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); } + + $value = \constant($enumName); + if (!$useValue) { return $value; } if (!$value instanceof \BackedEnum) { - throw new ParseException(sprintf('The enum "%s" defines no value next to its name.', $enum), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); + throw new ParseException(sprintf('The enum "%s" defines no value next to its name.', $enumName), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); } return $value->value; diff --git a/src/Symfony/Component/Yaml/Parser.php b/src/Symfony/Component/Yaml/Parser.php index 14e8c089d9fb6..1109f8568dd52 100644 --- a/src/Symfony/Component/Yaml/Parser.php +++ b/src/Symfony/Component/Yaml/Parser.php @@ -556,7 +556,7 @@ private function getCurrentLineIndentation(): int * * @throws ParseException When indentation problem are detected */ - private function getNextEmbedBlock(int $indentation = null, bool $inSequence = false): string + private function getNextEmbedBlock(?int $indentation = null, bool $inSequence = false): string { $oldLineIndentation = $this->getCurrentLineIndentation(); @@ -1038,7 +1038,7 @@ private function isStringUnIndentedCollectionItem(): bool * * @internal */ - public static function preg_match(string $pattern, string $subject, array &$matches = null, int $flags = 0, int $offset = 0): int + public static function preg_match(string $pattern, string $subject, ?array &$matches = null, int $flags = 0, int $offset = 0): int { if (false === $ret = preg_match($pattern, $subject, $matches, $flags, $offset)) { throw new ParseException(preg_last_error_msg()); diff --git a/src/Symfony/Component/Yaml/Tests/InlineTest.php b/src/Symfony/Component/Yaml/Tests/InlineTest.php index 36b93e967da48..2485cda753469 100644 --- a/src/Symfony/Component/Yaml/Tests/InlineTest.php +++ b/src/Symfony/Component/Yaml/Tests/InlineTest.php @@ -76,14 +76,21 @@ public function testParsePhpConstantThrowsExceptionWhenUndefined() public function testParsePhpEnumThrowsExceptionWhenUndefined() { $this->expectException(ParseException::class); - $this->expectExceptionMessage('The enum "SomeEnum::Foo" is not defined'); - Inline::parse('!php/enum SomeEnum::Foo', Yaml::PARSE_CONSTANT); + $this->expectExceptionMessage('The enum "SomeEnum" is not defined'); + Inline::parse('!php/enum SomeEnum', Yaml::PARSE_CONSTANT); + } + + public function testParsePhpEnumThrowsExceptionWhenNameUndefined() + { + $this->expectException(ParseException::class); + $this->expectExceptionMessage('The string "Symfony\Component\Yaml\Tests\Fixtures\FooUnitEnum::Foo" is not the name of a valid enum'); + Inline::parse('!php/enum Symfony\Component\Yaml\Tests\Fixtures\FooUnitEnum::Foo', Yaml::PARSE_CONSTANT); } public function testParsePhpEnumThrowsExceptionWhenNotAnEnum() { $this->expectException(ParseException::class); - $this->expectExceptionMessage('The string "PHP_INT_MAX" is not the name of a valid enum'); + $this->expectExceptionMessage('The enum "PHP_INT_MAX" is not defined'); Inline::parse('!php/enum PHP_INT_MAX', Yaml::PARSE_CONSTANT); } @@ -715,7 +722,12 @@ public static function getNumericKeyData() public function testDumpUnitEnum() { - $this->assertSame("!php/const Symfony\Component\Yaml\Tests\Fixtures\FooUnitEnum::BAR", Inline::dump(FooUnitEnum::BAR)); + $this->assertSame("!php/enum Symfony\Component\Yaml\Tests\Fixtures\FooUnitEnum::BAR", Inline::dump(FooUnitEnum::BAR)); + } + + public function testParseUnitEnumCases() + { + $this->assertSame(FooUnitEnum::cases(), Inline::parse("!php/enum Symfony\Component\Yaml\Tests\Fixtures\FooUnitEnum", Yaml::PARSE_CONSTANT)); } public function testParseUnitEnum() diff --git a/src/Symfony/Contracts/Cache/CacheInterface.php b/src/Symfony/Contracts/Cache/CacheInterface.php index a4fcea731e596..3e4aaf65c48d1 100644 --- a/src/Symfony/Contracts/Cache/CacheInterface.php +++ b/src/Symfony/Contracts/Cache/CacheInterface.php @@ -44,7 +44,7 @@ interface CacheInterface * * @throws InvalidArgumentException When $key is not valid or when $beta is negative */ - public function get(string $key, callable $callback, float $beta = null, array &$metadata = null): mixed; + public function get(string $key, callable $callback, ?float $beta = null, ?array &$metadata = null): mixed; /** * Removes an item from the pool. diff --git a/src/Symfony/Contracts/Cache/CacheTrait.php b/src/Symfony/Contracts/Cache/CacheTrait.php index 8a4b0bda8229f..c2f6580480035 100644 --- a/src/Symfony/Contracts/Cache/CacheTrait.php +++ b/src/Symfony/Contracts/Cache/CacheTrait.php @@ -25,7 +25,7 @@ class_exists(InvalidArgumentException::class); */ trait CacheTrait { - public function get(string $key, callable $callback, float $beta = null, array &$metadata = null): mixed + public function get(string $key, callable $callback, ?float $beta = null, ?array &$metadata = null): mixed { return $this->doGet($this, $key, $callback, $beta, $metadata); } @@ -35,7 +35,7 @@ public function delete(string $key): bool return $this->deleteItem($key); } - private function doGet(CacheItemPoolInterface $pool, string $key, callable $callback, ?float $beta, array &$metadata = null, LoggerInterface $logger = null): mixed + private function doGet(CacheItemPoolInterface $pool, string $key, callable $callback, ?float $beta, ?array &$metadata = null, ?LoggerInterface $logger = null): mixed { if (0 > $beta ??= 1.0) { throw new class(sprintf('Argument "$beta" provided to "%s::get()" must be a positive number, %f given.', static::class, $beta)) extends \InvalidArgumentException implements InvalidArgumentException {}; diff --git a/src/Symfony/Contracts/EventDispatcher/EventDispatcherInterface.php b/src/Symfony/Contracts/EventDispatcher/EventDispatcherInterface.php index 610d6ac069d4d..2d7840d32dff4 100644 --- a/src/Symfony/Contracts/EventDispatcher/EventDispatcherInterface.php +++ b/src/Symfony/Contracts/EventDispatcher/EventDispatcherInterface.php @@ -29,5 +29,5 @@ interface EventDispatcherInterface extends PsrEventDispatcherInterface * * @return T The passed $event MUST be returned */ - public function dispatch(object $event, string $eventName = null): object; + public function dispatch(object $event, ?string $eventName = null): object; } diff --git a/src/Symfony/Contracts/HttpClient/HttpClientInterface.php b/src/Symfony/Contracts/HttpClient/HttpClientInterface.php index 59636258ff6e3..4bb1dd376a08e 100644 --- a/src/Symfony/Contracts/HttpClient/HttpClientInterface.php +++ b/src/Symfony/Contracts/HttpClient/HttpClientInterface.php @@ -90,7 +90,7 @@ public function request(string $method, string $url, array $options = []): Respo * @param ResponseInterface|iterable $responses One or more responses created by the current HTTP client * @param float|null $timeout The idle timeout before yielding timeout chunks */ - public function stream(ResponseInterface|iterable $responses, float $timeout = null): ResponseStreamInterface; + public function stream(ResponseInterface|iterable $responses, ?float $timeout = null): ResponseStreamInterface; /** * Returns a new instance of the client with new default options. diff --git a/src/Symfony/Contracts/HttpClient/ResponseInterface.php b/src/Symfony/Contracts/HttpClient/ResponseInterface.php index 62d0f8f52f36b..387345cc1afae 100644 --- a/src/Symfony/Contracts/HttpClient/ResponseInterface.php +++ b/src/Symfony/Contracts/HttpClient/ResponseInterface.php @@ -105,5 +105,5 @@ public function cancel(): void; * @return mixed An array of all available info, or one of them when $type is * provided, or null when an unsupported type is requested */ - public function getInfo(string $type = null): mixed; + public function getInfo(?string $type = null): mixed; } diff --git a/src/Symfony/Contracts/Translation/TranslatableInterface.php b/src/Symfony/Contracts/Translation/TranslatableInterface.php index 47fd6fa029f04..8554697ec018d 100644 --- a/src/Symfony/Contracts/Translation/TranslatableInterface.php +++ b/src/Symfony/Contracts/Translation/TranslatableInterface.php @@ -16,5 +16,5 @@ */ interface TranslatableInterface { - public function trans(TranslatorInterface $translator, string $locale = null): string; + public function trans(TranslatorInterface $translator, ?string $locale = null): string; } diff --git a/src/Symfony/Contracts/Translation/TranslatorInterface.php b/src/Symfony/Contracts/Translation/TranslatorInterface.php index 018db07ebf425..7fa69878f816d 100644 --- a/src/Symfony/Contracts/Translation/TranslatorInterface.php +++ b/src/Symfony/Contracts/Translation/TranslatorInterface.php @@ -59,7 +59,7 @@ interface TranslatorInterface * * @throws \InvalidArgumentException If the locale contains invalid characters */ - public function trans(string $id, array $parameters = [], string $domain = null, string $locale = null): string; + public function trans(string $id, array $parameters = [], ?string $domain = null, ?string $locale = null): string; /** * Returns the default locale. diff --git a/src/Symfony/Contracts/Translation/TranslatorTrait.php b/src/Symfony/Contracts/Translation/TranslatorTrait.php index e3b0adff05980..63f6fb333da26 100644 --- a/src/Symfony/Contracts/Translation/TranslatorTrait.php +++ b/src/Symfony/Contracts/Translation/TranslatorTrait.php @@ -35,7 +35,7 @@ public function getLocale(): string return $this->locale ?: (class_exists(\Locale::class) ? \Locale::getDefault() : 'en'); } - public function trans(?string $id, array $parameters = [], string $domain = null, string $locale = null): string + public function trans(?string $id, array $parameters = [], ?string $domain = null, ?string $locale = null): string { if (null === $id || '' === $id) { return '';