From 537eeff13c43426f5e126e480a7858a7ab38e574 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Wed, 19 May 2021 15:18:37 +0200 Subject: [PATCH 001/167] Bump Symfony 6 to PHP 8 --- composer.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/composer.json b/composer.json index f4e70b09..dd0e3e1a 100644 --- a/composer.json +++ b/composer.json @@ -16,8 +16,7 @@ } ], "require": { - "php": ">=7.2.5", - "symfony/polyfill-php80": "^1.15", + "php": ">=8.0.2", "symfony/translation-contracts": "^1.1|^2", "twig/twig": "^2.13|^3.0.4" }, From e1d6e40c2259520f02ab700883f94a9dd99bb1d9 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Thu, 20 May 2021 14:59:02 +0200 Subject: [PATCH 002/167] Bump symfony/* deps to ^5.4|^6.0 --- composer.json | 54 +++++++++++++++++++++++++-------------------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/composer.json b/composer.json index 8a24ad62..a2356ac1 100644 --- a/composer.json +++ b/composer.json @@ -24,29 +24,29 @@ "doctrine/annotations": "^1.12", "egulias/email-validator": "^2.1.10|^3", "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", - "symfony/asset": "^4.4|^5.0|^6.0", - "symfony/dependency-injection": "^4.4|^5.0|^6.0", - "symfony/finder": "^4.4|^5.0|^6.0", - "symfony/form": "^5.3|^6.0", - "symfony/http-foundation": "^5.3|^6.0", - "symfony/http-kernel": "^4.4|^5.0|^6.0", - "symfony/intl": "^4.4|^5.0|^6.0", - "symfony/mime": "^5.2|^6.0", + "symfony/asset": "^5.4|^6.0", + "symfony/dependency-injection": "^5.4|^6.0", + "symfony/finder": "^5.4|^6.0", + "symfony/form": "^5.4|^6.0", + "symfony/http-foundation": "^5.4|^6.0", + "symfony/http-kernel": "^5.4|^6.0", + "symfony/intl": "^5.4|^6.0", + "symfony/mime": "^5.4|^6.0", "symfony/polyfill-intl-icu": "~1.0", - "symfony/property-info": "^4.4|^5.1|^6.0", - "symfony/routing": "^4.4|^5.0|^6.0", - "symfony/translation": "^5.2|^6.0", - "symfony/yaml": "^4.4|^5.0|^6.0", + "symfony/property-info": "^5.4|^6.0", + "symfony/routing": "^5.4|^6.0", + "symfony/translation": "^5.4|^6.0", + "symfony/yaml": "^5.4|^6.0", "symfony/security-acl": "^2.8|^3.0", - "symfony/security-core": "^4.4|^5.0|^6.0", - "symfony/security-csrf": "^4.4|^5.0|^6.0", - "symfony/security-http": "^4.4|^5.0|^6.0", - "symfony/serializer": "^5.2|^6.0", - "symfony/stopwatch": "^4.4|^5.0|^6.0", - "symfony/console": "^4.4|^5.0|^6.0", - "symfony/expression-language": "^4.4|^5.0|^6.0", - "symfony/web-link": "^4.4|^5.0|^6.0", - "symfony/workflow": "^5.2|^6.0", + "symfony/security-core": "^5.4|^6.0", + "symfony/security-csrf": "^5.4|^6.0", + "symfony/security-http": "^5.4|^6.0", + "symfony/serializer": "^5.4|^6.0", + "symfony/stopwatch": "^5.4|^6.0", + "symfony/console": "^5.4|^6.0", + "symfony/expression-language": "^5.4|^6.0", + "symfony/web-link": "^5.4|^6.0", + "symfony/workflow": "^5.4|^6.0", "twig/cssinliner-extra": "^2.12|^3", "twig/inky-extra": "^2.12|^3", "twig/markdown-extra": "^2.12|^3" @@ -54,12 +54,12 @@ "conflict": { "phpdocumentor/reflection-docblock": "<3.2.2", "phpdocumentor/type-resolver": "<1.4.0", - "symfony/console": "<4.4", - "symfony/form": "<5.3", - "symfony/http-foundation": "<5.3", - "symfony/http-kernel": "<4.4", - "symfony/translation": "<5.2", - "symfony/workflow": "<5.2" + "symfony/console": "<5.4", + "symfony/form": "<5.4", + "symfony/http-foundation": "<5.4", + "symfony/http-kernel": "<5.4", + "symfony/translation": "<5.4", + "symfony/workflow": "<5.4" }, "suggest": { "symfony/finder": "", From e47f4ae5309da7801ae6f2002aae3c9efc2ae41a Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Wed, 2 Jun 2021 18:09:43 +0200 Subject: [PATCH 003/167] Update phpunit.xml.dist files for phpunit >= 9.3 --- phpunit.xml.dist | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 6e1ada1b..e5a59c8c 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,7 +1,7 @@ - - + + ./ - - ./Resources - ./Tests - ./vendor - - - + + + ./Resources + ./Tests + ./vendor + + From ebfff039c2dbb0623a6b3c79503b8fa6ca5e0364 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Thu, 27 May 2021 19:18:44 +0200 Subject: [PATCH 004/167] Add union types --- AppVariable.php | 2 +- Command/DebugCommand.php | 4 ++-- ErrorRenderer/TwigErrorRenderer.php | 6 +----- Extension/CodeExtension.php | 5 +---- Extension/FormExtension.php | 8 +++----- Extension/HttpKernelRuntime.php | 8 ++------ Extension/SecurityExtension.php | 5 +---- Extension/SerializerRuntime.php | 2 +- Extension/TranslationExtension.php | 5 ++--- Extension/WorkflowExtension.php | 2 +- Extension/YamlExtension.php | 4 ++-- Form/TwigRendererEngine.php | 2 +- Mime/NotificationEmail.php | 10 ++-------- Node/DumpNode.php | 2 +- NodeVisitor/Scope.php | 4 ++-- 15 files changed, 23 insertions(+), 46 deletions(-) diff --git a/AppVariable.php b/AppVariable.php index f5a6494e..d6f4cf9a 100644 --- a/AppVariable.php +++ b/AppVariable.php @@ -152,7 +152,7 @@ public function getDebug() * * @return array */ - public function getFlashes($types = null) + public function getFlashes(string|array|null $types = null) { try { if (null === $session = $this->getSession()) { diff --git a/Command/DebugCommand.php b/Command/DebugCommand.php index 5a79e42c..40ae2d07 100644 --- a/Command/DebugCommand.php +++ b/Command/DebugCommand.php @@ -293,7 +293,7 @@ private function getLoaderPaths(string $name = null): array return $loaderPaths; } - private function getMetadata(string $type, $entity) + private function getMetadata(string $type, mixed $entity) { if ('globals' === $type) { return $entity; @@ -351,7 +351,7 @@ private function getMetadata(string $type, $entity) return null; } - private function getPrettyMetadata(string $type, $entity, bool $decorated): ?string + private function getPrettyMetadata(string $type, mixed $entity, bool $decorated): ?string { if ('tests' === $type) { return ''; diff --git a/ErrorRenderer/TwigErrorRenderer.php b/ErrorRenderer/TwigErrorRenderer.php index b0ccd684..407af0ad 100644 --- a/ErrorRenderer/TwigErrorRenderer.php +++ b/ErrorRenderer/TwigErrorRenderer.php @@ -32,12 +32,8 @@ 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, $debug = false) + public function __construct(Environment $twig, HtmlErrorRenderer $fallbackErrorRenderer = null, bool|callable $debug = false) { - if (!\is_bool($debug) && !\is_callable($debug)) { - throw new \TypeError(sprintf('Argument 3 passed to "%s()" must be a boolean or a callable, "%s" given.', __METHOD__, get_debug_type($debug))); - } - $this->twig = $twig; $this->fallbackErrorRenderer = $fallbackErrorRenderer ?? new HtmlErrorRenderer(); $this->debug = $debug; diff --git a/Extension/CodeExtension.php b/Extension/CodeExtension.php index 5ecc0906..b57bb3de 100644 --- a/Extension/CodeExtension.php +++ b/Extension/CodeExtension.php @@ -26,10 +26,7 @@ final class CodeExtension extends AbstractExtension private $charset; private $projectDir; - /** - * @param string|FileLinkFormatter $fileLinkFormat The format for links to source files - */ - public function __construct($fileLinkFormat, string $projectDir, string $charset) + public function __construct(string|FileLinkFormatter $fileLinkFormat, string $projectDir, string $charset) { $this->fileLinkFormat = $fileLinkFormat ?: ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format'); $this->projectDir = str_replace('\\', '/', $projectDir).'/'; diff --git a/Extension/FormExtension.php b/Extension/FormExtension.php index 7f0b1ed5..cba08318 100644 --- a/Extension/FormExtension.php +++ b/Extension/FormExtension.php @@ -158,7 +158,7 @@ public function getFieldChoices(FormView $view): iterable yield from $this->createFieldChoicesList($view->vars['choices'], $view->vars['choice_translation_domain']); } - private function createFieldChoicesList(iterable $choices, $translationDomain): iterable + private function createFieldChoicesList(iterable $choices, string|false|null $translationDomain): iterable { foreach ($choices as $choice) { $translatableLabel = $this->createFieldTranslation($choice->label, [], $translationDomain); @@ -174,7 +174,7 @@ private function createFieldChoicesList(iterable $choices, $translationDomain): } } - private function createFieldTranslation(?string $value, array $parameters, $domain): ?string + private function createFieldTranslation(?string $value, array $parameters, string|false|null $domain): ?string { if (!$this->translator || !$value || false === $domain) { return $value; @@ -189,11 +189,9 @@ private function createFieldTranslation(?string $value, array $parameters, $doma * * This is a function and not callable due to performance reasons. * - * @param string|array $selectedValue The selected value to compare - * * @see ChoiceView::isSelected() */ -function twig_is_selected_choice(ChoiceView $choice, $selectedValue): bool +function twig_is_selected_choice(ChoiceView $choice, string|array $selectedValue): bool { if (\is_array($selectedValue)) { return \in_array($choice->value, $selectedValue, true); diff --git a/Extension/HttpKernelRuntime.php b/Extension/HttpKernelRuntime.php index ab83054a..7c86d7dd 100644 --- a/Extension/HttpKernelRuntime.php +++ b/Extension/HttpKernelRuntime.php @@ -34,11 +34,9 @@ public function __construct(FragmentHandler $handler, FragmentUriGeneratorInterf /** * Renders a fragment. * - * @param string|ControllerReference $uri A URI as a string or a ControllerReference instance - * * @see FragmentHandler::render() */ - public function renderFragment($uri, array $options = []): string + public function renderFragment(string|ControllerReference $uri, array $options = []): string { $strategy = $options['strategy'] ?? 'inline'; unset($options['strategy']); @@ -49,11 +47,9 @@ public function renderFragment($uri, array $options = []): string /** * Renders a fragment. * - * @param string|ControllerReference $uri A URI as a string or a ControllerReference instance - * * @see FragmentHandler::render() */ - public function renderFragmentStrategy(string $strategy, $uri, array $options = []): string + public function renderFragmentStrategy(string $strategy, string|ControllerReference $uri, array $options = []): string { return $this->handler->render($uri, $strategy, $options); } diff --git a/Extension/SecurityExtension.php b/Extension/SecurityExtension.php index 0e58fc0e..2387520e 100644 --- a/Extension/SecurityExtension.php +++ b/Extension/SecurityExtension.php @@ -35,10 +35,7 @@ public function __construct(AuthorizationCheckerInterface $securityChecker = nul $this->impersonateUrlGenerator = $impersonateUrlGenerator; } - /** - * @param mixed $object - */ - public function isGranted($role, $object = null, string $field = null): bool + public function isGranted(mixed $role, mixed $object = null, string $field = null): bool { if (null === $this->securityChecker) { return false; diff --git a/Extension/SerializerRuntime.php b/Extension/SerializerRuntime.php index 3a4087aa..dbffa31c 100644 --- a/Extension/SerializerRuntime.php +++ b/Extension/SerializerRuntime.php @@ -26,7 +26,7 @@ public function __construct(SerializerInterface $serializer) $this->serializer = $serializer; } - public function serialize($data, string $format = 'json', array $context = []): string + public function serialize(mixed $data, string $format = 'json', array $context = []): string { return $this->serializer->serialize($data, $format, $context); } diff --git a/Extension/TranslationExtension.php b/Extension/TranslationExtension.php index c2797d83..fa5c66fd 100644 --- a/Extension/TranslationExtension.php +++ b/Extension/TranslationExtension.php @@ -106,10 +106,9 @@ public function getTranslationNodeVisitor(): TranslationNodeVisitor } /** - * @param string|\Stringable|TranslatableInterface|null $message - * @param array|string $arguments Can be the locale as a string when $message is a TranslatableInterface + * @param array|string $arguments Can be the locale as a string when $message is a TranslatableInterface */ - public function trans($message, $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)) { diff --git a/Extension/WorkflowExtension.php b/Extension/WorkflowExtension.php index ea7cd17a..3470ed4b 100644 --- a/Extension/WorkflowExtension.php +++ b/Extension/WorkflowExtension.php @@ -102,7 +102,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, $metadataSubject = null, string $name = null) + public function getMetadata(object $subject, string $key, string|Transition|null $metadataSubject = null, string $name = null) { return $this ->workflowRegistry diff --git a/Extension/YamlExtension.php b/Extension/YamlExtension.php index 63df1336..919834e2 100644 --- a/Extension/YamlExtension.php +++ b/Extension/YamlExtension.php @@ -33,7 +33,7 @@ public function getFilters(): array ]; } - public function encode($input, int $inline = 0, int $dumpObjects = 0): string + public function encode(mixed $input, int $inline = 0, int $dumpObjects = 0): string { static $dumper; @@ -48,7 +48,7 @@ public function encode($input, int $inline = 0, int $dumpObjects = 0): string return $dumper->dump($input, $inline, 0, false, $dumpObjects); } - public function dump($value, int $inline = 0, int $dumpObjects = 0): string + public function dump(mixed $value, int $inline = 0, int $dumpObjects = 0): string { if (\is_resource($value)) { return '%Resource%'; diff --git a/Form/TwigRendererEngine.php b/Form/TwigRendererEngine.php index bc3b82d2..2ee76a79 100644 --- a/Form/TwigRendererEngine.php +++ b/Form/TwigRendererEngine.php @@ -40,7 +40,7 @@ public function __construct(array $defaultThemes, Environment $environment) /** * {@inheritdoc} */ - public function renderBlock(FormView $view, $resource, string $blockName, array $variables = []) + public function renderBlock(FormView $view, mixed $resource, string $blockName, array $variables = []) { $cacheKey = $view->vars[self::CACHE_KEY_VAR]; diff --git a/Mime/NotificationEmail.php b/Mime/NotificationEmail.php index 2058b8e6..8492f3de 100644 --- a/Mime/NotificationEmail.php +++ b/Mime/NotificationEmail.php @@ -124,16 +124,10 @@ public function importance(string $importance) } /** - * @param \Throwable|FlattenException $exception - * * @return $this */ - public function exception($exception) + public function exception(\Throwable|FlattenException $exception) { - if (!$exception instanceof \Throwable && !$exception instanceof FlattenException) { - throw new \LogicException(sprintf('"%s" accepts "%s" or "%s" instances.', __METHOD__, \Throwable::class, FlattenException::class)); - } - $exceptionAsString = $this->getExceptionAsString($exception); $this->context['exception'] = true; @@ -208,7 +202,7 @@ private function determinePriority(string $importance): int } } - private function getExceptionAsString($exception): string + private function getExceptionAsString(\Throwable|FlattenException $exception): string { if (class_exists(FlattenException::class)) { $exception = $exception instanceof FlattenException ? $exception : FlattenException::createFromThrowable($exception); diff --git a/Node/DumpNode.php b/Node/DumpNode.php index 16718e8a..68c00556 100644 --- a/Node/DumpNode.php +++ b/Node/DumpNode.php @@ -21,7 +21,7 @@ final class DumpNode extends Node { private $varPrefix; - public function __construct($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/NodeVisitor/Scope.php b/NodeVisitor/Scope.php index 765b4b69..3b20b2fd 100644 --- a/NodeVisitor/Scope.php +++ b/NodeVisitor/Scope.php @@ -54,7 +54,7 @@ public function leave() * * @throws \LogicException */ - public function set(string $key, $value) + public function set(string $key, mixed $value) { if ($this->left) { throw new \LogicException('Left scope is not mutable.'); @@ -88,7 +88,7 @@ public function has(string $key) * * @return mixed */ - public function get(string $key, $default = null) + public function get(string $key, mixed $default = null) { if (\array_key_exists($key, $this->data)) { return $this->data[$key]; From c9e366812577c9c0a5ff5c920ae5e35aaca714a7 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Wed, 7 Jul 2021 15:44:05 +0200 Subject: [PATCH 005/167] Add some more union types --- Form/TwigRendererEngine.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Form/TwigRendererEngine.php b/Form/TwigRendererEngine.php index 2ee76a79..c264c633 100644 --- a/Form/TwigRendererEngine.php +++ b/Form/TwigRendererEngine.php @@ -145,7 +145,7 @@ protected function loadResourceForBlockName(string $cacheKey, FormView $view, st * this variable will be kept and be available upon * further calls to this method using the same theme. */ - protected function loadResourcesFromTheme(string $cacheKey, &$theme) + protected function loadResourcesFromTheme(string $cacheKey, mixed &$theme) { if (!$theme instanceof Template) { /* @var Template $theme */ From b31c96ef50843674ad52573ec10201ce83cbe8d6 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Mon, 12 Jul 2021 11:26:55 +0200 Subject: [PATCH 006/167] Add return types, round 1 --- DataCollector/TwigDataCollector.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DataCollector/TwigDataCollector.php b/DataCollector/TwigDataCollector.php index be432838..de5166b6 100644 --- a/DataCollector/TwigDataCollector.php +++ b/DataCollector/TwigDataCollector.php @@ -198,7 +198,7 @@ private function computeData(Profile $profile) /** * {@inheritdoc} */ - public function getName() + public function getName(): string { return 'twig'; } From 4cf148149ebbc37afa652bdc984b0af2eb34769f Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 13 Jul 2021 13:47:05 +0200 Subject: [PATCH 007/167] [Contracts] add return types and bump to v3 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index a2356ac1..0b474b61 100644 --- a/composer.json +++ b/composer.json @@ -17,7 +17,7 @@ ], "require": { "php": ">=8.0.2", - "symfony/translation-contracts": "^1.1|^2", + "symfony/translation-contracts": "^1.1|^2.0|^3.0", "twig/twig": "^2.13|^3.0.4" }, "require-dev": { From ef1636fa81a6e7218ad2fa65e46000ac90616444 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 20 Jul 2021 15:07:23 +0200 Subject: [PATCH 008/167] Add return type unions to private/internal/final/test methods --- Extension/CodeExtension.php | 7 +------ Extension/FormExtension.php | 5 +---- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/Extension/CodeExtension.php b/Extension/CodeExtension.php index b57bb3de..2dc4df47 100644 --- a/Extension/CodeExtension.php +++ b/Extension/CodeExtension.php @@ -169,12 +169,7 @@ public function formatFile(string $file, int $line, string $text = null): string return $text; } - /** - * Returns the link for a given file/line pair. - * - * @return string|false A link or false - */ - public function getFileLink(string $file, int $line) + public function getFileLink(string $file, int $line): string|false { if ($fmt = $this->fileLinkFormat) { return \is_string($fmt) ? strtr($fmt, ['%f' => $file, '%l' => $line]) : $fmt->format($file, $line); diff --git a/Extension/FormExtension.php b/Extension/FormExtension.php index cba08318..f7d82e04 100644 --- a/Extension/FormExtension.php +++ b/Extension/FormExtension.php @@ -103,10 +103,7 @@ public function getFieldName(FormView $view): string return $view->vars['full_name']; } - /** - * @return string|array - */ - public function getFieldValue(FormView $view) + public function getFieldValue(FormView $view): string|array { return $view->vars['value']; } From ec0c2d6e8bdf08a5f223599b798443f8dd95077a Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Wed, 21 Jul 2021 12:04:28 +0200 Subject: [PATCH 009/167] Narrow existing return types on private/internal/final/test methods --- Mime/WrappedTemplatedEmail.php | 40 +++++++--------------------------- 1 file changed, 8 insertions(+), 32 deletions(-) diff --git a/Mime/WrappedTemplatedEmail.php b/Mime/WrappedTemplatedEmail.php index f1726914..a6a90bf1 100644 --- a/Mime/WrappedTemplatedEmail.php +++ b/Mime/WrappedTemplatedEmail.php @@ -57,10 +57,7 @@ public function attach(string $file, string $name = null, string $contentType = } } - /** - * @return $this - */ - public function setSubject(string $subject): self + public function setSubject(string $subject): static { $this->message->subject($subject); @@ -72,10 +69,7 @@ public function getSubject(): ?string return $this->message->getSubject(); } - /** - * @return $this - */ - public function setReturnPath(string $address): self + public function setReturnPath(string $address): static { $this->message->returnPath($address); @@ -87,10 +81,7 @@ public function getReturnPath(): string return $this->message->getReturnPath(); } - /** - * @return $this - */ - public function addFrom(string $address, string $name = ''): self + public function addFrom(string $address, string $name = ''): static { $this->message->addFrom(new Address($address, $name)); @@ -105,10 +96,7 @@ public function getFrom(): array return $this->message->getFrom(); } - /** - * @return $this - */ - public function addReplyTo(string $address): self + public function addReplyTo(string $address): static { $this->message->addReplyTo($address); @@ -123,10 +111,7 @@ public function getReplyTo(): array return $this->message->getReplyTo(); } - /** - * @return $this - */ - public function addTo(string $address, string $name = ''): self + public function addTo(string $address, string $name = ''): static { $this->message->addTo(new Address($address, $name)); @@ -141,10 +126,7 @@ public function getTo(): array return $this->message->getTo(); } - /** - * @return $this - */ - public function addCc(string $address, string $name = ''): self + public function addCc(string $address, string $name = ''): static { $this->message->addCc(new Address($address, $name)); @@ -159,10 +141,7 @@ public function getCc(): array return $this->message->getCc(); } - /** - * @return $this - */ - public function addBcc(string $address, string $name = ''): self + public function addBcc(string $address, string $name = ''): static { $this->message->addBcc(new Address($address, $name)); @@ -177,10 +156,7 @@ public function getBcc(): array return $this->message->getBcc(); } - /** - * @return $this - */ - public function setPriority(int $priority): self + public function setPriority(int $priority): static { $this->message->priority($priority); From 4cd0c6790520b8261fbe0c53e21009e43b0b7b8a Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Thu, 12 Aug 2021 16:31:53 +0200 Subject: [PATCH 010/167] Add return types to bridges --- AppVariable.php | 14 +++++++------- Command/DebugCommand.php | 2 +- Command/LintCommand.php | 2 +- Form/TwigRendererEngine.php | 4 ++-- Mime/NotificationEmail.php | 12 ++++++------ Mime/TemplatedEmail.php | 6 +++--- NodeVisitor/Scope.php | 10 +++++----- Translation/TwigExtractor.php | 4 ++-- UndefinedCallableHandler.php | 4 ++-- 9 files changed, 29 insertions(+), 29 deletions(-) diff --git a/AppVariable.php b/AppVariable.php index fc647e07..0e88ab95 100644 --- a/AppVariable.php +++ b/AppVariable.php @@ -56,7 +56,7 @@ public function setDebug(bool $debug) * * @throws \RuntimeException When the TokenStorage is not available */ - public function getToken() + public function getToken(): ?TokenInterface { if (null === $tokenStorage = $this->tokenStorage) { throw new \RuntimeException('The "app.token" variable is not available.'); @@ -72,7 +72,7 @@ public function getToken() * * @see TokenInterface::getUser() */ - public function getUser() + public function getUser(): ?object { if (null === $tokenStorage = $this->tokenStorage) { throw new \RuntimeException('The "app.user" variable is not available.'); @@ -92,7 +92,7 @@ public function getUser() * * @return Request|null The HTTP request object */ - public function getRequest() + public function getRequest(): ?Request { if (null === $this->requestStack) { throw new \RuntimeException('The "app.request" variable is not available.'); @@ -106,7 +106,7 @@ public function getRequest() * * @return Session|null The session */ - public function getSession() + public function getSession(): ?Session { if (null === $this->requestStack) { throw new \RuntimeException('The "app.session" variable is not available.'); @@ -121,7 +121,7 @@ public function getSession() * * @return string The current environment string (e.g 'dev') */ - public function getEnvironment() + public function getEnvironment(): string { if (null === $this->environment) { throw new \RuntimeException('The "app.environment" variable is not available.'); @@ -135,7 +135,7 @@ public function getEnvironment() * * @return bool The current debug mode */ - public function getDebug() + public function getDebug(): bool { if (null === $this->debug) { throw new \RuntimeException('The "app.debug" variable is not available.'); @@ -152,7 +152,7 @@ public function getDebug() * * @return array */ - public function getFlashes(string|array $types = null) + public function getFlashes(string|array $types = null): array { try { if (null === $session = $this->getSession()) { diff --git a/Command/DebugCommand.php b/Command/DebugCommand.php index c782fb23..8a5a13be 100644 --- a/Command/DebugCommand.php +++ b/Command/DebugCommand.php @@ -86,7 +86,7 @@ protected function configure() ; } - protected function execute(InputInterface $input, OutputInterface $output) + protected function execute(InputInterface $input, OutputInterface $output): int { $io = new SymfonyStyle($input, $output); $name = $input->getArgument('name'); diff --git a/Command/LintCommand.php b/Command/LintCommand.php index 53e1d0bb..d5beaeab 100644 --- a/Command/LintCommand.php +++ b/Command/LintCommand.php @@ -81,7 +81,7 @@ protected function configure() ; } - protected function execute(InputInterface $input, OutputInterface $output) + protected function execute(InputInterface $input, OutputInterface $output): int { $io = new SymfonyStyle($input, $output); $filenames = $input->getArgument('filename'); diff --git a/Form/TwigRendererEngine.php b/Form/TwigRendererEngine.php index c264c633..1b965ab2 100644 --- a/Form/TwigRendererEngine.php +++ b/Form/TwigRendererEngine.php @@ -40,7 +40,7 @@ public function __construct(array $defaultThemes, Environment $environment) /** * {@inheritdoc} */ - public function renderBlock(FormView $view, mixed $resource, string $blockName, array $variables = []) + public function renderBlock(FormView $view, mixed $resource, string $blockName, array $variables = []): string { $cacheKey = $view->vars[self::CACHE_KEY_VAR]; @@ -72,7 +72,7 @@ public function renderBlock(FormView $view, mixed $resource, string $blockName, * * @return bool True if the resource could be loaded, false otherwise */ - protected function loadResourceForBlockName(string $cacheKey, FormView $view, string $blockName) + protected function loadResourceForBlockName(string $cacheKey, FormView $view, string $blockName): bool { // The caller guarantees that $this->resources[$cacheKey][$block] is // not set, but it doesn't have to check whether $this->resources[$cacheKey] diff --git a/Mime/NotificationEmail.php b/Mime/NotificationEmail.php index 8492f3de..604e10b2 100644 --- a/Mime/NotificationEmail.php +++ b/Mime/NotificationEmail.php @@ -80,7 +80,7 @@ public function markAsPublic(): self /** * @return $this */ - public function markdown(string $content) + public function markdown(string $content): static { if (!class_exists(MarkdownExtension::class)) { throw new \LogicException(sprintf('You cannot use "%s" if the Markdown Twig extension is not available; try running "composer require twig/markdown-extra".', __METHOD__)); @@ -94,7 +94,7 @@ public function markdown(string $content) /** * @return $this */ - public function content(string $content, bool $raw = false) + public function content(string $content, bool $raw = false): static { $this->context['content'] = $content; $this->context['raw'] = $raw; @@ -105,7 +105,7 @@ public function content(string $content, bool $raw = false) /** * @return $this */ - public function action(string $text, string $url) + public function action(string $text, string $url): static { $this->context['action_text'] = $text; $this->context['action_url'] = $url; @@ -116,7 +116,7 @@ public function action(string $text, string $url) /** * @return $this */ - public function importance(string $importance) + public function importance(string $importance): static { $this->context['importance'] = $importance; @@ -126,7 +126,7 @@ public function importance(string $importance) /** * @return $this */ - public function exception(\Throwable|FlattenException $exception) + public function exception(\Throwable|FlattenException $exception): static { $exceptionAsString = $this->getExceptionAsString($exception); @@ -144,7 +144,7 @@ public function exception(\Throwable|FlattenException $exception) /** * @return $this */ - public function theme(string $theme) + public function theme(string $theme): static { $this->theme = $theme; diff --git a/Mime/TemplatedEmail.php b/Mime/TemplatedEmail.php index 6dd9202d..aceef09e 100644 --- a/Mime/TemplatedEmail.php +++ b/Mime/TemplatedEmail.php @@ -25,7 +25,7 @@ class TemplatedEmail extends Email /** * @return $this */ - public function textTemplate(?string $template) + public function textTemplate(?string $template): static { $this->textTemplate = $template; @@ -35,7 +35,7 @@ public function textTemplate(?string $template) /** * @return $this */ - public function htmlTemplate(?string $template) + public function htmlTemplate(?string $template): static { $this->htmlTemplate = $template; @@ -55,7 +55,7 @@ public function getHtmlTemplate(): ?string /** * @return $this */ - public function context(array $context) + public function context(array $context): static { $this->context = $context; diff --git a/NodeVisitor/Scope.php b/NodeVisitor/Scope.php index 3b20b2fd..b7a998d2 100644 --- a/NodeVisitor/Scope.php +++ b/NodeVisitor/Scope.php @@ -30,7 +30,7 @@ public function __construct(self $parent = null) * * @return self */ - public function enter() + public function enter(): \Symfony\Bridge\Twig\NodeVisitor\Scope { return new self($this); } @@ -40,7 +40,7 @@ public function enter() * * @return self|null */ - public function leave() + public function leave(): ?\Symfony\Bridge\Twig\NodeVisitor\Scope { $this->left = true; @@ -54,7 +54,7 @@ public function leave() * * @throws \LogicException */ - public function set(string $key, mixed $value) + public function set(string $key, mixed $value): static { if ($this->left) { throw new \LogicException('Left scope is not mutable.'); @@ -70,7 +70,7 @@ public function set(string $key, mixed $value) * * @return bool */ - public function has(string $key) + public function has(string $key): bool { if (\array_key_exists($key, $this->data)) { return true; @@ -88,7 +88,7 @@ public function has(string $key) * * @return mixed */ - public function get(string $key, mixed $default = null) + public function get(string $key, mixed $default = null): mixed { if (\array_key_exists($key, $this->data)) { return $this->data[$key]; diff --git a/Translation/TwigExtractor.php b/Translation/TwigExtractor.php index e79ec697..3c7a61cf 100644 --- a/Translation/TwigExtractor.php +++ b/Translation/TwigExtractor.php @@ -87,7 +87,7 @@ protected function extractTemplate(string $template, MessageCatalogue $catalogue /** * @return bool */ - protected function canBeExtracted(string $file) + protected function canBeExtracted(string $file): bool { return $this->isFile($file) && 'twig' === pathinfo($file, \PATHINFO_EXTENSION); } @@ -95,7 +95,7 @@ protected function canBeExtracted(string $file) /** * {@inheritdoc} */ - protected function extractFromDirectory($directory) + protected function extractFromDirectory($directory): iterable { $finder = new Finder(); diff --git a/UndefinedCallableHandler.php b/UndefinedCallableHandler.php index 608bbaa8..360a386f 100644 --- a/UndefinedCallableHandler.php +++ b/UndefinedCallableHandler.php @@ -71,7 +71,7 @@ class UndefinedCallableHandler /** * @return TwigFilter|false */ - public static function onUndefinedFilter(string $name) + public static function onUndefinedFilter(string $name): TwigFilter|false { if (!isset(self::FILTER_COMPONENTS[$name])) { return false; @@ -83,7 +83,7 @@ public static function onUndefinedFilter(string $name) /** * @return TwigFunction|false */ - public static function onUndefinedFunction(string $name) + public static function onUndefinedFunction(string $name): TwigFunction|false { if (!isset(self::FUNCTION_COMPONENTS[$name])) { return false; From b18199179a47a4de9b9478d6d495747f46b964a4 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Mon, 16 Aug 2021 18:31:32 +0200 Subject: [PATCH 011/167] Run php-cs-fixer --- AppVariable.php | 6 ------ NodeVisitor/Scope.php | 8 ++------ Translation/TwigExtractor.php | 3 --- 3 files changed, 2 insertions(+), 15 deletions(-) diff --git a/AppVariable.php b/AppVariable.php index 833269d1..ca598dce 100644 --- a/AppVariable.php +++ b/AppVariable.php @@ -52,8 +52,6 @@ public function setDebug(bool $debug) /** * Returns the current token. * - * @return TokenInterface|null - * * @throws \RuntimeException When the TokenStorage is not available */ public function getToken(): ?TokenInterface @@ -68,8 +66,6 @@ public function getToken(): ?TokenInterface /** * Returns the current user. * - * @return object|null - * * @see TokenInterface::getUser() */ public function getUser(): ?object @@ -150,8 +146,6 @@ public function getDebug(): bool * * getFlashes() returns all the flash messages * * getFlashes('notice') returns a simple array with flash messages of that type * * getFlashes(['notice', 'error']) returns a nested array of type => messages. - * - * @return array */ public function getFlashes(string|array $types = null): array { diff --git a/NodeVisitor/Scope.php b/NodeVisitor/Scope.php index b7a998d2..dd2ad0fe 100644 --- a/NodeVisitor/Scope.php +++ b/NodeVisitor/Scope.php @@ -30,7 +30,7 @@ public function __construct(self $parent = null) * * @return self */ - public function enter(): \Symfony\Bridge\Twig\NodeVisitor\Scope + public function enter(): self { return new self($this); } @@ -40,7 +40,7 @@ public function enter(): \Symfony\Bridge\Twig\NodeVisitor\Scope * * @return self|null */ - public function leave(): ?\Symfony\Bridge\Twig\NodeVisitor\Scope + public function leave(): ?self { $this->left = true; @@ -67,8 +67,6 @@ public function set(string $key, mixed $value): static /** * Tests if a data is visible from current scope. - * - * @return bool */ public function has(string $key): bool { @@ -85,8 +83,6 @@ public function has(string $key): bool /** * Returns data visible from current scope. - * - * @return mixed */ public function get(string $key, mixed $default = null): mixed { diff --git a/Translation/TwigExtractor.php b/Translation/TwigExtractor.php index 3c7a61cf..e73ae42f 100644 --- a/Translation/TwigExtractor.php +++ b/Translation/TwigExtractor.php @@ -84,9 +84,6 @@ protected function extractTemplate(string $template, MessageCatalogue $catalogue $visitor->disable(); } - /** - * @return bool - */ protected function canBeExtracted(string $file): bool { return $this->isFile($file) && 'twig' === pathinfo($file, \PATHINFO_EXTENSION); From 7fbce4db62cdf746d47a75b8cf2310ee2dc8fbb8 Mon Sep 17 00:00:00 2001 From: Wouter de Jong Date: Fri, 4 Jun 2021 20:15:48 +0200 Subject: [PATCH 012/167] [Security] Remove everything related to the deprecated authentication manager --- AppVariable.php | 5 +---- Tests/AppVariableTest.php | 7 ------- 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/AppVariable.php b/AppVariable.php index 78791f95..f816ba29 100644 --- a/AppVariable.php +++ b/AppVariable.php @@ -78,10 +78,7 @@ public function getUser(): ?object return null; } - $user = $token->getUser(); - - // @deprecated since 5.4, $user will always be a UserInterface instance - return \is_object($user) ? $user : null; + return $token->getUser(); } /** diff --git a/Tests/AppVariableTest.php b/Tests/AppVariableTest.php index f5fcbead..462f9f78 100644 --- a/Tests/AppVariableTest.php +++ b/Tests/AppVariableTest.php @@ -95,13 +95,6 @@ public function testGetUser() $this->assertEquals($user, $this->appVariable->getUser()); } - public function testGetUserWithUsernameAsTokenUser() - { - $this->setTokenStorage($user = 'username'); - - $this->assertNull($this->appVariable->getUser()); - } - public function testGetTokenWithNoToken() { $tokenStorage = $this->createMock(TokenStorageInterface::class); From 03f87102b2a97c6d06307a12c65d24dec5a85531 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 24 Aug 2021 22:21:00 +0200 Subject: [PATCH 013/167] Add back `@return $this` annotations --- Mime/WrappedTemplatedEmail.php | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/Mime/WrappedTemplatedEmail.php b/Mime/WrappedTemplatedEmail.php index a6a90bf1..b5676678 100644 --- a/Mime/WrappedTemplatedEmail.php +++ b/Mime/WrappedTemplatedEmail.php @@ -57,6 +57,9 @@ public function attach(string $file, string $name = null, string $contentType = } } + /** + * @return $this + */ public function setSubject(string $subject): static { $this->message->subject($subject); @@ -69,6 +72,9 @@ public function getSubject(): ?string return $this->message->getSubject(); } + /** + * @return $this + */ public function setReturnPath(string $address): static { $this->message->returnPath($address); @@ -81,6 +87,9 @@ public function getReturnPath(): string return $this->message->getReturnPath(); } + /** + * @return $this + */ public function addFrom(string $address, string $name = ''): static { $this->message->addFrom(new Address($address, $name)); @@ -96,6 +105,9 @@ public function getFrom(): array return $this->message->getFrom(); } + /** + * @return $this + */ public function addReplyTo(string $address): static { $this->message->addReplyTo($address); @@ -111,6 +123,9 @@ public function getReplyTo(): array return $this->message->getReplyTo(); } + /** + * @return $this + */ public function addTo(string $address, string $name = ''): static { $this->message->addTo(new Address($address, $name)); @@ -126,6 +141,9 @@ public function getTo(): array return $this->message->getTo(); } + /** + * @return $this + */ public function addCc(string $address, string $name = ''): static { $this->message->addCc(new Address($address, $name)); @@ -141,6 +159,9 @@ public function getCc(): array return $this->message->getCc(); } + /** + * @return $this + */ public function addBcc(string $address, string $name = ''): static { $this->message->addBcc(new Address($address, $name)); @@ -156,6 +177,9 @@ public function getBcc(): array return $this->message->getBcc(); } + /** + * @return $this + */ public function setPriority(int $priority): static { $this->message->priority($priority); From b3724b7b4774cc8c529818584436aee4b49d4492 Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Tue, 7 Sep 2021 23:29:55 +0200 Subject: [PATCH 014/167] [TwigBridge] Add types to private properties Signed-off-by: Alexander M. Turek --- AppVariable.php | 28 ++++++++----------- Command/DebugCommand.php | 19 ++++++++----- Command/LintCommand.php | 14 ++-------- DataCollector/TwigDataCollector.php | 18 ++++-------- ErrorRenderer/TwigErrorRenderer.php | 8 +++--- Extension/AssetExtension.php | 2 +- Extension/CodeExtension.php | 6 ++-- Extension/CsrfRuntime.php | 2 +- Extension/DumpExtension.php | 4 +-- Extension/FormExtension.php | 2 +- Extension/HttpFoundationExtension.php | 2 +- Extension/HttpKernelRuntime.php | 4 +-- Extension/LogoutUrlExtension.php | 2 +- Extension/ProfilerExtension.php | 4 +-- Extension/RoutingExtension.php | 2 +- Extension/SecurityExtension.php | 5 ++-- Extension/SerializerRuntime.php | 2 +- Extension/StopwatchExtension.php | 4 +-- Extension/TranslationExtension.php | 4 +-- Extension/WebLinkExtension.php | 2 +- Extension/WorkflowExtension.php | 2 +- Form/TwigRendererEngine.php | 24 +++++----------- Mime/BodyRenderer.php | 8 +++--- Mime/NotificationEmail.php | 4 +-- Mime/TemplatedEmail.php | 6 ++-- Mime/WrappedTemplatedEmail.php | 4 +-- Node/DumpNode.php | 2 +- NodeVisitor/Scope.php | 6 ++-- .../TranslationDefaultDomainNodeVisitor.php | 2 +- NodeVisitor/TranslationNodeVisitor.php | 4 +-- Translation/TwigExtractor.php | 10 ++----- 31 files changed, 89 insertions(+), 117 deletions(-) diff --git a/AppVariable.php b/AppVariable.php index d613e079..d21f6448 100644 --- a/AppVariable.php +++ b/AppVariable.php @@ -25,10 +25,10 @@ */ class AppVariable { - private $tokenStorage; - private $requestStack; - private $environment; - private $debug; + private TokenStorageInterface $tokenStorage; + private RequestStack $requestStack; + private string $environment; + private bool $debug; public function setTokenStorage(TokenStorageInterface $tokenStorage) { @@ -57,11 +57,11 @@ public function setDebug(bool $debug) */ public function getToken(): ?TokenInterface { - if (null === $tokenStorage = $this->tokenStorage) { + if (!isset($this->tokenStorage)) { throw new \RuntimeException('The "app.token" variable is not available.'); } - return $tokenStorage->getToken(); + return $this->tokenStorage->getToken(); } /** @@ -71,15 +71,11 @@ public function getToken(): ?TokenInterface */ public function getUser(): ?UserInterface { - if (null === $tokenStorage = $this->tokenStorage) { + if (!isset($this->tokenStorage)) { throw new \RuntimeException('The "app.user" variable is not available.'); } - if (!$token = $tokenStorage->getToken()) { - return null; - } - - return $token->getUser(); + return $this->tokenStorage->getToken()?->getUser(); } /** @@ -87,7 +83,7 @@ public function getUser(): ?UserInterface */ public function getRequest(): ?Request { - if (null === $this->requestStack) { + if (!isset($this->requestStack)) { throw new \RuntimeException('The "app.request" variable is not available.'); } @@ -99,7 +95,7 @@ public function getRequest(): ?Request */ public function getSession(): ?Session { - if (null === $this->requestStack) { + if (!isset($this->requestStack)) { throw new \RuntimeException('The "app.session" variable is not available.'); } $request = $this->getRequest(); @@ -112,7 +108,7 @@ public function getSession(): ?Session */ public function getEnvironment(): string { - if (null === $this->environment) { + if (!isset($this->environment)) { throw new \RuntimeException('The "app.environment" variable is not available.'); } @@ -124,7 +120,7 @@ public function getEnvironment(): string */ public function getDebug(): bool { - if (null === $this->debug) { + if (!isset($this->debug)) { throw new \RuntimeException('The "app.debug" variable is not available.'); } diff --git a/Command/DebugCommand.php b/Command/DebugCommand.php index 8a5a13be..902c434c 100644 --- a/Command/DebugCommand.php +++ b/Command/DebugCommand.php @@ -35,12 +35,17 @@ class DebugCommand extends Command protected static $defaultName = 'debug:twig'; protected static $defaultDescription = 'Show a list of twig functions, filters, globals and tests'; - private $twig; - private $projectDir; - private $bundlesMetadata; - private $twigDefaultPath; - private $filesystemLoaders; - private $fileLinkFormatter; + private Environment $twig; + private ?string $projectDir; + private array $bundlesMetadata; + private ?string $twigDefaultPath; + + /** + * @var FilesystemLoader[] + */ + private array $filesystemLoaders; + + private ?FileLinkFormatter $fileLinkFormatter; public function __construct(Environment $twig, string $projectDir = null, array $bundlesMetadata = [], string $twigDefaultPath = null, FileLinkFormatter $fileLinkFormatter = null) { @@ -557,7 +562,7 @@ private function isAbsolutePath(string $file): bool */ private function getFilesystemLoaders(): array { - if (null !== $this->filesystemLoaders) { + if (isset($this->filesystemLoaders)) { return $this->filesystemLoaders; } $this->filesystemLoaders = []; diff --git a/Command/LintCommand.php b/Command/LintCommand.php index a1848a1a..dfac1242 100644 --- a/Command/LintCommand.php +++ b/Command/LintCommand.php @@ -38,12 +38,8 @@ class LintCommand extends Command protected static $defaultName = 'lint:twig'; protected static $defaultDescription = 'Lint a Twig template and outputs encountered errors'; - private $twig; - - /** - * @var string|null - */ - private $format; + private Environment $twig; + private string $format; public function __construct(Environment $twig) { @@ -86,11 +82,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $io = new SymfonyStyle($input, $output); $filenames = $input->getArgument('filename'); $showDeprecations = $input->getOption('show-deprecations'); - $this->format = $input->getOption('format'); - - if (null === $this->format) { - $this->format = GithubActionReporter::isGithubActionEnvironment() ? 'github' : 'txt'; - } + $this->format = $input->getOption('format') ?? (GithubActionReporter::isGithubActionEnvironment() ? 'github' : 'txt'); if (['-'] === $filenames) { return $this->display($input, $output, $io, [$this->validate(file_get_contents('php://stdin'), uniqid('sf_', true))]); diff --git a/DataCollector/TwigDataCollector.php b/DataCollector/TwigDataCollector.php index 4a469781..f41aa479 100644 --- a/DataCollector/TwigDataCollector.php +++ b/DataCollector/TwigDataCollector.php @@ -28,9 +28,9 @@ */ class TwigDataCollector extends DataCollector implements LateDataCollectorInterface { - private $profile; - private $twig; - private $computed; + private Profile $profile; + private ?Environment $twig; + private array $computed; public function __construct(Profile $profile, Environment $twig = null) { @@ -51,7 +51,7 @@ public function collect(Request $request, Response $response, \Throwable $except public function reset() { $this->profile->reset(); - $this->computed = null; + unset($this->computed); $this->data = []; } @@ -140,18 +140,12 @@ public function getHtmlCallGraph() public function getProfile() { - if (null === $this->profile) { - $this->profile = unserialize($this->data['profile'], ['allowed_classes' => ['Twig_Profiler_Profile', 'Twig\Profiler\Profile']]); - } - - return $this->profile; + return $this->profile ??= unserialize($this->data['profile'], ['allowed_classes' => ['Twig_Profiler_Profile', 'Twig\Profiler\Profile']]); } private function getComputedData(string $index) { - if (null === $this->computed) { - $this->computed = $this->computeData($this->getProfile()); - } + $this->computed ??= $this->computeData($this->getProfile()); return $this->computed[$index]; } diff --git a/ErrorRenderer/TwigErrorRenderer.php b/ErrorRenderer/TwigErrorRenderer.php index 407af0ad..ef3d433b 100644 --- a/ErrorRenderer/TwigErrorRenderer.php +++ b/ErrorRenderer/TwigErrorRenderer.php @@ -25,9 +25,9 @@ */ class TwigErrorRenderer implements ErrorRendererInterface { - private $twig; - private $fallbackErrorRenderer; - private $debug; + private Environment $twig; + private HtmlErrorRenderer $fallbackErrorRenderer; + private \Closure|bool $debug; /** * @param bool|callable $debug The debugging mode as a boolean or a callable that should return it @@ -36,7 +36,7 @@ public function __construct(Environment $twig, HtmlErrorRenderer $fallbackErrorR { $this->twig = $twig; $this->fallbackErrorRenderer = $fallbackErrorRenderer ?? new HtmlErrorRenderer(); - $this->debug = $debug; + $this->debug = !\is_callable($debug) || $debug instanceof \Closure ? $debug : \Closure::fromCallable($debug); } /** diff --git a/Extension/AssetExtension.php b/Extension/AssetExtension.php index 694821f7..35c69b69 100644 --- a/Extension/AssetExtension.php +++ b/Extension/AssetExtension.php @@ -22,7 +22,7 @@ */ final class AssetExtension extends AbstractExtension { - private $packages; + private Packages $packages; public function __construct(Packages $packages) { diff --git a/Extension/CodeExtension.php b/Extension/CodeExtension.php index 1426b56a..62573d9f 100644 --- a/Extension/CodeExtension.php +++ b/Extension/CodeExtension.php @@ -22,9 +22,9 @@ */ final class CodeExtension extends AbstractExtension { - private $fileLinkFormat; - private $charset; - private $projectDir; + private string|FileLinkFormatter|array|false $fileLinkFormat; + private string $charset; + private string $projectDir; public function __construct(string|FileLinkFormatter $fileLinkFormat, string $projectDir, string $charset) { diff --git a/Extension/CsrfRuntime.php b/Extension/CsrfRuntime.php index c3d5da64..216d9c92 100644 --- a/Extension/CsrfRuntime.php +++ b/Extension/CsrfRuntime.php @@ -19,7 +19,7 @@ */ final class CsrfRuntime { - private $csrfTokenManager; + private CsrfTokenManagerInterface $csrfTokenManager; public function __construct(CsrfTokenManagerInterface $csrfTokenManager) { diff --git a/Extension/DumpExtension.php b/Extension/DumpExtension.php index 46ad8eaf..1ce6593a 100644 --- a/Extension/DumpExtension.php +++ b/Extension/DumpExtension.php @@ -26,8 +26,8 @@ */ final class DumpExtension extends AbstractExtension { - private $cloner; - private $dumper; + private ClonerInterface $cloner; + private ?HtmlDumper $dumper; public function __construct(ClonerInterface $cloner, HtmlDumper $dumper = null) { diff --git a/Extension/FormExtension.php b/Extension/FormExtension.php index f7d82e04..6e42928a 100644 --- a/Extension/FormExtension.php +++ b/Extension/FormExtension.php @@ -30,7 +30,7 @@ */ final class FormExtension extends AbstractExtension { - private $translator; + private ?TranslatorInterface $translator; public function __construct(TranslatorInterface $translator = null) { diff --git a/Extension/HttpFoundationExtension.php b/Extension/HttpFoundationExtension.php index a9ee05c4..365e1173 100644 --- a/Extension/HttpFoundationExtension.php +++ b/Extension/HttpFoundationExtension.php @@ -23,7 +23,7 @@ */ final class HttpFoundationExtension extends AbstractExtension { - private $urlHelper; + private UrlHelper $urlHelper; public function __construct(UrlHelper $urlHelper) { diff --git a/Extension/HttpKernelRuntime.php b/Extension/HttpKernelRuntime.php index 7c86d7dd..b059bf1a 100644 --- a/Extension/HttpKernelRuntime.php +++ b/Extension/HttpKernelRuntime.php @@ -22,8 +22,8 @@ */ final class HttpKernelRuntime { - private $handler; - private $fragmentUriGenerator; + private FragmentHandler $handler; + private ?FragmentUriGeneratorInterface $fragmentUriGenerator; public function __construct(FragmentHandler $handler, FragmentUriGeneratorInterface $fragmentUriGenerator = null) { diff --git a/Extension/LogoutUrlExtension.php b/Extension/LogoutUrlExtension.php index 071b9ff2..5b29b489 100644 --- a/Extension/LogoutUrlExtension.php +++ b/Extension/LogoutUrlExtension.php @@ -22,7 +22,7 @@ */ final class LogoutUrlExtension extends AbstractExtension { - private $generator; + private LogoutUrlGenerator $generator; public function __construct(LogoutUrlGenerator $generator) { diff --git a/Extension/ProfilerExtension.php b/Extension/ProfilerExtension.php index fcc4396f..c9ead796 100644 --- a/Extension/ProfilerExtension.php +++ b/Extension/ProfilerExtension.php @@ -20,8 +20,8 @@ */ final class ProfilerExtension extends BaseProfilerExtension { - private $stopwatch; - private $events; + private ?Stopwatch $stopwatch; + private \SplObjectStorage $events; public function __construct(Profile $profile, Stopwatch $stopwatch = null) { diff --git a/Extension/RoutingExtension.php b/Extension/RoutingExtension.php index 800c22f6..26591fd5 100644 --- a/Extension/RoutingExtension.php +++ b/Extension/RoutingExtension.php @@ -25,7 +25,7 @@ */ final class RoutingExtension extends AbstractExtension { - private $generator; + private UrlGeneratorInterface $generator; public function __construct(UrlGeneratorInterface $generator) { diff --git a/Extension/SecurityExtension.php b/Extension/SecurityExtension.php index 2387520e..3f24b82d 100644 --- a/Extension/SecurityExtension.php +++ b/Extension/SecurityExtension.php @@ -25,9 +25,8 @@ */ final class SecurityExtension extends AbstractExtension { - private $securityChecker; - - private $impersonateUrlGenerator; + private ?AuthorizationCheckerInterface $securityChecker; + private ?ImpersonateUrlGenerator $impersonateUrlGenerator; public function __construct(AuthorizationCheckerInterface $securityChecker = null, ImpersonateUrlGenerator $impersonateUrlGenerator = null) { diff --git a/Extension/SerializerRuntime.php b/Extension/SerializerRuntime.php index dbffa31c..b48be3aa 100644 --- a/Extension/SerializerRuntime.php +++ b/Extension/SerializerRuntime.php @@ -19,7 +19,7 @@ */ final class SerializerRuntime implements RuntimeExtensionInterface { - private $serializer; + private SerializerInterface $serializer; public function __construct(SerializerInterface $serializer) { diff --git a/Extension/StopwatchExtension.php b/Extension/StopwatchExtension.php index 80a25a94..972cd1ac 100644 --- a/Extension/StopwatchExtension.php +++ b/Extension/StopwatchExtension.php @@ -23,8 +23,8 @@ */ final class StopwatchExtension extends AbstractExtension { - private $stopwatch; - private $enabled; + private ?Stopwatch $stopwatch; + private bool $enabled; public function __construct(Stopwatch $stopwatch = null, bool $enabled = true) { diff --git a/Extension/TranslationExtension.php b/Extension/TranslationExtension.php index fa5c66fd..8371291d 100644 --- a/Extension/TranslationExtension.php +++ b/Extension/TranslationExtension.php @@ -34,8 +34,8 @@ class_exists(TranslatorTrait::class); */ final class TranslationExtension extends AbstractExtension { - private $translator; - private $translationNodeVisitor; + private ?TranslatorInterface $translator; + private ?TranslationNodeVisitor $translationNodeVisitor; public function __construct(TranslatorInterface $translator = null, TranslationNodeVisitor $translationNodeVisitor = null) { diff --git a/Extension/WebLinkExtension.php b/Extension/WebLinkExtension.php index 652a7576..de2884a7 100644 --- a/Extension/WebLinkExtension.php +++ b/Extension/WebLinkExtension.php @@ -24,7 +24,7 @@ */ final class WebLinkExtension extends AbstractExtension { - private $requestStack; + private RequestStack $requestStack; public function __construct(RequestStack $requestStack) { diff --git a/Extension/WorkflowExtension.php b/Extension/WorkflowExtension.php index 3ee1ac3d..2b13d291 100644 --- a/Extension/WorkflowExtension.php +++ b/Extension/WorkflowExtension.php @@ -25,7 +25,7 @@ */ final class WorkflowExtension extends AbstractExtension { - private $workflowRegistry; + private Registry $workflowRegistry; public function __construct(Registry $workflowRegistry) { diff --git a/Form/TwigRendererEngine.php b/Form/TwigRendererEngine.php index 32c7de3b..6f408ebb 100644 --- a/Form/TwigRendererEngine.php +++ b/Form/TwigRendererEngine.php @@ -21,15 +21,8 @@ */ class TwigRendererEngine extends AbstractRendererEngine { - /** - * @var Environment - */ - private $environment; - - /** - * @var Template - */ - private $template; + private Environment $environment; + private Template $template; public function __construct(array $defaultThemes, Environment $environment) { @@ -146,17 +139,14 @@ protected function loadResourceForBlockName(string $cacheKey, FormView $view, st protected function loadResourcesFromTheme(string $cacheKey, mixed &$theme) { if (!$theme instanceof Template) { - /* @var Template $theme */ $theme = $this->environment->load($theme)->unwrap(); } - if (null === $this->template) { - // Store the first Template instance that we find so that - // we can call displayBlock() later on. It doesn't matter *which* - // template we use for that, since we pass the used blocks manually - // anyway. - $this->template = $theme; - } + // Store the first Template instance that we find so that + // we can call displayBlock() later on. It doesn't matter *which* + // template we use for that, since we pass the used blocks manually + // anyway. + $this->template ??= $theme; // Use a separate variable for the inheritance traversal, because // theme is a reference and we don't want to change it. diff --git a/Mime/BodyRenderer.php b/Mime/BodyRenderer.php index 47901d31..e1b27046 100644 --- a/Mime/BodyRenderer.php +++ b/Mime/BodyRenderer.php @@ -22,9 +22,9 @@ */ final class BodyRenderer implements BodyRendererInterface { - private $twig; - private $context; - private $converter; + private Environment $twig; + private array $context; + private HtmlConverter $converter; public function __construct(Environment $twig, array $context = []) { @@ -96,7 +96,7 @@ private function getFingerPrint(TemplatedEmail $message): string private function convertHtmlToText(string $html): string { - if (null !== $this->converter) { + if (isset($this->converter)) { return $this->converter->convert($html); } diff --git a/Mime/NotificationEmail.php b/Mime/NotificationEmail.php index 604e10b2..00098cc7 100644 --- a/Mime/NotificationEmail.php +++ b/Mime/NotificationEmail.php @@ -28,8 +28,8 @@ class NotificationEmail extends TemplatedEmail public const IMPORTANCE_MEDIUM = 'medium'; public const IMPORTANCE_LOW = 'low'; - private $theme = 'default'; - private $context = [ + private string $theme = 'default'; + private array $context = [ 'importance' => self::IMPORTANCE_LOW, 'content' => '', 'exception' => false, diff --git a/Mime/TemplatedEmail.php b/Mime/TemplatedEmail.php index aceef09e..083b0077 100644 --- a/Mime/TemplatedEmail.php +++ b/Mime/TemplatedEmail.php @@ -18,9 +18,9 @@ */ class TemplatedEmail extends Email { - private $htmlTemplate; - private $textTemplate; - private $context = []; + private ?string $htmlTemplate = null; + private ?string $textTemplate = null; + private array $context = []; /** * @return $this diff --git a/Mime/WrappedTemplatedEmail.php b/Mime/WrappedTemplatedEmail.php index b5676678..e0b3bef2 100644 --- a/Mime/WrappedTemplatedEmail.php +++ b/Mime/WrappedTemplatedEmail.php @@ -21,8 +21,8 @@ */ final class WrappedTemplatedEmail { - private $twig; - private $message; + private Environment $twig; + private TemplatedEmail $message; public function __construct(Environment $twig, TemplatedEmail $message) { diff --git a/Node/DumpNode.php b/Node/DumpNode.php index 68c00556..8ce2bd8c 100644 --- a/Node/DumpNode.php +++ b/Node/DumpNode.php @@ -19,7 +19,7 @@ */ final class DumpNode extends Node { - private $varPrefix; + private string $varPrefix; public function __construct(string $varPrefix, ?Node $values, int $lineno, string $tag = null) { diff --git a/NodeVisitor/Scope.php b/NodeVisitor/Scope.php index 9ff6882b..efa354d0 100644 --- a/NodeVisitor/Scope.php +++ b/NodeVisitor/Scope.php @@ -16,9 +16,9 @@ */ class Scope { - private $parent; - private $data = []; - private $left = false; + private ?self $parent; + private array $data = []; + private bool $left = false; public function __construct(self $parent = null) { diff --git a/NodeVisitor/TranslationDefaultDomainNodeVisitor.php b/NodeVisitor/TranslationDefaultDomainNodeVisitor.php index 213365ed..61c8b5ff 100644 --- a/NodeVisitor/TranslationDefaultDomainNodeVisitor.php +++ b/NodeVisitor/TranslationDefaultDomainNodeVisitor.php @@ -30,7 +30,7 @@ */ final class TranslationDefaultDomainNodeVisitor extends AbstractNodeVisitor { - private $scope; + private Scope $scope; public function __construct() { diff --git a/NodeVisitor/TranslationNodeVisitor.php b/NodeVisitor/TranslationNodeVisitor.php index d42245e2..c8bee150 100644 --- a/NodeVisitor/TranslationNodeVisitor.php +++ b/NodeVisitor/TranslationNodeVisitor.php @@ -29,8 +29,8 @@ final class TranslationNodeVisitor extends AbstractNodeVisitor { public const UNDEFINED_DOMAIN = '_undefined'; - private $enabled = false; - private $messages = []; + private bool $enabled = false; + private array $messages = []; public function enable(): void { diff --git a/Translation/TwigExtractor.php b/Translation/TwigExtractor.php index e73ae42f..b53ab80d 100644 --- a/Translation/TwigExtractor.php +++ b/Translation/TwigExtractor.php @@ -29,19 +29,15 @@ class TwigExtractor extends AbstractFileExtractor implements ExtractorInterface { /** * Default domain for found messages. - * - * @var string */ - private $defaultDomain = 'messages'; + private string $defaultDomain = 'messages'; /** * Prefix for found message. - * - * @var string */ - private $prefix = ''; + private string $prefix = ''; - private $twig; + private Environment $twig; public function __construct(Environment $twig) { From a6b4bd60b176c4e8cf6438a1ac5447944a2bd103 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Thu, 9 Sep 2021 14:56:10 +0200 Subject: [PATCH 015/167] Fix return types --- Mime/NotificationEmail.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mime/NotificationEmail.php b/Mime/NotificationEmail.php index b4e3bdf1..084dc348 100644 --- a/Mime/NotificationEmail.php +++ b/Mime/NotificationEmail.php @@ -72,7 +72,7 @@ public static function asPublicEmail(Headers $headers = null, AbstractPart $body /** * @return $this */ - public function markAsPublic(): self + public function markAsPublic(): static { $this->context['importance'] = null; $this->context['footer_text'] = null; From 1eb46f121911e2c499f42718cb6be9543492753a Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Thu, 30 Sep 2021 16:50:34 +0200 Subject: [PATCH 016/167] Use #[AsCommand] to describe commands --- Command/DebugCommand.php | 6 ++---- Command/LintCommand.php | 6 ++---- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/Command/DebugCommand.php b/Command/DebugCommand.php index 902c434c..52ee143d 100644 --- a/Command/DebugCommand.php +++ b/Command/DebugCommand.php @@ -11,6 +11,7 @@ namespace Symfony\Bridge\Twig\Command; +use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Exception\InvalidArgumentException; use Symfony\Component\Console\Formatter\OutputFormatter; @@ -30,11 +31,9 @@ * * @author Jordi Boggiano */ +#[AsCommand(name: 'debug:twig', description: 'Show a list of twig functions, filters, globals and tests')] class DebugCommand extends Command { - protected static $defaultName = 'debug:twig'; - protected static $defaultDescription = 'Show a list of twig functions, filters, globals and tests'; - private Environment $twig; private ?string $projectDir; private array $bundlesMetadata; @@ -66,7 +65,6 @@ protected function configure() new InputOption('filter', null, InputOption::VALUE_REQUIRED, 'Show details for all entries matching this filter'), new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (text or json)', 'text'), ]) - ->setDescription(self::$defaultDescription) ->setHelp(<<<'EOF' The %command.name% command outputs a list of twig functions, filters, globals and tests. diff --git a/Command/LintCommand.php b/Command/LintCommand.php index dfac1242..12b3dd96 100644 --- a/Command/LintCommand.php +++ b/Command/LintCommand.php @@ -11,6 +11,7 @@ namespace Symfony\Bridge\Twig\Command; +use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\CI\GithubActionReporter; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Exception\InvalidArgumentException; @@ -33,11 +34,9 @@ * @author Marc Weistroff * @author Jérôme Tamarelle */ +#[AsCommand(name: 'lint:twig', description: 'Lint a Twig template and outputs encountered errors')] class LintCommand extends Command { - protected static $defaultName = 'lint:twig'; - protected static $defaultDescription = 'Lint a Twig template and outputs encountered errors'; - private Environment $twig; private string $format; @@ -51,7 +50,6 @@ public function __construct(Environment $twig) protected function configure() { $this - ->setDescription(self::$defaultDescription) ->addOption('format', null, InputOption::VALUE_REQUIRED, 'The output format') ->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') From e6c156d2bcda6bb23e8033a2361243005ecec369 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 5 Oct 2021 17:32:15 +0200 Subject: [PATCH 017/167] Add type to final/internal public/protected properties --- TokenParser/StopwatchTokenParser.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TokenParser/StopwatchTokenParser.php b/TokenParser/StopwatchTokenParser.php index a70e94b8..f0b3ac2e 100644 --- a/TokenParser/StopwatchTokenParser.php +++ b/TokenParser/StopwatchTokenParser.php @@ -24,7 +24,7 @@ */ final class StopwatchTokenParser extends AbstractTokenParser { - protected $stopwatchIsAvailable; + private bool $stopwatchIsAvailable; public function __construct(bool $stopwatchIsAvailable) { From c99666abbab82023367b94770af089bd55b29e88 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Tue, 7 Dec 2021 12:27:08 +0100 Subject: [PATCH 018/167] Remove FQCN type hints on properties --- AppVariable.php | 4 ++-- Command/DebugCommand.php | 4 ++-- Command/LintCommand.php | 2 +- DataCollector/TwigDataCollector.php | 4 ++-- ErrorRenderer/TwigErrorRenderer.php | 4 ++-- Extension/AssetExtension.php | 2 +- Extension/CsrfRuntime.php | 2 +- Extension/DumpExtension.php | 4 ++-- Extension/FormExtension.php | 2 +- Extension/HttpFoundationExtension.php | 2 +- Extension/HttpKernelRuntime.php | 4 ++-- Extension/LogoutUrlExtension.php | 2 +- Extension/ProfilerExtension.php | 2 +- Extension/RoutingExtension.php | 2 +- Extension/SecurityExtension.php | 4 ++-- Extension/SerializerRuntime.php | 2 +- Extension/StopwatchExtension.php | 2 +- Extension/TranslationExtension.php | 4 ++-- Extension/WebLinkExtension.php | 2 +- Extension/WorkflowExtension.php | 2 +- Form/TwigRendererEngine.php | 4 ++-- Mime/BodyRenderer.php | 4 ++-- Mime/WrappedTemplatedEmail.php | 4 ++-- NodeVisitor/TranslationDefaultDomainNodeVisitor.php | 2 +- Translation/TwigExtractor.php | 2 +- 25 files changed, 36 insertions(+), 36 deletions(-) diff --git a/AppVariable.php b/AppVariable.php index d21f6448..a6103a25 100644 --- a/AppVariable.php +++ b/AppVariable.php @@ -25,8 +25,8 @@ */ class AppVariable { - private TokenStorageInterface $tokenStorage; - private RequestStack $requestStack; + private $tokenStorage; + private $requestStack; private string $environment; private bool $debug; diff --git a/Command/DebugCommand.php b/Command/DebugCommand.php index 542ec782..e78723be 100644 --- a/Command/DebugCommand.php +++ b/Command/DebugCommand.php @@ -36,7 +36,7 @@ #[AsCommand(name: 'debug:twig', description: 'Show a list of twig functions, filters, globals and tests')] class DebugCommand extends Command { - private Environment $twig; + private $twig; private ?string $projectDir; private array $bundlesMetadata; private ?string $twigDefaultPath; @@ -46,7 +46,7 @@ class DebugCommand extends Command */ private array $filesystemLoaders; - private ?FileLinkFormatter $fileLinkFormatter; + private $fileLinkFormatter; public function __construct(Environment $twig, string $projectDir = null, array $bundlesMetadata = [], string $twigDefaultPath = null, FileLinkFormatter $fileLinkFormatter = null) { diff --git a/Command/LintCommand.php b/Command/LintCommand.php index 6e73b9c3..b7295c87 100644 --- a/Command/LintCommand.php +++ b/Command/LintCommand.php @@ -39,7 +39,7 @@ #[AsCommand(name: 'lint:twig', description: 'Lint a Twig template and outputs encountered errors')] class LintCommand extends Command { - private Environment $twig; + private $twig; private string $format; public function __construct(Environment $twig) diff --git a/DataCollector/TwigDataCollector.php b/DataCollector/TwigDataCollector.php index f41aa479..ed27f6b8 100644 --- a/DataCollector/TwigDataCollector.php +++ b/DataCollector/TwigDataCollector.php @@ -28,8 +28,8 @@ */ class TwigDataCollector extends DataCollector implements LateDataCollectorInterface { - private Profile $profile; - private ?Environment $twig; + private $profile; + private $twig; private array $computed; public function __construct(Profile $profile, Environment $twig = null) diff --git a/ErrorRenderer/TwigErrorRenderer.php b/ErrorRenderer/TwigErrorRenderer.php index ef3d433b..50cc9cc9 100644 --- a/ErrorRenderer/TwigErrorRenderer.php +++ b/ErrorRenderer/TwigErrorRenderer.php @@ -25,8 +25,8 @@ */ class TwigErrorRenderer implements ErrorRendererInterface { - private Environment $twig; - private HtmlErrorRenderer $fallbackErrorRenderer; + private $twig; + private $fallbackErrorRenderer; private \Closure|bool $debug; /** diff --git a/Extension/AssetExtension.php b/Extension/AssetExtension.php index 35c69b69..694821f7 100644 --- a/Extension/AssetExtension.php +++ b/Extension/AssetExtension.php @@ -22,7 +22,7 @@ */ final class AssetExtension extends AbstractExtension { - private Packages $packages; + private $packages; public function __construct(Packages $packages) { diff --git a/Extension/CsrfRuntime.php b/Extension/CsrfRuntime.php index 216d9c92..c3d5da64 100644 --- a/Extension/CsrfRuntime.php +++ b/Extension/CsrfRuntime.php @@ -19,7 +19,7 @@ */ final class CsrfRuntime { - private CsrfTokenManagerInterface $csrfTokenManager; + private $csrfTokenManager; public function __construct(CsrfTokenManagerInterface $csrfTokenManager) { diff --git a/Extension/DumpExtension.php b/Extension/DumpExtension.php index 1ce6593a..46ad8eaf 100644 --- a/Extension/DumpExtension.php +++ b/Extension/DumpExtension.php @@ -26,8 +26,8 @@ */ final class DumpExtension extends AbstractExtension { - private ClonerInterface $cloner; - private ?HtmlDumper $dumper; + private $cloner; + private $dumper; public function __construct(ClonerInterface $cloner, HtmlDumper $dumper = null) { diff --git a/Extension/FormExtension.php b/Extension/FormExtension.php index 6e42928a..f7d82e04 100644 --- a/Extension/FormExtension.php +++ b/Extension/FormExtension.php @@ -30,7 +30,7 @@ */ final class FormExtension extends AbstractExtension { - private ?TranslatorInterface $translator; + private $translator; public function __construct(TranslatorInterface $translator = null) { diff --git a/Extension/HttpFoundationExtension.php b/Extension/HttpFoundationExtension.php index 365e1173..a9ee05c4 100644 --- a/Extension/HttpFoundationExtension.php +++ b/Extension/HttpFoundationExtension.php @@ -23,7 +23,7 @@ */ final class HttpFoundationExtension extends AbstractExtension { - private UrlHelper $urlHelper; + private $urlHelper; public function __construct(UrlHelper $urlHelper) { diff --git a/Extension/HttpKernelRuntime.php b/Extension/HttpKernelRuntime.php index b059bf1a..7c86d7dd 100644 --- a/Extension/HttpKernelRuntime.php +++ b/Extension/HttpKernelRuntime.php @@ -22,8 +22,8 @@ */ final class HttpKernelRuntime { - private FragmentHandler $handler; - private ?FragmentUriGeneratorInterface $fragmentUriGenerator; + private $handler; + private $fragmentUriGenerator; public function __construct(FragmentHandler $handler, FragmentUriGeneratorInterface $fragmentUriGenerator = null) { diff --git a/Extension/LogoutUrlExtension.php b/Extension/LogoutUrlExtension.php index 5b29b489..071b9ff2 100644 --- a/Extension/LogoutUrlExtension.php +++ b/Extension/LogoutUrlExtension.php @@ -22,7 +22,7 @@ */ final class LogoutUrlExtension extends AbstractExtension { - private LogoutUrlGenerator $generator; + private $generator; public function __construct(LogoutUrlGenerator $generator) { diff --git a/Extension/ProfilerExtension.php b/Extension/ProfilerExtension.php index f63aa41c..cba3ab8d 100644 --- a/Extension/ProfilerExtension.php +++ b/Extension/ProfilerExtension.php @@ -21,7 +21,7 @@ */ final class ProfilerExtension extends BaseProfilerExtension { - private ?Stopwatch $stopwatch; + private $stopwatch; /** * @var \SplObjectStorage diff --git a/Extension/RoutingExtension.php b/Extension/RoutingExtension.php index 26591fd5..800c22f6 100644 --- a/Extension/RoutingExtension.php +++ b/Extension/RoutingExtension.php @@ -25,7 +25,7 @@ */ final class RoutingExtension extends AbstractExtension { - private UrlGeneratorInterface $generator; + private $generator; public function __construct(UrlGeneratorInterface $generator) { diff --git a/Extension/SecurityExtension.php b/Extension/SecurityExtension.php index 3f24b82d..aedeefdc 100644 --- a/Extension/SecurityExtension.php +++ b/Extension/SecurityExtension.php @@ -25,8 +25,8 @@ */ final class SecurityExtension extends AbstractExtension { - private ?AuthorizationCheckerInterface $securityChecker; - private ?ImpersonateUrlGenerator $impersonateUrlGenerator; + private $securityChecker; + private $impersonateUrlGenerator; public function __construct(AuthorizationCheckerInterface $securityChecker = null, ImpersonateUrlGenerator $impersonateUrlGenerator = null) { diff --git a/Extension/SerializerRuntime.php b/Extension/SerializerRuntime.php index b48be3aa..dbffa31c 100644 --- a/Extension/SerializerRuntime.php +++ b/Extension/SerializerRuntime.php @@ -19,7 +19,7 @@ */ final class SerializerRuntime implements RuntimeExtensionInterface { - private SerializerInterface $serializer; + private $serializer; public function __construct(SerializerInterface $serializer) { diff --git a/Extension/StopwatchExtension.php b/Extension/StopwatchExtension.php index 972cd1ac..635216f2 100644 --- a/Extension/StopwatchExtension.php +++ b/Extension/StopwatchExtension.php @@ -23,7 +23,7 @@ */ final class StopwatchExtension extends AbstractExtension { - private ?Stopwatch $stopwatch; + private $stopwatch; private bool $enabled; public function __construct(Stopwatch $stopwatch = null, bool $enabled = true) diff --git a/Extension/TranslationExtension.php b/Extension/TranslationExtension.php index 8371291d..fa5c66fd 100644 --- a/Extension/TranslationExtension.php +++ b/Extension/TranslationExtension.php @@ -34,8 +34,8 @@ class_exists(TranslatorTrait::class); */ final class TranslationExtension extends AbstractExtension { - private ?TranslatorInterface $translator; - private ?TranslationNodeVisitor $translationNodeVisitor; + private $translator; + private $translationNodeVisitor; public function __construct(TranslatorInterface $translator = null, TranslationNodeVisitor $translationNodeVisitor = null) { diff --git a/Extension/WebLinkExtension.php b/Extension/WebLinkExtension.php index de2884a7..652a7576 100644 --- a/Extension/WebLinkExtension.php +++ b/Extension/WebLinkExtension.php @@ -24,7 +24,7 @@ */ final class WebLinkExtension extends AbstractExtension { - private RequestStack $requestStack; + private $requestStack; public function __construct(RequestStack $requestStack) { diff --git a/Extension/WorkflowExtension.php b/Extension/WorkflowExtension.php index 2b13d291..3ee1ac3d 100644 --- a/Extension/WorkflowExtension.php +++ b/Extension/WorkflowExtension.php @@ -25,7 +25,7 @@ */ final class WorkflowExtension extends AbstractExtension { - private Registry $workflowRegistry; + private $workflowRegistry; public function __construct(Registry $workflowRegistry) { diff --git a/Form/TwigRendererEngine.php b/Form/TwigRendererEngine.php index 6f408ebb..693ee812 100644 --- a/Form/TwigRendererEngine.php +++ b/Form/TwigRendererEngine.php @@ -21,8 +21,8 @@ */ class TwigRendererEngine extends AbstractRendererEngine { - private Environment $environment; - private Template $template; + private $environment; + private $template; public function __construct(array $defaultThemes, Environment $environment) { diff --git a/Mime/BodyRenderer.php b/Mime/BodyRenderer.php index e1b27046..e50efa06 100644 --- a/Mime/BodyRenderer.php +++ b/Mime/BodyRenderer.php @@ -22,9 +22,9 @@ */ final class BodyRenderer implements BodyRendererInterface { - private Environment $twig; + private $twig; private array $context; - private HtmlConverter $converter; + private $converter; public function __construct(Environment $twig, array $context = []) { diff --git a/Mime/WrappedTemplatedEmail.php b/Mime/WrappedTemplatedEmail.php index e0b3bef2..b5676678 100644 --- a/Mime/WrappedTemplatedEmail.php +++ b/Mime/WrappedTemplatedEmail.php @@ -21,8 +21,8 @@ */ final class WrappedTemplatedEmail { - private Environment $twig; - private TemplatedEmail $message; + private $twig; + private $message; public function __construct(Environment $twig, TemplatedEmail $message) { diff --git a/NodeVisitor/TranslationDefaultDomainNodeVisitor.php b/NodeVisitor/TranslationDefaultDomainNodeVisitor.php index 61c8b5ff..213365ed 100644 --- a/NodeVisitor/TranslationDefaultDomainNodeVisitor.php +++ b/NodeVisitor/TranslationDefaultDomainNodeVisitor.php @@ -30,7 +30,7 @@ */ final class TranslationDefaultDomainNodeVisitor extends AbstractNodeVisitor { - private Scope $scope; + private $scope; public function __construct() { diff --git a/Translation/TwigExtractor.php b/Translation/TwigExtractor.php index b53ab80d..1ec9a13d 100644 --- a/Translation/TwigExtractor.php +++ b/Translation/TwigExtractor.php @@ -37,7 +37,7 @@ class TwigExtractor extends AbstractFileExtractor implements ExtractorInterface */ private string $prefix = ''; - private Environment $twig; + private $twig; public function __construct(Environment $twig) { From fee75ad949422b72ef2167a848c617a458252af6 Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Mon, 13 Dec 2021 16:44:47 +0100 Subject: [PATCH 019/167] Make use of the nullsafe operator --- Command/LintCommand.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Command/LintCommand.php b/Command/LintCommand.php index 6e73b9c3..dfe66054 100644 --- a/Command/LintCommand.php +++ b/Command/LintCommand.php @@ -230,9 +230,7 @@ private function renderException(SymfonyStyle $output, string $template, Error $ { $line = $exception->getTemplateLine(); - if ($githubReporter) { - $githubReporter->error($exception->getRawMessage(), $file, $line <= 0 ? null : $line); - } + $githubReporter?->error($exception->getRawMessage(), $file, $line <= 0 ? null : $line); if ($file) { $output->text(sprintf(' ERROR in %s (line %s)', $file, $line)); From 52217ee6ca95a94177f7cfc584d8171784623209 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Thu, 16 Dec 2021 23:13:01 +0100 Subject: [PATCH 020/167] [6.0] cs fixes --- UndefinedCallableHandler.php | 6 ------ 1 file changed, 6 deletions(-) diff --git a/UndefinedCallableHandler.php b/UndefinedCallableHandler.php index 360a386f..30dd92ff 100644 --- a/UndefinedCallableHandler.php +++ b/UndefinedCallableHandler.php @@ -68,9 +68,6 @@ class UndefinedCallableHandler 'workflow' => 'enable "framework.workflows"', ]; - /** - * @return TwigFilter|false - */ public static function onUndefinedFilter(string $name): TwigFilter|false { if (!isset(self::FILTER_COMPONENTS[$name])) { @@ -80,9 +77,6 @@ public static function onUndefinedFilter(string $name): TwigFilter|false throw new SyntaxError(self::onUndefined($name, 'filter', self::FILTER_COMPONENTS[$name])); } - /** - * @return TwigFunction|false - */ public static function onUndefinedFunction(string $name): TwigFunction|false { if (!isset(self::FUNCTION_COMPONENTS[$name])) { From 262d7dc6ac2760c276702f2d56b2e45d4aa9b718 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Thu, 16 Dec 2021 11:11:51 +0100 Subject: [PATCH 021/167] Add more nullsafe operators --- AppVariable.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AppVariable.php b/AppVariable.php index d21f6448..b46646b5 100644 --- a/AppVariable.php +++ b/AppVariable.php @@ -100,7 +100,7 @@ public function getSession(): ?Session } $request = $this->getRequest(); - return $request && $request->hasSession() ? $request->getSession() : null; + return $request?->hasSession() ? $request->getSession() : null; } /** From 546edaf49cfa0cfe5067abd06da384fec225f77e Mon Sep 17 00:00:00 2001 From: Thomas Calvet Date: Tue, 15 Feb 2022 16:42:18 +0100 Subject: [PATCH 022/167] Leverage the match expression --- Command/LintCommand.php | 16 ++++++---------- Mime/NotificationEmail.php | 17 ++++++----------- 2 files changed, 12 insertions(+), 21 deletions(-) diff --git a/Command/LintCommand.php b/Command/LintCommand.php index dfe66054..6aede16f 100644 --- a/Command/LintCommand.php +++ b/Command/LintCommand.php @@ -172,16 +172,12 @@ private function validate(string $template, string $file): array private function display(InputInterface $input, OutputInterface $output, SymfonyStyle $io, array $files) { - switch ($this->format) { - case 'txt': - return $this->displayTxt($output, $io, $files); - case 'json': - return $this->displayJson($output, $files); - case 'github': - return $this->displayTxt($output, $io, $files, true); - default: - throw new InvalidArgumentException(sprintf('The format "%s" is not supported.', $input->getOption('format'))); - } + return match ($this->format) { + 'txt' => $this->displayTxt($output, $io, $files), + 'json' => $this->displayJson($output, $files), + 'github' => $this->displayTxt($output, $io, $files, true), + default => throw new InvalidArgumentException(sprintf('The format "%s" is not supported.', $input->getOption('format'))), + }; } private function displayTxt(OutputInterface $output, SymfonyStyle $io, array $filesInfo, bool $errorAsGithubAnnotations = false) diff --git a/Mime/NotificationEmail.php b/Mime/NotificationEmail.php index 084dc348..45cd06b2 100644 --- a/Mime/NotificationEmail.php +++ b/Mime/NotificationEmail.php @@ -192,17 +192,12 @@ public function getPreparedHeaders(): Headers private function determinePriority(string $importance): int { - switch ($importance) { - case self::IMPORTANCE_URGENT: - return self::PRIORITY_HIGHEST; - case self::IMPORTANCE_HIGH: - return self::PRIORITY_HIGH; - case self::IMPORTANCE_MEDIUM: - return self::PRIORITY_NORMAL; - case self::IMPORTANCE_LOW: - default: - return self::PRIORITY_LOW; - } + return match ($importance) { + self::IMPORTANCE_URGENT => self::PRIORITY_HIGHEST, + self::IMPORTANCE_HIGH => self::PRIORITY_HIGH, + self::IMPORTANCE_MEDIUM => self::PRIORITY_NORMAL, + default => self::PRIORITY_LOW, + }; } private function getExceptionAsString(\Throwable|FlattenException $exception): string From df3275ec1ecb1a12f54acd61443a649008461529 Mon Sep 17 00:00:00 2001 From: Thomas Calvet Date: Sun, 20 Feb 2022 16:28:15 +0100 Subject: [PATCH 023/167] Use ::class instead of FQCN --- DataCollector/TwigDataCollector.php | 2 +- Extension/FormExtension.php | 27 +++++++++++++++------------ Translation/TwigExtractor.php | 3 ++- 3 files changed, 18 insertions(+), 14 deletions(-) diff --git a/DataCollector/TwigDataCollector.php b/DataCollector/TwigDataCollector.php index f41aa479..af579f24 100644 --- a/DataCollector/TwigDataCollector.php +++ b/DataCollector/TwigDataCollector.php @@ -140,7 +140,7 @@ public function getHtmlCallGraph() public function getProfile() { - return $this->profile ??= unserialize($this->data['profile'], ['allowed_classes' => ['Twig_Profiler_Profile', 'Twig\Profiler\Profile']]); + return $this->profile ??= unserialize($this->data['profile'], ['allowed_classes' => ['Twig_Profiler_Profile', Profile::class]]); } private function getComputedData(string $index) diff --git a/Extension/FormExtension.php b/Extension/FormExtension.php index 6e42928a..fd9eaf96 100644 --- a/Extension/FormExtension.php +++ b/Extension/FormExtension.php @@ -11,10 +11,13 @@ namespace Symfony\Bridge\Twig\Extension; +use Symfony\Bridge\Twig\Node\RenderBlockNode; +use Symfony\Bridge\Twig\Node\SearchAndRenderBlockNode; use Symfony\Bridge\Twig\TokenParser\FormThemeTokenParser; use Symfony\Component\Form\ChoiceList\View\ChoiceGroupView; use Symfony\Component\Form\ChoiceList\View\ChoiceView; use Symfony\Component\Form\FormError; +use Symfony\Component\Form\FormRenderer; use Symfony\Component\Form\FormView; use Symfony\Contracts\Translation\TranslatorInterface; use Twig\Extension\AbstractExtension; @@ -54,16 +57,16 @@ public function getTokenParsers(): array public function getFunctions(): array { return [ - new TwigFunction('form_widget', null, ['node_class' => 'Symfony\Bridge\Twig\Node\SearchAndRenderBlockNode', 'is_safe' => ['html']]), - new TwigFunction('form_errors', null, ['node_class' => 'Symfony\Bridge\Twig\Node\SearchAndRenderBlockNode', 'is_safe' => ['html']]), - new TwigFunction('form_label', null, ['node_class' => 'Symfony\Bridge\Twig\Node\SearchAndRenderBlockNode', 'is_safe' => ['html']]), - new TwigFunction('form_help', null, ['node_class' => 'Symfony\Bridge\Twig\Node\SearchAndRenderBlockNode', 'is_safe' => ['html']]), - new TwigFunction('form_row', null, ['node_class' => 'Symfony\Bridge\Twig\Node\SearchAndRenderBlockNode', 'is_safe' => ['html']]), - new TwigFunction('form_rest', null, ['node_class' => 'Symfony\Bridge\Twig\Node\SearchAndRenderBlockNode', 'is_safe' => ['html']]), - new TwigFunction('form', null, ['node_class' => 'Symfony\Bridge\Twig\Node\RenderBlockNode', 'is_safe' => ['html']]), - new TwigFunction('form_start', null, ['node_class' => 'Symfony\Bridge\Twig\Node\RenderBlockNode', 'is_safe' => ['html']]), - new TwigFunction('form_end', null, ['node_class' => 'Symfony\Bridge\Twig\Node\RenderBlockNode', 'is_safe' => ['html']]), - new TwigFunction('csrf_token', ['Symfony\Component\Form\FormRenderer', 'renderCsrfToken']), + new TwigFunction('form_widget', null, ['node_class' => SearchAndRenderBlockNode::class, 'is_safe' => ['html']]), + new TwigFunction('form_errors', null, ['node_class' => SearchAndRenderBlockNode::class, 'is_safe' => ['html']]), + new TwigFunction('form_label', null, ['node_class' => SearchAndRenderBlockNode::class, 'is_safe' => ['html']]), + new TwigFunction('form_help', null, ['node_class' => SearchAndRenderBlockNode::class, 'is_safe' => ['html']]), + new TwigFunction('form_row', null, ['node_class' => SearchAndRenderBlockNode::class, 'is_safe' => ['html']]), + new TwigFunction('form_rest', null, ['node_class' => SearchAndRenderBlockNode::class, 'is_safe' => ['html']]), + new TwigFunction('form', null, ['node_class' => RenderBlockNode::class, 'is_safe' => ['html']]), + new TwigFunction('form_start', null, ['node_class' => RenderBlockNode::class, 'is_safe' => ['html']]), + new TwigFunction('form_end', null, ['node_class' => RenderBlockNode::class, 'is_safe' => ['html']]), + new TwigFunction('csrf_token', [FormRenderer::class, 'renderCsrfToken']), new TwigFunction('form_parent', 'Symfony\Bridge\Twig\Extension\twig_get_form_parent'), new TwigFunction('field_name', [$this, 'getFieldName']), new TwigFunction('field_value', [$this, 'getFieldValue']), @@ -80,8 +83,8 @@ public function getFunctions(): array public function getFilters(): array { return [ - new TwigFilter('humanize', ['Symfony\Component\Form\FormRenderer', 'humanize']), - new TwigFilter('form_encode_currency', ['Symfony\Component\Form\FormRenderer', 'encodeCurrency'], ['is_safe' => ['html'], 'needs_environment' => true]), + new TwigFilter('humanize', [FormRenderer::class, 'humanize']), + new TwigFilter('form_encode_currency', [FormRenderer::class, 'encodeCurrency'], ['is_safe' => ['html'], 'needs_environment' => true]), ]; } diff --git a/Translation/TwigExtractor.php b/Translation/TwigExtractor.php index b53ab80d..7625498f 100644 --- a/Translation/TwigExtractor.php +++ b/Translation/TwigExtractor.php @@ -11,6 +11,7 @@ namespace Symfony\Bridge\Twig\Translation; +use Symfony\Bridge\Twig\Extension\TranslationExtension; use Symfony\Component\Finder\Finder; use Symfony\Component\Translation\Extractor\AbstractFileExtractor; use Symfony\Component\Translation\Extractor\ExtractorInterface; @@ -68,7 +69,7 @@ public function setPrefix(string $prefix) protected function extractTemplate(string $template, MessageCatalogue $catalogue) { - $visitor = $this->twig->getExtension('Symfony\Bridge\Twig\Extension\TranslationExtension')->getTranslationNodeVisitor(); + $visitor = $this->twig->getExtension(TranslationExtension::class)->getTranslationNodeVisitor(); $visitor->enable(); $this->twig->parse($this->twig->tokenize(new Source($template, ''))); From 644db5cf5334c655bdfc4a797ad79734848871d7 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Wed, 9 Feb 2022 15:00:38 +0100 Subject: [PATCH 024/167] Bump minimum version of PHP to 8.1 --- Command/DebugCommand.php | 2 +- ErrorRenderer/TwigErrorRenderer.php | 2 +- Extension/AssetExtension.php | 4 ++-- Extension/CodeExtension.php | 20 ++++++++++---------- Extension/DumpExtension.php | 2 +- Extension/ExpressionExtension.php | 2 +- Extension/FormExtension.php | 12 ++++++------ Extension/HttpFoundationExtension.php | 4 ++-- Extension/LogoutUrlExtension.php | 4 ++-- Extension/RoutingExtension.php | 4 ++-- Extension/SecurityExtension.php | 6 +++--- Extension/TranslationExtension.php | 4 ++-- Extension/WebLinkExtension.php | 12 ++++++------ Extension/WorkflowExtension.php | 14 +++++++------- Extension/YamlExtension.php | 4 ++-- Tests/Translation/TwigExtractorTest.php | 1 - TokenParser/StopwatchTokenParser.php | 2 +- TokenParser/TransTokenParser.php | 2 +- composer.json | 2 +- 19 files changed, 51 insertions(+), 52 deletions(-) diff --git a/Command/DebugCommand.php b/Command/DebugCommand.php index 542ec782..d5e64859 100644 --- a/Command/DebugCommand.php +++ b/Command/DebugCommand.php @@ -294,7 +294,7 @@ private function getLoaderPaths(string $name = null): array } foreach ($namespaces as $namespace) { - $paths = array_map([$this, 'getRelativePath'], $loader->getPaths($namespace)); + $paths = array_map($this->getRelativePath(...), $loader->getPaths($namespace)); if (FilesystemLoader::MAIN_NAMESPACE === $namespace) { $namespace = '(None)'; diff --git a/ErrorRenderer/TwigErrorRenderer.php b/ErrorRenderer/TwigErrorRenderer.php index ef3d433b..4dde73f6 100644 --- a/ErrorRenderer/TwigErrorRenderer.php +++ b/ErrorRenderer/TwigErrorRenderer.php @@ -36,7 +36,7 @@ public function __construct(Environment $twig, HtmlErrorRenderer $fallbackErrorR { $this->twig = $twig; $this->fallbackErrorRenderer = $fallbackErrorRenderer ?? new HtmlErrorRenderer(); - $this->debug = !\is_callable($debug) || $debug instanceof \Closure ? $debug : \Closure::fromCallable($debug); + $this->debug = \is_bool($debug) ? $debug : $debug(...); } /** diff --git a/Extension/AssetExtension.php b/Extension/AssetExtension.php index 35c69b69..3e12371b 100644 --- a/Extension/AssetExtension.php +++ b/Extension/AssetExtension.php @@ -35,8 +35,8 @@ public function __construct(Packages $packages) public function getFunctions(): array { return [ - new TwigFunction('asset', [$this, 'getAssetUrl']), - new TwigFunction('asset_version', [$this, 'getAssetVersion']), + new TwigFunction('asset', $this->getAssetUrl(...)), + new TwigFunction('asset_version', $this->getAssetVersion(...)), ]; } diff --git a/Extension/CodeExtension.php b/Extension/CodeExtension.php index 62573d9f..7287ec8e 100644 --- a/Extension/CodeExtension.php +++ b/Extension/CodeExtension.php @@ -39,16 +39,16 @@ public function __construct(string|FileLinkFormatter $fileLinkFormat, string $pr public function getFilters(): array { return [ - new TwigFilter('abbr_class', [$this, 'abbrClass'], ['is_safe' => ['html']]), - new TwigFilter('abbr_method', [$this, 'abbrMethod'], ['is_safe' => ['html']]), - new TwigFilter('format_args', [$this, 'formatArgs'], ['is_safe' => ['html']]), - new TwigFilter('format_args_as_text', [$this, 'formatArgsAsText']), - new TwigFilter('file_excerpt', [$this, 'fileExcerpt'], ['is_safe' => ['html']]), - new TwigFilter('format_file', [$this, 'formatFile'], ['is_safe' => ['html']]), - new TwigFilter('format_file_from_text', [$this, 'formatFileFromText'], ['is_safe' => ['html']]), - new TwigFilter('format_log_message', [$this, 'formatLogMessage'], ['is_safe' => ['html']]), - new TwigFilter('file_link', [$this, 'getFileLink']), - new TwigFilter('file_relative', [$this, 'getFileRelative']), + new TwigFilter('abbr_class', $this->abbrClass(...), ['is_safe' => ['html']]), + new TwigFilter('abbr_method', $this->abbrMethod(...), ['is_safe' => ['html']]), + new TwigFilter('format_args', $this->formatArgs(...), ['is_safe' => ['html']]), + new TwigFilter('format_args_as_text', $this->formatArgsAsText(...)), + new TwigFilter('file_excerpt', $this->fileExcerpt(...), ['is_safe' => ['html']]), + new TwigFilter('format_file', $this->formatFile(...), ['is_safe' => ['html']]), + new TwigFilter('format_file_from_text', $this->formatFileFromText(...), ['is_safe' => ['html']]), + new TwigFilter('format_log_message', $this->formatLogMessage(...), ['is_safe' => ['html']]), + new TwigFilter('file_link', $this->getFileLink(...)), + new TwigFilter('file_relative', $this->getFileRelative(...)), ]; } diff --git a/Extension/DumpExtension.php b/Extension/DumpExtension.php index 1ce6593a..910fece8 100644 --- a/Extension/DumpExtension.php +++ b/Extension/DumpExtension.php @@ -41,7 +41,7 @@ public function __construct(ClonerInterface $cloner, HtmlDumper $dumper = null) public function getFunctions(): array { return [ - new TwigFunction('dump', [$this, 'dump'], ['is_safe' => ['html'], 'needs_context' => true, 'needs_environment' => true]), + new TwigFunction('dump', $this->dump(...), ['is_safe' => ['html'], 'needs_context' => true, 'needs_environment' => true]), ]; } diff --git a/Extension/ExpressionExtension.php b/Extension/ExpressionExtension.php index 8d2a35c9..a3049962 100644 --- a/Extension/ExpressionExtension.php +++ b/Extension/ExpressionExtension.php @@ -28,7 +28,7 @@ final class ExpressionExtension extends AbstractExtension public function getFunctions(): array { return [ - new TwigFunction('expression', [$this, 'createExpression']), + new TwigFunction('expression', $this->createExpression(...)), ]; } diff --git a/Extension/FormExtension.php b/Extension/FormExtension.php index fd9eaf96..f3484ca7 100644 --- a/Extension/FormExtension.php +++ b/Extension/FormExtension.php @@ -68,12 +68,12 @@ public function getFunctions(): array new TwigFunction('form_end', null, ['node_class' => RenderBlockNode::class, 'is_safe' => ['html']]), new TwigFunction('csrf_token', [FormRenderer::class, 'renderCsrfToken']), new TwigFunction('form_parent', 'Symfony\Bridge\Twig\Extension\twig_get_form_parent'), - new TwigFunction('field_name', [$this, 'getFieldName']), - new TwigFunction('field_value', [$this, 'getFieldValue']), - new TwigFunction('field_label', [$this, 'getFieldLabel']), - new TwigFunction('field_help', [$this, 'getFieldHelp']), - new TwigFunction('field_errors', [$this, 'getFieldErrors']), - new TwigFunction('field_choices', [$this, 'getFieldChoices']), + new TwigFunction('field_name', $this->getFieldName(...)), + new TwigFunction('field_value', $this->getFieldValue(...)), + new TwigFunction('field_label', $this->getFieldLabel(...)), + new TwigFunction('field_help', $this->getFieldHelp(...)), + new TwigFunction('field_errors', $this->getFieldErrors(...)), + new TwigFunction('field_choices', $this->getFieldChoices(...)), ]; } diff --git a/Extension/HttpFoundationExtension.php b/Extension/HttpFoundationExtension.php index 365e1173..104bd323 100644 --- a/Extension/HttpFoundationExtension.php +++ b/Extension/HttpFoundationExtension.php @@ -36,8 +36,8 @@ public function __construct(UrlHelper $urlHelper) public function getFunctions(): array { return [ - new TwigFunction('absolute_url', [$this, 'generateAbsoluteUrl']), - new TwigFunction('relative_path', [$this, 'generateRelativePath']), + new TwigFunction('absolute_url', $this->generateAbsoluteUrl(...)), + new TwigFunction('relative_path', $this->generateRelativePath(...)), ]; } diff --git a/Extension/LogoutUrlExtension.php b/Extension/LogoutUrlExtension.php index 5b29b489..3ac7582b 100644 --- a/Extension/LogoutUrlExtension.php +++ b/Extension/LogoutUrlExtension.php @@ -35,8 +35,8 @@ public function __construct(LogoutUrlGenerator $generator) public function getFunctions(): array { return [ - new TwigFunction('logout_url', [$this, 'getLogoutUrl']), - new TwigFunction('logout_path', [$this, 'getLogoutPath']), + new TwigFunction('logout_url', $this->getLogoutUrl(...)), + new TwigFunction('logout_path', $this->getLogoutPath(...)), ]; } diff --git a/Extension/RoutingExtension.php b/Extension/RoutingExtension.php index 26591fd5..3b3a2ac5 100644 --- a/Extension/RoutingExtension.php +++ b/Extension/RoutingExtension.php @@ -38,8 +38,8 @@ public function __construct(UrlGeneratorInterface $generator) public function getFunctions(): array { return [ - new TwigFunction('url', [$this, 'getUrl'], ['is_safe_callback' => [$this, 'isUrlGenerationSafe']]), - new TwigFunction('path', [$this, 'getPath'], ['is_safe_callback' => [$this, 'isUrlGenerationSafe']]), + new TwigFunction('url', $this->getUrl(...), ['is_safe_callback' => $this->isUrlGenerationSafe(...)]), + new TwigFunction('path', $this->getPath(...), ['is_safe_callback' => $this->isUrlGenerationSafe(...)]), ]; } diff --git a/Extension/SecurityExtension.php b/Extension/SecurityExtension.php index 3f24b82d..de75f8ff 100644 --- a/Extension/SecurityExtension.php +++ b/Extension/SecurityExtension.php @@ -75,9 +75,9 @@ public function getImpersonateExitPath(string $exitTo = null): string public function getFunctions(): array { return [ - new TwigFunction('is_granted', [$this, 'isGranted']), - new TwigFunction('impersonation_exit_url', [$this, 'getImpersonateExitUrl']), - new TwigFunction('impersonation_exit_path', [$this, 'getImpersonateExitPath']), + new TwigFunction('is_granted', $this->isGranted(...)), + new TwigFunction('impersonation_exit_url', $this->getImpersonateExitUrl(...)), + new TwigFunction('impersonation_exit_path', $this->getImpersonateExitPath(...)), ]; } } diff --git a/Extension/TranslationExtension.php b/Extension/TranslationExtension.php index 8371291d..cbb7394e 100644 --- a/Extension/TranslationExtension.php +++ b/Extension/TranslationExtension.php @@ -64,7 +64,7 @@ public function getTranslator(): TranslatorInterface public function getFunctions(): array { return [ - new TwigFunction('t', [$this, 'createTranslatable']), + new TwigFunction('t', $this->createTranslatable(...)), ]; } @@ -74,7 +74,7 @@ public function getFunctions(): array public function getFilters(): array { return [ - new TwigFilter('trans', [$this, 'trans']), + new TwigFilter('trans', $this->trans(...)), ]; } diff --git a/Extension/WebLinkExtension.php b/Extension/WebLinkExtension.php index de2884a7..fcc34ecb 100644 --- a/Extension/WebLinkExtension.php +++ b/Extension/WebLinkExtension.php @@ -37,12 +37,12 @@ public function __construct(RequestStack $requestStack) public function getFunctions(): array { return [ - new TwigFunction('link', [$this, 'link']), - new TwigFunction('preload', [$this, 'preload']), - new TwigFunction('dns_prefetch', [$this, 'dnsPrefetch']), - new TwigFunction('preconnect', [$this, 'preconnect']), - new TwigFunction('prefetch', [$this, 'prefetch']), - new TwigFunction('prerender', [$this, 'prerender']), + new TwigFunction('link', $this->link(...)), + new TwigFunction('preload', $this->preload(...)), + new TwigFunction('dns_prefetch', $this->dnsPrefetch(...)), + new TwigFunction('preconnect', $this->preconnect(...)), + new TwigFunction('prefetch', $this->prefetch(...)), + new TwigFunction('prerender', $this->prerender(...)), ]; } diff --git a/Extension/WorkflowExtension.php b/Extension/WorkflowExtension.php index 2b13d291..895faf45 100644 --- a/Extension/WorkflowExtension.php +++ b/Extension/WorkflowExtension.php @@ -38,13 +38,13 @@ public function __construct(Registry $workflowRegistry) public function getFunctions(): array { return [ - new TwigFunction('workflow_can', [$this, 'canTransition']), - new TwigFunction('workflow_transitions', [$this, 'getEnabledTransitions']), - new TwigFunction('workflow_transition', [$this, 'getEnabledTransition']), - new TwigFunction('workflow_has_marked_place', [$this, 'hasMarkedPlace']), - new TwigFunction('workflow_marked_places', [$this, 'getMarkedPlaces']), - new TwigFunction('workflow_metadata', [$this, 'getMetadata']), - new TwigFunction('workflow_transition_blockers', [$this, 'buildTransitionBlockerList']), + new TwigFunction('workflow_can', $this->canTransition(...)), + new TwigFunction('workflow_transitions', $this->getEnabledTransitions(...)), + new TwigFunction('workflow_transition', $this->getEnabledTransition(...)), + new TwigFunction('workflow_has_marked_place', $this->hasMarkedPlace(...)), + new TwigFunction('workflow_marked_places', $this->getMarkedPlaces(...)), + new TwigFunction('workflow_metadata', $this->getMetadata(...)), + new TwigFunction('workflow_transition_blockers', $this->buildTransitionBlockerList(...)), ]; } diff --git a/Extension/YamlExtension.php b/Extension/YamlExtension.php index 919834e2..2d0595b7 100644 --- a/Extension/YamlExtension.php +++ b/Extension/YamlExtension.php @@ -28,8 +28,8 @@ final class YamlExtension extends AbstractExtension public function getFilters(): array { return [ - new TwigFilter('yaml_encode', [$this, 'encode']), - new TwigFilter('yaml_dump', [$this, 'dump']), + new TwigFilter('yaml_encode', $this->encode(...)), + new TwigFilter('yaml_dump', $this->dump(...)), ]; } diff --git a/Tests/Translation/TwigExtractorTest.php b/Tests/Translation/TwigExtractorTest.php index 4a8b4d19..c93bd718 100644 --- a/Tests/Translation/TwigExtractorTest.php +++ b/Tests/Translation/TwigExtractorTest.php @@ -41,7 +41,6 @@ public function testExtract($template, $messages) $catalogue = new MessageCatalogue('en'); $m = new \ReflectionMethod($extractor, 'extractTemplate'); - $m->setAccessible(true); $m->invoke($extractor, $template, $catalogue); if (0 === \count($messages)) { diff --git a/TokenParser/StopwatchTokenParser.php b/TokenParser/StopwatchTokenParser.php index f0b3ac2e..b332485d 100644 --- a/TokenParser/StopwatchTokenParser.php +++ b/TokenParser/StopwatchTokenParser.php @@ -42,7 +42,7 @@ public function parse(Token $token): Node $stream->expect(Token::BLOCK_END_TYPE); // {% endstopwatch %} - $body = $this->parser->subparse([$this, 'decideStopwatchEnd'], true); + $body = $this->parser->subparse($this->decideStopwatchEnd(...), true); $stream->expect(Token::BLOCK_END_TYPE); if ($this->stopwatchIsAvailable) { diff --git a/TokenParser/TransTokenParser.php b/TokenParser/TransTokenParser.php index ffe88285..b440931b 100644 --- a/TokenParser/TransTokenParser.php +++ b/TokenParser/TransTokenParser.php @@ -69,7 +69,7 @@ public function parse(Token $token): Node // {% trans %}message{% endtrans %} $stream->expect(Token::BLOCK_END_TYPE); - $body = $this->parser->subparse([$this, 'decideTransFork'], true); + $body = $this->parser->subparse($this->decideTransFork(...), true); if (!$body instanceof TextNode && !$body instanceof AbstractExpression) { throw new SyntaxError('A message inside a trans tag must be a simple text.', $body->getTemplateLine(), $stream->getSourceContext()); diff --git a/composer.json b/composer.json index 07658183..44fb5527 100644 --- a/composer.json +++ b/composer.json @@ -16,7 +16,7 @@ } ], "require": { - "php": ">=8.0.2", + "php": ">=8.1", "symfony/translation-contracts": "^1.1|^2|^3", "twig/twig": "^2.13|^3.0.4" }, From a1054ecc26adc0ccecdbe7a081b38896015e1e30 Mon Sep 17 00:00:00 2001 From: Tijs Verkoyen Date: Thu, 17 Mar 2022 15:30:44 +0100 Subject: [PATCH 025/167] [TwigBridge] Use a div instead of p to prevent invalid HTML --- CHANGELOG.md | 5 +++++ .../views/Form/form_div_layout.html.twig | 4 ++-- ...AbstractBootstrap5HorizontalLayoutTest.php | 4 ++-- .../AbstractBootstrap5LayoutTest.php | 20 +++++++++---------- .../Extension/FormExtensionDivLayoutTest.php | 14 ++++++------- .../FormExtensionTableLayoutTest.php | 14 ++++++------- composer.json | 4 ++-- 7 files changed, 35 insertions(+), 30 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 535df0c0..819251bf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +6.1 +--- + + * Wrap help messages on form elements in `div` instead of `p` + 5.4 --- diff --git a/Resources/views/Form/form_div_layout.html.twig b/Resources/views/Form/form_div_layout.html.twig index 94f87dc1..4769585c 100644 --- a/Resources/views/Form/form_div_layout.html.twig +++ b/Resources/views/Form/form_div_layout.html.twig @@ -325,7 +325,7 @@ {% block form_help -%} {%- if help is not empty -%} {%- set help_attr = help_attr|merge({class: (help_attr.class|default('') ~ ' help-text')|trim}) -%} -

+

{%- if translation_domain is same as(false) -%} {%- if help_html is same as(false) -%} {{- help -}} @@ -339,7 +339,7 @@ {{- help|trans(help_translation_parameters, translation_domain)|raw -}} {%- endif -%} {%- endif -%} -

+
{%- endif -%} {%- endblock form_help %} diff --git a/Tests/Extension/AbstractBootstrap5HorizontalLayoutTest.php b/Tests/Extension/AbstractBootstrap5HorizontalLayoutTest.php index 9fe231bf..1c2a40c2 100644 --- a/Tests/Extension/AbstractBootstrap5HorizontalLayoutTest.php +++ b/Tests/Extension/AbstractBootstrap5HorizontalLayoutTest.php @@ -245,7 +245,7 @@ public function testCheckboxRowWithHelp() ./div[@class="col-sm-2" or @class="col-sm-10"] /following-sibling::div[@class="col-sm-2" or @class="col-sm-10"] [ - ./p + ./div [@class="form-text mb-0 help-text"] [.="[trans]really helpful text[/trans]"] ] @@ -266,7 +266,7 @@ public function testRadioRowWithHelp() ./div[@class="col-sm-2" or @class="col-sm-10"] /following-sibling::div[@class="col-sm-2" or @class="col-sm-10"] [ - ./p + ./div [@class="form-text mb-0 help-text"] [.="[trans]really helpful text[/trans]"] ] diff --git a/Tests/Extension/AbstractBootstrap5LayoutTest.php b/Tests/Extension/AbstractBootstrap5LayoutTest.php index ff35789a..1403372b 100644 --- a/Tests/Extension/AbstractBootstrap5LayoutTest.php +++ b/Tests/Extension/AbstractBootstrap5LayoutTest.php @@ -198,7 +198,7 @@ public function testHelp() $html = $this->renderHelp($view); $this->assertMatchesXpath($html, - '/p + '/div [@id="name_help"] [@class="form-text mb-0 help-text"] [.="[trans]Help text test![/trans]"] @@ -218,7 +218,7 @@ public function testHelpAttr() $html = $this->renderHelp($view); $this->assertMatchesXpath($html, - '/p + '/div [@id="name_help"] [@class="class-test form-text mb-0 help-text"] [.="[trans]Help text test![/trans]"] @@ -236,7 +236,7 @@ public function testHelpHtmlDefaultIsFalse() $html = $this->renderHelp($view); $this->assertMatchesXpath($html, - '/p + '/div [@id="name_help"] [@class="form-text mb-0 help-text"] [.="[trans]Help text test![/trans]"] @@ -244,7 +244,7 @@ public function testHelpHtmlDefaultIsFalse() ); $this->assertMatchesXpath($html, - '/p + '/div [@id="name_help"] [@class="form-text mb-0 help-text"] /b @@ -264,7 +264,7 @@ public function testHelpHtmlIsFalse() $html = $this->renderHelp($view); $this->assertMatchesXpath($html, - '/p + '/div [@id="name_help"] [@class="form-text mb-0 help-text"] [.="[trans]Help text test![/trans]"] @@ -272,7 +272,7 @@ public function testHelpHtmlIsFalse() ); $this->assertMatchesXpath($html, - '/p + '/div [@id="name_help"] [@class="form-text mb-0 help-text"] /b @@ -290,7 +290,7 @@ public function testHelpHtmlIsTrue() $html = $this->renderHelp($form->createView()); $this->assertMatchesXpath($html, - '/p + '/div [@id="name_help"] [@class="form-text mb-0 help-text"] [.="[trans]Help text test![/trans]"] @@ -298,7 +298,7 @@ public function testHelpHtmlIsTrue() ); $this->assertMatchesXpath($html, - '/p + '/div [@id="name_help"] [@class="form-text mb-0 help-text"] /b @@ -377,7 +377,7 @@ public function testCheckboxRowWithHelp() [@for="name"] [.="[trans]foo[/trans]"] ] - /following-sibling::p + /following-sibling::div [@class="form-text mb-0 help-text"] [.="[trans]really helpful text[/trans]"] ] @@ -871,7 +871,7 @@ public function testRadioRowWithHelp() '/div [@class="mb-3"] [ - ./p + ./div [@class="form-text mb-0 help-text"] [.="[trans]really helpful text[/trans]"] ] diff --git a/Tests/Extension/FormExtensionDivLayoutTest.php b/Tests/Extension/FormExtensionDivLayoutTest.php index e7991240..cae1a1c6 100644 --- a/Tests/Extension/FormExtensionDivLayoutTest.php +++ b/Tests/Extension/FormExtensionDivLayoutTest.php @@ -203,7 +203,7 @@ public function testHelpAttr() $html = $this->renderHelp($view); $this->assertMatchesXpath($html, - '/p + '/div [@id="name_help"] [@class="class-test help-text"] [.="[trans]Help text test![/trans]"] @@ -221,7 +221,7 @@ public function testHelpHtmlDefaultIsFalse() $html = $this->renderHelp($view); $this->assertMatchesXpath($html, - '/p + '/div [@id="name_help"] [@class="help-text"] [.="[trans]Help text test![/trans]"] @@ -229,7 +229,7 @@ public function testHelpHtmlDefaultIsFalse() ); $this->assertMatchesXpath($html, - '/p + '/div [@id="name_help"] [@class="help-text"] /b @@ -249,7 +249,7 @@ public function testHelpHtmlIsFalse() $html = $this->renderHelp($view); $this->assertMatchesXpath($html, - '/p + '/div [@id="name_help"] [@class="help-text"] [.="[trans]Help text test![/trans]"] @@ -257,7 +257,7 @@ public function testHelpHtmlIsFalse() ); $this->assertMatchesXpath($html, - '/p + '/div [@id="name_help"] [@class="help-text"] /b @@ -277,7 +277,7 @@ public function testHelpHtmlIsTrue() $html = $this->renderHelp($view); $this->assertMatchesXpath($html, - '/p + '/div [@id="name_help"] [@class="help-text"] [.="[trans]Help text test![/trans]"] @@ -285,7 +285,7 @@ public function testHelpHtmlIsTrue() ); $this->assertMatchesXpath($html, - '/p + '/div [@id="name_help"] [@class="help-text"] /b diff --git a/Tests/Extension/FormExtensionTableLayoutTest.php b/Tests/Extension/FormExtensionTableLayoutTest.php index 20b46541..7b75be23 100644 --- a/Tests/Extension/FormExtensionTableLayoutTest.php +++ b/Tests/Extension/FormExtensionTableLayoutTest.php @@ -89,7 +89,7 @@ public function testHelpAttr() $html = $this->renderHelp($view); $this->assertMatchesXpath($html, - '/p + '/div [@id="name_help"] [@class="class-test help-text"] [.="[trans]Help text test![/trans]"] @@ -107,7 +107,7 @@ public function testHelpHtmlDefaultIsFalse() $html = $this->renderHelp($view); $this->assertMatchesXpath($html, - '/p + '/div [@id="name_help"] [@class="help-text"] [.="[trans]Help text test![/trans]"] @@ -115,7 +115,7 @@ public function testHelpHtmlDefaultIsFalse() ); $this->assertMatchesXpath($html, - '/p + '/div [@id="name_help"] [@class="help-text"] /b @@ -135,7 +135,7 @@ public function testHelpHtmlIsFalse() $html = $this->renderHelp($view); $this->assertMatchesXpath($html, - '/p + '/div [@id="name_help"] [@class="help-text"] [.="[trans]Help text test![/trans]"] @@ -143,7 +143,7 @@ public function testHelpHtmlIsFalse() ); $this->assertMatchesXpath($html, - '/p + '/div [@id="name_help"] [@class="help-text"] /b @@ -163,7 +163,7 @@ public function testHelpHtmlIsTrue() $html = $this->renderHelp($view); $this->assertMatchesXpath($html, - '/p + '/div [@id="name_help"] [@class="help-text"] [.="[trans]Help text test![/trans]"] @@ -171,7 +171,7 @@ public function testHelpHtmlIsTrue() ); $this->assertMatchesXpath($html, - '/p + '/div [@id="name_help"] [@class="help-text"] /b diff --git a/composer.json b/composer.json index 44fb5527..46cea3b1 100644 --- a/composer.json +++ b/composer.json @@ -27,7 +27,7 @@ "symfony/asset": "^5.4|^6.0", "symfony/dependency-injection": "^5.4|^6.0", "symfony/finder": "^5.4|^6.0", - "symfony/form": "^5.4|^6.0", + "symfony/form": "^6.1", "symfony/http-foundation": "^5.4|^6.0", "symfony/http-kernel": "^5.4|^6.0", "symfony/intl": "^5.4|^6.0", @@ -55,7 +55,7 @@ "phpdocumentor/reflection-docblock": "<3.2.2", "phpdocumentor/type-resolver": "<1.4.0", "symfony/console": "<5.4", - "symfony/form": "<5.4", + "symfony/form": "<6.1", "symfony/http-foundation": "<5.4", "symfony/http-kernel": "<5.4", "symfony/translation": "<5.4", From f1899f45237cc99cd2b1d044937c3d58d4cb2b26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Fri, 25 Mar 2022 09:30:45 +0100 Subject: [PATCH 026/167] Use an option for twig file names in cache warmer and linter --- Command/LintCommand.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Command/LintCommand.php b/Command/LintCommand.php index 6aede16f..d5ec05ea 100644 --- a/Command/LintCommand.php +++ b/Command/LintCommand.php @@ -39,14 +39,16 @@ #[AsCommand(name: 'lint:twig', description: 'Lint a Twig template and outputs encountered errors')] class LintCommand extends Command { + protected string|array $namePatterns; private Environment $twig; private string $format; - public function __construct(Environment $twig) + public function __construct(Environment $twig, string|array $namePatterns = ['*.twig']) { parent::__construct(); $this->twig = $twig; + $this->namePatterns = $namePatterns; } protected function configure() @@ -146,7 +148,7 @@ protected function findFiles(string $filename) if (is_file($filename)) { return [$filename]; } elseif (is_dir($filename)) { - return Finder::create()->files()->in($filename)->name('*.twig'); + return Finder::create()->files()->in($filename)->name($this->namePatterns); } throw new RuntimeException(sprintf('File or directory "%s" is not readable.', $filename)); From 74c08c11567288c3e052dcd4f5ba63da17148644 Mon Sep 17 00:00:00 2001 From: Thomas Calvet Date: Thu, 31 Mar 2022 18:23:12 +0200 Subject: [PATCH 027/167] Leverage non-capturing catches --- AppVariable.php | 2 +- DataCollector/TwigDataCollector.php | 2 +- Extension/SecurityExtension.php | 2 +- Mime/BodyRenderer.php | 2 +- Translation/TwigExtractor.php | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/AppVariable.php b/AppVariable.php index b46646b5..43d6e3ff 100644 --- a/AppVariable.php +++ b/AppVariable.php @@ -139,7 +139,7 @@ public function getFlashes(string|array $types = null): array if (null === $session = $this->getSession()) { return []; } - } catch (\RuntimeException $e) { + } catch (\RuntimeException) { return []; } diff --git a/DataCollector/TwigDataCollector.php b/DataCollector/TwigDataCollector.php index af579f24..a26a620c 100644 --- a/DataCollector/TwigDataCollector.php +++ b/DataCollector/TwigDataCollector.php @@ -71,7 +71,7 @@ public function lateCollect() if ($profile->isTemplate()) { try { $template = $this->twig->load($name = $profile->getName()); - } catch (LoaderError $e) { + } catch (LoaderError) { $template = null; } diff --git a/Extension/SecurityExtension.php b/Extension/SecurityExtension.php index de75f8ff..dd1c57fb 100644 --- a/Extension/SecurityExtension.php +++ b/Extension/SecurityExtension.php @@ -46,7 +46,7 @@ public function isGranted(mixed $role, mixed $object = null, string $field = nul try { return $this->securityChecker->isGranted($role, $object); - } catch (AuthenticationCredentialsNotFoundException $e) { + } catch (AuthenticationCredentialsNotFoundException) { return false; } } diff --git a/Mime/BodyRenderer.php b/Mime/BodyRenderer.php index e1b27046..85e0ba93 100644 --- a/Mime/BodyRenderer.php +++ b/Mime/BodyRenderer.php @@ -85,7 +85,7 @@ private function getFingerPrint(TemplatedEmail $message): string $payload = [$messageContext, $message->getTextTemplate(), $message->getHtmlTemplate()]; try { $serialized = serialize($payload); - } catch (\Exception $e) { + } catch (\Exception) { // Serialization of 'Closure' is not allowed // Happens when context contain a closure, in that case, we assume that context always change. $serialized = random_bytes(8); diff --git a/Translation/TwigExtractor.php b/Translation/TwigExtractor.php index 7625498f..86ef2693 100644 --- a/Translation/TwigExtractor.php +++ b/Translation/TwigExtractor.php @@ -53,7 +53,7 @@ public function extract($resource, MessageCatalogue $catalogue) foreach ($this->extractFiles($resource) as $file) { try { $this->extractTemplate(file_get_contents($file->getPathname()), $catalogue); - } catch (Error $e) { + } catch (Error) { // ignore errors, these should be fixed by using the linter } } From 15b6ca77304c728fbc7d2eba3f2a7b8115bd9d3c Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Mon, 4 Apr 2022 20:01:42 +0200 Subject: [PATCH 028/167] [TwigBridge] Make LintCommand::$namePatterns private --- Command/LintCommand.php | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/Command/LintCommand.php b/Command/LintCommand.php index d5ec05ea..54f4e233 100644 --- a/Command/LintCommand.php +++ b/Command/LintCommand.php @@ -39,16 +39,13 @@ #[AsCommand(name: 'lint:twig', description: 'Lint a Twig template and outputs encountered errors')] class LintCommand extends Command { - protected string|array $namePatterns; - private Environment $twig; private string $format; - public function __construct(Environment $twig, string|array $namePatterns = ['*.twig']) - { + public function __construct( + private Environment $twig, + private array $namePatterns = ['*.twig'], + ) { parent::__construct(); - - $this->twig = $twig; - $this->namePatterns = $namePatterns; } protected function configure() From 5ceff214cf8ed1192d61de3572eef191daaa1543 Mon Sep 17 00:00:00 2001 From: Thomas Calvet Date: Fri, 15 Apr 2022 15:56:01 +0200 Subject: [PATCH 029/167] Clean skippable tests that are never skipped --- Tests/Command/DebugCommandTest.php | 4 ---- Tests/Command/LintCommandTest.php | 4 ---- Tests/Extension/HttpFoundationExtensionTest.php | 12 ------------ Tests/Extension/HttpKernelExtensionTest.php | 4 ---- Tests/Extension/WorkflowExtensionTest.php | 10 ---------- 5 files changed, 34 deletions(-) diff --git a/Tests/Command/DebugCommandTest.php b/Tests/Command/DebugCommandTest.php index 2488a276..f0d7d661 100644 --- a/Tests/Command/DebugCommandTest.php +++ b/Tests/Command/DebugCommandTest.php @@ -299,10 +299,6 @@ public function testWithFilter() */ public function testComplete(array $input, array $expectedSuggestions) { - if (!class_exists(CommandCompletionTester::class)) { - $this->markTestSkipped('Test command completion requires symfony/console 5.4+.'); - } - $projectDir = \dirname(__DIR__).\DIRECTORY_SEPARATOR.'Fixtures'; $loader = new FilesystemLoader([], $projectDir); $environment = new Environment($loader); diff --git a/Tests/Command/LintCommandTest.php b/Tests/Command/LintCommandTest.php index 6a3d640b..6428f486 100644 --- a/Tests/Command/LintCommandTest.php +++ b/Tests/Command/LintCommandTest.php @@ -141,10 +141,6 @@ public function testLintAutodetectsGithubActionEnvironment() */ public function testComplete(array $input, array $expectedSuggestions) { - if (!class_exists(CommandCompletionTester::class)) { - $this->markTestSkipped('Test command completion requires symfony/console 5.4+.'); - } - $tester = new CommandCompletionTester($this->createCommand()); $this->assertSame($expectedSuggestions, $tester->complete($input)); diff --git a/Tests/Extension/HttpFoundationExtensionTest.php b/Tests/Extension/HttpFoundationExtensionTest.php index ea3bb17b..131cb3d0 100644 --- a/Tests/Extension/HttpFoundationExtensionTest.php +++ b/Tests/Extension/HttpFoundationExtensionTest.php @@ -60,10 +60,6 @@ public function getGenerateAbsoluteUrlData() */ public function testGenerateAbsoluteUrlWithRequestContext($path, $baseUrl, $host, $scheme, $httpPort, $httpsPort, $expected) { - if (!class_exists(RequestContext::class)) { - $this->markTestSkipped('The Routing component is needed to run tests that depend on its request context.'); - } - $requestContext = new RequestContext($baseUrl, 'GET', $host, $scheme, $httpPort, $httpsPort, $path); $extension = new HttpFoundationExtension(new UrlHelper(new RequestStack(), $requestContext)); @@ -75,10 +71,6 @@ public function testGenerateAbsoluteUrlWithRequestContext($path, $baseUrl, $host */ public function testGenerateAbsoluteUrlWithoutRequestAndRequestContext($path) { - if (!class_exists(RequestContext::class)) { - $this->markTestSkipped('The Routing component is needed to run tests that depend on its request context.'); - } - $extension = new HttpFoundationExtension(new UrlHelper(new RequestStack())); $this->assertEquals($path, $extension->generateAbsoluteUrl($path)); @@ -118,10 +110,6 @@ public function testGenerateAbsoluteUrlWithScriptFileName() */ public function testGenerateRelativePath($expected, $path, $pathinfo) { - if (!method_exists(Request::class, 'getRelativeUriForPath')) { - $this->markTestSkipped('Your version of Symfony HttpFoundation is too old.'); - } - $stack = new RequestStack(); $stack->push(Request::create($pathinfo)); $extension = new HttpFoundationExtension(new UrlHelper($stack)); diff --git a/Tests/Extension/HttpKernelExtensionTest.php b/Tests/Extension/HttpKernelExtensionTest.php index a3294db0..ee987d16 100644 --- a/Tests/Extension/HttpKernelExtensionTest.php +++ b/Tests/Extension/HttpKernelExtensionTest.php @@ -58,10 +58,6 @@ public function testUnknownFragmentRenderer() public function testGenerateFragmentUri() { - if (!class_exists(FragmentUriGenerator::class)) { - $this->markTestSkipped('HttpKernel 5.3+ is required'); - } - $requestStack = new RequestStack(); $requestStack->push(Request::create('/')); diff --git a/Tests/Extension/WorkflowExtensionTest.php b/Tests/Extension/WorkflowExtensionTest.php index 23dcc64b..1252efb8 100644 --- a/Tests/Extension/WorkflowExtensionTest.php +++ b/Tests/Extension/WorkflowExtensionTest.php @@ -30,10 +30,6 @@ class WorkflowExtensionTest extends TestCase protected function setUp(): void { - if (!class_exists(Workflow::class)) { - $this->markTestSkipped('The Workflow component is needed to run tests for this extension.'); - } - $places = ['ordered', 'waiting_for_payment', 'processed']; $transitions = [ $this->t1 = new Transition('t1', 'ordered', 'waiting_for_payment'), @@ -110,9 +106,6 @@ public function testGetMarkedPlaces() public function testGetMetadata() { - if (!class_exists(InMemoryMetadataStore::class)) { - $this->markTestSkipped('This test requires symfony/workflow:4.1.'); - } $subject = new Subject(); $this->assertSame('workflow title', $this->extension->getMetadata($subject, 'title')); @@ -124,9 +117,6 @@ public function testGetMetadata() public function testbuildTransitionBlockerList() { - if (!class_exists(TransitionBlockerList::class)) { - $this->markTestSkipped('This test requires symfony/workflow:4.1.'); - } $subject = new Subject(); $list = $this->extension->buildTransitionBlockerList($subject, 't1'); From 5b8c7b2bff04d47672327c51496832144968b5b5 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Thu, 12 May 2022 18:47:43 +0200 Subject: [PATCH 030/167] [FrameworkBundle][TwigBundle][Form] Add Twig filter, form-type extension and improve service definitions for HtmlSanitizer --- Extension/HtmlSanitizerExtension.php | 40 ++++++++++++++ .../Extension/HtmlSanitizerExtensionTest.php | 52 +++++++++++++++++++ UndefinedCallableHandler.php | 2 + composer.json | 2 + 4 files changed, 96 insertions(+) create mode 100644 Extension/HtmlSanitizerExtension.php create mode 100644 Tests/Extension/HtmlSanitizerExtensionTest.php diff --git a/Extension/HtmlSanitizerExtension.php b/Extension/HtmlSanitizerExtension.php new file mode 100644 index 00000000..bec5ceb9 --- /dev/null +++ b/Extension/HtmlSanitizerExtension.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\Extension; + +use Psr\Container\ContainerInterface; +use Twig\Extension\AbstractExtension; +use Twig\TwigFilter; + +/** + * @author Titouan Galopin + */ +final class HtmlSanitizerExtension extends AbstractExtension +{ + public function __construct( + private ContainerInterface $sanitizers, + private string $defaultSanitizer = 'default', + ) { + } + + public function getFilters(): array + { + return [ + new TwigFilter('sanitize_html', $this->sanitize(...), ['is_safe' => ['html']]), + ]; + } + + public function sanitize(string $html, string $sanitizer = null): string + { + return $this->sanitizers->get($sanitizer ?? $this->defaultSanitizer)->sanitize($html); + } +} diff --git a/Tests/Extension/HtmlSanitizerExtensionTest.php b/Tests/Extension/HtmlSanitizerExtensionTest.php new file mode 100644 index 00000000..23755d03 --- /dev/null +++ b/Tests/Extension/HtmlSanitizerExtensionTest.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\Bridge\Twig\Tests\Extension; + +use PHPUnit\Framework\TestCase; +use Symfony\Bridge\Twig\Extension\HtmlSanitizerExtension; +use Symfony\Component\DependencyInjection\ServiceLocator; +use Symfony\Component\HtmlSanitizer\HtmlSanitizerInterface; +use Twig\Environment; +use Twig\Loader\ArrayLoader; + +class HtmlSanitizerExtensionTest extends TestCase +{ + public function testSanitizeHtml() + { + $loader = new ArrayLoader([ + 'foo' => '{{ "foobar"|sanitize_html }}', + 'bar' => '{{ "foobar"|sanitize_html("bar") }}', + ]); + + $twig = new Environment($loader, ['debug' => true, 'cache' => false, 'autoescape' => 'html', 'optimizations' => 0]); + + $fooSanitizer = $this->createMock(HtmlSanitizerInterface::class); + $fooSanitizer->expects($this->once()) + ->method('sanitize') + ->with('foobar') + ->willReturn('foo'); + + $barSanitizer = $this->createMock(HtmlSanitizerInterface::class); + $barSanitizer->expects($this->once()) + ->method('sanitize') + ->with('foobar') + ->willReturn('bar'); + + $twig->addExtension(new HtmlSanitizerExtension(new ServiceLocator([ + 'foo' => fn () => $fooSanitizer, + 'bar' => fn () => $barSanitizer, + ]), 'foo')); + + $this->assertSame('foo', $twig->render('foo')); + $this->assertSame('bar', $twig->render('bar')); + } +} diff --git a/UndefinedCallableHandler.php b/UndefinedCallableHandler.php index 30dd92ff..472330a8 100644 --- a/UndefinedCallableHandler.php +++ b/UndefinedCallableHandler.php @@ -24,6 +24,7 @@ class UndefinedCallableHandler private const FILTER_COMPONENTS = [ 'humanize' => 'form', 'trans' => 'translation', + 'sanitize_html' => 'html-sanitizer', 'yaml_encode' => 'yaml', 'yaml_dump' => 'yaml', ]; @@ -61,6 +62,7 @@ class UndefinedCallableHandler ]; private const FULL_STACK_ENABLE = [ + 'html-sanitizer' => 'enable "framework.html_sanitizer"', 'form' => 'enable "framework.form"', 'security-core' => 'add the "SecurityBundle"', 'security-http' => 'add the "SecurityBundle"', diff --git a/composer.json b/composer.json index 46cea3b1..f182dfa4 100644 --- a/composer.json +++ b/composer.json @@ -28,6 +28,7 @@ "symfony/dependency-injection": "^5.4|^6.0", "symfony/finder": "^5.4|^6.0", "symfony/form": "^6.1", + "symfony/html-sanitizer": "^6.1", "symfony/http-foundation": "^5.4|^6.0", "symfony/http-kernel": "^5.4|^6.0", "symfony/intl": "^5.4|^6.0", @@ -65,6 +66,7 @@ "symfony/finder": "", "symfony/asset": "For using the AssetExtension", "symfony/form": "For using the FormExtension", + "symfony/html-sanitizer": "For using the HtmlSanitizerExtension", "symfony/http-kernel": "For using the HttpKernelExtension", "symfony/routing": "For using the RoutingExtension", "symfony/translation": "For using the TranslationExtension", From b45cdc00789caf109762f572c2c3be4428d7caaf Mon Sep 17 00:00:00 2001 From: Alexander Schranz Date: Sun, 10 Apr 2022 17:03:32 +0200 Subject: [PATCH 031/167] [TwigBridge] Add form_label_content and form_help_content block to form_div_layout --- CHANGELOG.md | 5 ++ .../views/Form/bootstrap_4_layout.html.twig | 62 +------------- .../views/Form/form_div_layout.html.twig | 80 ++++++++++--------- 3 files changed, 52 insertions(+), 95 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 819251bf..e986f9b2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +6.2 +--- + + * Add `form_label_content` and `form_help_content` block to form themes + 6.1 --- diff --git a/Resources/views/Form/bootstrap_4_layout.html.twig b/Resources/views/Form/bootstrap_4_layout.html.twig index 0e808405..4bf2f9cd 100644 --- a/Resources/views/Form/bootstrap_4_layout.html.twig +++ b/Resources/views/Form/bootstrap_4_layout.html.twig @@ -233,30 +233,8 @@ {% if required -%} {% set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' required')|trim}) %} {%- endif -%} - {% if label is empty -%} - {%- if label_format is not empty -%} - {% set label = label_format|replace({ - '%name%': name, - '%id%': id, - }) %} - {%- else -%} - {% set label = name|humanize %} - {%- endif -%} - {%- endif -%} <{{ element|default('label') }}{% if label_attr %}{% with { attr: label_attr } %}{{ block('attributes') }}{% endwith %}{% endif %}> - {%- if translation_domain is same as(false) -%} - {%- if label_html is same as(false) -%} - {{- label -}} - {%- else -%} - {{- label|raw -}} - {%- endif -%} - {%- else -%} - {%- if label_html is same as(false) -%} - {{- label|trans(label_translation_parameters, translation_domain) -}} - {%- else -%} - {{- label|trans(label_translation_parameters, translation_domain)|raw -}} - {%- endif -%} - {%- endif -%} + {{- block('form_label_content') -}} {% block form_label_errors %}{{- form_errors(form) -}}{% endblock form_label_errors %} {%- else -%} {%- if errors|length > 0 -%} @@ -283,33 +261,11 @@ {%- if required -%} {%- set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' required')|trim}) -%} {%- endif -%} - {%- if label is not same as(false) and label is empty -%} - {%- if label_format is not empty -%} - {%- set label = label_format|replace({ - '%name%': name, - '%id%': id, - }) -%} - {%- else -%} - {%- set label = name|humanize -%} - {%- endif -%} - {%- endif -%} {{ widget|raw }} {%- if label is not same as(false) -%} - {%- if translation_domain is same as(false) -%} - {%- if label_html is same as(false) -%} - {{- label -}} - {%- else -%} - {{- label|raw -}} - {%- endif -%} - {%- else -%} - {%- if label_html is same as(false) -%} - {{- label|trans(label_translation_parameters, translation_domain) -}} - {%- else -%} - {{- label|trans(label_translation_parameters, translation_domain)|raw -}} - {%- endif -%} - {%- endif -%} + {{- block('form_label_content') -}} {%- endif -%} {{- form_errors(form) -}} @@ -353,19 +309,7 @@ {%- if help is not empty -%} {%- set help_attr = help_attr|merge({class: (help_attr.class|default('') ~ ' form-text text-muted')|trim}) -%} - {%- if translation_domain is same as(false) -%} - {%- if help_html is same as(false) -%} - {{- help -}} - {%- else -%} - {{- help|raw -}} - {%- endif -%} - {%- else -%} - {%- if help_html is same as(false) -%} - {{- help|trans(help_translation_parameters, translation_domain) -}} - {%- else -%} - {{- help|trans(help_translation_parameters, translation_domain)|raw -}} - {%- endif -%} - {%- endif -%} + {{- block('form_help_content') -}} {%- endif -%} {%- endblock form_help %} diff --git a/Resources/views/Form/form_div_layout.html.twig b/Resources/views/Form/form_div_layout.html.twig index 4769585c..61f64e33 100644 --- a/Resources/views/Form/form_div_layout.html.twig +++ b/Resources/views/Form/form_div_layout.html.twig @@ -290,34 +290,38 @@ {% if required -%} {% set label_attr = label_attr|merge({'class': (label_attr.class|default('') ~ ' required')|trim}) %} {%- endif -%} - {% if label is empty -%} - {%- if label_format is not empty -%} - {% set label = label_format|replace({ - '%name%': name, - '%id%': id, - }) %} - {%- else -%} - {% set label = name|humanize %} - {%- endif -%} - {%- endif -%} <{{ element|default('label') }}{% if label_attr %}{% with { attr: label_attr } %}{{ block('attributes') }}{% endwith %}{% endif %}> - {%- if translation_domain is same as(false) -%} - {%- if label_html is same as(false) -%} - {{- label -}} - {%- else -%} - {{- label|raw -}} - {%- endif -%} - {%- else -%} - {%- if label_html is same as(false) -%} - {{- label|trans(label_translation_parameters, translation_domain) -}} - {%- else -%} - {{- label|trans(label_translation_parameters, translation_domain)|raw -}} - {%- endif -%} - {%- endif -%} + {{- block('form_label_content') -}} {%- endif -%} {%- endblock form_label -%} +{%- block form_label_content -%} + {%- if label is empty -%} + {%- if label_format is not empty -%} + {% set label = label_format|replace({ + '%name%': name, + '%id%': id, + }) %} + {%- else -%} + {% set label = name|humanize %} + {%- endif -%} + {%- endif -%} + {%- if translation_domain is same as(false) -%} + {%- if label_html is same as(false) -%} + {{- label -}} + {%- else -%} + {{- label|raw -}} + {%- endif -%} + {%- else -%} + {%- if label_html is same as(false) -%} + {{- label|trans(label_translation_parameters, translation_domain) -}} + {%- else -%} + {{- label|trans(label_translation_parameters, translation_domain)|raw -}} + {%- endif -%} + {%- endif -%} +{%- endblock form_label_content -%} + {%- block button_label -%}{%- endblock -%} {# Help #} @@ -326,23 +330,27 @@ {%- if help is not empty -%} {%- set help_attr = help_attr|merge({class: (help_attr.class|default('') ~ ' help-text')|trim}) -%}
- {%- if translation_domain is same as(false) -%} - {%- if help_html is same as(false) -%} - {{- help -}} - {%- else -%} - {{- help|raw -}} - {%- endif -%} - {%- else -%} - {%- if help_html is same as(false) -%} - {{- help|trans(help_translation_parameters, translation_domain) -}} - {%- else -%} - {{- help|trans(help_translation_parameters, translation_domain)|raw -}} - {%- endif -%} - {%- endif -%} + {{- block('form_help_content') -}}
{%- endif -%} {%- endblock form_help %} +{% block form_help_content -%} + {%- if translation_domain is same as(false) -%} + {%- if help_html is same as(false) -%} + {{- help -}} + {%- else -%} + {{- help|raw -}} + {%- endif -%} + {%- else -%} + {%- if help_html is same as(false) -%} + {{- help|trans(help_translation_parameters, translation_domain) -}} + {%- else -%} + {{- help|trans(help_translation_parameters, translation_domain)|raw -}} + {%- endif -%} + {%- endif -%} +{%- endblock form_help_content %} + {# Rows #} {%- block repeated_row -%} From 4eae2e2a08d737bdd293fb4fa53239a655c4ea80 Mon Sep 17 00:00:00 2001 From: Thomas Lallement Date: Fri, 17 Jun 2022 17:20:52 +0200 Subject: [PATCH 032/167] Allow passing null in twig_is_selected_choice In the cached file of my application I can see the following code fragment that have been generated by Symfony: ```php if (( !((array_key_exists("render_preferred_choices", $context)) ? (_twig_default_filter((isset($context["render_preferred_choices"]) || array_key_exists("render_preferred_choices", $context) ? $context["render_preferred_choices"] : (function () { throw new RuntimeError('Variable "render_preferred_choices" does not exist.', 88, $this->source); })()), false)) : (false)) && Symfony\Bridge\Twig\Extension\twig_is_selected_choice($context["choice"], (isset($context["value"]) || array_key_exists("value", $context) ? $context["value"] : (function () { throw new RuntimeError('Variable "value" does not exist.', 88, $this->source); })())))) { echo " selected=\"selected\""; } ``` The 2nd Arg passed when calling ``twig_is_selected_choice`` if the result of ``(isset($context["value"]) || array_key_exists("value", $context) ? $context["value"]``. In that condition, if ``$context['value'] = null, we pass the null Currently I got the following error: ``` Symfony\Bridge\Twig\Extension\twig_is_selected_choice(): Argument #2 ($selectedValue) must be of type array|string, null given, called in X:\workspace-novento\novento-vip-lounge\var\cache\admin_dev\twig\01\01615438ee40292438687b29025d08a9.php on line 534 ``` --- Extension/FormExtension.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Extension/FormExtension.php b/Extension/FormExtension.php index f7d82e04..c726b058 100644 --- a/Extension/FormExtension.php +++ b/Extension/FormExtension.php @@ -188,7 +188,7 @@ private function createFieldTranslation(?string $value, array $parameters, strin * * @see ChoiceView::isSelected() */ -function twig_is_selected_choice(ChoiceView $choice, string|array $selectedValue): bool +function twig_is_selected_choice(ChoiceView $choice, string|array|null $selectedValue): bool { if (\is_array($selectedValue)) { return \in_array($choice->value, $selectedValue, true); From b15f8aee906cae0048cd723c4b2bac8fc507bbb3 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Mon, 11 Jul 2022 15:08:45 +0200 Subject: [PATCH 033/167] [TwigBridge] Add `#[Template()]` to describe how to render arrays returned by controllers --- Attribute/Template.php | 34 ++++++++ CHANGELOG.md | 1 + EventListener/TemplateAttributeListener.php | 84 +++++++++++++++++++ .../TemplateAttributeListenerTest.php | 72 ++++++++++++++++ .../Fixtures/TemplateAttributeController.php | 22 +++++ composer.json | 4 +- 6 files changed, 215 insertions(+), 2 deletions(-) create mode 100644 Attribute/Template.php create mode 100644 EventListener/TemplateAttributeListener.php create mode 100644 Tests/EventListener/TemplateAttributeListenerTest.php create mode 100644 Tests/Fixtures/TemplateAttributeController.php diff --git a/Attribute/Template.php b/Attribute/Template.php new file mode 100644 index 00000000..f094f42a --- /dev/null +++ b/Attribute/Template.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\Attribute; + +#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::TARGET_FUNCTION)] +class Template +{ + public function __construct( + /** + * The name of the template to render. + */ + public string $template, + + /** + * The controller method arguments to pass to the template. + */ + public ?array $vars = null, + + /** + * Enables streaming the template. + */ + public bool $stream = false, + ) { + } +} diff --git a/CHANGELOG.md b/CHANGELOG.md index e986f9b2..b44911b9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ CHANGELOG --- * Add `form_label_content` and `form_help_content` block to form themes + * Add `#[Template()]` to describe how to render arrays returned by controllers 6.1 --- diff --git a/EventListener/TemplateAttributeListener.php b/EventListener/TemplateAttributeListener.php new file mode 100644 index 00000000..96924442 --- /dev/null +++ b/EventListener/TemplateAttributeListener.php @@ -0,0 +1,84 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\EventListener; + +use Symfony\Bridge\Twig\Attribute\Template; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\Form\FormInterface; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\StreamedResponse; +use Symfony\Component\HttpKernel\Event\ControllerArgumentsEvent; +use Symfony\Component\HttpKernel\Event\ViewEvent; +use Symfony\Component\HttpKernel\KernelEvents; +use Twig\Environment; + +class TemplateAttributeListener implements EventSubscriberInterface +{ + public function __construct( + private Environment $twig, + ) { + } + + public function onKernelView(ViewEvent $event) + { + $parameters = $event->getControllerResult(); + + if (!\is_array($parameters ?? [])) { + return; + } + $attribute = $event->getRequest()->attributes->get('_template'); + + if (!$attribute instanceof Template && !$attribute = $event->controllerArgumentsEvent?->getAttributes()[Template::class][0] ?? null) { + return; + } + + $parameters ??= $this->resolveParameters($event->controllerArgumentsEvent, $attribute->vars); + $status = 200; + + foreach ($parameters as $k => $v) { + if (!$v instanceof FormInterface) { + continue; + } + if ($v->isSubmitted() && !$v->isValid()) { + $status = 422; + } + $parameters[$k] = $v->createView(); + } + + $event->setResponse($attribute->stream + ? new StreamedResponse(fn () => $this->twig->display($attribute->template, $parameters), $status) + : new Response($this->twig->render($attribute->template, $parameters), $status) + ); + } + + public static function getSubscribedEvents(): array + { + return [ + KernelEvents::VIEW => ['onKernelView', -128], + ]; + } + + private function resolveParameters(ControllerArgumentsEvent $event, ?array $vars): array + { + if ([] === $vars) { + return []; + } + + $parameters = $event->getNamedArguments(); + + if (null !== $vars) { + $parameters = array_intersect_key($parameters, array_flip($vars)); + } + + return $parameters; + } +} diff --git a/Tests/EventListener/TemplateAttributeListenerTest.php b/Tests/EventListener/TemplateAttributeListenerTest.php new file mode 100644 index 00000000..87712ea5 --- /dev/null +++ b/Tests/EventListener/TemplateAttributeListenerTest.php @@ -0,0 +1,72 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\Tests\EventListener; + +use PHPUnit\Framework\TestCase; +use Symfony\Bridge\Twig\EventListener\TemplateAttributeListener; +use Symfony\Bridge\Twig\Tests\Fixtures\TemplateAttributeController; +use Symfony\Component\Form\FormInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Event\ControllerArgumentsEvent; +use Symfony\Component\HttpKernel\Event\ViewEvent; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Twig\Environment; + +class TemplateAttributeListenerTest extends TestCase +{ + public function testAttribute() + { + $twig = $this->createMock(Environment::class); + $twig->expects($this->exactly(2)) + ->method('render') + ->withConsecutive( + ['templates/foo.html.twig', ['foo' => 'bar']], + ['templates/foo.html.twig', ['bar' => 'Bar', 'buz' => 'def']] + ) + ->willReturn('Bar'); + + $request = new Request(); + $kernel = $this->createMock(HttpKernelInterface::class); + $controllerArgumentsEvent = new ControllerArgumentsEvent($kernel, [new TemplateAttributeController(), 'foo'], ['Bar'], $request, null); + $listener = new TemplateAttributeListener($twig); + + $event = new ViewEvent($kernel, $request, HttpKernelInterface::MAIN_REQUEST, ['foo' => 'bar'], $controllerArgumentsEvent); + $listener->onKernelView($event); + $this->assertSame('Bar', $event->getResponse()->getContent()); + + $event = new ViewEvent($kernel, $request, HttpKernelInterface::MAIN_REQUEST, null, $controllerArgumentsEvent); + $listener->onKernelView($event); + $this->assertSame('Bar', $event->getResponse()->getContent()); + + $event = new ViewEvent($kernel, $request, HttpKernelInterface::MAIN_REQUEST, null); + $listener->onKernelView($event); + $this->assertNull($event->getResponse()); + } + + public function testForm() + { + $request = new Request(); + $kernel = $this->createMock(HttpKernelInterface::class); + $controllerArgumentsEvent = new ControllerArgumentsEvent($kernel, [new TemplateAttributeController(), 'foo'], [], $request, null); + $listener = new TemplateAttributeListener($this->createMock(Environment::class)); + + $form = $this->createMock(FormInterface::class); + $form->expects($this->once())->method('createView'); + $form->expects($this->once())->method('isSubmitted')->willReturn(true); + $form->expects($this->once())->method('isValid')->willReturn(false); + + $event = new ViewEvent($kernel, $request, HttpKernelInterface::MAIN_REQUEST, ['bar' => $form], $controllerArgumentsEvent); + $listener->onKernelView($event); + + $this->assertSame(422, $event->getResponse()->getStatusCode()); + } +} diff --git a/Tests/Fixtures/TemplateAttributeController.php b/Tests/Fixtures/TemplateAttributeController.php new file mode 100644 index 00000000..3e69e0d2 --- /dev/null +++ b/Tests/Fixtures/TemplateAttributeController.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\Tests\Fixtures; + +use Symfony\Bridge\Twig\Attribute\Template; + +class TemplateAttributeController +{ + #[Template('templates/foo.html.twig', vars: ['bar', 'buz'])] + public function foo($bar, $baz = 'abc', $buz = 'def') + { + } +} diff --git a/composer.json b/composer.json index f182dfa4..09d37228 100644 --- a/composer.json +++ b/composer.json @@ -30,7 +30,7 @@ "symfony/form": "^6.1", "symfony/html-sanitizer": "^6.1", "symfony/http-foundation": "^5.4|^6.0", - "symfony/http-kernel": "^5.4|^6.0", + "symfony/http-kernel": "^6.2", "symfony/intl": "^5.4|^6.0", "symfony/mime": "^5.4|^6.0", "symfony/polyfill-intl-icu": "~1.0", @@ -58,7 +58,7 @@ "symfony/console": "<5.4", "symfony/form": "<6.1", "symfony/http-foundation": "<5.4", - "symfony/http-kernel": "<5.4", + "symfony/http-kernel": "<6.2", "symfony/translation": "<5.4", "symfony/workflow": "<5.4" }, From c717c026faa41b53c56af87253ddc9dd55ae6ee6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Tue, 12 Jul 2022 11:18:54 +0200 Subject: [PATCH 034/167] Test _template attribute for Twig listener --- Tests/EventListener/TemplateAttributeListenerTest.php | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/Tests/EventListener/TemplateAttributeListenerTest.php b/Tests/EventListener/TemplateAttributeListenerTest.php index 87712ea5..8f6d6a2c 100644 --- a/Tests/EventListener/TemplateAttributeListenerTest.php +++ b/Tests/EventListener/TemplateAttributeListenerTest.php @@ -12,6 +12,7 @@ namespace Symfony\Bridge\Twig\Tests\EventListener; use PHPUnit\Framework\TestCase; +use Symfony\Bridge\Twig\Attribute\Template; use Symfony\Bridge\Twig\EventListener\TemplateAttributeListener; use Symfony\Bridge\Twig\Tests\Fixtures\TemplateAttributeController; use Symfony\Component\Form\FormInterface; @@ -26,11 +27,12 @@ class TemplateAttributeListenerTest extends TestCase public function testAttribute() { $twig = $this->createMock(Environment::class); - $twig->expects($this->exactly(2)) + $twig->expects($this->exactly(3)) ->method('render') ->withConsecutive( ['templates/foo.html.twig', ['foo' => 'bar']], - ['templates/foo.html.twig', ['bar' => 'Bar', 'buz' => 'def']] + ['templates/foo.html.twig', ['bar' => 'Bar', 'buz' => 'def']], + ['templates/foo.html.twig', []], ) ->willReturn('Bar'); @@ -50,6 +52,11 @@ public function testAttribute() $event = new ViewEvent($kernel, $request, HttpKernelInterface::MAIN_REQUEST, null); $listener->onKernelView($event); $this->assertNull($event->getResponse()); + + $request->attributes->set('_template', new Template('templates/foo.html.twig')); + $event = new ViewEvent($kernel, $request, HttpKernelInterface::MAIN_REQUEST, []); + $listener->onKernelView($event); + $this->assertSame('Bar', $event->getResponse()->getContent()); } public function testForm() From 424ccbe390a45022d195ccaf1b4c0b02c32baab7 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Wed, 27 Jul 2022 09:27:59 +0200 Subject: [PATCH 035/167] [Mime] Change the way we avoid rendering an email twice --- Mime/BodyRenderer.php | 32 ++++++++------------------------ 1 file changed, 8 insertions(+), 24 deletions(-) diff --git a/Mime/BodyRenderer.php b/Mime/BodyRenderer.php index 85e0ba93..d82dca6f 100644 --- a/Mime/BodyRenderer.php +++ b/Mime/BodyRenderer.php @@ -45,15 +45,13 @@ public function render(Message $message): void return; } - $messageContext = $message->getContext(); - - $previousRenderingKey = $messageContext[__CLASS__] ?? null; - unset($messageContext[__CLASS__]); - $currentRenderingKey = $this->getFingerPrint($message); - if ($previousRenderingKey === $currentRenderingKey) { + if (null === $message->getTextTemplate() && null === $message->getHtmlTemplate()) { + // email has already been rendered return; } + $messageContext = $message->getContext(); + if (isset($messageContext['email'])) { throw new InvalidArgumentException(sprintf('A "%s" context cannot have an "email" entry as this is a reserved variable.', get_debug_type($message))); } @@ -64,34 +62,20 @@ public function render(Message $message): void if ($template = $message->getTextTemplate()) { $message->text($this->twig->render($template, $vars)); + $message->textTemplate(null); } if ($template = $message->getHtmlTemplate()) { $message->html($this->twig->render($template, $vars)); + $message->htmlTemplate(null); } + $message->context([]); + // if text body is empty, compute one from the HTML body if (!$message->getTextBody() && null !== $html = $message->getHtmlBody()) { $message->text($this->convertHtmlToText(\is_resource($html) ? stream_get_contents($html) : $html)); } - $message->context($message->getContext() + [__CLASS__ => $currentRenderingKey]); - } - - private function getFingerPrint(TemplatedEmail $message): string - { - $messageContext = $message->getContext(); - unset($messageContext[__CLASS__]); - - $payload = [$messageContext, $message->getTextTemplate(), $message->getHtmlTemplate()]; - try { - $serialized = serialize($payload); - } catch (\Exception) { - // Serialization of 'Closure' is not allowed - // Happens when context contain a closure, in that case, we assume that context always change. - $serialized = random_bytes(8); - } - - return md5($serialized); } private function convertHtmlToText(string $html): string From e628e26df91b6ef773a3693a8fc9c9f6714660ae Mon Sep 17 00:00:00 2001 From: Romain Monteil Date: Mon, 1 Nov 2021 11:24:12 +0100 Subject: [PATCH 036/167] [TwigBridge] Add support for toggle buttons in Bootstrap 5 form theme --- CHANGELOG.md | 1 + .../views/Form/bootstrap_5_layout.html.twig | 44 ++++++++++++++----- .../AbstractBootstrap5LayoutTest.php | 15 +++++++ 3 files changed, 49 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b44911b9..1a58330e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ CHANGELOG * Add `form_label_content` and `form_help_content` block to form themes * Add `#[Template()]` to describe how to render arrays returned by controllers + * Add support for toggle buttons in Bootstrap 5 form theme 6.1 --- diff --git a/Resources/views/Form/bootstrap_5_layout.html.twig b/Resources/views/Form/bootstrap_5_layout.html.twig index eef6f606..a8b381ec 100644 --- a/Resources/views/Form/bootstrap_5_layout.html.twig +++ b/Resources/views/Form/bootstrap_5_layout.html.twig @@ -209,30 +209,48 @@ {%- endblock submit_widget %} {%- block checkbox_widget -%} - {%- set attr = attr|merge({class: (attr.class|default('') ~ ' form-check-input')|trim}) -%} + {%- set attr_class = attr_class|default(attr.class|default('')) -%} + {%- set row_class = '' -%} + {%- if 'btn-check' not in attr_class -%} + {%- set attr_class = attr_class ~ ' form-check-input' -%} + {%- set row_class = 'form-check' -%} + {%- endif -%} + {%- set attr = attr|merge({class: attr_class|trim}) -%} {%- set parent_label_class = parent_label_class|default(label_attr.class|default('')) -%} - {%- set row_class = 'form-check' -%} {%- if 'checkbox-inline' in parent_label_class %} {% set row_class = row_class ~ ' form-check-inline' %} {% endif -%} {%- if 'checkbox-switch' in parent_label_class %} {% set row_class = row_class ~ ' form-switch' %} {% endif -%} -
- {{- form_label(form, null, { widget: parent() }) -}} -
+ {%- if row_class is not empty -%} +
+ {%- endif -%} + {{- form_label(form, null, { widget: parent() }) -}} + {%- if row_class is not empty -%} +
+ {%- endif -%} {%- endblock checkbox_widget %} {%- block radio_widget -%} - {%- set attr = attr|merge({class: (attr.class|default('') ~ ' form-check-input')|trim}) -%} + {%- set attr_class = attr_class|default(attr.class|default('')) -%} + {%- set row_class = '' -%} + {%- if 'btn-check' not in attr_class -%} + {%- set attr_class = attr_class ~ ' form-check-input' -%} + {%- set row_class = 'form-check' -%} + {%- endif -%} + {%- set attr = attr|merge({class: attr_class|trim}) -%} {%- set parent_label_class = parent_label_class|default(label_attr.class|default('')) -%} - {%- set row_class = 'form-check' -%} {%- if 'radio-inline' in parent_label_class -%} {%- set row_class = row_class ~ ' form-check-inline' -%} {%- endif -%} -
- {{- form_label(form, null, { widget: parent() }) -}} -
+ {%- if row_class is not empty -%} +
+ {%- endif -%} + {{- form_label(form, null, { widget: parent() }) -}} + {%- if row_class is not empty -%} +
+ {%- endif -%} {%- endblock radio_widget %} {%- block choice_widget_collapsed -%} @@ -276,7 +294,11 @@ {%- block checkbox_radio_label -%} {#- Do not display the label if widget is not defined in order to prevent double label rendering -#} {%- if widget is defined -%} - {%- set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' form-check-label')|trim}) -%} + {%- set label_attr_class = label_attr_class|default(label_attr.class|default('')) -%} + {%- if 'btn' not in label_attr_class -%} + {%- set label_attr_class = label_attr_class ~ ' form-check-label' -%} + {%- endif -%} + {%- set label_attr = label_attr|merge({class: label_attr_class|trim}) -%} {%- if not compound -%} {% set label_attr = label_attr|merge({'for': id}) %} {%- endif -%} diff --git a/Tests/Extension/AbstractBootstrap5LayoutTest.php b/Tests/Extension/AbstractBootstrap5LayoutTest.php index 1403372b..7ac695ec 100644 --- a/Tests/Extension/AbstractBootstrap5LayoutTest.php +++ b/Tests/Extension/AbstractBootstrap5LayoutTest.php @@ -404,6 +404,21 @@ public function testCheckboxSwitchWithValue() ); } + public function testCheckboxToggleWithValue() + { + $form = $this->factory->createNamed('name', CheckboxType::class, false, [ + 'value' => 'foo&bar', + ]); + + $this->assertWidgetMatchesXpath($form->createView(), ['id' => 'my&id', 'attr' => ['class' => 'btn-check my&class'], 'label_attr' => ['class' => 'btn btn-primary']], + '/input[@type="checkbox"][@name="name"][@id="my&id"][@class="btn-check my&class"][@value="foo&bar"] + /following-sibling::label + [@class="btn btn-primary required"] + [.="[trans]Name[/trans]"] +' + ); + } + public function testMultipleChoiceSkipsPlaceholder() { $form = $this->factory->createNamed('name', ChoiceType::class, ['&a'], [ From 47b3400c44d76aa9998fd990cde7a64eb0d828a9 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Fri, 5 Aug 2022 13:56:35 +0200 Subject: [PATCH 037/167] [Mime] Add a way to control the HTML to text conversion --- Mime/BodyRenderer.php | 29 +++++++++-------------------- Tests/Mime/BodyRendererTest.php | 26 +++++++++++++++++++------- composer.json | 4 +++- 3 files changed, 31 insertions(+), 28 deletions(-) diff --git a/Mime/BodyRenderer.php b/Mime/BodyRenderer.php index d82dca6f..397b35f1 100644 --- a/Mime/BodyRenderer.php +++ b/Mime/BodyRenderer.php @@ -11,9 +11,12 @@ namespace Symfony\Bridge\Twig\Mime; -use League\HTMLToMarkdown\HtmlConverter; +use League\HTMLToMarkdown\HtmlConverterInterface; use Symfony\Component\Mime\BodyRendererInterface; use Symfony\Component\Mime\Exception\InvalidArgumentException; +use Symfony\Component\Mime\HtmlToTextConverter\DefaultHtmlToTextConverter; +use Symfony\Component\Mime\HtmlToTextConverter\HtmlToTextConverterInterface; +use Symfony\Component\Mime\HtmlToTextConverter\LeagueHtmlToMarkdownConverter; use Symfony\Component\Mime\Message; use Twig\Environment; @@ -24,19 +27,13 @@ final class BodyRenderer implements BodyRendererInterface { private Environment $twig; private array $context; - private HtmlConverter $converter; + private HtmlToTextConverterInterface $converter; - public function __construct(Environment $twig, array $context = []) + public function __construct(Environment $twig, array $context = [], HtmlToTextConverterInterface $converter = null) { $this->twig = $twig; $this->context = $context; - if (class_exists(HtmlConverter::class)) { - $this->converter = new HtmlConverter([ - 'hard_break' => true, - 'strip_tags' => true, - 'remove_nodes' => 'head style', - ]); - } + $this->converter = $converter ?: (interface_exists(HtmlConverterInterface::class) ? new LeagueHtmlToMarkdownConverter() : new DefaultHtmlToTextConverter()); } public function render(Message $message): void @@ -74,16 +71,8 @@ public function render(Message $message): void // if text body is empty, compute one from the HTML body if (!$message->getTextBody() && null !== $html = $message->getHtmlBody()) { - $message->text($this->convertHtmlToText(\is_resource($html) ? stream_get_contents($html) : $html)); - } - } - - private function convertHtmlToText(string $html): string - { - if (isset($this->converter)) { - return $this->converter->convert($html); + $text = $this->converter->convert(\is_resource($html) ? stream_get_contents($html) : $html, $message->getHtmlCharset()); + $message->text($text, $message->getHtmlCharset()); } - - return strip_tags(preg_replace('{<(head|style)\b.*?}is', '', $html)); } } diff --git a/Tests/Mime/BodyRendererTest.php b/Tests/Mime/BodyRendererTest.php index 8ff343b6..231067e0 100644 --- a/Tests/Mime/BodyRendererTest.php +++ b/Tests/Mime/BodyRendererTest.php @@ -15,6 +15,8 @@ use Symfony\Bridge\Twig\Mime\BodyRenderer; use Symfony\Bridge\Twig\Mime\TemplatedEmail; use Symfony\Component\Mime\Exception\InvalidArgumentException; +use Symfony\Component\Mime\HtmlToTextConverter\DefaultHtmlToTextConverter; +use Symfony\Component\Mime\HtmlToTextConverter\HtmlToTextConverterInterface; use Symfony\Component\Mime\Part\Multipart\AlternativePart; use Twig\Environment; use Twig\Loader\ArrayLoader; @@ -27,14 +29,24 @@ public function testRenderTextOnly() $this->assertEquals('Text', $email->getBody()->bodyToString()); } - public function testRenderHtmlOnly() + public function testRenderHtmlOnlyWithDefaultConverter() { - $html = 'headHTML'; - $email = $this->prepareEmail(null, $html); + $html = 'HTML'; + $email = $this->prepareEmail(null, $html, [], new DefaultHtmlToTextConverter()); $body = $email->getBody(); $this->assertInstanceOf(AlternativePart::class, $body); $this->assertEquals('HTML', $body->getParts()[0]->bodyToString()); - $this->assertEquals(str_replace('=', '=3D', $html), $body->getParts()[1]->bodyToString()); + $this->assertEquals(str_replace(['=', "\n"], ['=3D', "\r\n"], $html), $body->getParts()[1]->bodyToString()); + } + + public function testRenderHtmlOnlyWithLeagueConverter() + { + $html = 'HTML'; + $email = $this->prepareEmail(null, $html); + $body = $email->getBody(); + $this->assertInstanceOf(AlternativePart::class, $body); + $this->assertEquals('**HTML**', $body->getParts()[0]->bodyToString()); + $this->assertEquals(str_replace(['=', "\n"], ['=3D', "\r\n"], $html), $body->getParts()[1]->bodyToString()); } public function testRenderMultiLineHtmlOnly() @@ -50,7 +62,7 @@ public function testRenderMultiLineHtmlOnly() $email = $this->prepareEmail(null, $html); $body = $email->getBody(); $this->assertInstanceOf(AlternativePart::class, $body); - $this->assertEquals('HTML', str_replace(["\r", "\n"], '', $body->getParts()[0]->bodyToString())); + $this->assertEquals('**HTML**', str_replace(["\r", "\n"], '', $body->getParts()[0]->bodyToString())); $this->assertEquals(str_replace(['=', "\n"], ['=3D', "\r\n"], $html), $body->getParts()[1]->bodyToString()); } @@ -121,7 +133,7 @@ public function testRenderedOnceUnserializableContext() $this->assertEquals('Text', $email->getTextBody()); } - private function prepareEmail(?string $text, ?string $html, array $context = []): TemplatedEmail + private function prepareEmail(?string $text, ?string $html, array $context = [], HtmlToTextConverterInterface $converter = null): TemplatedEmail { $twig = new Environment(new ArrayLoader([ 'text' => $text, @@ -129,7 +141,7 @@ private function prepareEmail(?string $text, ?string $html, array $context = []) 'document.txt' => 'Some text document...', 'image.jpg' => 'Some image data', ])); - $renderer = new BodyRenderer($twig); + $renderer = new BodyRenderer($twig, [], $converter); $email = (new TemplatedEmail()) ->to('fabien@symfony.com') ->from('helene@symfony.com') diff --git a/composer.json b/composer.json index 09d37228..be7c2d2a 100644 --- a/composer.json +++ b/composer.json @@ -23,6 +23,7 @@ "require-dev": { "doctrine/annotations": "^1.12", "egulias/email-validator": "^2.1.10|^3", + "league/html-to-markdown": "^5.0", "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", "symfony/asset": "^5.4|^6.0", "symfony/dependency-injection": "^5.4|^6.0", @@ -32,7 +33,7 @@ "symfony/http-foundation": "^5.4|^6.0", "symfony/http-kernel": "^6.2", "symfony/intl": "^5.4|^6.0", - "symfony/mime": "^5.4|^6.0", + "symfony/mime": "^6.2", "symfony/polyfill-intl-icu": "~1.0", "symfony/property-info": "^5.4|^6.0", "symfony/routing": "^5.4|^6.0", @@ -59,6 +60,7 @@ "symfony/form": "<6.1", "symfony/http-foundation": "<5.4", "symfony/http-kernel": "<6.2", + "symfony/mime": "<6.2", "symfony/translation": "<5.4", "symfony/workflow": "<5.4" }, From ee7a2000e793b102cc2c80ca69982f1c76f16ac2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Pineau?= Date: Thu, 25 Aug 2022 16:59:21 +0200 Subject: [PATCH 038/167] [CS] Remove @inheritdoc PHPDoc --- DataCollector/TwigDataCollector.php | 12 ------------ ErrorRenderer/TwigErrorRenderer.php | 3 --- Extension/AssetExtension.php | 3 --- Extension/CodeExtension.php | 3 --- Extension/CsrfExtension.php | 3 --- Extension/DumpExtension.php | 6 ------ Extension/ExpressionExtension.php | 3 --- Extension/FormExtension.php | 12 ------------ Extension/HttpFoundationExtension.php | 3 --- Extension/HttpKernelExtension.php | 3 --- Extension/LogoutUrlExtension.php | 3 --- Extension/RoutingExtension.php | 3 --- Extension/SecurityExtension.php | 3 --- Extension/TranslationExtension.php | 12 ------------ Extension/WebLinkExtension.php | 3 --- Extension/WorkflowExtension.php | 3 --- Extension/YamlExtension.php | 3 --- Form/TwigRendererEngine.php | 3 --- NodeVisitor/TranslationDefaultDomainNodeVisitor.php | 9 --------- NodeVisitor/TranslationNodeVisitor.php | 9 --------- TokenParser/DumpTokenParser.php | 6 ------ TokenParser/FormThemeTokenParser.php | 6 ------ TokenParser/TransDefaultDomainTokenParser.php | 6 ------ TokenParser/TransTokenParser.php | 6 ------ Translation/TwigExtractor.php | 9 --------- 25 files changed, 135 deletions(-) diff --git a/DataCollector/TwigDataCollector.php b/DataCollector/TwigDataCollector.php index a26a620c..a8da618b 100644 --- a/DataCollector/TwigDataCollector.php +++ b/DataCollector/TwigDataCollector.php @@ -38,16 +38,10 @@ public function __construct(Profile $profile, Environment $twig = null) $this->twig = $twig; } - /** - * {@inheritdoc} - */ public function collect(Request $request, Response $response, \Throwable $exception = null) { } - /** - * {@inheritdoc} - */ public function reset() { $this->profile->reset(); @@ -55,9 +49,6 @@ public function reset() $this->data = []; } - /** - * {@inheritdoc} - */ public function lateCollect() { $this->data['profile'] = serialize($this->profile); @@ -187,9 +178,6 @@ private function computeData(Profile $profile) return $data; } - /** - * {@inheritdoc} - */ public function getName(): string { return 'twig'; diff --git a/ErrorRenderer/TwigErrorRenderer.php b/ErrorRenderer/TwigErrorRenderer.php index 4dde73f6..3155422e 100644 --- a/ErrorRenderer/TwigErrorRenderer.php +++ b/ErrorRenderer/TwigErrorRenderer.php @@ -39,9 +39,6 @@ public function __construct(Environment $twig, HtmlErrorRenderer $fallbackErrorR $this->debug = \is_bool($debug) ? $debug : $debug(...); } - /** - * {@inheritdoc} - */ public function render(\Throwable $exception): FlattenException { $exception = $this->fallbackErrorRenderer->render($exception); diff --git a/Extension/AssetExtension.php b/Extension/AssetExtension.php index 3e12371b..feb25ed5 100644 --- a/Extension/AssetExtension.php +++ b/Extension/AssetExtension.php @@ -29,9 +29,6 @@ public function __construct(Packages $packages) $this->packages = $packages; } - /** - * {@inheritdoc} - */ public function getFunctions(): array { return [ diff --git a/Extension/CodeExtension.php b/Extension/CodeExtension.php index af6ea706..dd2e0682 100644 --- a/Extension/CodeExtension.php +++ b/Extension/CodeExtension.php @@ -33,9 +33,6 @@ public function __construct(string|FileLinkFormatter $fileLinkFormat, string $pr $this->charset = $charset; } - /** - * {@inheritdoc} - */ public function getFilters(): array { return [ diff --git a/Extension/CsrfExtension.php b/Extension/CsrfExtension.php index 646a4879..951fc31d 100644 --- a/Extension/CsrfExtension.php +++ b/Extension/CsrfExtension.php @@ -20,9 +20,6 @@ */ final class CsrfExtension extends AbstractExtension { - /** - * {@inheritdoc} - */ public function getFunctions(): array { return [ diff --git a/Extension/DumpExtension.php b/Extension/DumpExtension.php index 910fece8..a4da2975 100644 --- a/Extension/DumpExtension.php +++ b/Extension/DumpExtension.php @@ -35,9 +35,6 @@ public function __construct(ClonerInterface $cloner, HtmlDumper $dumper = null) $this->dumper = $dumper; } - /** - * {@inheritdoc} - */ public function getFunctions(): array { return [ @@ -45,9 +42,6 @@ public function getFunctions(): array ]; } - /** - * {@inheritdoc} - */ public function getTokenParsers(): array { return [new DumpTokenParser()]; diff --git a/Extension/ExpressionExtension.php b/Extension/ExpressionExtension.php index a3049962..49e4c95c 100644 --- a/Extension/ExpressionExtension.php +++ b/Extension/ExpressionExtension.php @@ -22,9 +22,6 @@ */ final class ExpressionExtension extends AbstractExtension { - /** - * {@inheritdoc} - */ public function getFunctions(): array { return [ diff --git a/Extension/FormExtension.php b/Extension/FormExtension.php index c041a51f..82714596 100644 --- a/Extension/FormExtension.php +++ b/Extension/FormExtension.php @@ -40,9 +40,6 @@ public function __construct(TranslatorInterface $translator = null) $this->translator = $translator; } - /** - * {@inheritdoc} - */ public function getTokenParsers(): array { return [ @@ -51,9 +48,6 @@ public function getTokenParsers(): array ]; } - /** - * {@inheritdoc} - */ public function getFunctions(): array { return [ @@ -77,9 +71,6 @@ public function getFunctions(): array ]; } - /** - * {@inheritdoc} - */ public function getFilters(): array { return [ @@ -88,9 +79,6 @@ public function getFilters(): array ]; } - /** - * {@inheritdoc} - */ public function getTests(): array { return [ diff --git a/Extension/HttpFoundationExtension.php b/Extension/HttpFoundationExtension.php index 104bd323..938d3dda 100644 --- a/Extension/HttpFoundationExtension.php +++ b/Extension/HttpFoundationExtension.php @@ -30,9 +30,6 @@ public function __construct(UrlHelper $urlHelper) $this->urlHelper = $urlHelper; } - /** - * {@inheritdoc} - */ public function getFunctions(): array { return [ diff --git a/Extension/HttpKernelExtension.php b/Extension/HttpKernelExtension.php index 1af9ddb2..072d890d 100644 --- a/Extension/HttpKernelExtension.php +++ b/Extension/HttpKernelExtension.php @@ -22,9 +22,6 @@ */ final class HttpKernelExtension extends AbstractExtension { - /** - * {@inheritdoc} - */ public function getFunctions(): array { return [ diff --git a/Extension/LogoutUrlExtension.php b/Extension/LogoutUrlExtension.php index 3ac7582b..abced287 100644 --- a/Extension/LogoutUrlExtension.php +++ b/Extension/LogoutUrlExtension.php @@ -29,9 +29,6 @@ public function __construct(LogoutUrlGenerator $generator) $this->generator = $generator; } - /** - * {@inheritdoc} - */ public function getFunctions(): array { return [ diff --git a/Extension/RoutingExtension.php b/Extension/RoutingExtension.php index 3b3a2ac5..a22ad14d 100644 --- a/Extension/RoutingExtension.php +++ b/Extension/RoutingExtension.php @@ -32,9 +32,6 @@ public function __construct(UrlGeneratorInterface $generator) $this->generator = $generator; } - /** - * {@inheritdoc} - */ public function getFunctions(): array { return [ diff --git a/Extension/SecurityExtension.php b/Extension/SecurityExtension.php index dd1c57fb..25d1cab2 100644 --- a/Extension/SecurityExtension.php +++ b/Extension/SecurityExtension.php @@ -69,9 +69,6 @@ public function getImpersonateExitPath(string $exitTo = null): string return $this->impersonateUrlGenerator->generateExitPath($exitTo); } - /** - * {@inheritdoc} - */ public function getFunctions(): array { return [ diff --git a/Extension/TranslationExtension.php b/Extension/TranslationExtension.php index cbb7394e..7891dab1 100644 --- a/Extension/TranslationExtension.php +++ b/Extension/TranslationExtension.php @@ -58,9 +58,6 @@ public function getTranslator(): TranslatorInterface return $this->translator; } - /** - * {@inheritdoc} - */ public function getFunctions(): array { return [ @@ -68,9 +65,6 @@ public function getFunctions(): array ]; } - /** - * {@inheritdoc} - */ public function getFilters(): array { return [ @@ -78,9 +72,6 @@ public function getFilters(): array ]; } - /** - * {@inheritdoc} - */ public function getTokenParsers(): array { return [ @@ -92,9 +83,6 @@ public function getTokenParsers(): array ]; } - /** - * {@inheritdoc} - */ public function getNodeVisitors(): array { return [$this->getTranslationNodeVisitor(), new TranslationDefaultDomainNodeVisitor()]; diff --git a/Extension/WebLinkExtension.php b/Extension/WebLinkExtension.php index fcc34ecb..11eca517 100644 --- a/Extension/WebLinkExtension.php +++ b/Extension/WebLinkExtension.php @@ -31,9 +31,6 @@ public function __construct(RequestStack $requestStack) $this->requestStack = $requestStack; } - /** - * {@inheritdoc} - */ public function getFunctions(): array { return [ diff --git a/Extension/WorkflowExtension.php b/Extension/WorkflowExtension.php index 895faf45..71f556ae 100644 --- a/Extension/WorkflowExtension.php +++ b/Extension/WorkflowExtension.php @@ -32,9 +32,6 @@ public function __construct(Registry $workflowRegistry) $this->workflowRegistry = $workflowRegistry; } - /** - * {@inheritdoc} - */ public function getFunctions(): array { return [ diff --git a/Extension/YamlExtension.php b/Extension/YamlExtension.php index 2d0595b7..e265dbfa 100644 --- a/Extension/YamlExtension.php +++ b/Extension/YamlExtension.php @@ -22,9 +22,6 @@ */ final class YamlExtension extends AbstractExtension { - /** - * {@inheritdoc} - */ public function getFilters(): array { return [ diff --git a/Form/TwigRendererEngine.php b/Form/TwigRendererEngine.php index 6f408ebb..7f733455 100644 --- a/Form/TwigRendererEngine.php +++ b/Form/TwigRendererEngine.php @@ -30,9 +30,6 @@ public function __construct(array $defaultThemes, Environment $environment) $this->environment = $environment; } - /** - * {@inheritdoc} - */ public function renderBlock(FormView $view, mixed $resource, string $blockName, array $variables = []): string { $cacheKey = $view->vars[self::CACHE_KEY_VAR]; diff --git a/NodeVisitor/TranslationDefaultDomainNodeVisitor.php b/NodeVisitor/TranslationDefaultDomainNodeVisitor.php index 61c8b5ff..d0e3337a 100644 --- a/NodeVisitor/TranslationDefaultDomainNodeVisitor.php +++ b/NodeVisitor/TranslationDefaultDomainNodeVisitor.php @@ -37,9 +37,6 @@ public function __construct() $this->scope = new Scope(); } - /** - * {@inheritdoc} - */ protected function doEnterNode(Node $node, Environment $env): Node { if ($node instanceof BlockNode || $node instanceof ModuleNode) { @@ -86,9 +83,6 @@ protected function doEnterNode(Node $node, Environment $env): Node return $node; } - /** - * {@inheritdoc} - */ protected function doLeaveNode(Node $node, Environment $env): ?Node { if ($node instanceof TransDefaultDomainNode) { @@ -102,9 +96,6 @@ protected function doLeaveNode(Node $node, Environment $env): ?Node return $node; } - /** - * {@inheritdoc} - */ public function getPriority(): int { return -10; diff --git a/NodeVisitor/TranslationNodeVisitor.php b/NodeVisitor/TranslationNodeVisitor.php index c8bee150..29cb13d0 100644 --- a/NodeVisitor/TranslationNodeVisitor.php +++ b/NodeVisitor/TranslationNodeVisitor.php @@ -49,9 +49,6 @@ public function getMessages(): array return $this->messages; } - /** - * {@inheritdoc} - */ protected function doEnterNode(Node $node, Environment $env): Node { if (!$this->enabled) { @@ -101,17 +98,11 @@ protected function doEnterNode(Node $node, Environment $env): Node return $node; } - /** - * {@inheritdoc} - */ protected function doLeaveNode(Node $node, Environment $env): ?Node { return $node; } - /** - * {@inheritdoc} - */ public function getPriority(): int { return 0; diff --git a/TokenParser/DumpTokenParser.php b/TokenParser/DumpTokenParser.php index 341dc418..d4996dbe 100644 --- a/TokenParser/DumpTokenParser.php +++ b/TokenParser/DumpTokenParser.php @@ -29,9 +29,6 @@ */ final class DumpTokenParser extends AbstractTokenParser { - /** - * {@inheritdoc} - */ public function parse(Token $token): Node { $values = null; @@ -43,9 +40,6 @@ public function parse(Token $token): Node return new DumpNode($this->parser->getVarName(), $values, $token->getLine(), $this->getTag()); } - /** - * {@inheritdoc} - */ public function getTag(): string { return 'dump'; diff --git a/TokenParser/FormThemeTokenParser.php b/TokenParser/FormThemeTokenParser.php index ef5dacb5..b95a2a05 100644 --- a/TokenParser/FormThemeTokenParser.php +++ b/TokenParser/FormThemeTokenParser.php @@ -24,9 +24,6 @@ */ final class FormThemeTokenParser extends AbstractTokenParser { - /** - * {@inheritdoc} - */ public function parse(Token $token): Node { $lineno = $token->getLine(); @@ -54,9 +51,6 @@ public function parse(Token $token): Node return new FormThemeNode($form, $resources, $lineno, $this->getTag(), $only); } - /** - * {@inheritdoc} - */ public function getTag(): string { return 'form_theme'; diff --git a/TokenParser/TransDefaultDomainTokenParser.php b/TokenParser/TransDefaultDomainTokenParser.php index 19b82049..c6d850d0 100644 --- a/TokenParser/TransDefaultDomainTokenParser.php +++ b/TokenParser/TransDefaultDomainTokenParser.php @@ -23,9 +23,6 @@ */ final class TransDefaultDomainTokenParser extends AbstractTokenParser { - /** - * {@inheritdoc} - */ public function parse(Token $token): Node { $expr = $this->parser->getExpressionParser()->parseExpression(); @@ -35,9 +32,6 @@ public function parse(Token $token): Node return new TransDefaultDomainNode($expr, $token->getLine(), $this->getTag()); } - /** - * {@inheritdoc} - */ public function getTag(): string { return 'trans_default_domain'; diff --git a/TokenParser/TransTokenParser.php b/TokenParser/TransTokenParser.php index b440931b..e60263a4 100644 --- a/TokenParser/TransTokenParser.php +++ b/TokenParser/TransTokenParser.php @@ -27,9 +27,6 @@ */ final class TransTokenParser extends AbstractTokenParser { - /** - * {@inheritdoc} - */ public function parse(Token $token): Node { $lineno = $token->getLine(); @@ -85,9 +82,6 @@ public function decideTransFork(Token $token): bool return $token->test(['endtrans']); } - /** - * {@inheritdoc} - */ public function getTag(): string { return 'trans'; diff --git a/Translation/TwigExtractor.php b/Translation/TwigExtractor.php index 86ef2693..0707359d 100644 --- a/Translation/TwigExtractor.php +++ b/Translation/TwigExtractor.php @@ -45,9 +45,6 @@ public function __construct(Environment $twig) $this->twig = $twig; } - /** - * {@inheritdoc} - */ public function extract($resource, MessageCatalogue $catalogue) { foreach ($this->extractFiles($resource) as $file) { @@ -59,9 +56,6 @@ public function extract($resource, MessageCatalogue $catalogue) } } - /** - * {@inheritdoc} - */ public function setPrefix(string $prefix) { $this->prefix = $prefix; @@ -86,9 +80,6 @@ protected function canBeExtracted(string $file): bool return $this->isFile($file) && 'twig' === pathinfo($file, \PATHINFO_EXTENSION); } - /** - * {@inheritdoc} - */ protected function extractFromDirectory($directory): iterable { $finder = new Finder(); From a1917c20607d6ad024b2d949f866963be0c1d13b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois-Xavier=20de=20Guillebon?= Date: Fri, 26 Aug 2022 16:19:22 +0200 Subject: [PATCH 039/167] Replace get_class() calls by ::class --- Mime/NotificationEmail.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mime/NotificationEmail.php b/Mime/NotificationEmail.php index 60a29304..3148d60a 100644 --- a/Mime/NotificationEmail.php +++ b/Mime/NotificationEmail.php @@ -208,7 +208,7 @@ private function getExceptionAsString(\Throwable|FlattenException $exception): s return $exception->getAsString(); } - $message = \get_class($exception); + $message = $exception::class; if ('' !== $exception->getMessage()) { $message .= ': '.$exception->getMessage(); } From 1345265bd6c73bf39396876ea377bb31aa801dac Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Fri, 2 Sep 2022 16:55:07 +0200 Subject: [PATCH 040/167] CS fixes --- Command/DebugCommand.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Command/DebugCommand.php b/Command/DebugCommand.php index d5e64859..889ecb91 100644 --- a/Command/DebugCommand.php +++ b/Command/DebugCommand.php @@ -384,7 +384,7 @@ private function getPrettyMetadata(string $type, mixed $entity, bool $decorated) if ('globals' === $type) { if (\is_object($meta)) { - return ' = object('.\get_class($meta).')'; + return ' = object('.$meta::class.')'; } $description = substr(@json_encode($meta), 0, 50); From dedf70480f1bed0bef9bc378c92eb190c689525d Mon Sep 17 00:00:00 2001 From: Jules Pietri Date: Sat, 10 Sep 2022 10:12:35 +0200 Subject: [PATCH 041/167] [TwigBridge] Expose current route in `AppVariable` --- AppVariable.php | 21 +++++++++++++++++++++ CHANGELOG.md | 1 + Tests/AppVariableTest.php | 34 ++++++++++++++++++++++++++++++++++ 3 files changed, 56 insertions(+) diff --git a/AppVariable.php b/AppVariable.php index 43d6e3ff..d342f9fb 100644 --- a/AppVariable.php +++ b/AppVariable.php @@ -158,4 +158,25 @@ public function getFlashes(string|array $types = null): array return $result; } + + public function getCurrent_Route(): ?string + { + if (!isset($this->requestStack)) { + throw new \RuntimeException('The "app.current_route" variable is not available.'); + } + + return $this->getRequest()?->attributes->get('_route'); + } + + /** + * @return array + */ + public function getCurrent_Route_Parameters(): array + { + if (!isset($this->requestStack)) { + throw new \RuntimeException('The "app.current_route_parameters" variable is not available.'); + } + + return $this->getRequest()?->attributes->get('_route_params') ?? []; + } } diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a58330e..8edc9b80 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ CHANGELOG * Add `form_label_content` and `form_help_content` block to form themes * Add `#[Template()]` to describe how to render arrays returned by controllers * Add support for toggle buttons in Bootstrap 5 form theme + * Add `app.current_route` and `app.current_route_parameters` variables 6.1 --- diff --git a/Tests/AppVariableTest.php b/Tests/AppVariableTest.php index 6ba4ca77..01db4452 100644 --- a/Tests/AppVariableTest.php +++ b/Tests/AppVariableTest.php @@ -228,6 +228,40 @@ public function testGetFlashes() ); } + public function testGetCurrentRoute() + { + $this->setRequestStack(new Request(attributes: ['_route' => 'some_route'])); + + $this->assertSame('some_route', $this->appVariable->getCurrent_Route()); + } + + public function testGetCurrentRouteWithRequestStackNotSet() + { + $this->expectException(\RuntimeException::class); + $this->appVariable->getCurrent_Route(); + } + + public function testGetCurrentRouteParameters() + { + $routeParams = ['some_param' => true]; + $this->setRequestStack(new Request(attributes: ['_route_params' => $routeParams])); + + $this->assertSame($routeParams, $this->appVariable->getCurrent_Route_Parameters()); + } + + public function testGetCurrentRouteParametersWithoutAttribute() + { + $this->setRequestStack(new Request()); + + $this->assertSame([], $this->appVariable->getCurrent_Route_Parameters()); + } + + public function testGetCurrentRouteParametersWithRequestStackNotSet() + { + $this->expectException(\RuntimeException::class); + $this->appVariable->getCurrent_Route_Parameters(); + } + protected function setRequestStack($request) { $requestStackMock = $this->createMock(RequestStack::class); From add1d4037233d6d0fcb59b8fdc47871a75372191 Mon Sep 17 00:00:00 2001 From: tigitz Date: Wed, 21 Sep 2022 16:43:57 +0200 Subject: [PATCH 042/167] Add a few more ??= --- Extension/DumpExtension.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Extension/DumpExtension.php b/Extension/DumpExtension.php index a4da2975..c84e1e75 100644 --- a/Extension/DumpExtension.php +++ b/Extension/DumpExtension.php @@ -68,7 +68,7 @@ public function dump(Environment $env, array $context): ?string } $dump = fopen('php://memory', 'r+'); - $this->dumper = $this->dumper ?? new HtmlDumper(); + $this->dumper ??= new HtmlDumper(); $this->dumper->setCharset($env->getCharset()); foreach ($vars as $value) { From df29c373e9767e6db2a999e7db9b931ce9267819 Mon Sep 17 00:00:00 2001 From: tigitz Date: Wed, 21 Sep 2022 21:07:39 +0200 Subject: [PATCH 043/167] Convert switch cases to match expression --- Command/DebugCommand.php | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/Command/DebugCommand.php b/Command/DebugCommand.php index 889ecb91..6fae02cb 100644 --- a/Command/DebugCommand.php +++ b/Command/DebugCommand.php @@ -101,16 +101,11 @@ protected function execute(InputInterface $input, OutputInterface $output): int throw new InvalidArgumentException(sprintf('Argument "name" not supported, it requires the Twig loader "%s".', FilesystemLoader::class)); } - switch ($input->getOption('format')) { - case 'text': - $name ? $this->displayPathsText($io, $name) : $this->displayGeneralText($io, $filter); - break; - case 'json': - $name ? $this->displayPathsJson($io, $name) : $this->displayGeneralJson($io, $filter); - break; - default: - throw new InvalidArgumentException(sprintf('The format "%s" is not supported.', $input->getOption('format'))); - } + match ($input->getOption('format')) { + 'text' => $name ? $this->displayPathsText($io, $name) : $this->displayGeneralText($io, $filter), + 'json' => $name ? $this->displayPathsJson($io, $name) : $this->displayGeneralJson($io, $filter), + default => throw new InvalidArgumentException(sprintf('The format "%s" is not supported.', $input->getOption('format'))), + }; return 0; } From c531abcdf41d21953338e26fc1c3eb4e1cdcaf57 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Wed, 31 Aug 2022 08:44:02 +0200 Subject: [PATCH 044/167] [Mime] Simplify adding Parts to an Email --- Tests/Mime/TemplatedEmailTest.php | 10 ++++++++-- composer.json | 2 +- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/Tests/Mime/TemplatedEmailTest.php b/Tests/Mime/TemplatedEmailTest.php index 77548fb1..cb2786d8 100644 --- a/Tests/Mime/TemplatedEmailTest.php +++ b/Tests/Mime/TemplatedEmailTest.php @@ -74,10 +74,16 @@ public function testSymfonySerialize() "htmlCharset": null, "attachments": [ { + "filename": "test.txt", + "mediaType": "application", "body": "Some Text file", + "charset": null, + "subtype": "octet-stream", + "disposition": "attachment", "name": "test.txt", - "content-type": null, - "inline": false + "encoding": "base64", + "headers": [], + "class": "Symfony\\\Component\\\Mime\\\Part\\\DataPart" } ], "headers": { diff --git a/composer.json b/composer.json index be7c2d2a..5a4f501e 100644 --- a/composer.json +++ b/composer.json @@ -43,7 +43,7 @@ "symfony/security-core": "^5.4|^6.0", "symfony/security-csrf": "^5.4|^6.0", "symfony/security-http": "^5.4|^6.0", - "symfony/serializer": "^5.4|^6.0", + "symfony/serializer": "^6.2", "symfony/stopwatch": "^5.4|^6.0", "symfony/console": "^5.4|^6.0", "symfony/expression-language": "^5.4|^6.0", From 6a88aaf83843cb641e48ca213f5a640f2930df67 Mon Sep 17 00:00:00 2001 From: Yoann Chocteau Date: Thu, 29 Sep 2022 16:24:02 +0200 Subject: [PATCH 045/167] adding the new block form_label_content for bootstrap_5_layout.html.twig to simplify it --- .../views/Form/bootstrap_5_layout.html.twig | 24 +------------------ 1 file changed, 1 insertion(+), 23 deletions(-) diff --git a/Resources/views/Form/bootstrap_5_layout.html.twig b/Resources/views/Form/bootstrap_5_layout.html.twig index eef6f606..ea593081 100644 --- a/Resources/views/Form/bootstrap_5_layout.html.twig +++ b/Resources/views/Form/bootstrap_5_layout.html.twig @@ -286,33 +286,11 @@ {%- if parent_label_class is defined -%} {%- set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' ' ~ parent_label_class)|replace({'checkbox-inline': '', 'radio-inline': ''})|trim}) -%} {%- endif -%} - {%- if label is not same as(false) and label is empty -%} - {%- if label_format is not empty -%} - {%- set label = label_format|replace({ - '%name%': name, - '%id%': id, - }) -%} - {%- else -%} - {%- set label = name|humanize -%} - {%- endif -%} - {%- endif -%} {{ widget|raw }} {%- if label is not same as(false) -%} - {%- if translation_domain is same as(false) -%} - {%- if label_html is same as(false) -%} - {{- label -}} - {%- else -%} - {{- label|raw -}} - {%- endif -%} - {%- else -%} - {%- if label_html is same as(false) -%} - {{- label|trans(label_translation_parameters, translation_domain) -}} - {%- else -%} - {{- label|trans(label_translation_parameters, translation_domain)|raw -}} - {%- endif -%} - {%- endif -%} + {{- block('form_label_content') -}} {%- endif -%} {%- endif -%} From f170742d92aed03e7137c245b847af67ffeae655 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Pineau?= Date: Fri, 7 Oct 2022 10:31:57 +0200 Subject: [PATCH 046/167] [TwigBridge] Avoid double render when an exception is thrown in !debug --- ErrorRenderer/TwigErrorRenderer.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/ErrorRenderer/TwigErrorRenderer.php b/ErrorRenderer/TwigErrorRenderer.php index 3155422e..9086c22d 100644 --- a/ErrorRenderer/TwigErrorRenderer.php +++ b/ErrorRenderer/TwigErrorRenderer.php @@ -41,17 +41,17 @@ public function __construct(Environment $twig, HtmlErrorRenderer $fallbackErrorR public function render(\Throwable $exception): FlattenException { - $exception = $this->fallbackErrorRenderer->render($exception); - $debug = \is_bool($this->debug) ? $this->debug : ($this->debug)($exception); + $flattenException = FlattenException::createFromThrowable($exception); + $debug = \is_bool($this->debug) ? $this->debug : ($this->debug)($flattenException); - if ($debug || !$template = $this->findTemplate($exception->getStatusCode())) { - return $exception; + if ($debug || !$template = $this->findTemplate($flattenException->getStatusCode())) { + return $this->fallbackErrorRenderer->render($exception); } - return $exception->setAsString($this->twig->render($template, [ - 'exception' => $exception, - 'status_code' => $exception->getStatusCode(), - 'status_text' => $exception->getStatusText(), + return $flattenException->setAsString($this->twig->render($template, [ + 'exception' => $flattenException, + 'status_code' => $flattenException->getStatusCode(), + 'status_text' => $flattenException->getStatusText(), ])); } From 51ab8b1bda4459d9d48905f023366367700bcc2a Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Sun, 25 Sep 2022 20:15:46 +0200 Subject: [PATCH 047/167] [Mime] deprecate attach/embed methods in favor of Email::addPart() --- Mime/NotificationEmail.php | 3 ++- Mime/WrappedTemplatedEmail.php | 16 ++++++---------- Tests/Mime/TemplatedEmailTest.php | 3 ++- 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/Mime/NotificationEmail.php b/Mime/NotificationEmail.php index 3148d60a..215b53a0 100644 --- a/Mime/NotificationEmail.php +++ b/Mime/NotificationEmail.php @@ -14,6 +14,7 @@ use Symfony\Component\ErrorHandler\Exception\FlattenException; use Symfony\Component\Mime\Header\Headers; use Symfony\Component\Mime\Part\AbstractPart; +use Symfony\Component\Mime\Part\DataPart; use Twig\Extra\CssInliner\CssInlinerExtension; use Twig\Extra\Inky\InkyExtension; use Twig\Extra\Markdown\MarkdownExtension; @@ -134,7 +135,7 @@ public function exception(\Throwable|FlattenException $exception): static $exceptionAsString = $this->getExceptionAsString($exception); $this->context['exception'] = true; - $this->attach($exceptionAsString, 'exception.txt', 'text/plain'); + $this->addPart(new DataPart($exceptionAsString, 'exception.txt', 'text/plain')); $this->importance(self::IMPORTANCE_URGENT); if (!$this->getSubject()) { diff --git a/Mime/WrappedTemplatedEmail.php b/Mime/WrappedTemplatedEmail.php index e0b3bef2..1d3b92d6 100644 --- a/Mime/WrappedTemplatedEmail.php +++ b/Mime/WrappedTemplatedEmail.php @@ -12,6 +12,8 @@ namespace Symfony\Bridge\Twig\Mime; use Symfony\Component\Mime\Address; +use Symfony\Component\Mime\Part\BodyFile; +use Symfony\Component\Mime\Part\DataPart; use Twig\Environment; /** @@ -38,11 +40,8 @@ public function toName(): string public function image(string $image, string $contentType = null): string { $file = $this->twig->getLoader()->getSourceContext($image); - if ($path = $file->getPath()) { - $this->message->embedFromPath($path, $image, $contentType); - } else { - $this->message->embed($file->getCode(), $image, $contentType); - } + $body = $file->getPath() ? new BodyFile($file->getPath()) : $file->getCode(); + $this->message->addPart((new DataPart($body, $image, $contentType))->asInline()); return 'cid:'.$image; } @@ -50,11 +49,8 @@ public function image(string $image, string $contentType = null): string public function attach(string $file, string $name = null, string $contentType = null): void { $file = $this->twig->getLoader()->getSourceContext($file); - if ($path = $file->getPath()) { - $this->message->attachFromPath($path, $name, $contentType); - } else { - $this->message->attach($file->getCode(), $name, $contentType); - } + $body = $file->getPath() ? new BodyFile($file->getPath()) : $file->getCode(); + $this->message->addPart(new DataPart($body, $name, $contentType)); } /** diff --git a/Tests/Mime/TemplatedEmailTest.php b/Tests/Mime/TemplatedEmailTest.php index cb2786d8..96d60fa4 100644 --- a/Tests/Mime/TemplatedEmailTest.php +++ b/Tests/Mime/TemplatedEmailTest.php @@ -13,6 +13,7 @@ use PHPUnit\Framework\TestCase; use Symfony\Bridge\Twig\Mime\TemplatedEmail; +use Symfony\Component\Mime\Part\DataPart; use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor; use Symfony\Component\Serializer\Encoder\JsonEncoder; use Symfony\Component\Serializer\Normalizer\ArrayDenormalizer; @@ -58,7 +59,7 @@ public function testSymfonySerialize() $e->textTemplate('email.txt.twig'); $e->htmlTemplate('email.html.twig'); $e->context(['foo' => 'bar']); - $e->attach('Some Text file', 'test.txt'); + $e->addPart(new DataPart('Some Text file', 'test.txt')); $expected = clone $e; $expectedJson = << Date: Wed, 19 Oct 2022 10:10:53 +0200 Subject: [PATCH 048/167] Relax tests to make them compatible with Mime v6.2+ --- Tests/Mime/TemplatedEmailTest.php | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/Tests/Mime/TemplatedEmailTest.php b/Tests/Mime/TemplatedEmailTest.php index 77548fb1..b2101719 100644 --- a/Tests/Mime/TemplatedEmailTest.php +++ b/Tests/Mime/TemplatedEmailTest.php @@ -73,11 +73,9 @@ public function testSymfonySerialize() "html": null, "htmlCharset": null, "attachments": [ - { - "body": "Some Text file", - "name": "test.txt", - "content-type": null, - "inline": false + {%A + "body": "Some Text file",%A + "name": "test.txt",%A } ], "headers": { @@ -111,11 +109,11 @@ public function testSymfonySerialize() ], [new JsonEncoder()]); $serialized = $serializer->serialize($e, 'json', [ObjectNormalizer::IGNORED_ATTRIBUTES => ['cachedBody']]); - $this->assertSame($expectedJson, json_encode(json_decode($serialized), \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES)); + $this->assertStringMatchesFormat($expectedJson, json_encode(json_decode($serialized), \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES)); $n = $serializer->deserialize($serialized, TemplatedEmail::class, 'json'); $serialized = $serializer->serialize($e, 'json', [ObjectNormalizer::IGNORED_ATTRIBUTES => ['cachedBody']]); - $this->assertSame($expectedJson, json_encode(json_decode($serialized), \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES)); + $this->assertStringMatchesFormat($expectedJson, json_encode(json_decode($serialized), \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES)); $n->from('fabien@symfony.com'); $expected->from('fabien@symfony.com'); From 0ba50654b027483bfad03010e87b0e46dfb4a0ab Mon Sep 17 00:00:00 2001 From: "William Pinaud (DocFX)" Date: Tue, 18 Oct 2022 15:50:14 +0200 Subject: [PATCH 049/167] [TwigBridge] Provide a default non-empty label on empty options to validate HTML5 --- Resources/views/Form/form_div_layout.html.twig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Resources/views/Form/form_div_layout.html.twig b/Resources/views/Form/form_div_layout.html.twig index 61f64e33..2e331f70 100644 --- a/Resources/views/Form/form_div_layout.html.twig +++ b/Resources/views/Form/form_div_layout.html.twig @@ -61,7 +61,7 @@ {%- endif -%} {%- if placeholder is not none -%} - + {%- endif -%} {%- if preferred_choices|length > 0 -%} {% set options = preferred_choices %} From 574a41c9c6aa0b8c8da8340d3744a7a12c6e4534 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Mon, 31 Oct 2022 19:29:16 +0100 Subject: [PATCH 051/167] [Mime] rename Part/BodyFile to Part/File --- Mime/WrappedTemplatedEmail.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Mime/WrappedTemplatedEmail.php b/Mime/WrappedTemplatedEmail.php index 1d3b92d6..d8a5eea9 100644 --- a/Mime/WrappedTemplatedEmail.php +++ b/Mime/WrappedTemplatedEmail.php @@ -12,8 +12,8 @@ namespace Symfony\Bridge\Twig\Mime; use Symfony\Component\Mime\Address; -use Symfony\Component\Mime\Part\BodyFile; use Symfony\Component\Mime\Part\DataPart; +use Symfony\Component\Mime\Part\File; use Twig\Environment; /** @@ -40,7 +40,7 @@ public function toName(): string public function image(string $image, string $contentType = null): string { $file = $this->twig->getLoader()->getSourceContext($image); - $body = $file->getPath() ? new BodyFile($file->getPath()) : $file->getCode(); + $body = $file->getPath() ? new File($file->getPath()) : $file->getCode(); $this->message->addPart((new DataPart($body, $image, $contentType))->asInline()); return 'cid:'.$image; @@ -49,7 +49,7 @@ public function image(string $image, string $contentType = null): string public function attach(string $file, string $name = null, string $contentType = null): void { $file = $this->twig->getLoader()->getSourceContext($file); - $body = $file->getPath() ? new BodyFile($file->getPath()) : $file->getCode(); + $body = $file->getPath() ? new File($file->getPath()) : $file->getCode(); $this->message->addPart(new DataPart($body, $name, $contentType)); } From 96be740c6a8e1fbf50200865c96420faaecab11f Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 1 Nov 2022 22:49:27 +0100 Subject: [PATCH 052/167] Use ??= more --- Extension/YamlExtension.php | 4 +--- Tests/Extension/TranslationExtensionTest.php | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/Extension/YamlExtension.php b/Extension/YamlExtension.php index e265dbfa..cbfcc32a 100644 --- a/Extension/YamlExtension.php +++ b/Extension/YamlExtension.php @@ -34,9 +34,7 @@ public function encode(mixed $input, int $inline = 0, int $dumpObjects = 0): str { static $dumper; - if (null === $dumper) { - $dumper = new YamlDumper(); - } + $dumper ??= new YamlDumper(); if (\defined('Symfony\Component\Yaml\Yaml::DUMP_OBJECT')) { return $dumper->dump($input, $inline, 0, $dumpObjects); diff --git a/Tests/Extension/TranslationExtensionTest.php b/Tests/Extension/TranslationExtensionTest.php index 86f50da0..a0c2cad1 100644 --- a/Tests/Extension/TranslationExtensionTest.php +++ b/Tests/Extension/TranslationExtensionTest.php @@ -207,9 +207,7 @@ public function testDefaultTranslationDomainWithNamedArguments() private function getTemplate($template, TranslatorInterface $translator = null): TemplateWrapper { - if (null === $translator) { - $translator = new Translator('en'); - } + $translator ??= new Translator('en'); if (\is_array($template)) { $loader = new TwigArrayLoader($template); From 414302d702e51ae8282315d1d50bb5d01631f077 Mon Sep 17 00:00:00 2001 From: Nowfel Terki Date: Sun, 27 Nov 2022 18:38:51 +0100 Subject: [PATCH 053/167] [DependencyInjection][ErrorHandler][FrameworkBundle][HttpKernel][TwigBridge] Fix "a" before "URL" --- Extension/RoutingExtension.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Extension/RoutingExtension.php b/Extension/RoutingExtension.php index a22ad14d..270583f2 100644 --- a/Extension/RoutingExtension.php +++ b/Extension/RoutingExtension.php @@ -55,7 +55,7 @@ public function getUrl(string $name, array $parameters = [], bool $schemeRelativ * saving the unneeded automatic escaping for performance reasons. * * The URL generation process percent encodes non-alphanumeric characters. So there is no risk - * that malicious/invalid characters are part of the URL. The only character within an URL that + * that malicious/invalid characters are part of the URL. The only character within a URL that * must be escaped in html is the ampersand ("&") which separates query params. So we cannot mark * the URL generation as always safe, but only when we are sure there won't be multiple query * params. This is the case when there are none or only one constant parameter given. From 5b76548a86909c644fb844f4775d33d9e8bacd09 Mon Sep 17 00:00:00 2001 From: Hugo Alliaume Date: Fri, 2 Dec 2022 00:04:56 +0100 Subject: [PATCH 054/167] [TwigBridge] Fix casing of currentRoute/currentRouteParameters methods --- AppVariable.php | 4 ++-- Tests/AppVariableTest.php | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/AppVariable.php b/AppVariable.php index d342f9fb..6cdbaa83 100644 --- a/AppVariable.php +++ b/AppVariable.php @@ -159,7 +159,7 @@ public function getFlashes(string|array $types = null): array return $result; } - public function getCurrent_Route(): ?string + public function getCurrent_route(): ?string { if (!isset($this->requestStack)) { throw new \RuntimeException('The "app.current_route" variable is not available.'); @@ -171,7 +171,7 @@ public function getCurrent_Route(): ?string /** * @return array */ - public function getCurrent_Route_Parameters(): array + public function getCurrent_route_parameters(): array { if (!isset($this->requestStack)) { throw new \RuntimeException('The "app.current_route_parameters" variable is not available.'); diff --git a/Tests/AppVariableTest.php b/Tests/AppVariableTest.php index 01db4452..ab3702fa 100644 --- a/Tests/AppVariableTest.php +++ b/Tests/AppVariableTest.php @@ -232,13 +232,13 @@ public function testGetCurrentRoute() { $this->setRequestStack(new Request(attributes: ['_route' => 'some_route'])); - $this->assertSame('some_route', $this->appVariable->getCurrent_Route()); + $this->assertSame('some_route', $this->appVariable->getCurrent_route()); } public function testGetCurrentRouteWithRequestStackNotSet() { $this->expectException(\RuntimeException::class); - $this->appVariable->getCurrent_Route(); + $this->appVariable->getCurrent_route(); } public function testGetCurrentRouteParameters() @@ -246,20 +246,20 @@ public function testGetCurrentRouteParameters() $routeParams = ['some_param' => true]; $this->setRequestStack(new Request(attributes: ['_route_params' => $routeParams])); - $this->assertSame($routeParams, $this->appVariable->getCurrent_Route_Parameters()); + $this->assertSame($routeParams, $this->appVariable->getCurrent_route_parameters()); } public function testGetCurrentRouteParametersWithoutAttribute() { $this->setRequestStack(new Request()); - $this->assertSame([], $this->appVariable->getCurrent_Route_Parameters()); + $this->assertSame([], $this->appVariable->getCurrent_route_parameters()); } public function testGetCurrentRouteParametersWithRequestStackNotSet() { $this->expectException(\RuntimeException::class); - $this->appVariable->getCurrent_Route_Parameters(); + $this->appVariable->getCurrent_route_parameters(); } protected function setRequestStack($request) From 4d8abf95a2142a9d1622e5757e815d2e5d76441a Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Tue, 6 Dec 2022 08:09:14 +0100 Subject: [PATCH 055/167] [Mailer] Fix rendered templates for notifications --- Mime/BodyRenderer.php | 4 +--- Mime/NotificationEmail.php | 19 +++++++++++++++++-- Mime/TemplatedEmail.php | 12 ++++++++++++ 3 files changed, 30 insertions(+), 5 deletions(-) diff --git a/Mime/BodyRenderer.php b/Mime/BodyRenderer.php index 397b35f1..d418ee2f 100644 --- a/Mime/BodyRenderer.php +++ b/Mime/BodyRenderer.php @@ -59,15 +59,13 @@ public function render(Message $message): void if ($template = $message->getTextTemplate()) { $message->text($this->twig->render($template, $vars)); - $message->textTemplate(null); } if ($template = $message->getHtmlTemplate()) { $message->html($this->twig->render($template, $vars)); - $message->htmlTemplate(null); } - $message->context([]); + $message->markAsRendered(); // if text body is empty, compute one from the HTML body if (!$message->getTextBody() && null !== $html = $message->getHtmlBody()) { diff --git a/Mime/NotificationEmail.php b/Mime/NotificationEmail.php index 7f70d322..be6fea5c 100644 --- a/Mime/NotificationEmail.php +++ b/Mime/NotificationEmail.php @@ -40,6 +40,7 @@ class NotificationEmail extends TemplatedEmail 'raw' => false, 'footer_text' => 'Notification e-mail sent by Symfony', ]; + private bool $rendered = false; public function __construct(Headers $headers = null, AbstractPart $body = null) { @@ -178,6 +179,18 @@ public function getContext(): array return array_merge($this->context, parent::getContext()); } + public function isRendered(): bool + { + return $this->rendered; + } + + public function markAsRendered(): void + { + parent::markAsRendered(); + + $this->rendered = true; + } + public function getPreparedHeaders(): Headers { $headers = parent::getPreparedHeaders(); @@ -225,7 +238,7 @@ private function getExceptionAsString(\Throwable|FlattenException $exception): s */ public function __serialize(): array { - return [$this->context, $this->theme, parent::__serialize()]; + return [$this->context, $this->theme, $this->rendered, parent::__serialize()]; } /** @@ -233,7 +246,9 @@ public function __serialize(): array */ public function __unserialize(array $data): void { - if (3 === \count($data)) { + if (4 === \count($data)) { + [$this->context, $this->theme, $this->rendered, $parentData] = $data; + } elseif (3 === \count($data)) { [$this->context, $this->theme, $parentData] = $data; } else { // Backwards compatibility for deserializing data structures that were serialized without the theme diff --git a/Mime/TemplatedEmail.php b/Mime/TemplatedEmail.php index 083b0077..777cc06b 100644 --- a/Mime/TemplatedEmail.php +++ b/Mime/TemplatedEmail.php @@ -67,6 +67,18 @@ public function getContext(): array return $this->context; } + public function isRendered(): bool + { + return null === $this->htmlTemplate && null === $this->textTemplate; + } + + public function markAsRendered(): void + { + $this->textTemplate = null; + $this->htmlTemplate = null; + $this->context = []; + } + /** * @internal */ From 57333eebae3fd73ce2bffa5757552c3635d1398b Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Wed, 28 Dec 2022 15:47:09 +0100 Subject: [PATCH 056/167] Drop v1 contracts packages everywhere --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index afcd87d3..26b04afa 100644 --- a/composer.json +++ b/composer.json @@ -17,7 +17,7 @@ ], "require": { "php": ">=8.1", - "symfony/translation-contracts": "^1.1|^2|^3", + "symfony/translation-contracts": "^2.5|^3", "twig/twig": "^2.13|^3.0.4" }, "require-dev": { From 8a6f4810735460fe5a1b2ddb68e790f7cc40d914 Mon Sep 17 00:00:00 2001 From: Reyo Stallenberg Date: Tue, 29 Nov 2022 09:13:21 +0100 Subject: [PATCH 057/167] Fix spelling of emails Use correct spelling for email --- Mime/NotificationEmail.php | 2 +- Tests/Mime/NotificationEmailTest.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Mime/NotificationEmail.php b/Mime/NotificationEmail.php index be6fea5c..e9681df4 100644 --- a/Mime/NotificationEmail.php +++ b/Mime/NotificationEmail.php @@ -38,7 +38,7 @@ class NotificationEmail extends TemplatedEmail 'action_url' => null, 'markdown' => false, 'raw' => false, - 'footer_text' => 'Notification e-mail sent by Symfony', + 'footer_text' => 'Notification email sent by Symfony', ]; private bool $rendered = false; diff --git a/Tests/Mime/NotificationEmailTest.php b/Tests/Mime/NotificationEmailTest.php index ceafea1b..6e48f2b4 100644 --- a/Tests/Mime/NotificationEmailTest.php +++ b/Tests/Mime/NotificationEmailTest.php @@ -35,7 +35,7 @@ public function test() 'markdown' => true, 'raw' => false, 'a' => 'b', - 'footer_text' => 'Notification e-mail sent by Symfony', + 'footer_text' => 'Notification email sent by Symfony', ], $email->getContext()); } @@ -58,7 +58,7 @@ public function testSerialize() 'markdown' => false, 'raw' => true, 'a' => 'b', - 'footer_text' => 'Notification e-mail sent by Symfony', + 'footer_text' => 'Notification email sent by Symfony', ], $email->getContext()); $this->assertSame('@email/example/notification/body.html.twig', $email->getHtmlTemplate()); From ab70fbb5f768366f662fd39e9c2d102ca319f7ae Mon Sep 17 00:00:00 2001 From: tigitz Date: Sun, 1 Jan 2023 19:45:34 +0100 Subject: [PATCH 058/167] Leverage arrow function syntax for closure --- Command/DebugCommand.php | 6 ++---- Extension/CodeExtension.php | 8 ++------ Tests/Command/LintCommandTest.php | 4 +--- Tests/Extension/AbstractBootstrap3LayoutTest.php | 8 ++------ Tests/Extension/AbstractBootstrap4LayoutTest.php | 8 ++------ Tests/Mime/BodyRendererTest.php | 4 +--- UndefinedCallableHandler.php | 2 +- 7 files changed, 11 insertions(+), 29 deletions(-) diff --git a/Command/DebugCommand.php b/Command/DebugCommand.php index 6fae02cb..d4e8528e 100644 --- a/Command/DebugCommand.php +++ b/Command/DebugCommand.php @@ -162,9 +162,7 @@ private function displayPathsText(SymfonyStyle $io, string $name) [$namespace, $shortname] = $this->parseTemplateName($name); $alternatives = $this->findAlternatives($shortname, $shortnames); if (FilesystemLoader::MAIN_NAMESPACE !== $namespace) { - $alternatives = array_map(function ($shortname) use ($namespace) { - return '@'.$namespace.'/'.$shortname; - }, $alternatives); + $alternatives = array_map(fn ($shortname) => '@'.$namespace.'/'.$shortname, $alternatives); } } @@ -543,7 +541,7 @@ private function findAlternatives(string $name, array $collection): array } $threshold = 1e3; - $alternatives = array_filter($alternatives, function ($lev) use ($threshold) { return $lev < 2 * $threshold; }); + $alternatives = array_filter($alternatives, fn ($lev) => $lev < 2 * $threshold); ksort($alternatives, \SORT_NATURAL | \SORT_FLAG_CASE); return array_keys($alternatives); diff --git a/Extension/CodeExtension.php b/Extension/CodeExtension.php index dd2e0682..748d60cb 100644 --- a/Extension/CodeExtension.php +++ b/Extension/CodeExtension.php @@ -120,9 +120,7 @@ public function fileExcerpt(string $file, int $line, int $srcContext = 3): ?stri // remove main code/span tags $code = preg_replace('#^\s*(.*)\s*#s', '\\1', $code); // split multiline spans - $code = preg_replace_callback('#]++)>((?:[^<]*+
)++[^<]*+)
#', function ($m) { - return "".str_replace('
', "

", $m[2]).''; - }, $code); + $code = preg_replace_callback('#]++)>((?:[^<]*+
)++[^<]*+)
#', fn ($m) => "".str_replace('
', "

", $m[2]).'', $code); $content = explode('
', $code); $lines = []; @@ -188,9 +186,7 @@ public function getFileRelative(string $file): ?string public function formatFileFromText(string $text): string { - return preg_replace_callback('/in ("|")?(.+?)\1(?: +(?:on|at))? +line (\d+)/s', function ($match) { - return 'in '.$this->formatFile($match[2], $match[3]); - }, $text); + return preg_replace_callback('/in ("|")?(.+?)\1(?: +(?:on|at))? +line (\d+)/s', fn ($match) => 'in '.$this->formatFile($match[2], $match[3]), $text); } /** diff --git a/Tests/Command/LintCommandTest.php b/Tests/Command/LintCommandTest.php index 6428f486..05bef211 100644 --- a/Tests/Command/LintCommandTest.php +++ b/Tests/Command/LintCommandTest.php @@ -159,9 +159,7 @@ private function createCommandTester(): CommandTester private function createCommand(): Command { $environment = new Environment(new FilesystemLoader(\dirname(__DIR__).'/Fixtures/templates/')); - $environment->addFilter(new TwigFilter('deprecated_filter', function ($v) { - return $v; - }, ['deprecated' => true])); + $environment->addFilter(new TwigFilter('deprecated_filter', fn ($v) => $v, ['deprecated' => true])); $command = new LintCommand($environment); diff --git a/Tests/Extension/AbstractBootstrap3LayoutTest.php b/Tests/Extension/AbstractBootstrap3LayoutTest.php index 80835230..9f1626bd 100644 --- a/Tests/Extension/AbstractBootstrap3LayoutTest.php +++ b/Tests/Extension/AbstractBootstrap3LayoutTest.php @@ -1039,9 +1039,7 @@ public function testSingleChoiceExpandedWithLabelsSetFalseByCallable() { $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', '&a', [ 'choices' => ['Choice&A' => '&a', 'Choice&B' => '&b'], - 'choice_label' => function () { - return false; - }, + 'choice_label' => fn () => false, 'multiple' => false, 'expanded' => true, ]); @@ -1404,9 +1402,7 @@ public function testMultipleChoiceExpandedWithLabelsSetFalseByCallable() { $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', ['&a'], [ 'choices' => ['Choice&A' => '&a', 'Choice&B' => '&b'], - 'choice_label' => function () { - return false; - }, + 'choice_label' => fn () => false, 'multiple' => true, 'expanded' => true, ]); diff --git a/Tests/Extension/AbstractBootstrap4LayoutTest.php b/Tests/Extension/AbstractBootstrap4LayoutTest.php index 8689df83..af303e49 100644 --- a/Tests/Extension/AbstractBootstrap4LayoutTest.php +++ b/Tests/Extension/AbstractBootstrap4LayoutTest.php @@ -580,9 +580,7 @@ public function testSingleChoiceExpandedWithLabelsSetFalseByCallable() { $form = $this->factory->createNamed('name', ChoiceType::class, '&a', [ 'choices' => ['Choice&A' => '&a', 'Choice&B' => '&b'], - 'choice_label' => function () { - return false; - }, + 'choice_label' => fn () => false, 'multiple' => false, 'expanded' => true, ]); @@ -901,9 +899,7 @@ public function testMultipleChoiceExpandedWithLabelsSetFalseByCallable() { $form = $this->factory->createNamed('name', ChoiceType::class, ['&a'], [ 'choices' => ['Choice&A' => '&a', 'Choice&B' => '&b'], - 'choice_label' => function () { - return false; - }, + 'choice_label' => fn () => false, 'multiple' => true, 'expanded' => true, ]); diff --git a/Tests/Mime/BodyRendererTest.php b/Tests/Mime/BodyRendererTest.php index 231067e0..6af152da 100644 --- a/Tests/Mime/BodyRendererTest.php +++ b/Tests/Mime/BodyRendererTest.php @@ -124,9 +124,7 @@ public function testRenderedOnceUnserializableContext() ; $email->textTemplate('text'); $email->context([ - 'foo' => static function () { - return 'bar'; - }, + 'foo' => static fn () => 'bar', ]); $renderer->render($email); diff --git a/UndefinedCallableHandler.php b/UndefinedCallableHandler.php index c4d3971e..9368f15b 100644 --- a/UndefinedCallableHandler.php +++ b/UndefinedCallableHandler.php @@ -87,7 +87,7 @@ public static function onUndefinedFunction(string $name): TwigFunction|false } if ('webpack-encore-bundle' === self::FUNCTION_COMPONENTS[$name]) { - return new TwigFunction($name, static function () { return ''; }); + return new TwigFunction($name, static fn () => ''); } throw new SyntaxError(self::onUndefined($name, 'function', self::FUNCTION_COMPONENTS[$name])); From f24a58379e6ce06c88155d0f88d8ce317427aa46 Mon Sep 17 00:00:00 2001 From: Takashi Kanemoto Date: Sun, 5 Feb 2023 17:27:55 +0900 Subject: [PATCH 059/167] [TwigBridge] Improve form_errors of bootstrap5 form theme --- .../views/Form/bootstrap_5_layout.html.twig | 2 +- ...AbstractBootstrap5HorizontalLayoutTest.php | 12 +++--- .../AbstractBootstrap5LayoutTest.php | 37 +++++++++++++++---- 3 files changed, 37 insertions(+), 14 deletions(-) diff --git a/Resources/views/Form/bootstrap_5_layout.html.twig b/Resources/views/Form/bootstrap_5_layout.html.twig index 252b95e4..cb5a60e0 100644 --- a/Resources/views/Form/bootstrap_5_layout.html.twig +++ b/Resources/views/Form/bootstrap_5_layout.html.twig @@ -353,7 +353,7 @@ {%- block form_errors -%} {%- if errors|length > 0 -%} {%- for error in errors -%} -
{{ error.message }}
+
{{ error.message }}
{%- endfor -%} {%- endif %} {%- endblock form_errors %} diff --git a/Tests/Extension/AbstractBootstrap5HorizontalLayoutTest.php b/Tests/Extension/AbstractBootstrap5HorizontalLayoutTest.php index e6a8846c..ffae59e6 100644 --- a/Tests/Extension/AbstractBootstrap5HorizontalLayoutTest.php +++ b/Tests/Extension/AbstractBootstrap5HorizontalLayoutTest.php @@ -28,9 +28,9 @@ abstract class AbstractBootstrap5HorizontalLayoutTest extends AbstractBootstrap5 { public function testRow() { - $form = $this->factory->createNamed('name', TextType::class); - $form->addError(new FormError('[trans]Error![/trans]')); - $html = $this->renderRow($form->createView()); + $form = $this->factory->createNamed('')->add('name', TextType::class); + $form->get('name')->addError(new FormError('[trans]Error![/trans]')); + $html = $this->renderRow($form->get('name')->createView()); $this->assertMatchesXpath($html, '/div @@ -55,9 +55,9 @@ public function testRow() public function testRowWithCustomClass() { - $form = $this->factory->createNamed('name', TextType::class); - $form->addError(new FormError('[trans]Error![/trans]')); - $html = $this->renderRow($form->createView(), [ + $form = $this->factory->createNamed('')->add('name', TextType::class); + $form->get('name')->addError(new FormError('[trans]Error![/trans]')); + $html = $this->renderRow($form->get('name')->createView(), [ 'row_attr' => [ 'class' => 'mb-5', ], diff --git a/Tests/Extension/AbstractBootstrap5LayoutTest.php b/Tests/Extension/AbstractBootstrap5LayoutTest.php index e85ad797..dcb76b19 100644 --- a/Tests/Extension/AbstractBootstrap5LayoutTest.php +++ b/Tests/Extension/AbstractBootstrap5LayoutTest.php @@ -40,9 +40,9 @@ abstract class AbstractBootstrap5LayoutTest extends AbstractBootstrap4LayoutTest { public function testRow() { - $form = $this->factory->createNamed('name', TextType::class); - $form->addError(new FormError('[trans]Error![/trans]')); - $html = $this->renderRow($form->createView()); + $form = $this->factory->createNamed('')->add('name', TextType::class); + $form->get('name')->addError(new FormError('[trans]Error![/trans]')); + $html = $this->renderRow($form->get('name')->createView()); $this->assertMatchesXpath($html, '/div @@ -61,9 +61,9 @@ public function testRow() public function testRowWithCustomClass() { - $form = $this->factory->createNamed('name', TextType::class); - $form->addError(new FormError('[trans]Error![/trans]')); - $html = $this->renderRow($form->createView(), [ + $form = $this->factory->createNamed('')->add('name', TextType::class); + $form->get('name')->addError(new FormError('[trans]Error![/trans]')); + $html = $this->renderRow($form->get('name')->createView(), [ 'row_attr' => [ 'class' => 'mb-5', ], @@ -309,11 +309,34 @@ public function testHelpHtmlIsTrue() public function testErrors() { - $form = $this->factory->createNamed('name', TextType::class); + self::markTestSkipped('This method has been split into testRootErrors() and testRowErrors().'); + } + + public function testRootErrors() + { + $form = $this->factory->createNamed(''); $form->addError(new FormError('[trans]Error 1[/trans]')); $form->addError(new FormError('[trans]Error 2[/trans]')); $html = $this->renderErrors($form->createView()); + $this->assertMatchesXpath($html, + '/div + [@class="alert alert-danger d-block"] + [.="[trans]Error 1[/trans]"] + /following-sibling::div + [@class="alert alert-danger d-block"] + [.="[trans]Error 2[/trans]"] +' + ); + } + + public function testRowErrors() + { + $form = $this->factory->createNamed('')->add('name', TextType::class); + $form->get('name')->addError(new FormError('[trans]Error 1[/trans]')); + $form->get('name')->addError(new FormError('[trans]Error 2[/trans]')); + $html = $this->renderErrors($form->get('name')->createView()); + $this->assertMatchesXpath($html, '/div [@class="invalid-feedback d-block"] From 887b195a89683fb12f7d4993972c8ea8d18466d1 Mon Sep 17 00:00:00 2001 From: Oskar Stark Date: Mon, 6 Feb 2023 06:06:48 +0100 Subject: [PATCH 060/167] [PHPUnit 10] Use `TestCase` suffix for abstract tests in `/Tests/` --- ...est.php => AbstractBootstrap3HorizontalLayoutTestCase.php} | 2 +- ...ap3LayoutTest.php => AbstractBootstrap3LayoutTestCase.php} | 4 ++-- ...est.php => AbstractBootstrap4HorizontalLayoutTestCase.php} | 2 +- ...ap4LayoutTest.php => AbstractBootstrap4LayoutTestCase.php} | 2 +- ...est.php => AbstractBootstrap5HorizontalLayoutTestCase.php} | 2 +- ...ap5LayoutTest.php => AbstractBootstrap5LayoutTestCase.php} | 2 +- .../Extension/FormExtensionBootstrap3HorizontalLayoutTest.php | 2 +- Tests/Extension/FormExtensionBootstrap3LayoutTest.php | 2 +- .../Extension/FormExtensionBootstrap4HorizontalLayoutTest.php | 2 +- Tests/Extension/FormExtensionBootstrap4LayoutTest.php | 2 +- .../Extension/FormExtensionBootstrap5HorizontalLayoutTest.php | 2 +- Tests/Extension/FormExtensionBootstrap5LayoutTest.php | 2 +- Tests/Extension/FormExtensionDivLayoutTest.php | 4 ++-- Tests/Extension/FormExtensionTableLayoutTest.php | 4 ++-- composer.json | 4 ++-- 15 files changed, 19 insertions(+), 19 deletions(-) rename Tests/Extension/{AbstractBootstrap3HorizontalLayoutTest.php => AbstractBootstrap3HorizontalLayoutTestCase.php} (98%) rename Tests/Extension/{AbstractBootstrap3LayoutTest.php => AbstractBootstrap3LayoutTestCase.php} (99%) rename Tests/Extension/{AbstractBootstrap4HorizontalLayoutTest.php => AbstractBootstrap4HorizontalLayoutTestCase.php} (98%) rename Tests/Extension/{AbstractBootstrap4LayoutTest.php => AbstractBootstrap4LayoutTestCase.php} (99%) rename Tests/Extension/{AbstractBootstrap5HorizontalLayoutTest.php => AbstractBootstrap5HorizontalLayoutTestCase.php} (99%) rename Tests/Extension/{AbstractBootstrap5LayoutTest.php => AbstractBootstrap5LayoutTestCase.php} (99%) diff --git a/Tests/Extension/AbstractBootstrap3HorizontalLayoutTest.php b/Tests/Extension/AbstractBootstrap3HorizontalLayoutTestCase.php similarity index 98% rename from Tests/Extension/AbstractBootstrap3HorizontalLayoutTest.php rename to Tests/Extension/AbstractBootstrap3HorizontalLayoutTestCase.php index c4874c34..e79b0c31 100644 --- a/Tests/Extension/AbstractBootstrap3HorizontalLayoutTest.php +++ b/Tests/Extension/AbstractBootstrap3HorizontalLayoutTestCase.php @@ -11,7 +11,7 @@ namespace Symfony\Bridge\Twig\Tests\Extension; -abstract class AbstractBootstrap3HorizontalLayoutTest extends AbstractBootstrap3LayoutTest +abstract class AbstractBootstrap3HorizontalLayoutTestCase extends AbstractBootstrap3LayoutTestCase { public function testLabelOnForm() { diff --git a/Tests/Extension/AbstractBootstrap3LayoutTest.php b/Tests/Extension/AbstractBootstrap3LayoutTestCase.php similarity index 99% rename from Tests/Extension/AbstractBootstrap3LayoutTest.php rename to Tests/Extension/AbstractBootstrap3LayoutTestCase.php index 9f1626bd..aa88d7bb 100644 --- a/Tests/Extension/AbstractBootstrap3LayoutTest.php +++ b/Tests/Extension/AbstractBootstrap3LayoutTestCase.php @@ -13,9 +13,9 @@ use Symfony\Component\Form\Extension\Core\Type\PercentType; use Symfony\Component\Form\FormError; -use Symfony\Component\Form\Tests\AbstractLayoutTest; +use Symfony\Component\Form\Tests\AbstractLayoutTestCase; -abstract class AbstractBootstrap3LayoutTest extends AbstractLayoutTest +abstract class AbstractBootstrap3LayoutTestCase extends AbstractLayoutTestCase { public function testLabelOnForm() { diff --git a/Tests/Extension/AbstractBootstrap4HorizontalLayoutTest.php b/Tests/Extension/AbstractBootstrap4HorizontalLayoutTestCase.php similarity index 98% rename from Tests/Extension/AbstractBootstrap4HorizontalLayoutTest.php rename to Tests/Extension/AbstractBootstrap4HorizontalLayoutTestCase.php index 1ef14ecf..ce8a7336 100644 --- a/Tests/Extension/AbstractBootstrap4HorizontalLayoutTest.php +++ b/Tests/Extension/AbstractBootstrap4HorizontalLayoutTestCase.php @@ -18,7 +18,7 @@ * * @author Hidde Wieringa */ -abstract class AbstractBootstrap4HorizontalLayoutTest extends AbstractBootstrap4LayoutTest +abstract class AbstractBootstrap4HorizontalLayoutTestCase extends AbstractBootstrap4LayoutTestCase { public function testRow() { diff --git a/Tests/Extension/AbstractBootstrap4LayoutTest.php b/Tests/Extension/AbstractBootstrap4LayoutTestCase.php similarity index 99% rename from Tests/Extension/AbstractBootstrap4LayoutTest.php rename to Tests/Extension/AbstractBootstrap4LayoutTestCase.php index af303e49..08b4bbe7 100644 --- a/Tests/Extension/AbstractBootstrap4LayoutTest.php +++ b/Tests/Extension/AbstractBootstrap4LayoutTestCase.php @@ -28,7 +28,7 @@ * * @author Hidde Wieringa */ -abstract class AbstractBootstrap4LayoutTest extends AbstractBootstrap3LayoutTest +abstract class AbstractBootstrap4LayoutTestCase extends AbstractBootstrap3LayoutTestCase { public function testRow() { diff --git a/Tests/Extension/AbstractBootstrap5HorizontalLayoutTest.php b/Tests/Extension/AbstractBootstrap5HorizontalLayoutTestCase.php similarity index 99% rename from Tests/Extension/AbstractBootstrap5HorizontalLayoutTest.php rename to Tests/Extension/AbstractBootstrap5HorizontalLayoutTestCase.php index ffae59e6..428d5a43 100644 --- a/Tests/Extension/AbstractBootstrap5HorizontalLayoutTest.php +++ b/Tests/Extension/AbstractBootstrap5HorizontalLayoutTestCase.php @@ -24,7 +24,7 @@ * * @author Romain Monteil */ -abstract class AbstractBootstrap5HorizontalLayoutTest extends AbstractBootstrap5LayoutTest +abstract class AbstractBootstrap5HorizontalLayoutTestCase extends AbstractBootstrap5LayoutTestCase { public function testRow() { diff --git a/Tests/Extension/AbstractBootstrap5LayoutTest.php b/Tests/Extension/AbstractBootstrap5LayoutTestCase.php similarity index 99% rename from Tests/Extension/AbstractBootstrap5LayoutTest.php rename to Tests/Extension/AbstractBootstrap5LayoutTestCase.php index dcb76b19..1057dc77 100644 --- a/Tests/Extension/AbstractBootstrap5LayoutTest.php +++ b/Tests/Extension/AbstractBootstrap5LayoutTestCase.php @@ -36,7 +36,7 @@ * * @author Romain Monteil */ -abstract class AbstractBootstrap5LayoutTest extends AbstractBootstrap4LayoutTest +abstract class AbstractBootstrap5LayoutTestCase extends AbstractBootstrap4LayoutTestCase { public function testRow() { diff --git a/Tests/Extension/FormExtensionBootstrap3HorizontalLayoutTest.php b/Tests/Extension/FormExtensionBootstrap3HorizontalLayoutTest.php index a0953a2d..e746a267 100644 --- a/Tests/Extension/FormExtensionBootstrap3HorizontalLayoutTest.php +++ b/Tests/Extension/FormExtensionBootstrap3HorizontalLayoutTest.php @@ -21,7 +21,7 @@ use Twig\Environment; use Twig\Loader\FilesystemLoader; -class FormExtensionBootstrap3HorizontalLayoutTest extends AbstractBootstrap3HorizontalLayoutTest +class FormExtensionBootstrap3HorizontalLayoutTest extends AbstractBootstrap3HorizontalLayoutTestCase { use RuntimeLoaderProvider; diff --git a/Tests/Extension/FormExtensionBootstrap3LayoutTest.php b/Tests/Extension/FormExtensionBootstrap3LayoutTest.php index a3a6c751..2b7e186c 100644 --- a/Tests/Extension/FormExtensionBootstrap3LayoutTest.php +++ b/Tests/Extension/FormExtensionBootstrap3LayoutTest.php @@ -21,7 +21,7 @@ use Twig\Environment; use Twig\Loader\FilesystemLoader; -class FormExtensionBootstrap3LayoutTest extends AbstractBootstrap3LayoutTest +class FormExtensionBootstrap3LayoutTest extends AbstractBootstrap3LayoutTestCase { use RuntimeLoaderProvider; diff --git a/Tests/Extension/FormExtensionBootstrap4HorizontalLayoutTest.php b/Tests/Extension/FormExtensionBootstrap4HorizontalLayoutTest.php index 33e1862a..f95e6784 100644 --- a/Tests/Extension/FormExtensionBootstrap4HorizontalLayoutTest.php +++ b/Tests/Extension/FormExtensionBootstrap4HorizontalLayoutTest.php @@ -26,7 +26,7 @@ * * @author Hidde Wieringa */ -class FormExtensionBootstrap4HorizontalLayoutTest extends AbstractBootstrap4HorizontalLayoutTest +class FormExtensionBootstrap4HorizontalLayoutTest extends AbstractBootstrap4HorizontalLayoutTestCase { use RuntimeLoaderProvider; diff --git a/Tests/Extension/FormExtensionBootstrap4LayoutTest.php b/Tests/Extension/FormExtensionBootstrap4LayoutTest.php index 00fee10e..2d0ca9fa 100644 --- a/Tests/Extension/FormExtensionBootstrap4LayoutTest.php +++ b/Tests/Extension/FormExtensionBootstrap4LayoutTest.php @@ -26,7 +26,7 @@ * * @author Hidde Wieringa */ -class FormExtensionBootstrap4LayoutTest extends AbstractBootstrap4LayoutTest +class FormExtensionBootstrap4LayoutTest extends AbstractBootstrap4LayoutTestCase { use RuntimeLoaderProvider; /** diff --git a/Tests/Extension/FormExtensionBootstrap5HorizontalLayoutTest.php b/Tests/Extension/FormExtensionBootstrap5HorizontalLayoutTest.php index ef924884..d6d2c3c2 100644 --- a/Tests/Extension/FormExtensionBootstrap5HorizontalLayoutTest.php +++ b/Tests/Extension/FormExtensionBootstrap5HorizontalLayoutTest.php @@ -26,7 +26,7 @@ * * @author Romain Monteil */ -class FormExtensionBootstrap5HorizontalLayoutTest extends AbstractBootstrap5HorizontalLayoutTest +class FormExtensionBootstrap5HorizontalLayoutTest extends AbstractBootstrap5HorizontalLayoutTestCase { use RuntimeLoaderProvider; diff --git a/Tests/Extension/FormExtensionBootstrap5LayoutTest.php b/Tests/Extension/FormExtensionBootstrap5LayoutTest.php index ed69ca81..94174615 100644 --- a/Tests/Extension/FormExtensionBootstrap5LayoutTest.php +++ b/Tests/Extension/FormExtensionBootstrap5LayoutTest.php @@ -28,7 +28,7 @@ * * @author Romain Monteil */ -class FormExtensionBootstrap5LayoutTest extends AbstractBootstrap5LayoutTest +class FormExtensionBootstrap5LayoutTest extends AbstractBootstrap5LayoutTestCase { use RuntimeLoaderProvider; diff --git a/Tests/Extension/FormExtensionDivLayoutTest.php b/Tests/Extension/FormExtensionDivLayoutTest.php index cae1a1c6..1ceae9f7 100644 --- a/Tests/Extension/FormExtensionDivLayoutTest.php +++ b/Tests/Extension/FormExtensionDivLayoutTest.php @@ -18,12 +18,12 @@ use Symfony\Component\Form\ChoiceList\View\ChoiceView; use Symfony\Component\Form\FormRenderer; use Symfony\Component\Form\FormView; -use Symfony\Component\Form\Tests\AbstractDivLayoutTest; +use Symfony\Component\Form\Tests\AbstractDivLayoutTestCase; use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; use Twig\Environment; use Twig\Loader\FilesystemLoader; -class FormExtensionDivLayoutTest extends AbstractDivLayoutTest +class FormExtensionDivLayoutTest extends AbstractDivLayoutTestCase { use RuntimeLoaderProvider; diff --git a/Tests/Extension/FormExtensionTableLayoutTest.php b/Tests/Extension/FormExtensionTableLayoutTest.php index 7b75be23..c0a7a9e8 100644 --- a/Tests/Extension/FormExtensionTableLayoutTest.php +++ b/Tests/Extension/FormExtensionTableLayoutTest.php @@ -17,12 +17,12 @@ use Symfony\Bridge\Twig\Tests\Extension\Fixtures\StubTranslator; use Symfony\Component\Form\FormRenderer; use Symfony\Component\Form\FormView; -use Symfony\Component\Form\Tests\AbstractTableLayoutTest; +use Symfony\Component\Form\Tests\AbstractTableLayoutTestCase; use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; use Twig\Environment; use Twig\Loader\FilesystemLoader; -class FormExtensionTableLayoutTest extends AbstractTableLayoutTest +class FormExtensionTableLayoutTest extends AbstractTableLayoutTestCase { use RuntimeLoaderProvider; diff --git a/composer.json b/composer.json index f44efbc4..1639803c 100644 --- a/composer.json +++ b/composer.json @@ -28,7 +28,7 @@ "symfony/asset": "^5.4|^6.0", "symfony/dependency-injection": "^5.4|^6.0", "symfony/finder": "^5.4|^6.0", - "symfony/form": "^6.1", + "symfony/form": "^6.3", "symfony/html-sanitizer": "^6.1", "symfony/http-foundation": "^5.4|^6.0", "symfony/http-kernel": "^6.2", @@ -57,7 +57,7 @@ "phpdocumentor/reflection-docblock": "<3.2.2", "phpdocumentor/type-resolver": "<1.4.0", "symfony/console": "<5.4", - "symfony/form": "<6.1", + "symfony/form": "<6.3", "symfony/http-foundation": "<5.4", "symfony/http-kernel": "<6.2", "symfony/mime": "<6.2", From 7be54bb93d1293a81a07906fa85ce42f781dedca Mon Sep 17 00:00:00 2001 From: Wouter de Jong Date: Mon, 13 Feb 2023 00:00:27 +0100 Subject: [PATCH 061/167] Add PHP types to private methods and functions --- Command/LintCommand.php | 4 ++-- DataCollector/TwigDataCollector.php | 18 +++++++++--------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Command/LintCommand.php b/Command/LintCommand.php index 54f4e233..1429f4ff 100644 --- a/Command/LintCommand.php +++ b/Command/LintCommand.php @@ -179,7 +179,7 @@ private function display(InputInterface $input, OutputInterface $output, Symfony }; } - private function displayTxt(OutputInterface $output, SymfonyStyle $io, array $filesInfo, bool $errorAsGithubAnnotations = false) + private function displayTxt(OutputInterface $output, SymfonyStyle $io, array $filesInfo, bool $errorAsGithubAnnotations = false): int { $errors = 0; $githubReporter = $errorAsGithubAnnotations ? new GithubActionReporter($output) : null; @@ -254,7 +254,7 @@ private function renderException(SymfonyStyle $output, string $template, Error $ } } - private function getContext(string $template, int $line, int $context = 3) + private function getContext(string $template, int $line, int $context = 3): array { $lines = explode("\n", $template); diff --git a/DataCollector/TwigDataCollector.php b/DataCollector/TwigDataCollector.php index a8da618b..2366433f 100644 --- a/DataCollector/TwigDataCollector.php +++ b/DataCollector/TwigDataCollector.php @@ -78,37 +78,37 @@ public function lateCollect() $templateFinder($this->profile); } - public function getTime() + public function getTime(): int { return $this->getProfile()->getDuration() * 1000; } - public function getTemplateCount() + public function getTemplateCount(): int { return $this->getComputedData('template_count'); } - public function getTemplatePaths() + public function getTemplatePaths(): array { return $this->data['template_paths']; } - public function getTemplates() + public function getTemplates(): array { return $this->getComputedData('templates'); } - public function getBlockCount() + public function getBlockCount(): int { return $this->getComputedData('block_count'); } - public function getMacroCount() + public function getMacroCount(): int { return $this->getComputedData('macro_count'); } - public function getHtmlCallGraph() + public function getHtmlCallGraph(): Markup { $dumper = new HtmlDumper(); $dump = $dumper->dump($this->getProfile()); @@ -129,7 +129,7 @@ public function getHtmlCallGraph() return new Markup($dump, 'UTF-8'); } - public function getProfile() + public function getProfile(): Profile { return $this->profile ??= unserialize($this->data['profile'], ['allowed_classes' => ['Twig_Profiler_Profile', Profile::class]]); } @@ -141,7 +141,7 @@ private function getComputedData(string $index) return $this->computed[$index]; } - private function computeData(Profile $profile) + private function computeData(Profile $profile): array { $data = [ 'template_count' => 0, From 8b904040177e8baf3f301761f4238ee45a021770 Mon Sep 17 00:00:00 2001 From: Wouter de Jong Date: Sun, 12 Feb 2023 23:57:18 +0100 Subject: [PATCH 062/167] Add void return types --- AppVariable.php | 12 ++++++++++++ Command/DebugCommand.php | 11 +++++++---- Command/LintCommand.php | 3 +++ DataCollector/TwigDataCollector.php | 6 +++--- EventListener/TemplateAttributeListener.php | 3 +++ Form/TwigRendererEngine.php | 2 ++ Translation/TwigExtractor.php | 9 +++++++++ 7 files changed, 39 insertions(+), 7 deletions(-) diff --git a/AppVariable.php b/AppVariable.php index 6cdbaa83..6ce2278c 100644 --- a/AppVariable.php +++ b/AppVariable.php @@ -30,21 +30,33 @@ class AppVariable private string $environment; private bool $debug; + /** + * @return void + */ public function setTokenStorage(TokenStorageInterface $tokenStorage) { $this->tokenStorage = $tokenStorage; } + /** + * @return void + */ public function setRequestStack(RequestStack $requestStack) { $this->requestStack = $requestStack; } + /** + * @return void + */ public function setEnvironment(string $environment) { $this->environment = $environment; } + /** + * @return void + */ public function setDebug(bool $debug) { $this->debug = $debug; diff --git a/Command/DebugCommand.php b/Command/DebugCommand.php index d4e8528e..cec03112 100644 --- a/Command/DebugCommand.php +++ b/Command/DebugCommand.php @@ -59,6 +59,9 @@ public function __construct(Environment $twig, string $projectDir = null, array $this->fileLinkFormatter = $fileLinkFormatter; } + /** + * @return void + */ protected function configure() { $this @@ -121,7 +124,7 @@ public function complete(CompletionInput $input, CompletionSuggestions $suggesti } } - private function displayPathsText(SymfonyStyle $io, string $name) + private function displayPathsText(SymfonyStyle $io, string $name): void { $file = new \ArrayIterator($this->findTemplateFiles($name)); $paths = $this->getLoaderPaths($name); @@ -196,7 +199,7 @@ private function displayPathsText(SymfonyStyle $io, string $name) } } - private function displayPathsJson(SymfonyStyle $io, string $name) + private function displayPathsJson(SymfonyStyle $io, string $name): void { $files = $this->findTemplateFiles($name); $paths = $this->getLoaderPaths($name); @@ -214,7 +217,7 @@ private function displayPathsJson(SymfonyStyle $io, string $name) $io->writeln(json_encode($data)); } - private function displayGeneralText(SymfonyStyle $io, string $filter = null) + private function displayGeneralText(SymfonyStyle $io, string $filter = null): void { $decorated = $io->isDecorated(); $types = ['functions', 'filters', 'tests', 'globals']; @@ -248,7 +251,7 @@ private function displayGeneralText(SymfonyStyle $io, string $filter = null) } } - private function displayGeneralJson(SymfonyStyle $io, ?string $filter) + private function displayGeneralJson(SymfonyStyle $io, ?string $filter): void { $decorated = $io->isDecorated(); $types = ['functions', 'filters', 'tests', 'globals']; diff --git a/Command/LintCommand.php b/Command/LintCommand.php index 1429f4ff..05f6319b 100644 --- a/Command/LintCommand.php +++ b/Command/LintCommand.php @@ -48,6 +48,9 @@ public function __construct( parent::__construct(); } + /** + * @return void + */ protected function configure() { $this diff --git a/DataCollector/TwigDataCollector.php b/DataCollector/TwigDataCollector.php index 2366433f..cfdba17b 100644 --- a/DataCollector/TwigDataCollector.php +++ b/DataCollector/TwigDataCollector.php @@ -38,18 +38,18 @@ public function __construct(Profile $profile, Environment $twig = null) $this->twig = $twig; } - public function collect(Request $request, Response $response, \Throwable $exception = null) + public function collect(Request $request, Response $response, \Throwable $exception = null): void { } - public function reset() + public function reset(): void { $this->profile->reset(); unset($this->computed); $this->data = []; } - public function lateCollect() + public function lateCollect(): void { $this->data['profile'] = serialize($this->profile); $this->data['template_paths'] = []; diff --git a/EventListener/TemplateAttributeListener.php b/EventListener/TemplateAttributeListener.php index 96924442..ef0f9ba9 100644 --- a/EventListener/TemplateAttributeListener.php +++ b/EventListener/TemplateAttributeListener.php @@ -28,6 +28,9 @@ public function __construct( ) { } + /** + * @return void + */ public function onKernelView(ViewEvent $event) { $parameters = $event->getControllerResult(); diff --git a/Form/TwigRendererEngine.php b/Form/TwigRendererEngine.php index 7f733455..fbe8e0b3 100644 --- a/Form/TwigRendererEngine.php +++ b/Form/TwigRendererEngine.php @@ -132,6 +132,8 @@ protected function loadResourceForBlockName(string $cacheKey, FormView $view, st * to initialize the theme first. Any changes made to * this variable will be kept and be available upon * further calls to this method using the same theme. + * + * @return void */ protected function loadResourcesFromTheme(string $cacheKey, mixed &$theme) { diff --git a/Translation/TwigExtractor.php b/Translation/TwigExtractor.php index 0707359d..2b44c5ef 100644 --- a/Translation/TwigExtractor.php +++ b/Translation/TwigExtractor.php @@ -45,6 +45,9 @@ public function __construct(Environment $twig) $this->twig = $twig; } + /** + * @return void + */ public function extract($resource, MessageCatalogue $catalogue) { foreach ($this->extractFiles($resource) as $file) { @@ -56,11 +59,17 @@ public function extract($resource, MessageCatalogue $catalogue) } } + /** + * @return void + */ public function setPrefix(string $prefix) { $this->prefix = $prefix; } + /** + * @return void + */ protected function extractTemplate(string $template, MessageCatalogue $catalogue) { $visitor = $this->twig->getExtension(TranslationExtension::class)->getTranslationNodeVisitor(); From d2d50dc32b8aea745bba3f3cdac3582d7a4fa988 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Wed, 22 Feb 2023 17:58:05 +0100 Subject: [PATCH 063/167] do not drop embed label classes --- Resources/views/Form/bootstrap_4_layout.html.twig | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Resources/views/Form/bootstrap_4_layout.html.twig b/Resources/views/Form/bootstrap_4_layout.html.twig index 3e9904ad..458cc684 100644 --- a/Resources/views/Form/bootstrap_4_layout.html.twig +++ b/Resources/views/Form/bootstrap_4_layout.html.twig @@ -261,6 +261,10 @@ {%- if required -%} {%- set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' required')|trim}) -%} {%- endif -%} + {%- if parent_label_class is defined -%} + {% set embed_label_classes = parent_label_class|split(' ')|filter(class => class in ['checkbox-inline', 'radio-inline']) %} + {%- set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' ' ~ embed_label_classes|join(' '))|trim}) -%} + {% endif %} {{ widget|raw }} From 177b3670c3a42225aa412613f337cddd8d61653d Mon Sep 17 00:00:00 2001 From: Thomas Calvet Date: Mon, 27 Feb 2023 17:42:38 +0100 Subject: [PATCH 064/167] [TwigBridge] Fix TwigDataCollector::getTime() return type --- DataCollector/TwigDataCollector.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DataCollector/TwigDataCollector.php b/DataCollector/TwigDataCollector.php index cfdba17b..e261a050 100644 --- a/DataCollector/TwigDataCollector.php +++ b/DataCollector/TwigDataCollector.php @@ -78,7 +78,7 @@ public function lateCollect(): void $templateFinder($this->profile); } - public function getTime(): int + public function getTime(): float { return $this->getProfile()->getDuration() * 1000; } From 3414196db79c0f1a1315cdfd76cfbd55f9e68bc1 Mon Sep 17 00:00:00 2001 From: Alexandre Daubois Date: Fri, 10 Mar 2023 17:29:15 +0100 Subject: [PATCH 065/167] [Tests] Remove `withConsecutive()` calls from tests --- .../TemplateAttributeListenerTest.php | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/Tests/EventListener/TemplateAttributeListenerTest.php b/Tests/EventListener/TemplateAttributeListenerTest.php index 8f6d6a2c..e1fb7f95 100644 --- a/Tests/EventListener/TemplateAttributeListenerTest.php +++ b/Tests/EventListener/TemplateAttributeListenerTest.php @@ -29,12 +29,18 @@ public function testAttribute() $twig = $this->createMock(Environment::class); $twig->expects($this->exactly(3)) ->method('render') - ->withConsecutive( - ['templates/foo.html.twig', ['foo' => 'bar']], - ['templates/foo.html.twig', ['bar' => 'Bar', 'buz' => 'def']], - ['templates/foo.html.twig', []], - ) - ->willReturn('Bar'); + ->willReturnCallback(function (...$args) { + static $series = [ + ['templates/foo.html.twig', ['foo' => 'bar']], + ['templates/foo.html.twig', ['bar' => 'Bar', 'buz' => 'def']], + ['templates/foo.html.twig', []], + ]; + + $this->assertSame(array_shift($series), $args); + + return 'Bar'; + }) + ; $request = new Request(); $kernel = $this->createMock(HttpKernelInterface::class); From 75d2c0511a374d9cd3dc78dedb8c1e23a1942821 Mon Sep 17 00:00:00 2001 From: Jordane Vaspard Date: Sat, 26 Nov 2022 15:45:54 +0100 Subject: [PATCH 066/167] [Form][ChoiceType] Add placeholder_attr field option --- Resources/views/Form/form_div_layout.html.twig | 2 +- Resources/views/Form/foundation_5_layout.html.twig | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Resources/views/Form/form_div_layout.html.twig b/Resources/views/Form/form_div_layout.html.twig index 61f64e33..48ef5583 100644 --- a/Resources/views/Form/form_div_layout.html.twig +++ b/Resources/views/Form/form_div_layout.html.twig @@ -61,7 +61,7 @@ {%- endif -%} {% if placeholder is not none -%} - + {%- endif %} {%- if preferred_choices|length > 0 -%} {% set options = preferred_choices %} From b370bd3ad360311227a4703a770361e6cb611151 Mon Sep 17 00:00:00 2001 From: Yassine Guedidi Date: Sun, 2 Apr 2023 04:08:55 +0200 Subject: [PATCH 067/167] Apply operator_linebreak PHP-CS-Fixer rule --- Extension/RoutingExtension.php | 4 ++-- NodeVisitor/TranslationNodeVisitor.php | 18 +++++++++--------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Extension/RoutingExtension.php b/Extension/RoutingExtension.php index 270583f2..5827640d 100644 --- a/Extension/RoutingExtension.php +++ b/Extension/RoutingExtension.php @@ -79,8 +79,8 @@ public function isUrlGenerationSafe(Node $argsNode): array $argsNode->hasNode(1) ? $argsNode->getNode(1) : null ); - if (null === $paramsNode || $paramsNode instanceof ArrayExpression && \count($paramsNode) <= 2 && - (!$paramsNode->hasNode(1) || $paramsNode->getNode(1) instanceof ConstantExpression) + if (null === $paramsNode || $paramsNode instanceof ArrayExpression && \count($paramsNode) <= 2 + && (!$paramsNode->hasNode(1) || $paramsNode->getNode(1) instanceof ConstantExpression) ) { return ['html']; } diff --git a/NodeVisitor/TranslationNodeVisitor.php b/NodeVisitor/TranslationNodeVisitor.php index 29cb13d0..d5e95040 100644 --- a/NodeVisitor/TranslationNodeVisitor.php +++ b/NodeVisitor/TranslationNodeVisitor.php @@ -56,9 +56,9 @@ protected function doEnterNode(Node $node, Environment $env): Node } if ( - $node instanceof FilterExpression && - 'trans' === $node->getNode('filter')->getAttribute('value') && - $node->getNode('node') instanceof ConstantExpression + $node instanceof FilterExpression + && 'trans' === $node->getNode('filter')->getAttribute('value') + && $node->getNode('node') instanceof ConstantExpression ) { // extract constant nodes with a trans filter $this->messages[] = [ @@ -66,8 +66,8 @@ protected function doEnterNode(Node $node, Environment $env): Node $this->getReadDomainFromArguments($node->getNode('arguments'), 1), ]; } elseif ( - $node instanceof FunctionExpression && - 't' === $node->getAttribute('name') + $node instanceof FunctionExpression + && 't' === $node->getAttribute('name') ) { $nodeArguments = $node->getNode('arguments'); @@ -84,10 +84,10 @@ protected function doEnterNode(Node $node, Environment $env): Node $node->hasNode('domain') ? $this->getReadDomainFromNode($node->getNode('domain')) : null, ]; } elseif ( - $node instanceof FilterExpression && - 'trans' === $node->getNode('filter')->getAttribute('value') && - $node->getNode('node') instanceof ConcatBinary && - $message = $this->getConcatValueFromNode($node->getNode('node'), null) + $node instanceof FilterExpression + && 'trans' === $node->getNode('filter')->getAttribute('value') + && $node->getNode('node') instanceof ConcatBinary + && $message = $this->getConcatValueFromNode($node->getNode('node'), null) ) { $this->messages[] = [ $message, From 41956b885033fa32cb20e7281d3a75f05ee79670 Mon Sep 17 00:00:00 2001 From: Antoine Lamirault Date: Mon, 27 Mar 2023 20:06:57 +0200 Subject: [PATCH 068/167] Harmonize command formats and ensure autocompletion is same --- Command/DebugCommand.php | 11 ++++++++--- Command/LintCommand.php | 11 ++++++++--- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/Command/DebugCommand.php b/Command/DebugCommand.php index cec03112..fe581b59 100644 --- a/Command/DebugCommand.php +++ b/Command/DebugCommand.php @@ -68,7 +68,7 @@ protected function configure() ->setDefinition([ new InputArgument('name', InputArgument::OPTIONAL, 'The template name'), new InputOption('filter', null, InputOption::VALUE_REQUIRED, 'Show details for all entries matching this filter'), - new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (text or json)', 'text'), + new InputOption('format', null, InputOption::VALUE_REQUIRED, sprintf('The output format ("%s")', implode('", "', $this->getAvailableFormatOptions())), 'text'), ]) ->setHelp(<<<'EOF' The %command.name% command outputs a list of twig functions, @@ -107,7 +107,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int match ($input->getOption('format')) { 'text' => $name ? $this->displayPathsText($io, $name) : $this->displayGeneralText($io, $filter), 'json' => $name ? $this->displayPathsJson($io, $name) : $this->displayGeneralJson($io, $filter), - default => throw new InvalidArgumentException(sprintf('The format "%s" is not supported.', $input->getOption('format'))), + default => throw new InvalidArgumentException(sprintf('Supported formats are "%s".', implode('", "', $this->getAvailableFormatOptions()))), }; return 0; @@ -120,7 +120,7 @@ public function complete(CompletionInput $input, CompletionSuggestions $suggesti } if ($input->mustSuggestOptionValuesFor('format')) { - $suggestions->suggestValues(['text', 'json']); + $suggestions->suggestValues($this->getAvailableFormatOptions()); } } @@ -596,4 +596,9 @@ private function getFileLink(string $absolutePath): string return (string) $this->fileLinkFormatter->format($absolutePath, 1); } + + private function getAvailableFormatOptions(): array + { + return ['text', 'json']; + } } diff --git a/Command/LintCommand.php b/Command/LintCommand.php index 05f6319b..fa8effc1 100644 --- a/Command/LintCommand.php +++ b/Command/LintCommand.php @@ -54,7 +54,7 @@ public function __construct( protected function configure() { $this - ->addOption('format', null, InputOption::VALUE_REQUIRED, 'The output format') + ->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') ->setHelp(<<<'EOF' @@ -178,7 +178,7 @@ private function display(InputInterface $input, OutputInterface $output, Symfony 'txt' => $this->displayTxt($output, $io, $files), 'json' => $this->displayJson($output, $files), 'github' => $this->displayTxt($output, $io, $files, true), - default => throw new InvalidArgumentException(sprintf('The format "%s" is not supported.', $input->getOption('format'))), + default => throw new InvalidArgumentException(sprintf('Supported formats are "%s".', implode('", "', $this->getAvailableFormatOptions()))), }; } @@ -276,7 +276,12 @@ private function getContext(string $template, int $line, int $context = 3): arra public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void { if ($input->mustSuggestOptionValuesFor('format')) { - $suggestions->suggestValues(['txt', 'json', 'github']); + $suggestions->suggestValues($this->getAvailableFormatOptions()); } } + + private function getAvailableFormatOptions(): array + { + return ['txt', 'json', 'github']; + } } From 7f1d798394aae22c7122faa20f797cb59d55faf7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sama=C3=ABl=20Villette?= Date: Mon, 3 Apr 2023 16:22:34 +0200 Subject: [PATCH 069/167] [TwigBridge][TwigBundle] Add current locale to `AppVariable` --- AppVariable.php | 16 ++++++++++++++++ CHANGELOG.md | 5 +++++ Tests/AppVariableTest.php | 18 ++++++++++++++++++ composer.json | 2 +- 4 files changed, 40 insertions(+), 1 deletion(-) diff --git a/AppVariable.php b/AppVariable.php index 6ce2278c..8bfaa0a2 100644 --- a/AppVariable.php +++ b/AppVariable.php @@ -17,6 +17,7 @@ use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\User\UserInterface; +use Symfony\Component\Translation\LocaleSwitcher; /** * Exposes some Symfony parameters and services as an "app" global variable. @@ -29,6 +30,7 @@ class AppVariable private RequestStack $requestStack; private string $environment; private bool $debug; + private LocaleSwitcher $localeSwitcher; /** * @return void @@ -62,6 +64,11 @@ public function setDebug(bool $debug) $this->debug = $debug; } + public function setLocaleSwitcher(LocaleSwitcher $localeSwitcher): void + { + $this->localeSwitcher = $localeSwitcher; + } + /** * Returns the current token. * @@ -139,6 +146,15 @@ public function getDebug(): bool return $this->debug; } + public function getLocale(): string + { + if (!isset($this->localeSwitcher)) { + throw new \RuntimeException('The "app.locale" variable is not available.'); + } + + return $this->localeSwitcher->getLocale(); + } + /** * Returns some or all the existing flash messages: * * getFlashes() returns all the flash messages diff --git a/CHANGELOG.md b/CHANGELOG.md index 8edc9b80..9613d9a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +6.3 +--- + + * Add `AppVariable::getLocale()` to retrieve the current locale when using the `LocaleSwitcher` + 6.2 --- diff --git a/Tests/AppVariableTest.php b/Tests/AppVariableTest.php index 219d47d1..764eade4 100644 --- a/Tests/AppVariableTest.php +++ b/Tests/AppVariableTest.php @@ -20,6 +20,7 @@ use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\User\UserInterface; +use Symfony\Component\Translation\LocaleSwitcher; class AppVariableTest extends TestCase { @@ -104,6 +105,16 @@ public function testGetUser() $this->assertEquals($user, $this->appVariable->getUser()); } + public function testGetLocale() + { + $localeSwitcher = $this->createMock(LocaleSwitcher::class); + $this->appVariable->setLocaleSwitcher($localeSwitcher); + + $localeSwitcher->method('getLocale')->willReturn('fr'); + + self::assertEquals('fr', $this->appVariable->getLocale()); + } + public function testGetTokenWithNoToken() { $tokenStorage = $this->createMock(TokenStorageInterface::class); @@ -156,6 +167,13 @@ public function testGetSessionWithRequestStackNotSet() $this->appVariable->getSession(); } + public function testGetLocaleWithLocaleSwitcherNotSet() + { + $this->expectException(\RuntimeException::class); + $this->expectExceptionMessage('The "app.locale" variable is not available.'); + $this->appVariable->getLocale(); + } + public function testGetFlashesWithNoRequest() { $this->setRequestStack(null); diff --git a/composer.json b/composer.json index fb2d0407..c12ad614 100644 --- a/composer.json +++ b/composer.json @@ -37,7 +37,7 @@ "symfony/polyfill-intl-icu": "~1.0", "symfony/property-info": "^5.4|^6.0", "symfony/routing": "^5.4|^6.0", - "symfony/translation": "^5.4|^6.0", + "symfony/translation": "^6.1", "symfony/yaml": "^5.4|^6.0", "symfony/security-acl": "^2.8|^3.0", "symfony/security-core": "^5.4|^6.0", From 885e42723fc7e733c1af47e486d60b24d3ec5035 Mon Sep 17 00:00:00 2001 From: "Phil E. Taylor" Date: Sun, 16 Apr 2023 15:27:41 +0100 Subject: [PATCH 070/167] Add impersonation_path twig function to generate impersonation path --- Extension/SecurityExtension.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Extension/SecurityExtension.php b/Extension/SecurityExtension.php index 25d1cab2..be222b05 100644 --- a/Extension/SecurityExtension.php +++ b/Extension/SecurityExtension.php @@ -69,12 +69,22 @@ public function getImpersonateExitPath(string $exitTo = null): string return $this->impersonateUrlGenerator->generateExitPath($exitTo); } + public function getImpersonatePath(string $identifier = null): string + { + if (null === $this->impersonateUrlGenerator) { + return ''; + } + + return $this->impersonateUrlGenerator->generateImpersonationPath($identifier); + } + public function getFunctions(): array { return [ new TwigFunction('is_granted', $this->isGranted(...)), new TwigFunction('impersonation_exit_url', $this->getImpersonateExitUrl(...)), new TwigFunction('impersonation_exit_path', $this->getImpersonateExitPath(...)), + new TwigFunction('impersonation_path', $this->getImpersonatePath(...)), ]; } } From a98fdb58da270d1539da1beefaabde4ad7648c3d Mon Sep 17 00:00:00 2001 From: Thomas Calvet Date: Wed, 19 Apr 2023 11:03:56 +0200 Subject: [PATCH 071/167] [Form] Don't render seconds for HTML5 date pickers unless "with_seconds" is explicitly set --- Tests/Extension/AbstractBootstrap3LayoutTestCase.php | 2 +- composer.json | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Tests/Extension/AbstractBootstrap3LayoutTestCase.php b/Tests/Extension/AbstractBootstrap3LayoutTestCase.php index ae66650b..af4880ef 100644 --- a/Tests/Extension/AbstractBootstrap3LayoutTestCase.php +++ b/Tests/Extension/AbstractBootstrap3LayoutTestCase.php @@ -1753,7 +1753,7 @@ public function testDateTimeWithWidgetSingleText() [@type="datetime-local"] [@name="name"] [@class="my&class form-control"] - [@value="2011-02-03T04:05:06"] + [@value="2011-02-03T04:05"] ' ); } diff --git a/composer.json b/composer.json index c12ad614..cd0ad795 100644 --- a/composer.json +++ b/composer.json @@ -28,7 +28,7 @@ "symfony/asset": "^5.4|^6.0", "symfony/dependency-injection": "^5.4|^6.0", "symfony/finder": "^5.4|^6.0", - "symfony/form": "^6.2.7", + "symfony/form": "^6.3", "symfony/html-sanitizer": "^6.1", "symfony/http-foundation": "^5.4|^6.0", "symfony/http-kernel": "^6.2", @@ -57,7 +57,7 @@ "phpdocumentor/reflection-docblock": "<3.2.2", "phpdocumentor/type-resolver": "<1.4.0", "symfony/console": "<5.4", - "symfony/form": "<6.2.7", + "symfony/form": "<6.3", "symfony/http-foundation": "<5.4", "symfony/http-kernel": "<6.2", "symfony/mime": "<6.2", From 56bb1d4ff0a39cd0c22e765e6fd1067dda409574 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 18 Apr 2023 23:21:31 +0200 Subject: [PATCH 072/167] [Form] Fix deprecation layer --- .../AbstractBootstrap3HorizontalLayoutTestCase.php | 2 +- Tests/Extension/AbstractBootstrap3LayoutTestCase.php | 12 +++++++++++- .../AbstractBootstrap4HorizontalLayoutTestCase.php | 2 +- Tests/Extension/AbstractBootstrap4LayoutTestCase.php | 2 +- .../AbstractBootstrap5HorizontalLayoutTestCase.php | 2 +- Tests/Extension/AbstractBootstrap5LayoutTestCase.php | 10 ++++++++++ 6 files changed, 25 insertions(+), 5 deletions(-) diff --git a/Tests/Extension/AbstractBootstrap3HorizontalLayoutTestCase.php b/Tests/Extension/AbstractBootstrap3HorizontalLayoutTestCase.php index e79b0c31..3a4104bb 100644 --- a/Tests/Extension/AbstractBootstrap3HorizontalLayoutTestCase.php +++ b/Tests/Extension/AbstractBootstrap3HorizontalLayoutTestCase.php @@ -15,7 +15,7 @@ abstract class AbstractBootstrap3HorizontalLayoutTestCase extends AbstractBootst { public function testLabelOnForm() { - $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\DateType'); + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\DateType', null, ['widget' => 'choice']); $view = $form->createView(); $this->renderWidget($view, ['label' => 'foo']); $html = $this->renderLabel($view); diff --git a/Tests/Extension/AbstractBootstrap3LayoutTestCase.php b/Tests/Extension/AbstractBootstrap3LayoutTestCase.php index af4880ef..969e3c78 100644 --- a/Tests/Extension/AbstractBootstrap3LayoutTestCase.php +++ b/Tests/Extension/AbstractBootstrap3LayoutTestCase.php @@ -19,7 +19,7 @@ abstract class AbstractBootstrap3LayoutTestCase extends AbstractLayoutTestCase { public function testLabelOnForm() { - $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\DateType'); + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\DateType', null, ['widget' => 'choice']); $view = $form->createView(); $this->renderWidget($view, ['label' => 'foo']); $html = $this->renderLabel($view); @@ -1561,6 +1561,7 @@ public function testDateTime() $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\DateTimeType', date('Y').'-02-03 04:05:06', [ 'input' => 'string', 'with_seconds' => false, + 'widget' => 'choice', ]); $this->assertWidgetMatchesXpath($form->createView(), ['attr' => ['class' => 'my&class']], @@ -1598,6 +1599,7 @@ public function testDateTimeWithPlaceholderGlobal() 'input' => 'string', 'placeholder' => 'Change&Me', 'required' => false, + 'widget' => 'choice', ]); $this->assertWidgetMatchesXpath($form->createView(), ['attr' => ['class' => 'my&class']], @@ -1637,6 +1639,7 @@ public function testDateTimeWithHourAndMinute() $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\DateTimeType', $data, [ 'input' => 'array', 'required' => false, + 'widget' => 'choice', ]); $this->assertWidgetMatchesXpath($form->createView(), ['attr' => ['class' => 'my&class']], @@ -1674,6 +1677,7 @@ public function testDateTimeWithSeconds() $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\DateTimeType', date('Y').'-02-03 04:05:06', [ 'input' => 'string', 'with_seconds' => true, + 'widget' => 'choice', ]); $this->assertWidgetMatchesXpath($form->createView(), ['attr' => ['class' => 'my&class']], @@ -1907,6 +1911,7 @@ public function testBirthDay() { $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\BirthdayType', '2000-02-03', [ 'input' => 'string', + 'widget' => 'choice', ]); $this->assertWidgetMatchesXpath($form->createView(), ['attr' => ['class' => 'my&class']], @@ -1937,6 +1942,7 @@ public function testBirthDayWithPlaceholder() 'input' => 'string', 'placeholder' => '', 'required' => false, + 'widget' => 'choice', ]); $this->assertWidgetMatchesXpath($form->createView(), ['attr' => ['class' => 'my&class']], @@ -2465,6 +2471,7 @@ public function testTime() $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\TimeType', '04:05:06', [ 'input' => 'string', 'with_seconds' => false, + 'widget' => 'choice', ]); $this->assertWidgetMatchesXpath($form->createView(), ['attr' => ['class' => 'my&class']], @@ -2492,6 +2499,7 @@ public function testTimeWithSeconds() $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\TimeType', '04:05:06', [ 'input' => 'string', 'with_seconds' => true, + 'widget' => 'choice', ]); $this->assertWidgetMatchesXpath($form->createView(), ['attr' => ['class' => 'my&class']], @@ -2579,6 +2587,7 @@ public function testTimeWithPlaceholderGlobal() 'input' => 'string', 'placeholder' => 'Change&Me', 'required' => false, + 'widget' => 'choice', ]); $this->assertWidgetMatchesXpath($form->createView(), ['attr' => ['class' => 'my&class']], @@ -2606,6 +2615,7 @@ public function testTimeWithPlaceholderOnYear() 'input' => 'string', 'required' => false, 'placeholder' => ['hour' => 'Change&Me'], + 'widget' => 'choice', ]); $this->assertWidgetMatchesXpath($form->createView(), ['attr' => ['class' => 'my&class']], diff --git a/Tests/Extension/AbstractBootstrap4HorizontalLayoutTestCase.php b/Tests/Extension/AbstractBootstrap4HorizontalLayoutTestCase.php index ce8a7336..723559ee 100644 --- a/Tests/Extension/AbstractBootstrap4HorizontalLayoutTestCase.php +++ b/Tests/Extension/AbstractBootstrap4HorizontalLayoutTestCase.php @@ -47,7 +47,7 @@ public function testRow() public function testLabelOnForm() { - $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\DateType'); + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\DateType', null, ['widget' => 'choice']); $view = $form->createView(); $this->renderWidget($view, ['label' => 'foo']); $html = $this->renderLabel($view); diff --git a/Tests/Extension/AbstractBootstrap4LayoutTestCase.php b/Tests/Extension/AbstractBootstrap4LayoutTestCase.php index 08b4bbe7..781d1c92 100644 --- a/Tests/Extension/AbstractBootstrap4LayoutTestCase.php +++ b/Tests/Extension/AbstractBootstrap4LayoutTestCase.php @@ -57,7 +57,7 @@ public function testRow() public function testLabelOnForm() { - $form = $this->factory->createNamed('name', DateType::class); + $form = $this->factory->createNamed('name', DateType::class, null, ['widget' => 'choice']); $view = $form->createView(); $this->renderWidget($view, ['label' => 'foo']); $html = $this->renderLabel($view); diff --git a/Tests/Extension/AbstractBootstrap5HorizontalLayoutTestCase.php b/Tests/Extension/AbstractBootstrap5HorizontalLayoutTestCase.php index 428d5a43..1c02f9e1 100644 --- a/Tests/Extension/AbstractBootstrap5HorizontalLayoutTestCase.php +++ b/Tests/Extension/AbstractBootstrap5HorizontalLayoutTestCase.php @@ -86,7 +86,7 @@ public function testRowWithCustomClass() public function testLabelOnForm() { - $form = $this->factory->createNamed('name', DateType::class); + $form = $this->factory->createNamed('name', DateType::class, null, ['widget' => 'choice']); $view = $form->createView(); $this->renderWidget($view, ['label' => 'foo']); $html = $this->renderLabel($view); diff --git a/Tests/Extension/AbstractBootstrap5LayoutTestCase.php b/Tests/Extension/AbstractBootstrap5LayoutTestCase.php index 1057dc77..630663a6 100644 --- a/Tests/Extension/AbstractBootstrap5LayoutTestCase.php +++ b/Tests/Extension/AbstractBootstrap5LayoutTestCase.php @@ -1006,6 +1006,7 @@ public function testDateTime() $form = $this->factory->createNamed('name', DateTimeType::class, date('Y').'-02-03 04:05:06', [ 'input' => 'string', 'with_seconds' => false, + 'widget' => 'choice', ]); $this->assertWidgetMatchesXpath($form->createView(), ['attr' => ['class' => 'my&class']], @@ -1059,6 +1060,7 @@ public function testDateTimeWithPlaceholderGlobal() 'input' => 'string', 'placeholder' => 'Change&Me', 'required' => false, + 'widget' => 'choice', ]); $this->assertWidgetMatchesXpath($form->createView(), ['attr' => ['class' => 'my&class']], @@ -1112,6 +1114,7 @@ public function testDateTimeWithHourAndMinute() $form = $this->factory->createNamed('name', DateTimeType::class, $data, [ 'input' => 'array', 'required' => false, + 'widget' => 'choice', ]); $this->assertWidgetMatchesXpath($form->createView(), ['attr' => ['class' => 'my&class']], @@ -1163,6 +1166,7 @@ public function testDateTimeWithSeconds() $form = $this->factory->createNamed('name', DateTimeType::class, date('Y').'-02-03 04:05:06', [ 'input' => 'string', 'with_seconds' => true, + 'widget' => 'choice', ]); $this->assertWidgetMatchesXpath($form->createView(), ['attr' => ['class' => 'my&class']], @@ -1387,6 +1391,7 @@ public function testBirthDay() { $form = $this->factory->createNamed('name', BirthdayType::class, '2000-02-03', [ 'input' => 'string', + 'widget' => 'choice', ]); $this->assertWidgetMatchesXpath($form->createView(), ['attr' => ['class' => 'my&class']], @@ -1421,6 +1426,7 @@ public function testBirthDayWithPlaceholder() 'input' => 'string', 'placeholder' => '', 'required' => false, + 'widget' => 'choice', ]); $this->assertWidgetMatchesXpath($form->createView(), ['attr' => ['class' => 'my&class']], @@ -1598,6 +1604,7 @@ public function testTime() $form = $this->factory->createNamed('name', TimeType::class, '04:05:06', [ 'input' => 'string', 'with_seconds' => false, + 'widget' => 'choice', ]); $this->assertWidgetMatchesXpath($form->createView(), ['attr' => ['class' => 'my&class']], @@ -1631,6 +1638,7 @@ public function testTimeWithSeconds() $form = $this->factory->createNamed('name', TimeType::class, '04:05:06', [ 'input' => 'string', 'with_seconds' => true, + 'widget' => 'choice', ]); $this->assertWidgetMatchesXpath($form->createView(), ['attr' => ['class' => 'my&class']], @@ -1714,6 +1722,7 @@ public function testTimeWithPlaceholderGlobal() 'input' => 'string', 'placeholder' => 'Change&Me', 'required' => false, + 'widget' => 'choice', ]); $this->assertWidgetMatchesXpath($form->createView(), ['attr' => ['class' => 'my&class']], @@ -1747,6 +1756,7 @@ public function testTimeWithPlaceholderOnYear() 'input' => 'string', 'required' => false, 'placeholder' => ['hour' => 'Change&Me'], + 'widget' => 'choice', ]); $this->assertWidgetMatchesXpath($form->createView(), ['attr' => ['class' => 'my&class']], From fa28affb754625f1a6f1f5fa13178c29079be77d Mon Sep 17 00:00:00 2001 From: Artyum Petrov <17199757+artyuum@users.noreply.github.com> Date: Thu, 20 Apr 2023 19:24:19 +0400 Subject: [PATCH 073/167] Add "composer require..." in all exception messages when needed --- Mime/NotificationEmail.php | 4 ++-- composer.json | 17 ----------------- 2 files changed, 2 insertions(+), 19 deletions(-) diff --git a/Mime/NotificationEmail.php b/Mime/NotificationEmail.php index e9681df4..5bd54e64 100644 --- a/Mime/NotificationEmail.php +++ b/Mime/NotificationEmail.php @@ -54,7 +54,7 @@ public function __construct(Headers $headers = null, AbstractPart $body = null) } if ($missingPackages) { - throw new \LogicException(sprintf('You cannot use "%s" if the "%s" Twig extension%s not available; try running "%s".', static::class, implode('" and "', $missingPackages), \count($missingPackages) > 1 ? 's are' : ' is', 'composer require '.implode(' ', array_keys($missingPackages)))); + throw new \LogicException(sprintf('You cannot use "%s" if the "%s" Twig extension%s not available. Try running "%s".', static::class, implode('" and "', $missingPackages), \count($missingPackages) > 1 ? 's are' : ' is', 'composer require '.implode(' ', array_keys($missingPackages)))); } parent::__construct($headers, $body); @@ -88,7 +88,7 @@ public function markAsPublic(): static public function markdown(string $content): static { if (!class_exists(MarkdownExtension::class)) { - throw new \LogicException(sprintf('You cannot use "%s" if the Markdown Twig extension is not available; try running "composer require twig/markdown-extra".', __METHOD__)); + throw new \LogicException(sprintf('You cannot use "%s" if the Markdown Twig extension is not available. Try running "composer require twig/markdown-extra".', __METHOD__)); } $this->context['markdown'] = true; diff --git a/composer.json b/composer.json index cd0ad795..8cf462fe 100644 --- a/composer.json +++ b/composer.json @@ -64,23 +64,6 @@ "symfony/translation": "<5.4", "symfony/workflow": "<5.4" }, - "suggest": { - "symfony/finder": "", - "symfony/asset": "For using the AssetExtension", - "symfony/form": "For using the FormExtension", - "symfony/html-sanitizer": "For using the HtmlSanitizerExtension", - "symfony/http-kernel": "For using the HttpKernelExtension", - "symfony/routing": "For using the RoutingExtension", - "symfony/translation": "For using the TranslationExtension", - "symfony/yaml": "For using the YamlExtension", - "symfony/security-core": "For using the SecurityExtension", - "symfony/security-csrf": "For using the CsrfExtension", - "symfony/security-http": "For using the LogoutUrlExtension", - "symfony/stopwatch": "For using the StopwatchExtension", - "symfony/var-dumper": "For using the DumpExtension", - "symfony/expression-language": "For using the ExpressionExtension", - "symfony/web-link": "For using the WebLinkExtension" - }, "autoload": { "psr-4": { "Symfony\\Bridge\\Twig\\": "" }, "exclude-from-classmap": [ From 8ebbb35323f66b1af653fb37ed6c6e2bd25e872a Mon Sep 17 00:00:00 2001 From: Romain Monteil Date: Sun, 23 Apr 2023 12:45:22 +0200 Subject: [PATCH 074/167] [Form] Move classes to Twig bridge --- .../AbstractBootstrap3LayoutTestCase.php | 1 - Tests/Extension/AbstractDivLayoutTestCase.php | 930 ++++++ Tests/Extension/AbstractLayoutTestCase.php | 2926 +++++++++++++++++ .../Extension/AbstractTableLayoutTestCase.php | 536 +++ .../Extension/FormExtensionDivLayoutTest.php | 1 - .../FormExtensionTableLayoutTest.php | 1 - 6 files changed, 4392 insertions(+), 3 deletions(-) create mode 100644 Tests/Extension/AbstractDivLayoutTestCase.php create mode 100644 Tests/Extension/AbstractLayoutTestCase.php create mode 100644 Tests/Extension/AbstractTableLayoutTestCase.php diff --git a/Tests/Extension/AbstractBootstrap3LayoutTestCase.php b/Tests/Extension/AbstractBootstrap3LayoutTestCase.php index 969e3c78..7f4a77e2 100644 --- a/Tests/Extension/AbstractBootstrap3LayoutTestCase.php +++ b/Tests/Extension/AbstractBootstrap3LayoutTestCase.php @@ -13,7 +13,6 @@ use Symfony\Component\Form\Extension\Core\Type\PercentType; use Symfony\Component\Form\FormError; -use Symfony\Component\Form\Tests\AbstractLayoutTestCase; abstract class AbstractBootstrap3LayoutTestCase extends AbstractLayoutTestCase { diff --git a/Tests/Extension/AbstractDivLayoutTestCase.php b/Tests/Extension/AbstractDivLayoutTestCase.php new file mode 100644 index 00000000..a02fca4b --- /dev/null +++ b/Tests/Extension/AbstractDivLayoutTestCase.php @@ -0,0 +1,930 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\Tests\Extension; + +use Symfony\Component\Form\FormError; +use Symfony\Component\Security\Csrf\CsrfToken; + +abstract class AbstractDivLayoutTestCase extends AbstractLayoutTestCase +{ + public function testRow() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\TextType'); + $form->addError(new FormError('[trans]Error![/trans]')); + $view = $form->createView(); + $html = $this->renderRow($view); + + $this->assertMatchesXpath($html, + '/div + [ + ./label[@for="name"] + /following-sibling::ul + [./li[.="[trans]Error![/trans]"]] + [count(./li)=1] + /following-sibling::input[@id="name"] + ] +' + ); + } + + public function testRowOverrideVariables() + { + $view = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\TextType')->createView(); + $html = $this->renderRow($view, [ + 'attr' => ['class' => 'my&class'], + 'label' => 'foo&bar', + 'label_attr' => ['class' => 'my&label&class'], + ]); + + $this->assertMatchesXpath($html, + '/div + [ + ./label[@for="name"][@class="my&label&class required"][.="[trans]foo&bar[/trans]"] + /following-sibling::input[@id="name"][@class="my&class"] + ] +' + ); + } + + public function testRepeatedRow() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\RepeatedType'); + $form->addError(new FormError('[trans]Error![/trans]')); + $view = $form->createView(); + $html = $this->renderRow($view); + + // The errors of the form are not rendered by intention! + // In practice, repeated fields cannot have errors as all errors + // on them are mapped to the first child. + // (see RepeatedTypeValidatorExtension) + + $this->assertMatchesXpath($html, + '/div + [ + ./label[@for="name_first"] + /following-sibling::input[@id="name_first"] + ] +/following-sibling::div + [ + ./label[@for="name_second"] + /following-sibling::input[@id="name_second"] + ] +' + ); + } + + public function testButtonRow() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ButtonType'); + $view = $form->createView(); + $html = $this->renderRow($view); + + $this->assertMatchesXpath($html, + '/div + [ + ./button[@type="button"][@name="name"] + ] + [count(//label)=0] +' + ); + } + + public function testRest() + { + $view = $this->factory->createNamedBuilder('name', 'Symfony\Component\Form\Extension\Core\Type\FormType') + ->add('field1', 'Symfony\Component\Form\Extension\Core\Type\TextType') + ->add('field2', 'Symfony\Component\Form\Extension\Core\Type\RepeatedType') + ->add('field3', 'Symfony\Component\Form\Extension\Core\Type\TextType') + ->add('field4', 'Symfony\Component\Form\Extension\Core\Type\TextType') + ->getForm() + ->createView(); + + // Render field2 row -> does not implicitly call renderWidget because + // it is a repeated field! + $this->renderRow($view['field2']); + + // Render field3 widget + $this->renderWidget($view['field3']); + + // Rest should only contain field1 and field4 + $html = $this->renderRest($view); + + $this->assertMatchesXpath($html, + '/div + [ + ./label[@for="name_field1"] + /following-sibling::input[@type="text"][@id="name_field1"] + ] +/following-sibling::div + [ + ./label[@for="name_field4"] + /following-sibling::input[@type="text"][@id="name_field4"] + ] + [count(../div)=2] + [count(..//label)=2] + [count(..//input)=3] +/following-sibling::input + [@type="hidden"] + [@id="name__token"] +' + ); + } + + public function testRestWithChildrenForms() + { + $child1 = $this->factory->createNamedBuilder('child1', 'Symfony\Component\Form\Extension\Core\Type\FormType') + ->add('field1', 'Symfony\Component\Form\Extension\Core\Type\TextType') + ->add('field2', 'Symfony\Component\Form\Extension\Core\Type\TextType'); + + $child2 = $this->factory->createNamedBuilder('child2', 'Symfony\Component\Form\Extension\Core\Type\FormType') + ->add('field1', 'Symfony\Component\Form\Extension\Core\Type\TextType') + ->add('field2', 'Symfony\Component\Form\Extension\Core\Type\TextType'); + + $view = $this->factory->createNamedBuilder('parent', 'Symfony\Component\Form\Extension\Core\Type\FormType') + ->add($child1) + ->add($child2) + ->getForm() + ->createView(); + + // Render child1.field1 row + $this->renderRow($view['child1']['field1']); + + // Render child2.field2 widget (remember that widget don't render label) + $this->renderWidget($view['child2']['field2']); + + // Rest should only contain child1.field2 and child2.field1 + $html = $this->renderRest($view); + + $this->assertMatchesXpath($html, + '/div + [ + ./label[not(@for)] + /following-sibling::div[@id="parent_child1"] + [ + ./div + [ + ./label[@for="parent_child1_field2"] + /following-sibling::input[@id="parent_child1_field2"] + ] + ] + ] + +/following-sibling::div + [ + ./label[not(@for)] + /following-sibling::div[@id="parent_child2"] + [ + ./div + [ + ./label[@for="parent_child2_field1"] + /following-sibling::input[@id="parent_child2_field1"] + ] + ] + ] + [count(//label)=4] + [count(//input[@type="text"])=2] +/following-sibling::input[@type="hidden"][@id="parent__token"] +' + ); + } + + public function testRestAndRepeatedWithRow() + { + $view = $this->factory->createNamedBuilder('name', 'Symfony\Component\Form\Extension\Core\Type\FormType') + ->add('first', 'Symfony\Component\Form\Extension\Core\Type\TextType') + ->add('password', 'Symfony\Component\Form\Extension\Core\Type\RepeatedType') + ->getForm() + ->createView(); + + $this->renderRow($view['password']); + + $html = $this->renderRest($view); + + $this->assertMatchesXpath($html, + '/div + [ + ./label[@for="name_first"] + /following-sibling::input[@type="text"][@id="name_first"] + ] + [count(.//input)=1] +/following-sibling::input + [@type="hidden"] + [@id="name__token"] +' + ); + } + + public function testRestAndRepeatedWithRowPerChild() + { + $view = $this->factory->createNamedBuilder('name', 'Symfony\Component\Form\Extension\Core\Type\FormType') + ->add('first', 'Symfony\Component\Form\Extension\Core\Type\TextType') + ->add('password', 'Symfony\Component\Form\Extension\Core\Type\RepeatedType') + ->getForm() + ->createView(); + + $this->renderRow($view['password']['first']); + $this->renderRow($view['password']['second']); + + $html = $this->renderRest($view); + + $this->assertMatchesXpath($html, + '/div + [ + ./label[@for="name_first"] + /following-sibling::input[@type="text"][@id="name_first"] + ] + [count(.//input)=1] + [count(.//label)=1] +/following-sibling::input + [@type="hidden"] + [@id="name__token"] +' + ); + } + + public function testRestAndRepeatedWithWidgetPerChild() + { + $view = $this->factory->createNamedBuilder('name', 'Symfony\Component\Form\Extension\Core\Type\FormType') + ->add('first', 'Symfony\Component\Form\Extension\Core\Type\TextType') + ->add('password', 'Symfony\Component\Form\Extension\Core\Type\RepeatedType') + ->getForm() + ->createView(); + + // The password form is considered as rendered as all its children + // are rendered + $this->renderWidget($view['password']['first']); + $this->renderWidget($view['password']['second']); + + $html = $this->renderRest($view); + + $this->assertMatchesXpath($html, + '/div + [ + ./label[@for="name_first"] + /following-sibling::input[@type="text"][@id="name_first"] + ] + [count(//input)=2] + [count(//label)=1] +/following-sibling::input + [@type="hidden"] + [@id="name__token"] +' + ); + } + + public function testCollection() + { + $form = $this->factory->createNamed('names', 'Symfony\Component\Form\Extension\Core\Type\CollectionType', ['a', 'b'], [ + 'entry_type' => 'Symfony\Component\Form\Extension\Core\Type\TextType', + ]); + + $this->assertWidgetMatchesXpath($form->createView(), [], + '/div + [ + ./div[./input[@type="text"][@value="a"]] + /following-sibling::div[./input[@type="text"][@value="b"]] + ] + [count(./div[./input])=2] +' + ); + } + + // https://github.com/symfony/symfony/issues/5038 + public function testCollectionWithAlternatingRowTypes() + { + $data = [ + ['title' => 'a'], + ['title' => 'b'], + ]; + $form = $this->factory->createNamed('names', 'Symfony\Component\Form\Extension\Core\Type\CollectionType', $data, [ + 'entry_type' => 'Symfony\Component\Form\Tests\Fixtures\AlternatingRowType', + ]); + + $this->assertWidgetMatchesXpath($form->createView(), [], + '/div + [ + ./div[./div/div/input[@type="text"][@value="a"]] + /following-sibling::div[./div/div/textarea[.="b"]] + ] + [count(./div[./div/div/input])=1] + [count(./div[./div/div/textarea])=1] +' + ); + } + + public function testEmptyCollection() + { + $form = $this->factory->createNamed('names', 'Symfony\Component\Form\Extension\Core\Type\CollectionType', [], [ + 'entry_type' => 'Symfony\Component\Form\Extension\Core\Type\TextType', + ]); + + $this->assertWidgetMatchesXpath($form->createView(), [], + '/div + [./input[@type="hidden"][@id="names__token"]] + [count(./div)=0] +' + ); + } + + public function testCollectionRow() + { + $collection = $this->factory->createNamedBuilder( + 'collection', + 'Symfony\Component\Form\Extension\Core\Type\CollectionType', + ['a', 'b'], + ['entry_type' => 'Symfony\Component\Form\Extension\Core\Type\TextType'] + ); + + $form = $this->factory->createNamedBuilder('form', 'Symfony\Component\Form\Extension\Core\Type\FormType') + ->add($collection) + ->getForm(); + + $this->assertWidgetMatchesXpath($form->createView(), [], + '/div + [ + ./div + [ + ./label[not(@for)] + /following-sibling::div + [ + ./div + [ + ./label[@for="form_collection_0"] + /following-sibling::input[@type="text"][@value="a"] + ] + /following-sibling::div + [ + ./label[@for="form_collection_1"] + /following-sibling::input[@type="text"][@value="b"] + ] + ] + ] + /following-sibling::input[@type="hidden"][@id="form__token"] + ] + [count(.//input)=3] +' + ); + } + + public function testForm() + { + $form = $this->factory->createNamedBuilder('name', 'Symfony\Component\Form\Extension\Core\Type\FormType') + ->setMethod('PUT') + ->setAction('http://example.com') + ->add('firstName', 'Symfony\Component\Form\Extension\Core\Type\TextType') + ->add('lastName', 'Symfony\Component\Form\Extension\Core\Type\TextType') + ->getForm(); + + // include ampersands everywhere to validate escaping + $html = $this->renderForm($form->createView(), [ + 'id' => 'my&id', + 'attr' => ['class' => 'my&class'], + ]); + + $this->assertMatchesXpath($html, + '/form + [ + ./input[@type="hidden"][@name="_method"][@value="PUT"] + /following-sibling::div + [ + ./div + [ + ./label[@for="name_firstName"] + /following-sibling::input[@type="text"][@id="name_firstName"] + ] + /following-sibling::div + [ + ./label[@for="name_lastName"] + /following-sibling::input[@type="text"][@id="name_lastName"] + ] + /following-sibling::input[@type="hidden"][@id="name__token"] + ] + [count(.//input)=3] + [@id="my&id"] + [@class="my&class"] + ] + [@method="post"] + [@action="http://example.com"] + [@class="my&class"] +' + ); + } + + public function testFormWidget() + { + $form = $this->factory->createNamedBuilder('name', 'Symfony\Component\Form\Extension\Core\Type\FormType') + ->add('firstName', 'Symfony\Component\Form\Extension\Core\Type\TextType') + ->add('lastName', 'Symfony\Component\Form\Extension\Core\Type\TextType') + ->getForm(); + + $this->assertWidgetMatchesXpath($form->createView(), [], + '/div + [ + ./div + [ + ./label[@for="name_firstName"] + /following-sibling::input[@type="text"][@id="name_firstName"] + ] + /following-sibling::div + [ + ./label[@for="name_lastName"] + /following-sibling::input[@type="text"][@id="name_lastName"] + ] + /following-sibling::input[@type="hidden"][@id="name__token"] + ] + [count(.//input)=3] +' + ); + } + + // https://github.com/symfony/symfony/issues/2308 + public function testNestedFormError() + { + $form = $this->factory->createNamedBuilder('name', 'Symfony\Component\Form\Extension\Core\Type\FormType') + ->add($this->factory + ->createNamedBuilder('child', 'Symfony\Component\Form\Extension\Core\Type\FormType', null, ['error_bubbling' => false]) + ->add('grandChild', 'Symfony\Component\Form\Extension\Core\Type\FormType') + ) + ->getForm(); + + $form->get('child')->addError(new FormError('[trans]Error![/trans]')); + + $this->assertWidgetMatchesXpath($form->createView(), [], + '/div + [ + ./div/label + /following-sibling::ul[./li[.="[trans]Error![/trans]"]] + ] + [count(.//li[.="[trans]Error![/trans]"])=1] +' + ); + } + + public function testCsrf() + { + $this->csrfTokenManager->expects($this->any()) + ->method('getToken') + ->willReturn(new CsrfToken('token_id', 'foo&bar')); + + $form = $this->factory->createNamedBuilder('name', 'Symfony\Component\Form\Extension\Core\Type\FormType') + ->add($this->factory + // No CSRF protection on nested forms + ->createNamedBuilder('child', 'Symfony\Component\Form\Extension\Core\Type\FormType') + ->add($this->factory->createNamedBuilder('grandchild', 'Symfony\Component\Form\Extension\Core\Type\TextType')) + ) + ->getForm(); + + $this->assertWidgetMatchesXpath($form->createView(), [], + '/div + [ + ./div + /following-sibling::input[@type="hidden"][@id="name__token"][@value="foo&bar"] + ] + [count(.//input[@type="hidden"])=1] +' + ); + } + + public function testRepeated() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\RepeatedType', 'foobar', [ + 'type' => 'Symfony\Component\Form\Extension\Core\Type\TextType', + ]); + + $this->assertWidgetMatchesXpath($form->createView(), [], + '/div + [ + ./div + [ + ./label[@for="name_first"] + /following-sibling::input[@type="text"][@id="name_first"] + ] + /following-sibling::div + [ + ./label[@for="name_second"] + /following-sibling::input[@type="text"][@id="name_second"] + ] + /following-sibling::input[@type="hidden"][@id="name__token"] + ] + [count(.//input)=3] +' + ); + } + + public function testRepeatedWithCustomOptions() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\RepeatedType', null, [ + // the global required value cannot be overridden + 'first_options' => ['label' => 'Test', 'required' => false], + 'second_options' => ['label' => 'Test2'], + ]); + + $this->assertWidgetMatchesXpath($form->createView(), [], + '/div + [ + ./div + [ + ./label[@for="name_first"][.="[trans]Test[/trans]"] + /following-sibling::input[@type="text"][@id="name_first"][@required="required"] + ] + /following-sibling::div + [ + ./label[@for="name_second"][.="[trans]Test2[/trans]"] + /following-sibling::input[@type="text"][@id="name_second"][@required="required"] + ] + /following-sibling::input[@type="hidden"][@id="name__token"] + ] + [count(.//input)=3] +' + ); + } + + public function testSearchInputName() + { + $form = $this->factory->createNamedBuilder('full', 'Symfony\Component\Form\Extension\Core\Type\FormType') + ->add('name', 'Symfony\Component\Form\Extension\Core\Type\SearchType') + ->getForm(); + + $this->assertWidgetMatchesXpath($form->createView(), [], + '/div + [ + ./div + [ + ./label[@for="full_name"] + /following-sibling::input[@type="search"][@id="full_name"][@name="full[name]"] + ] + /following-sibling::input[@type="hidden"][@id="full__token"] + ] + [count(//input)=2] +' + ); + } + + public function testLabelHasNoId() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\TextType'); + $html = $this->renderRow($form->createView()); + + $this->assertMatchesXpath($html, + '/div + [ + ./label[@for="name"][not(@id)] + /following-sibling::input[@id="name"] + ] +' + ); + } + + public function testLabelIsNotRenderedWhenSetToFalse() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\TextType', null, [ + 'label' => false, + ]); + $html = $this->renderRow($form->createView()); + + $this->assertMatchesXpath($html, + '/div + [ + ./input[@id="name"] + ] + [count(//label)=0] +' + ); + } + + /** + * @dataProvider themeBlockInheritanceProvider + */ + public function testThemeBlockInheritance($theme) + { + $view = $this->factory + ->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\EmailType') + ->createView() + ; + + $this->setTheme($view, $theme); + + $this->assertMatchesXpath( + $this->renderWidget($view), + '/input[@type="email"][@rel="theme"]' + ); + } + + /** + * @dataProvider themeInheritanceProvider + */ + public function testThemeInheritance($parentTheme, $childTheme) + { + $child = $this->factory->createNamedBuilder('child', 'Symfony\Component\Form\Extension\Core\Type\FormType') + ->add('field', 'Symfony\Component\Form\Extension\Core\Type\TextType'); + + $view = $this->factory->createNamedBuilder('parent', 'Symfony\Component\Form\Extension\Core\Type\FormType') + ->add('field', 'Symfony\Component\Form\Extension\Core\Type\TextType') + ->add($child) + ->getForm() + ->createView() + ; + + $this->setTheme($view, $parentTheme); + $this->setTheme($view['child'], $childTheme); + + $this->assertWidgetMatchesXpath($view, [], + '/div + [ + ./div + [ + ./label[.="parent"] + /following-sibling::input[@type="text"] + ] + /following-sibling::div + [ + ./label[.="child"] + /following-sibling::div + [ + ./div + [ + ./label[.="child"] + /following-sibling::input[@type="text"] + ] + ] + ] + /following-sibling::input[@type="hidden"] + ] +' + ); + } + + /** + * The block "_name_child_label" should be overridden in the theme of the + * implemented driver. + */ + public function testCollectionRowWithCustomBlock() + { + $collection = ['one', 'two', 'three']; + $form = $this->factory->createNamedBuilder('names', 'Symfony\Component\Form\Extension\Core\Type\CollectionType', $collection) + ->getForm(); + + $this->assertWidgetMatchesXpath($form->createView(), [], + '/div + [ + ./div[./label[.="Custom label: [trans]0[/trans]"]] + /following-sibling::div[./label[.="Custom label: [trans]1[/trans]"]] + /following-sibling::div[./label[.="Custom label: [trans]2[/trans]"]] + ] +' + ); + } + + /** + * The block "_name_c_entry_label" should be overridden in the theme of the + * implemented driver. + */ + public function testChoiceRowWithCustomBlock() + { + $form = $this->factory->createNamedBuilder('name_c', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', 'a', [ + 'choices' => ['ChoiceA' => 'a', 'ChoiceB' => 'b'], + 'expanded' => true, + ]) + ->getForm(); + + $this->assertWidgetMatchesXpath($form->createView(), [], + '/div + [ + ./label[.="Custom name label: [trans]ChoiceA[/trans]"] + /following-sibling::label[.="Custom name label: [trans]ChoiceB[/trans]"] + ] +' + ); + } + + public function testSingleChoiceExpandedWithLabelsAsFalse() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', '&a', [ + 'choices' => ['Choice&A' => '&a', 'Choice&B' => '&b'], + 'choice_label' => false, + 'multiple' => false, + 'expanded' => true, + ]); + + $this->assertWidgetMatchesXpath($form->createView(), [], + '/div + [ + ./input[@type="radio"][@name="name"][@id="name_0"][@value="&a"][@checked] + /following-sibling::input[@type="radio"][@name="name"][@id="name_1"][@value="&b"][not(@checked)] + /following-sibling::input[@type="hidden"][@id="name__token"] + ] + [count(./input)=3] + [count(./label)=1] +' + ); + } + + public function testSingleChoiceExpandedWithLabelsSetByCallable() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', '&a', [ + 'choices' => ['Choice&A' => '&a', 'Choice&B' => '&b', 'Choice&C' => '&c'], + 'choice_label' => function ($choice, $label, $value) { + if ('&b' === $choice) { + return false; + } + + return 'label.'.$value; + }, + 'multiple' => false, + 'expanded' => true, + ]); + + $this->assertWidgetMatchesXpath($form->createView(), [], + '/div + [ + ./input[@type="radio"][@name="name"][@id="name_0"][@value="&a"][@checked] + /following-sibling::label[@for="name_0"][.="[trans]label.&a[/trans]"] + /following-sibling::input[@type="radio"][@name="name"][@id="name_1"][@value="&b"][not(@checked)] + /following-sibling::input[@type="radio"][@name="name"][@id="name_2"][@value="&c"][not(@checked)] + /following-sibling::label[@for="name_2"][.="[trans]label.&c[/trans]"] + /following-sibling::input[@type="hidden"][@id="name__token"] + ] + [count(./input)=4] + [count(./label)=3] +' + ); + } + + public function testSingleChoiceExpandedWithLabelsSetFalseByCallable() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', '&a', [ + 'choices' => ['Choice&A' => '&a', 'Choice&B' => '&b'], + 'choice_label' => fn () => false, + 'multiple' => false, + 'expanded' => true, + ]); + + $this->assertWidgetMatchesXpath($form->createView(), [], + '/div + [ + ./input[@type="radio"][@name="name"][@id="name_0"][@value="&a"][@checked] + /following-sibling::input[@type="radio"][@name="name"][@id="name_1"][@value="&b"][not(@checked)] + /following-sibling::input[@type="hidden"][@id="name__token"] + ] + [count(./input)=3] + [count(./label)=1] +' + ); + } + + public function testMultipleChoiceExpandedWithLabelsAsFalse() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', ['&a'], [ + 'choices' => ['Choice&A' => '&a', 'Choice&B' => '&b'], + 'choice_label' => false, + 'multiple' => true, + 'expanded' => true, + ]); + + $this->assertWidgetMatchesXpath($form->createView(), [], + '/div + [ + ./input[@type="checkbox"][@name="name[]"][@id="name_0"][@value="&a"][@checked] + /following-sibling::input[@type="checkbox"][@name="name[]"][@id="name_1"][@value="&b"][not(@checked)] + /following-sibling::input[@type="hidden"][@id="name__token"] + ] + [count(./input)=3] + [count(./label)=1] +' + ); + } + + public function testMultipleChoiceExpandedWithLabelsSetByCallable() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', ['&a'], [ + 'choices' => ['Choice&A' => '&a', 'Choice&B' => '&b', 'Choice&C' => '&c'], + 'choice_label' => function ($choice, $label, $value) { + if ('&b' === $choice) { + return false; + } + + return 'label.'.$value; + }, + 'multiple' => true, + 'expanded' => true, + ]); + + $this->assertWidgetMatchesXpath($form->createView(), [], + '/div + [ + ./input[@type="checkbox"][@name="name[]"][@id="name_0"][@value="&a"][@checked] + /following-sibling::label[@for="name_0"][.="[trans]label.&a[/trans]"] + /following-sibling::input[@type="checkbox"][@name="name[]"][@id="name_1"][@value="&b"][not(@checked)] + /following-sibling::input[@type="checkbox"][@name="name[]"][@id="name_2"][@value="&c"][not(@checked)] + /following-sibling::label[@for="name_2"][.="[trans]label.&c[/trans]"] + /following-sibling::input[@type="hidden"][@id="name__token"] + ] + [count(./input)=4] + [count(./label)=3] +' + ); + } + + public function testMultipleChoiceExpandedWithLabelsSetFalseByCallable() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', ['&a'], [ + 'choices' => ['Choice&A' => '&a', 'Choice&B' => '&b'], + 'choice_label' => fn () => false, + 'multiple' => true, + 'expanded' => true, + ]); + + $this->assertWidgetMatchesXpath($form->createView(), [], + '/div + [ + ./input[@type="checkbox"][@name="name[]"][@id="name_0"][@value="&a"][@checked] + /following-sibling::input[@type="checkbox"][@name="name[]"][@id="name_1"][@value="&b"][not(@checked)] + /following-sibling::input[@type="hidden"][@id="name__token"] + ] + [count(./input)=3] + [count(./label)=1] +' + ); + } + + public function testFormEndWithRest() + { + $view = $this->factory->createNamedBuilder('name', 'Symfony\Component\Form\Extension\Core\Type\FormType') + ->add('field1', 'Symfony\Component\Form\Extension\Core\Type\TextType') + ->add('field2', 'Symfony\Component\Form\Extension\Core\Type\TextType') + ->getForm() + ->createView(); + + $this->renderWidget($view['field1']); + + // Rest should only contain field2 + $html = $this->renderEnd($view); + + // Insert the start tag, the end tag should be rendered by the helper + $this->assertMatchesXpath('
'.$html, + '/form + [ + ./div + [ + ./label[@for="name_field2"] + /following-sibling::input[@type="text"][@id="name_field2"] + ] + /following-sibling::input + [@type="hidden"] + [@id="name__token"] + ] +' + ); + } + + public function testFormEndWithoutRest() + { + $view = $this->factory->createNamedBuilder('name', 'Symfony\Component\Form\Extension\Core\Type\FormType') + ->add('field1', 'Symfony\Component\Form\Extension\Core\Type\TextType') + ->add('field2', 'Symfony\Component\Form\Extension\Core\Type\TextType') + ->getForm() + ->createView(); + + $this->renderWidget($view['field1']); + + // Rest should only contain field2, but isn't rendered + $html = $this->renderEnd($view, ['render_rest' => false]); + + $this->assertEquals('
', $html); + } + + public function testWidgetContainerAttributes() + { + $form = $this->factory->createNamed('form', 'Symfony\Component\Form\Extension\Core\Type\FormType', null, [ + 'attr' => ['class' => 'foobar', 'data-foo' => 'bar'], + ]); + + $form->add('text', 'Symfony\Component\Form\Extension\Core\Type\TextType'); + + $html = $this->renderWidget($form->createView()); + + // compare plain HTML to check the whitespace + $this->assertStringContainsString('
', $html); + } + + public function testWidgetContainerAttributeNameRepeatedIfTrue() + { + $form = $this->factory->createNamed('form', 'Symfony\Component\Form\Extension\Core\Type\FormType', null, [ + 'attr' => ['foo' => true], + ]); + + $html = $this->renderWidget($form->createView()); + + // foo="foo" + $this->assertStringContainsString('
', $html); + } +} diff --git a/Tests/Extension/AbstractLayoutTestCase.php b/Tests/Extension/AbstractLayoutTestCase.php new file mode 100644 index 00000000..af1a013a --- /dev/null +++ b/Tests/Extension/AbstractLayoutTestCase.php @@ -0,0 +1,2926 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\Tests\Extension; + +use PHPUnit\Framework\SkippedTestError; +use Symfony\Component\Form\Extension\Core\Type\PercentType; +use Symfony\Component\Form\Extension\Core\Type\SubmitType; +use Symfony\Component\Form\Extension\Csrf\CsrfExtension; +use Symfony\Component\Form\FormError; +use Symfony\Component\Form\FormView; +use Symfony\Component\Form\Test\FormIntegrationTestCase; +use Symfony\Component\Form\Tests\VersionAwareTest; +use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; +use Symfony\Component\Translation\TranslatableMessage; +use Symfony\Contracts\Translation\TranslatableInterface; +use Symfony\Contracts\Translation\TranslatorInterface; + +abstract class AbstractLayoutTestCase extends FormIntegrationTestCase +{ + use VersionAwareTest; + + protected $csrfTokenManager; + protected $testableFeatures = []; + private $defaultLocale; + + protected function setUp(): void + { + if (!\extension_loaded('intl')) { + $this->markTestSkipped('Extension intl is required.'); + } + + $this->defaultLocale = \Locale::getDefault(); + \Locale::setDefault('en'); + + $this->csrfTokenManager = $this->createMock(CsrfTokenManagerInterface::class); + + parent::setUp(); + } + + protected function getExtensions() + { + return [ + new CsrfExtension($this->csrfTokenManager), + ]; + } + + protected function tearDown(): void + { + $this->csrfTokenManager = null; + \Locale::setDefault($this->defaultLocale); + + parent::tearDown(); + } + + protected function assertXpathNodeValue(\DOMElement $element, $expression, $nodeValue) + { + $xpath = new \DOMXPath($element->ownerDocument); + $nodeList = $xpath->evaluate($expression); + $this->assertEquals(1, $nodeList->length); + $this->assertEquals($nodeValue, $nodeList->item(0)->nodeValue); + } + + protected function assertMatchesXpath($html, $expression, $count = 1) + { + $dom = new \DOMDocument('UTF-8'); + try { + // Wrap in node so we can load HTML with multiple tags at + // the top level + $dom->loadXML(''.$html.''); + } catch (\Exception $e) { + $this->fail(sprintf( + "Failed loading HTML:\n\n%s\n\nError: %s", + $html, + $e->getMessage() + )); + } + $xpath = new \DOMXPath($dom); + $nodeList = $xpath->evaluate('/root'.$expression); + + if ($nodeList->length != $count) { + $dom->formatOutput = true; + $this->fail(sprintf( + "Failed asserting that \n\n%s\n\nmatches exactly %s. Matches %s in \n\n%s", + $expression, + 1 == $count ? 'once' : $count.' times', + 1 == $nodeList->length ? 'once' : $nodeList->length.' times', + // strip away and + substr($dom->saveHTML(), 6, -8) + )); + } else { + $this->addToAssertionCount(1); + } + } + + protected function assertWidgetMatchesXpath(FormView $view, array $vars, $xpath) + { + // include ampersands everywhere to validate escaping + $html = $this->renderWidget($view, array_merge([ + 'id' => 'my&id', + 'attr' => ['class' => 'my&class'], + ], $vars)); + + if (!isset($vars['id'])) { + $xpath = trim($xpath).' + [@id="my&id"]'; + } + + if (!isset($vars['attr']['class'])) { + $xpath .= ' + [@class="my&class"]'; + } + + $this->assertMatchesXpath($html, $xpath); + } + + abstract protected function renderForm(FormView $view, array $vars = []); + + abstract protected function renderLabel(FormView $view, $label = null, array $vars = []); + + protected function renderHelp(FormView $view) + { + $this->markTestSkipped(sprintf('%s::renderHelp() is not implemented.', static::class)); + } + + abstract protected function renderErrors(FormView $view); + + abstract protected function renderWidget(FormView $view, array $vars = []); + + abstract protected function renderRow(FormView $view, array $vars = []); + + abstract protected function renderRest(FormView $view, array $vars = []); + + abstract protected function renderStart(FormView $view, array $vars = []); + + abstract protected function renderEnd(FormView $view, array $vars = []); + + abstract protected function setTheme(FormView $view, array $themes, $useDefaultThemes = true); + + public function testLabel() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\TextType'); + $view = $form->createView(); + $this->renderWidget($view, ['label' => 'foo']); + $html = $this->renderLabel($view); + + $this->assertMatchesXpath($html, + '/label + [@for="name"] + [.="[trans]Name[/trans]"] +' + ); + } + + public function testLabelWithoutTranslation() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\TextType', null, [ + 'translation_domain' => false, + ]); + + $this->assertMatchesXpath($this->renderLabel($form->createView()), + '/label + [@for="name"] + [.="Name"] +' + ); + } + + public function testLabelOnForm() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\DateType', null, ['widget' => 'choice']); + $view = $form->createView(); + $this->renderWidget($view, ['label' => 'foo']); + $html = $this->renderLabel($view); + + $this->assertMatchesXpath($html, + '/label + [@class="required"] + [.="[trans]Name[/trans]"] +' + ); + } + + public function testLabelWithCustomTextPassedAsOption() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\TextType', null, [ + 'label' => 'Custom label', + ]); + $html = $this->renderLabel($form->createView()); + + $this->assertMatchesXpath($html, + '/label + [@for="name"] + [.="[trans]Custom label[/trans]"] +' + ); + } + + public function testLabelWithCustomTextPassedDirectly() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\TextType'); + $html = $this->renderLabel($form->createView(), 'Custom label'); + + $this->assertMatchesXpath($html, + '/label + [@for="name"] + [.="[trans]Custom label[/trans]"] +' + ); + } + + public function testLabelWithCustomTextPassedAsOptionAndDirectly() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\TextType', null, [ + 'label' => 'Custom label', + ]); + $html = $this->renderLabel($form->createView(), 'Overridden label'); + + $this->assertMatchesXpath($html, + '/label + [@for="name"] + [.="[trans]Overridden label[/trans]"] +' + ); + } + + public function testLabelDoesNotRenderFieldAttributes() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\TextType'); + $html = $this->renderLabel($form->createView(), null, [ + 'attr' => [ + 'class' => 'my&class', + ], + ]); + + $this->assertMatchesXpath($html, + '/label + [@for="name"] + [@class="required"] +' + ); + } + + public function testLabelWithCustomAttributesPassedDirectly() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\TextType'); + $html = $this->renderLabel($form->createView(), null, [ + 'label_attr' => [ + 'class' => 'my&class', + ], + ]); + + $this->assertMatchesXpath($html, + '/label + [@for="name"] + [@class="my&class required"] +' + ); + } + + public function testLabelWithCustomTextAndCustomAttributesPassedDirectly() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\TextType'); + $html = $this->renderLabel($form->createView(), 'Custom label', [ + 'label_attr' => [ + 'class' => 'my&class', + ], + ]); + + $this->assertMatchesXpath($html, + '/label + [@for="name"] + [@class="my&class required"] + [.="[trans]Custom label[/trans]"] +' + ); + } + + // https://github.com/symfony/symfony/issues/5029 + public function testLabelWithCustomTextAsOptionAndCustomAttributesPassedDirectly() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\TextType', null, [ + 'label' => 'Custom label', + ]); + $html = $this->renderLabel($form->createView(), null, [ + 'label_attr' => [ + 'class' => 'my&class', + ], + ]); + + $this->assertMatchesXpath($html, + '/label + [@for="name"] + [@class="my&class required"] + [.="[trans]Custom label[/trans]"] +' + ); + } + + public function testLabelFormatName() + { + $form = $this->factory->createNamedBuilder('myform') + ->add('myfield', 'Symfony\Component\Form\Extension\Core\Type\TextType') + ->getForm(); + $view = $form->get('myfield')->createView(); + $html = $this->renderLabel($view, null, ['label_format' => 'form.%name%']); + + $this->assertMatchesXpath($html, + '/label + [@for="myform_myfield"] + [.="[trans]form.myfield[/trans]"] +' + ); + } + + public function testLabelFormatId() + { + $form = $this->factory->createNamedBuilder('myform') + ->add('myfield', 'Symfony\Component\Form\Extension\Core\Type\TextType') + ->getForm(); + $view = $form->get('myfield')->createView(); + $html = $this->renderLabel($view, null, ['label_format' => 'form.%id%']); + + $this->assertMatchesXpath($html, + '/label + [@for="myform_myfield"] + [.="[trans]form.myform_myfield[/trans]"] +' + ); + } + + public function testLabelFormatAsFormOption() + { + $options = ['label_format' => 'form.%name%']; + + $form = $this->factory->createNamedBuilder('myform', 'Symfony\Component\Form\Extension\Core\Type\FormType', null, $options) + ->add('myfield', 'Symfony\Component\Form\Extension\Core\Type\TextType') + ->getForm(); + $view = $form->get('myfield')->createView(); + $html = $this->renderLabel($view); + + $this->assertMatchesXpath($html, + '/label + [@for="myform_myfield"] + [.="[trans]form.myfield[/trans]"] +' + ); + } + + public function testLabelFormatOverriddenOption() + { + $options = ['label_format' => 'form.%name%']; + + $form = $this->factory->createNamedBuilder('myform', 'Symfony\Component\Form\Extension\Core\Type\FormType', null, $options) + ->add('myfield', 'Symfony\Component\Form\Extension\Core\Type\TextType', ['label_format' => 'field.%name%']) + ->getForm(); + $view = $form->get('myfield')->createView(); + $html = $this->renderLabel($view); + + $this->assertMatchesXpath($html, + '/label + [@for="myform_myfield"] + [.="[trans]field.myfield[/trans]"] +' + ); + } + + public function testLabelWithoutTranslationOnButton() + { + $form = $this->factory->createNamedBuilder('myform', 'Symfony\Component\Form\Extension\Core\Type\FormType', null, [ + 'translation_domain' => false, + ]) + ->add('mybutton', 'Symfony\Component\Form\Extension\Core\Type\ButtonType') + ->getForm(); + $view = $form->get('mybutton')->createView(); + $html = $this->renderWidget($view); + + $this->assertMatchesXpath($html, + '/button + [@type="button"] + [@name="myform[mybutton]"] + [.="Mybutton"] +' + ); + } + + public function testLabelFormatOnButton() + { + $form = $this->factory->createNamedBuilder('myform') + ->add('mybutton', 'Symfony\Component\Form\Extension\Core\Type\ButtonType') + ->getForm(); + $view = $form->get('mybutton')->createView(); + $html = $this->renderWidget($view, ['label_format' => 'form.%name%']); + + $this->assertMatchesXpath($html, + '/button + [@type="button"] + [@name="myform[mybutton]"] + [.="[trans]form.mybutton[/trans]"] +' + ); + } + + public function testLabelFormatOnButtonId() + { + $form = $this->factory->createNamedBuilder('myform') + ->add('mybutton', 'Symfony\Component\Form\Extension\Core\Type\ButtonType') + ->getForm(); + $view = $form->get('mybutton')->createView(); + $html = $this->renderWidget($view, ['label_format' => 'form.%id%']); + + $this->assertMatchesXpath($html, + '/button + [@type="button"] + [@name="myform[mybutton]"] + [.="[trans]form.myform_mybutton[/trans]"] +' + ); + } + + public function testHelp() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\TextType', null, [ + 'help' => 'Help text test!', + ]); + $view = $form->createView(); + $html = $this->renderHelp($view); + + $this->assertMatchesXpath($html, + '/*[self::div or self::p] + [@id="name_help"] + [@class="help-text"] + [.="[trans]Help text test![/trans]"] +' + ); + } + + public function testHelpNotSet() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\TextType'); + $view = $form->createView(); + $html = $this->renderHelp($view); + + $this->assertMatchesXpath($html, '/p', 0); + } + + public function testHelpSetLinkFromWidget() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\TextType', null, [ + 'help' => 'Help text test!', + ]); + $view = $form->createView(); + $html = $this->renderRow($view); + + // Test if renderHelp method is implemented (throw SkippedTestError if not) + $this->renderHelp($view); + + $this->assertMatchesXpath($html, + '//input + [@aria-describedby="name_help"] +' + ); + } + + public function testHelpNotSetNotLinkedFromWidget() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\TextType'); + $view = $form->createView(); + $html = $this->renderRow($view); + + // Test if renderHelp method is implemented (throw SkippedTestError if not) + $this->renderHelp($view); + + $this->assertMatchesXpath($html, + '//input + [not(@aria-describedby)] +' + ); + } + + public function testErrors() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\TextType'); + $form->addError(new FormError('[trans]Error 1[/trans]')); + $form->addError(new FormError('[trans]Error 2[/trans]')); + $view = $form->createView(); + $html = $this->renderErrors($view); + + $this->assertMatchesXpath($html, + '/ul + [ + ./li[.="[trans]Error 1[/trans]"] + /following-sibling::li[.="[trans]Error 2[/trans]"] + ] + [count(./li)=2] +' + ); + } + + public function testOverrideWidgetBlock() + { + // see custom_widgets.html.twig + $form = $this->factory->createNamed('text_id', 'Symfony\Component\Form\Extension\Core\Type\TextType'); + $html = $this->renderWidget($form->createView()); + + $this->assertMatchesXpath($html, + '/div + [ + ./input + [@type="text"] + [@id="text_id"] + ] + [@id="container"] +' + ); + } + + public function testCheckedCheckbox() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\CheckboxType', true); + + $this->assertWidgetMatchesXpath($form->createView(), [], + '/input + [@type="checkbox"] + [@name="name"] + [@checked="checked"] + [@value="1"] +' + ); + } + + public function testUncheckedCheckbox() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\CheckboxType', false); + + $this->assertWidgetMatchesXpath($form->createView(), [], + '/input + [@type="checkbox"] + [@name="name"] + [not(@checked)] +' + ); + } + + public function testCheckboxWithValue() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\CheckboxType', false, [ + 'value' => 'foo&bar', + ]); + + $this->assertWidgetMatchesXpath($form->createView(), [], + '/input + [@type="checkbox"] + [@name="name"] + [@value="foo&bar"] +' + ); + } + + public function testSingleChoice() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', '&a', [ + 'choices' => ['Choice&A' => '&a', 'Choice&B' => '&b'], + 'multiple' => false, + 'expanded' => false, + ]); + + // If the field is collapsed, has no "multiple" attribute, is required but + // has *no* empty value, the "required" must not be added, otherwise + // the resulting HTML is invalid. + // https://github.com/symfony/symfony/issues/8942 + + // HTML 5 spec + // http://www.w3.org/html/wg/drafts/html/master/forms.html#placeholder-label-option + + // "If a select element has a required attribute specified, does not + // have a multiple attribute specified, and has a display size of 1, + // then the select element must have a placeholder label option." + + $this->assertWidgetMatchesXpath($form->createView(), [], + '/select + [@name="name"] + [not(@required)] + [ + ./option[@value="&a"][@selected="selected"][.="[trans]Choice&A[/trans]"] + /following-sibling::option[@value="&b"][not(@selected)][.="[trans]Choice&B[/trans]"] + ] + [count(./option)=2] +' + ); + } + + public function testSelectWithSizeBiggerThanOneCanBeRequired() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', null, [ + 'choices' => ['a', 'b'], + 'multiple' => false, + 'expanded' => false, + 'attr' => ['size' => 2], + ]); + + $this->assertWidgetMatchesXpath($form->createView(), [], + '/select + [@name="name"] + [@required="required"] + [@size="2"] + [count(./option)=2] +' + ); + } + + public function testSingleChoiceWithoutTranslation() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', '&a', [ + 'choices' => ['Choice&A' => '&a', 'Choice&B' => '&b'], + 'multiple' => false, + 'expanded' => false, + 'choice_translation_domain' => false, + ]); + + $this->assertWidgetMatchesXpath($form->createView(), [], + '/select + [@name="name"] + [not(@required)] + [ + ./option[@value="&a"][@selected="selected"][.="Choice&A"] + /following-sibling::option[@value="&b"][not(@selected)][.="Choice&B"] + ] + [count(./option)=2] +' + ); + } + + public function testSingleChoiceWithPlaceholderWithoutTranslation() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', '&a', [ + 'choices' => ['Choice&A' => '&a', 'Choice&B' => '&b'], + 'multiple' => false, + 'expanded' => false, + 'required' => false, + 'translation_domain' => false, + 'placeholder' => 'Placeholder&Not&Translated', + ]); + + $this->assertWidgetMatchesXpath($form->createView(), [], + '/select + [@name="name"] + [not(@required)] + [ + ./option[@value=""][not(@selected)][not(@disabled)][.="Placeholder&Not&Translated"] + /following-sibling::option[@value="&a"][@selected="selected"][.="Choice&A"] + /following-sibling::option[@value="&b"][not(@selected)][.="Choice&B"] + ] + [count(./option)=3] +' + ); + } + + public function testSingleChoiceAttributes() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', '&a', [ + 'choices' => ['Choice&A' => '&a', 'Choice&B' => '&b'], + 'choice_attr' => ['Choice&B' => ['class' => 'foo&bar']], + 'multiple' => false, + 'expanded' => false, + ]); + + $this->assertWidgetMatchesXpath($form->createView(), [], + '/select + [@name="name"] + [not(@required)] + [ + ./option[@value="&a"][@selected="selected"][.="[trans]Choice&A[/trans]"] + /following-sibling::option[@value="&b"][@class="foo&bar"][not(@selected)][.="[trans]Choice&B[/trans]"] + ] + [count(./option)=2] +' + ); + } + + public function testSingleChoiceAttributesWithMainAttributes() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', '&a', [ + 'choices' => ['Choice&A' => '&a', 'Choice&B' => '&b'], + 'multiple' => false, + 'expanded' => false, + 'attr' => ['class' => 'bar&baz'], + ]); + + $this->assertWidgetMatchesXpath($form->createView(), ['attr' => ['class' => 'bar&baz']], + '/select + [@name="name"] + [@class="bar&baz"] + [not(@required)] + [ + ./option[@value="&a"][@selected="selected"][.="[trans]Choice&A[/trans]"][not(@id)][not(@name)] + /following-sibling::option[@value="&b"][not(@class)][not(@selected)][.="[trans]Choice&B[/trans]"][not(@id)][not(@name)] + ] + [count(./option)=2] +' + ); + } + + public function testSingleExpandedChoiceAttributesWithMainAttributes() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', '&a', [ + 'choices' => ['Choice&A' => '&a', 'Choice&B' => '&b'], + 'multiple' => false, + 'expanded' => true, + 'attr' => ['class' => 'bar&baz'], + ]); + + $this->assertWidgetMatchesXpath($form->createView(), ['attr' => ['class' => 'bar&baz']], + '/div + [@class="bar&baz"] + [ + ./input[@type="radio"][@name="name"][@id="name_0"][@value="&a"][@checked] + /following-sibling::label[@for="name_0"][.="[trans]Choice&A[/trans]"] + /following-sibling::input[@type="radio"][@name="name"][@id="name_1"][@value="&b"][not(@checked)] + /following-sibling::label[@for="name_1"][.="[trans]Choice&B[/trans]"] + /following-sibling::input[@type="hidden"][@id="name__token"] + ] + [count(./input)=3] +' + ); + } + + public function testSingleChoiceWithPreferred() + { + $this->requiresFeatureSet(404); + + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', '&a', [ + 'choices' => ['Choice&A' => '&a', 'Choice&B' => '&b'], + 'preferred_choices' => ['&b'], + 'multiple' => false, + 'expanded' => false, + ]); + + $this->assertWidgetMatchesXpath($form->createView(), ['separator' => '-- sep --'], + '/select + [@name="name"] + [not(@required)] + [ + ./option[@value="&b"][not(@selected)][.="[trans]Choice&B[/trans]"] + /following-sibling::option[@disabled="disabled"][not(@selected)][.="-- sep --"] + /following-sibling::option[@value="&a"][@selected="selected"][.="[trans]Choice&A[/trans]"] + /following-sibling::option[@value="&b"][.="[trans]Choice&B[/trans]"] + ] + [count(./option)=4] +' + ); + } + + public function testSingleChoiceWithPreferredAndNoSeparator() + { + $this->requiresFeatureSet(404); + + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', '&a', [ + 'choices' => ['Choice&A' => '&a', 'Choice&B' => '&b'], + 'preferred_choices' => ['&b'], + 'multiple' => false, + 'expanded' => false, + ]); + + $this->assertWidgetMatchesXpath($form->createView(), ['separator' => null], + '/select + [@name="name"] + [not(@required)] + [ + ./option[@value="&b"][not(@selected)][.="[trans]Choice&B[/trans]"] + /following-sibling::option[@value="&a"][@selected="selected"][.="[trans]Choice&A[/trans]"] + /following-sibling::option[@value="&b"][.="[trans]Choice&B[/trans]"] + ] + [count(./option)=3] +' + ); + } + + public function testSingleChoiceWithPreferredAndBlankSeparator() + { + $this->requiresFeatureSet(404); + + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', '&a', [ + 'choices' => ['Choice&A' => '&a', 'Choice&B' => '&b'], + 'preferred_choices' => ['&b'], + 'multiple' => false, + 'expanded' => false, + ]); + + $this->assertWidgetMatchesXpath($form->createView(), ['separator' => ''], + '/select + [@name="name"] + [not(@required)] + [ + ./option[@value="&b"][not(@selected)][.="[trans]Choice&B[/trans]"] + /following-sibling::option[@disabled="disabled"][not(@selected)][.=""] + /following-sibling::option[@value="&a"][@selected="selected"][.="[trans]Choice&A[/trans]"] + /following-sibling::option[@value="&b"][.="[trans]Choice&B[/trans]"] + ] + [count(./option)=4] +' + ); + } + + public function testChoiceWithOnlyPreferred() + { + $this->requiresFeatureSet(404); + + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', '&a', [ + 'choices' => ['Choice&A' => '&a', 'Choice&B' => '&b'], + 'preferred_choices' => ['&a', '&b'], + 'multiple' => false, + 'expanded' => false, + ]); + + $this->assertWidgetMatchesXpath($form->createView(), [], + '/select + [count(./option)=5] +' + ); + } + + public function testSingleChoiceNonRequired() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', '&a', [ + 'choices' => ['Choice&A' => '&a', 'Choice&B' => '&b'], + 'required' => false, + 'multiple' => false, + 'expanded' => false, + ]); + + $this->assertWidgetMatchesXpath($form->createView(), [], + '/select + [@name="name"] + [not(@required)] + [ + ./option[@value=""][.=""] + /following-sibling::option[@value="&a"][@selected="selected"][.="[trans]Choice&A[/trans]"] + /following-sibling::option[@value="&b"][not(@selected)][.="[trans]Choice&B[/trans]"] + ] + [count(./option)=3] +' + ); + } + + public function testSingleChoiceNonRequiredNoneSelected() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', null, [ + 'choices' => ['Choice&A' => '&a', 'Choice&B' => '&b'], + 'required' => false, + 'multiple' => false, + 'expanded' => false, + ]); + + $this->assertWidgetMatchesXpath($form->createView(), [], + '/select + [@name="name"] + [not(@required)] + [ + ./option[@value=""][.=""] + /following-sibling::option[@value="&a"][not(@selected)][.="[trans]Choice&A[/trans]"] + /following-sibling::option[@value="&b"][not(@selected)][.="[trans]Choice&B[/trans]"] + ] + [count(./option)=3] +' + ); + } + + public function testSingleChoiceNonRequiredWithPlaceholder() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', '&a', [ + 'choices' => ['Choice&A' => '&a', 'Choice&B' => '&b'], + 'multiple' => false, + 'expanded' => false, + 'required' => false, + 'placeholder' => 'Select&Anything&Not&Me', + ]); + + $this->assertWidgetMatchesXpath($form->createView(), [], + '/select + [@name="name"] + [not(@required)] + [ + ./option[@value=""][not(@selected)][not(@disabled)][.="[trans]Select&Anything&Not&Me[/trans]"] + /following-sibling::option[@value="&a"][@selected="selected"][.="[trans]Choice&A[/trans]"] + /following-sibling::option[@value="&b"][not(@selected)][.="[trans]Choice&B[/trans]"] + ] + [count(./option)=3] +' + ); + } + + public function testSingleChoiceRequiredWithPlaceholder() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', '&a', [ + 'choices' => ['Choice&A' => '&a', 'Choice&B' => '&b'], + 'required' => true, + 'multiple' => false, + 'expanded' => false, + 'placeholder' => 'Test&Me', + ]); + + // The "disabled" attribute was removed again due to a bug in the + // BlackBerry 10 browser. + // See https://github.com/symfony/symfony/pull/7678 + $this->assertWidgetMatchesXpath($form->createView(), [], + '/select + [@name="name"] + [@required="required"] + [ + ./option[@value=""][not(@selected)][not(@disabled)][.="[trans]Test&Me[/trans]"] + /following-sibling::option[@value="&a"][@selected="selected"][.="[trans]Choice&A[/trans]"] + /following-sibling::option[@value="&b"][not(@selected)][.="[trans]Choice&B[/trans]"] + ] + [count(./option)=3] +' + ); + } + + public function testSingleChoiceRequiredWithPlaceholderViaView() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', '&a', [ + 'choices' => ['Choice&A' => '&a', 'Choice&B' => '&b'], + 'required' => true, + 'multiple' => false, + 'expanded' => false, + ]); + + // The "disabled" attribute was removed again due to a bug in the + // BlackBerry 10 browser. + // See https://github.com/symfony/symfony/pull/7678 + $this->assertWidgetMatchesXpath($form->createView(), ['placeholder' => ''], + '/select + [@name="name"] + [@required="required"] + [ + ./option[@value=""][not(@selected)][not(@disabled)][.=""] + /following-sibling::option[@value="&a"][@selected="selected"][.="[trans]Choice&A[/trans]"] + /following-sibling::option[@value="&b"][not(@selected)][.="[trans]Choice&B[/trans]"] + ] + [count(./option)=3] +' + ); + } + + public function testSingleChoiceGrouped() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', '&a', [ + 'choices' => [ + 'Group&1' => ['Choice&A' => '&a', 'Choice&B' => '&b'], + 'Group&2' => ['Choice&C' => '&c'], + ], + 'multiple' => false, + 'expanded' => false, + ]); + + $this->assertWidgetMatchesXpath($form->createView(), [], + '/select + [@name="name"] + [./optgroup[@label="[trans]Group&1[/trans]"] + [ + ./option[@value="&a"][@selected="selected"][.="[trans]Choice&A[/trans]"] + /following-sibling::option[@value="&b"][not(@selected)][.="[trans]Choice&B[/trans]"] + ] + [count(./option)=2] + ] + [./optgroup[@label="[trans]Group&2[/trans]"] + [./option[@value="&c"][not(@selected)][.="[trans]Choice&C[/trans]"]] + [count(./option)=1] + ] + [count(./optgroup)=2] +' + ); + } + + public function testMultipleChoice() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', ['&a'], [ + 'choices' => ['Choice&A' => '&a', 'Choice&B' => '&b'], + 'required' => true, + 'multiple' => true, + 'expanded' => false, + ]); + + $this->assertWidgetMatchesXpath($form->createView(), [], + '/select + [@name="name[]"] + [@required="required"] + [@multiple="multiple"] + [ + ./option[@value="&a"][@selected="selected"][.="[trans]Choice&A[/trans]"] + /following-sibling::option[@value="&b"][not(@selected)][.="[trans]Choice&B[/trans]"] + ] + [count(./option)=2] +' + ); + } + + public function testMultipleChoiceAttributes() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', ['&a'], [ + 'choices' => ['Choice&A' => '&a', 'Choice&B' => '&b'], + 'choice_attr' => ['Choice&B' => ['class' => 'foo&bar']], + 'required' => true, + 'multiple' => true, + 'expanded' => false, + ]); + + $this->assertWidgetMatchesXpath($form->createView(), [], + '/select + [@name="name[]"] + [@required="required"] + [@multiple="multiple"] + [ + ./option[@value="&a"][@selected="selected"][.="[trans]Choice&A[/trans]"] + /following-sibling::option[@value="&b"][@class="foo&bar"][not(@selected)][.="[trans]Choice&B[/trans]"] + ] + [count(./option)=2] +' + ); + } + + public function testMultipleChoiceSkipsPlaceholder() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', ['&a'], [ + 'choices' => ['Choice&A' => '&a', 'Choice&B' => '&b'], + 'multiple' => true, + 'expanded' => false, + 'placeholder' => 'Test&Me', + ]); + + $this->assertWidgetMatchesXpath($form->createView(), [], + '/select + [@name="name[]"] + [@multiple="multiple"] + [ + ./option[@value="&a"][@selected="selected"][.="[trans]Choice&A[/trans]"] + /following-sibling::option[@value="&b"][not(@selected)][.="[trans]Choice&B[/trans]"] + ] + [count(./option)=2] +' + ); + } + + public function testMultipleChoiceNonRequired() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', ['&a'], [ + 'choices' => ['Choice&A' => '&a', 'Choice&B' => '&b'], + 'required' => false, + 'multiple' => true, + 'expanded' => false, + ]); + + $this->assertWidgetMatchesXpath($form->createView(), [], + '/select + [@name="name[]"] + [@multiple="multiple"] + [ + ./option[@value="&a"][@selected="selected"][.="[trans]Choice&A[/trans]"] + /following-sibling::option[@value="&b"][not(@selected)][.="[trans]Choice&B[/trans]"] + ] + [count(./option)=2] +' + ); + } + + public function testSingleChoiceExpanded() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', '&a', [ + 'choices' => ['Choice&A' => '&a', 'Choice&B' => '&b'], + 'multiple' => false, + 'expanded' => true, + ]); + + $this->assertWidgetMatchesXpath($form->createView(), [], + '/div + [ + ./input[@type="radio"][@name="name"][@id="name_0"][@value="&a"][@checked] + /following-sibling::label[@for="name_0"][.="[trans]Choice&A[/trans]"] + /following-sibling::input[@type="radio"][@name="name"][@id="name_1"][@value="&b"][not(@checked)] + /following-sibling::label[@for="name_1"][.="[trans]Choice&B[/trans]"] + /following-sibling::input[@type="hidden"][@id="name__token"] + ] + [count(./input)=3] +' + ); + } + + public function testSingleChoiceExpandedWithoutTranslation() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', '&a', [ + 'choices' => ['Choice&A' => '&a', 'Choice&B' => '&b'], + 'multiple' => false, + 'expanded' => true, + 'choice_translation_domain' => false, + 'placeholder' => 'Placeholder&Not&Translated', + ]); + + $this->assertWidgetMatchesXpath($form->createView(), [], + '/div + [ + ./input[@type="radio"][@name="name"][@id="name_0"][@value="&a"][@checked] + /following-sibling::label[@for="name_0"][.="Choice&A"] + /following-sibling::input[@type="radio"][@name="name"][@id="name_1"][@value="&b"][not(@checked)] + /following-sibling::label[@for="name_1"][.="Choice&B"] + /following-sibling::input[@type="hidden"][@id="name__token"] + ] + [count(./input)=3] +' + ); + } + + public function testSingleChoiceExpandedAttributes() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', '&a', [ + 'choices' => ['Choice&A' => '&a', 'Choice&B' => '&b'], + 'choice_attr' => ['Choice&B' => ['class' => 'foo&bar']], + 'multiple' => false, + 'expanded' => true, + ]); + + $this->assertWidgetMatchesXpath($form->createView(), [], + '/div + [ + ./input[@type="radio"][@name="name"][@id="name_0"][@value="&a"][@checked] + /following-sibling::label[@for="name_0"][.="[trans]Choice&A[/trans]"] + /following-sibling::input[@type="radio"][@name="name"][@id="name_1"][@value="&b"][@class="foo&bar"][not(@checked)] + /following-sibling::label[@for="name_1"][.="[trans]Choice&B[/trans]"] + /following-sibling::input[@type="hidden"][@id="name__token"] + ] + [count(./input)=3] +' + ); + } + + public function testSingleChoiceExpandedWithPlaceholder() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', '&a', [ + 'choices' => ['Choice&A' => '&a', 'Choice&B' => '&b'], + 'multiple' => false, + 'expanded' => true, + 'placeholder' => 'Test&Me', + 'required' => false, + ]); + + $this->assertWidgetMatchesXpath($form->createView(), [], + '/div + [ + ./input[@type="radio"][@name="name"][@id="name_placeholder"][not(@checked)] + /following-sibling::label[@for="name_placeholder"][.="[trans]Test&Me[/trans]"] + /following-sibling::input[@type="radio"][@name="name"][@id="name_0"][@checked] + /following-sibling::label[@for="name_0"][.="[trans]Choice&A[/trans]"] + /following-sibling::input[@type="radio"][@name="name"][@id="name_1"][not(@checked)] + /following-sibling::label[@for="name_1"][.="[trans]Choice&B[/trans]"] + /following-sibling::input[@type="hidden"][@id="name__token"] + ] + [count(./input)=4] +' + ); + } + + public function testSingleChoiceExpandedWithPlaceholderWithoutTranslation() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', '&a', [ + 'choices' => ['Choice&A' => '&a', 'Choice&B' => '&b'], + 'multiple' => false, + 'expanded' => true, + 'required' => false, + 'choice_translation_domain' => false, + 'placeholder' => 'Placeholder&Not&Translated', + ]); + + $this->assertWidgetMatchesXpath($form->createView(), [], + '/div + [ + ./input[@type="radio"][@name="name"][@id="name_placeholder"][not(@checked)] + /following-sibling::label[@for="name_placeholder"][.="Placeholder&Not&Translated"] + /following-sibling::input[@type="radio"][@name="name"][@id="name_0"][@checked] + /following-sibling::label[@for="name_0"][.="Choice&A"] + /following-sibling::input[@type="radio"][@name="name"][@id="name_1"][not(@checked)] + /following-sibling::label[@for="name_1"][.="Choice&B"] + /following-sibling::input[@type="hidden"][@id="name__token"] + ] + [count(./input)=4] +' + ); + } + + public function testSingleChoiceExpandedWithBooleanValue() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', true, [ + 'choices' => ['Choice&A' => '1', 'Choice&B' => '0'], + 'multiple' => false, + 'expanded' => true, + ]); + + $this->assertWidgetMatchesXpath($form->createView(), [], + '/div + [ + ./input[@type="radio"][@name="name"][@id="name_0"][@checked] + /following-sibling::label[@for="name_0"][.="[trans]Choice&A[/trans]"] + /following-sibling::input[@type="radio"][@name="name"][@id="name_1"][not(@checked)] + /following-sibling::label[@for="name_1"][.="[trans]Choice&B[/trans]"] + /following-sibling::input[@type="hidden"][@id="name__token"] + ] + [count(./input)=3] +' + ); + } + + public function testMultipleChoiceExpanded() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', ['&a', '&c'], [ + 'choices' => ['Choice&A' => '&a', 'Choice&B' => '&b', 'Choice&C' => '&c'], + 'multiple' => true, + 'expanded' => true, + 'required' => true, + ]); + + $this->assertWidgetMatchesXpath($form->createView(), [], + '/div + [ + ./input[@type="checkbox"][@name="name[]"][@id="name_0"][@checked][not(@required)] + /following-sibling::label[@for="name_0"][.="[trans]Choice&A[/trans]"] + /following-sibling::input[@type="checkbox"][@name="name[]"][@id="name_1"][not(@checked)][not(@required)] + /following-sibling::label[@for="name_1"][.="[trans]Choice&B[/trans]"] + /following-sibling::input[@type="checkbox"][@name="name[]"][@id="name_2"][@checked][not(@required)] + /following-sibling::label[@for="name_2"][.="[trans]Choice&C[/trans]"] + /following-sibling::input[@type="hidden"][@id="name__token"] + ] + [count(./input)=4] +' + ); + } + + public function testMultipleChoiceExpandedWithoutTranslation() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', ['&a', '&c'], [ + 'choices' => ['Choice&A' => '&a', 'Choice&B' => '&b', 'Choice&C' => '&c'], + 'multiple' => true, + 'expanded' => true, + 'required' => true, + 'choice_translation_domain' => false, + ]); + + $this->assertWidgetMatchesXpath($form->createView(), [], + '/div + [ + ./input[@type="checkbox"][@name="name[]"][@id="name_0"][@checked][not(@required)] + /following-sibling::label[@for="name_0"][.="Choice&A"] + /following-sibling::input[@type="checkbox"][@name="name[]"][@id="name_1"][not(@checked)][not(@required)] + /following-sibling::label[@for="name_1"][.="Choice&B"] + /following-sibling::input[@type="checkbox"][@name="name[]"][@id="name_2"][@checked][not(@required)] + /following-sibling::label[@for="name_2"][.="Choice&C"] + /following-sibling::input[@type="hidden"][@id="name__token"] + ] + [count(./input)=4] +' + ); + } + + public function testMultipleChoiceExpandedAttributes() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', ['&a', '&c'], [ + 'choices' => ['Choice&A' => '&a', 'Choice&B' => '&b', 'Choice&C' => '&c'], + 'choice_attr' => ['Choice&B' => ['class' => 'foo&bar']], + 'multiple' => true, + 'expanded' => true, + 'required' => true, + ]); + + $this->assertWidgetMatchesXpath($form->createView(), [], + '/div + [ + ./input[@type="checkbox"][@name="name[]"][@id="name_0"][@checked][not(@required)] + /following-sibling::label[@for="name_0"][.="[trans]Choice&A[/trans]"] + /following-sibling::input[@type="checkbox"][@name="name[]"][@id="name_1"][@class="foo&bar"][not(@checked)][not(@required)] + /following-sibling::label[@for="name_1"][.="[trans]Choice&B[/trans]"] + /following-sibling::input[@type="checkbox"][@name="name[]"][@id="name_2"][@checked][not(@required)] + /following-sibling::label[@for="name_2"][.="[trans]Choice&C[/trans]"] + /following-sibling::input[@type="hidden"][@id="name__token"] + ] + [count(./input)=4] +' + ); + } + + public function testCountry() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\CountryType', 'AT'); + + $this->assertWidgetMatchesXpath($form->createView(), [], + '/select + [@name="name"] + [./option[@value="AT"][@selected="selected"][.="Austria"]] + [count(./option)>200] +' + ); + } + + public function testCountryWithPlaceholder() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\CountryType', 'AT', [ + 'placeholder' => 'Select&Country', + 'required' => false, + ]); + + $this->assertWidgetMatchesXpath($form->createView(), [], + '/select + [@name="name"] + [./option[@value=""][not(@selected)][not(@disabled)][.="[trans]Select&Country[/trans]"]] + [./option[@value="AT"][@selected="selected"][.="Austria"]] + [count(./option)>201] +' + ); + } + + public function testDateTime() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\DateTimeType', date('Y').'-02-03 04:05:06', [ + 'input' => 'string', + 'with_seconds' => false, + 'widget' => 'choice', + ]); + + $this->assertWidgetMatchesXpath($form->createView(), [], + '/div + [ + ./div + [@id="name_date"] + [ + ./select + [@id="name_date_month"] + [./option[@value="2"][@selected="selected"]] + /following-sibling::select + [@id="name_date_day"] + [./option[@value="3"][@selected="selected"]] + /following-sibling::select + [@id="name_date_year"] + [./option[@value="'.date('Y').'"][@selected="selected"]] + ] + /following-sibling::div + [@id="name_time"] + [ + ./select + [@id="name_time_hour"] + [./option[@value="4"][@selected="selected"]] + /following-sibling::select + [@id="name_time_minute"] + [./option[@value="5"][@selected="selected"]] + ] + ] + [count(.//select)=5] +' + ); + } + + public function testDateTimeWithPlaceholderGlobal() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\DateTimeType', null, [ + 'input' => 'string', + 'placeholder' => 'Change&Me', + 'required' => false, + 'widget' => 'choice', + ]); + + $this->assertWidgetMatchesXpath($form->createView(), [], + '/div + [ + ./div + [@id="name_date"] + [ + ./select + [@id="name_date_month"] + [./option[@value=""][not(@selected)][not(@disabled)][.="[trans]Change&Me[/trans]"]] + /following-sibling::select + [@id="name_date_day"] + [./option[@value=""][not(@selected)][not(@disabled)][.="[trans]Change&Me[/trans]"]] + /following-sibling::select + [@id="name_date_year"] + [./option[@value=""][not(@selected)][not(@disabled)][.="[trans]Change&Me[/trans]"]] + ] + /following-sibling::div + [@id="name_time"] + [ + ./select + [@id="name_time_hour"] + [./option[@value=""][.="[trans]Change&Me[/trans]"]] + /following-sibling::select + [@id="name_time_minute"] + [./option[@value=""][.="[trans]Change&Me[/trans]"]] + ] + ] + [count(.//select)=5] +' + ); + } + + public function testDateTimeWithHourAndMinute() + { + $data = ['year' => date('Y'), 'month' => '2', 'day' => '3', 'hour' => '4', 'minute' => '5']; + + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\DateTimeType', $data, [ + 'input' => 'array', + 'required' => false, + 'widget' => 'choice', + ]); + + $this->assertWidgetMatchesXpath($form->createView(), [], + '/div + [ + ./div + [@id="name_date"] + [ + ./select + [@id="name_date_month"] + [./option[@value="2"][@selected="selected"]] + /following-sibling::select + [@id="name_date_day"] + [./option[@value="3"][@selected="selected"]] + /following-sibling::select + [@id="name_date_year"] + [./option[@value="'.date('Y').'"][@selected="selected"]] + ] + /following-sibling::div + [@id="name_time"] + [ + ./select + [@id="name_time_hour"] + [./option[@value="4"][@selected="selected"]] + /following-sibling::select + [@id="name_time_minute"] + [./option[@value="5"][@selected="selected"]] + ] + ] + [count(.//select)=5] +' + ); + } + + public function testDateTimeWithSeconds() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\DateTimeType', date('Y').'-02-03 04:05:06', [ + 'input' => 'string', + 'with_seconds' => true, + 'widget' => 'choice', + ]); + + $this->assertWidgetMatchesXpath($form->createView(), [], + '/div + [ + ./div + [@id="name_date"] + [ + ./select + [@id="name_date_month"] + [./option[@value="2"][@selected="selected"]] + /following-sibling::select + [@id="name_date_day"] + [./option[@value="3"][@selected="selected"]] + /following-sibling::select + [@id="name_date_year"] + [./option[@value="'.date('Y').'"][@selected="selected"]] + ] + /following-sibling::div + [@id="name_time"] + [ + ./select + [@id="name_time_hour"] + [./option[@value="4"][@selected="selected"]] + /following-sibling::select + [@id="name_time_minute"] + [./option[@value="5"][@selected="selected"]] + /following-sibling::select + [@id="name_time_second"] + [./option[@value="6"][@selected="selected"]] + ] + ] + [count(.//select)=6] +' + ); + } + + public function testDateTimeSingleText() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\DateTimeType', '2011-02-03 04:05:06', [ + 'input' => 'string', + 'date_widget' => 'single_text', + 'time_widget' => 'single_text', + ]); + + $this->assertWidgetMatchesXpath($form->createView(), [], + '/div + [ + ./input + [@type="date"] + [@id="name_date"] + [@name="name[date]"] + [@value="2011-02-03"] + /following-sibling::input + [@type="time"] + [@id="name_time"] + [@name="name[time]"] + [@value="04:05"] + ] +' + ); + } + + public function testDateTimeWithWidgetSingleText() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\DateTimeType', '2011-02-03 04:05:06', [ + 'input' => 'string', + 'widget' => 'single_text', + 'model_timezone' => 'UTC', + 'view_timezone' => 'UTC', + ]); + + $this->assertWidgetMatchesXpath($form->createView(), [], + '/input + [@type="datetime-local"] + [@name="name"] + [@value="2011-02-03T04:05"] +' + ); + } + + public function testDateChoice() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\DateType', date('Y').'-02-03', [ + 'input' => 'string', + 'widget' => 'choice', + ]); + + $this->assertWidgetMatchesXpath($form->createView(), [], + '/div + [ + ./select + [@id="name_month"] + [./option[@value="2"][@selected="selected"]] + /following-sibling::select + [@id="name_day"] + [./option[@value="3"][@selected="selected"]] + /following-sibling::select + [@id="name_year"] + [./option[@value="'.date('Y').'"][@selected="selected"]] + ] + [count(./select)=3] +' + ); + } + + public function testDateChoiceWithPlaceholderGlobal() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\DateType', null, [ + 'input' => 'string', + 'widget' => 'choice', + 'placeholder' => 'Change&Me', + 'required' => false, + ]); + + $this->assertWidgetMatchesXpath($form->createView(), [], + '/div + [ + ./select + [@id="name_month"] + [./option[@value=""][not(@selected)][not(@disabled)][.="[trans]Change&Me[/trans]"]] + /following-sibling::select + [@id="name_day"] + [./option[@value=""][not(@selected)][not(@disabled)][.="[trans]Change&Me[/trans]"]] + /following-sibling::select + [@id="name_year"] + [./option[@value=""][not(@selected)][not(@disabled)][.="[trans]Change&Me[/trans]"]] + ] + [count(./select)=3] +' + ); + } + + public function testDateChoiceWithPlaceholderOnYear() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\DateType', null, [ + 'input' => 'string', + 'widget' => 'choice', + 'required' => false, + 'placeholder' => ['year' => 'Change&Me'], + ]); + + $this->assertWidgetMatchesXpath($form->createView(), [], + '/div + [ + ./select + [@id="name_month"] + [./option[@value="1"]] + /following-sibling::select + [@id="name_day"] + [./option[@value="1"]] + /following-sibling::select + [@id="name_year"] + [./option[@value=""][not(@selected)][not(@disabled)][.="[trans]Change&Me[/trans]"]] + ] + [count(./select)=3] +' + ); + } + + public function testDateText() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\DateType', '2011-02-03', [ + 'input' => 'string', + 'widget' => 'text', + ]); + + $this->assertWidgetMatchesXpath($form->createView(), [], + '/div + [ + ./input + [@id="name_month"] + [@type="text"] + [@value="2"] + /following-sibling::input + [@id="name_day"] + [@type="text"] + [@value="3"] + /following-sibling::input + [@id="name_year"] + [@type="text"] + [@value="2011"] + ] + [count(./input)=3] +' + ); + } + + public function testDateSingleText() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\DateType', '2011-02-03', [ + 'input' => 'string', + 'widget' => 'single_text', + ]); + + $this->assertWidgetMatchesXpath($form->createView(), [], + '/input + [@type="date"] + [@name="name"] + [@value="2011-02-03"] +' + ); + } + + public function testDateErrorBubbling() + { + $form = $this->factory->createNamedBuilder('form', 'Symfony\Component\Form\Extension\Core\Type\FormType') + ->add('date', 'Symfony\Component\Form\Extension\Core\Type\DateType', ['widget' => 'choice']) + ->getForm(); + $form->get('date')->addError(new FormError('[trans]Error![/trans]')); + $view = $form->createView(); + + $this->assertEmpty($this->renderErrors($view)); + $this->assertNotEmpty($this->renderErrors($view['date'])); + } + + public function testBirthDay() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\BirthdayType', '2000-02-03', [ + 'input' => 'string', + 'widget' => 'choice', + ]); + + $this->assertWidgetMatchesXpath($form->createView(), [], + '/div + [ + ./select + [@id="name_month"] + [./option[@value="2"][@selected="selected"]] + /following-sibling::select + [@id="name_day"] + [./option[@value="3"][@selected="selected"]] + /following-sibling::select + [@id="name_year"] + [./option[@value="2000"][@selected="selected"]] + ] + [count(./select)=3] +' + ); + } + + public function testBirthDayWithPlaceholder() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\BirthdayType', '1950-01-01', [ + 'input' => 'string', + 'placeholder' => '', + 'required' => false, + 'widget' => 'choice', + ]); + + $this->assertWidgetMatchesXpath($form->createView(), [], + '/div + [ + ./select + [@id="name_month"] + [./option[@value=""][not(@selected)][not(@disabled)][.=""]] + [./option[@value="1"][@selected="selected"]] + /following-sibling::select + [@id="name_day"] + [./option[@value=""][not(@selected)][not(@disabled)][.=""]] + [./option[@value="1"][@selected="selected"]] + /following-sibling::select + [@id="name_year"] + [./option[@value=""][not(@selected)][not(@disabled)][.=""]] + [./option[@value="1950"][@selected="selected"]] + ] + [count(./select)=3] +' + ); + } + + public function testEmail() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\EmailType', 'foo&bar'); + + $this->assertWidgetMatchesXpath($form->createView(), [], + '/input + [@type="email"] + [@name="name"] + [@value="foo&bar"] + [not(@maxlength)] +' + ); + } + + public function testEmailWithMaxLength() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\EmailType', 'foo&bar', [ + 'attr' => ['maxlength' => 123], + ]); + + $this->assertWidgetMatchesXpath($form->createView(), [], + '/input + [@type="email"] + [@name="name"] + [@value="foo&bar"] + [@maxlength="123"] +' + ); + } + + public function testFile() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\FileType'); + + $this->assertWidgetMatchesXpath($form->createView(), [], + '/input + [@type="file"] +' + ); + } + + public function testHidden() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\HiddenType', 'foo&bar'); + + $this->assertWidgetMatchesXpath($form->createView(), [], + '/input + [@type="hidden"] + [@name="name"] + [@value="foo&bar"] +' + ); + } + + public function testDisabled() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\TextType', null, [ + 'disabled' => true, + ]); + + $this->assertWidgetMatchesXpath($form->createView(), [], + '/input + [@type="text"] + [@name="name"] + [@disabled="disabled"] +' + ); + } + + public function testInteger() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\IntegerType', 123); + + $this->assertWidgetMatchesXpath($form->createView(), [], + '/input + [@type="number"] + [@name="name"] + [@value="123"] +' + ); + } + + public function testIntegerTypeWithGroupingRendersAsTextInput() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\IntegerType', 123, [ + 'grouping' => true, + ]); + + $this->assertWidgetMatchesXpath($form->createView(), [], + '/input + [@type="text"] + [@name="name"] + [@value="123"] +' + ); + } + + public function testLanguage() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\LanguageType', 'de'); + + $this->assertWidgetMatchesXpath($form->createView(), [], + '/select + [@name="name"] + [./option[@value="de"][@selected="selected"][.="German"]] + [count(./option)>200] +' + ); + } + + public function testLocale() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\LocaleType', 'de_AT'); + + $this->assertWidgetMatchesXpath($form->createView(), [], + '/select + [@name="name"] + [./option[@value="de_AT"][@selected="selected"][.="German (Austria)"]] + [count(./option)>200] +' + ); + } + + public function testMoney() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\MoneyType', 1234.56, [ + 'currency' => 'EUR', + ]); + + $this->assertWidgetMatchesXpath($form->createView(), [], + '/input + [@type="text"] + [@name="name"] + [@value="1234.56"] + [contains(.., "€")] +' + ); + } + + public function testNumber() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\NumberType', 1234.56); + + $this->assertWidgetMatchesXpath($form->createView(), [], + '/input + [@type="text"] + [@name="name"] + [@value="1234.56"] +' + ); + } + + public function testRenderNumberWithHtml5NumberType() + { + $this->requiresFeatureSet(403); + + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\NumberType', 1234.56, [ + 'html5' => true, + ]); + + $this->assertWidgetMatchesXpath($form->createView(), [], + '/input + [@type="number"] + [@step="any"] + [@name="name"] + [@value="1234.56"] +' + ); + } + + public function testRenderNumberWithHtml5NumberTypeAndStepAttribute() + { + $this->requiresFeatureSet(403); + + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\NumberType', 1234.56, [ + 'html5' => true, + 'attr' => ['step' => '0.1'], + ]); + + $this->assertWidgetMatchesXpath($form->createView(), [], + '/input + [@type="number"] + [@step="0.1"] + [@name="name"] + [@value="1234.56"] +' + ); + } + + public function testPassword() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\PasswordType', 'foo&bar'); + + $this->assertWidgetMatchesXpath($form->createView(), [], + '/input + [@type="password"] + [@name="name"] +' + ); + } + + public function testPasswordSubmittedWithNotAlwaysEmpty() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\PasswordType', null, [ + 'always_empty' => false, + ]); + $form->submit('foo&bar'); + + $this->assertWidgetMatchesXpath($form->createView(), [], + '/input + [@type="password"] + [@name="name"] + [@value="foo&bar"] +' + ); + } + + public function testPasswordWithMaxLength() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\PasswordType', 'foo&bar', [ + 'attr' => ['maxlength' => 123], + ]); + + $this->assertWidgetMatchesXpath($form->createView(), [], + '/input + [@type="password"] + [@name="name"] + [@maxlength="123"] +' + ); + } + + public function testPercent() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\PercentType', 0.1, ['rounding_mode' => \NumberFormatter::ROUND_CEILING]); + + $this->assertWidgetMatchesXpath($form->createView(), [], + '/input + [@type="text"] + [@name="name"] + [@value="10"] + [contains(.., "%")] +' + ); + } + + public function testPercentNoSymbol() + { + $this->requiresFeatureSet(403); + + $form = $this->factory->createNamed('name', PercentType::class, 0.1, ['symbol' => false, 'rounding_mode' => \NumberFormatter::ROUND_CEILING]); + $this->assertWidgetMatchesXpath($form->createView(), [], + '/input + [@type="text"] + [@name="name"] + [@value="10"] + [not(contains(.., "%"))] +' + ); + } + + public function testPercentCustomSymbol() + { + $this->requiresFeatureSet(403); + + $form = $this->factory->createNamed('name', PercentType::class, 0.1, ['symbol' => '‱', 'rounding_mode' => \NumberFormatter::ROUND_CEILING]); + $this->assertWidgetMatchesXpath($form->createView(), [], + '/input + [@type="text"] + [@name="name"] + [@value="10"] + [contains(.., "‱")] +' + ); + } + + public function testCheckedRadio() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\RadioType', true); + + $this->assertWidgetMatchesXpath($form->createView(), [], + '/input + [@type="radio"] + [@name="name"] + [@checked="checked"] + [@value="1"] +' + ); + } + + public function testUncheckedRadio() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\RadioType', false); + + $this->assertWidgetMatchesXpath($form->createView(), [], + '/input + [@type="radio"] + [@name="name"] + [not(@checked)] +' + ); + } + + public function testRadioWithValue() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\RadioType', false, [ + 'value' => 'foo&bar', + ]); + + $this->assertWidgetMatchesXpath($form->createView(), [], + '/input + [@type="radio"] + [@name="name"] + [@value="foo&bar"] +' + ); + } + + public function testRange() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\RangeType', 42, ['attr' => ['min' => 5]]); + + $this->assertWidgetMatchesXpath($form->createView(), [], + '/input + [@type="range"] + [@name="name"] + [@value="42"] + [@min="5"] +' + ); + } + + public function testRangeWithMinMaxValues() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\RangeType', 42, ['attr' => ['min' => 5, 'max' => 57]]); + + $this->assertWidgetMatchesXpath($form->createView(), [], + '/input + [@type="range"] + [@name="name"] + [@value="42"] + [@min="5"] + [@max="57"] +' + ); + } + + public function testTextarea() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\TextareaType', 'foo&bar', [ + 'attr' => ['pattern' => 'foo'], + ]); + + $this->assertWidgetMatchesXpath($form->createView(), [], + '/textarea + [@name="name"] + [not(@pattern)] + [.="foo&bar"] +' + ); + } + + public function testText() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\TextType', 'foo&bar'); + + $this->assertWidgetMatchesXpath($form->createView(), [], + '/input + [@type="text"] + [@name="name"] + [@value="foo&bar"] + [not(@maxlength)] +' + ); + } + + public function testTextWithMaxLength() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\TextType', 'foo&bar', [ + 'attr' => ['maxlength' => 123], + ]); + + $this->assertWidgetMatchesXpath($form->createView(), [], + '/input + [@type="text"] + [@name="name"] + [@value="foo&bar"] + [@maxlength="123"] +' + ); + } + + public function testSearch() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\SearchType', 'foo&bar'); + + $this->assertWidgetMatchesXpath($form->createView(), [], + '/input + [@type="search"] + [@name="name"] + [@value="foo&bar"] + [not(@maxlength)] +' + ); + } + + public function testTime() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\TimeType', '04:05:06', [ + 'input' => 'string', + 'with_seconds' => false, + 'widget' => 'choice', + ]); + + $this->assertWidgetMatchesXpath($form->createView(), [], + '/div + [ + ./select + [@id="name_hour"] + [not(@size)] + [./option[@value="4"][@selected="selected"]] + /following-sibling::select + [@id="name_minute"] + [not(@size)] + [./option[@value="5"][@selected="selected"]] + ] + [count(./select)=2] +' + ); + } + + public function testTimeWithSeconds() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\TimeType', '04:05:06', [ + 'input' => 'string', + 'with_seconds' => true, + 'widget' => 'choice', + ]); + + $this->assertWidgetMatchesXpath($form->createView(), [], + '/div + [ + ./select + [@id="name_hour"] + [not(@size)] + [./option[@value="4"][@selected="selected"]] + [count(./option)>23] + /following-sibling::select + [@id="name_minute"] + [not(@size)] + [./option[@value="5"][@selected="selected"]] + [count(./option)>59] + /following-sibling::select + [@id="name_second"] + [not(@size)] + [./option[@value="6"][@selected="selected"]] + [count(./option)>59] + ] + [count(./select)=3] +' + ); + } + + public function testTimeText() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\TimeType', '04:05:06', [ + 'input' => 'string', + 'widget' => 'text', + ]); + + $this->assertWidgetMatchesXpath($form->createView(), [], + '/div + [ + ./input + [@type="text"] + [@id="name_hour"] + [@name="name[hour]"] + [@value="04"] + [@size="1"] + [@required="required"] + /following-sibling::input + [@type="text"] + [@id="name_minute"] + [@name="name[minute]"] + [@value="05"] + [@size="1"] + [@required="required"] + ] + [count(./input)=2] +' + ); + } + + public function testTimeSingleText() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\TimeType', '04:05:06', [ + 'input' => 'string', + 'widget' => 'single_text', + ]); + + $this->assertWidgetMatchesXpath($form->createView(), [], + '/input + [@type="time"] + [@name="name"] + [@value="04:05"] + [not(@size)] +' + ); + } + + public function testTimeWithPlaceholderGlobal() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\TimeType', null, [ + 'input' => 'string', + 'placeholder' => 'Change&Me', + 'required' => false, + 'widget' => 'choice', + ]); + + $this->assertWidgetMatchesXpath($form->createView(), [], + '/div + [ + ./select + [@id="name_hour"] + [./option[@value=""][not(@selected)][not(@disabled)][.="[trans]Change&Me[/trans]"]] + [count(./option)>24] + /following-sibling::select + [@id="name_minute"] + [./option[@value=""][not(@selected)][not(@disabled)][.="[trans]Change&Me[/trans]"]] + [count(./option)>60] + ] + [count(./select)=2] +' + ); + } + + public function testTimeWithPlaceholderOnYear() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\TimeType', null, [ + 'input' => 'string', + 'required' => false, + 'placeholder' => ['hour' => 'Change&Me'], + 'widget' => 'choice', + ]); + + $this->assertWidgetMatchesXpath($form->createView(), [], + '/div + [ + ./select + [@id="name_hour"] + [./option[@value=""][not(@selected)][not(@disabled)][.="[trans]Change&Me[/trans]"]] + [count(./option)>24] + /following-sibling::select + [@id="name_minute"] + [./option[@value="1"]] + [count(./option)>59] + ] + [count(./select)=2] +' + ); + } + + public function testTimeErrorBubbling() + { + $form = $this->factory->createNamedBuilder('form', 'Symfony\Component\Form\Extension\Core\Type\FormType') + ->add('time', 'Symfony\Component\Form\Extension\Core\Type\TimeType', ['widget' => 'choice']) + ->getForm(); + $form->get('time')->addError(new FormError('[trans]Error![/trans]')); + $view = $form->createView(); + + $this->assertEmpty($this->renderErrors($view)); + $this->assertNotEmpty($this->renderErrors($view['time'])); + } + + public function testTimezone() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\TimezoneType', 'Europe/Vienna'); + + $this->assertWidgetMatchesXpath($form->createView(), [], + '/select + [@name="name"] + [not(@required)] + [./option[@value="Europe/Vienna"][@selected="selected"][.="Europe / Vienna"]] + [count(./option)>200] +' + ); + } + + public function testTimezoneWithPlaceholder() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\TimezoneType', null, [ + 'placeholder' => 'Select&Timezone', + 'required' => false, + ]); + + $this->assertWidgetMatchesXpath($form->createView(), [], + '/select + [./option[@value=""][not(@selected)][not(@disabled)][.="[trans]Select&Timezone[/trans]"]] + [count(./option)>201] +' + ); + } + + public function testUrlWithDefaultProtocol() + { + $url = 'http://www.example.com?foo1=bar1&foo2=bar2'; + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\UrlType', $url, ['default_protocol' => 'http']); + + $this->assertWidgetMatchesXpath($form->createView(), [], + '/input + [@type="text"] + [@name="name"] + [@value="http://www.example.com?foo1=bar1&foo2=bar2"] + [@inputmode="url"] +' + ); + } + + public function testUrlWithoutDefaultProtocol() + { + $url = 'http://www.example.com?foo1=bar1&foo2=bar2'; + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\UrlType', $url, ['default_protocol' => null]); + + $this->assertWidgetMatchesXpath($form->createView(), [], + '/input + [@type="url"] + [@name="name"] + [@value="http://www.example.com?foo1=bar1&foo2=bar2"] +' + ); + } + + public function testCollectionPrototype() + { + $form = $this->factory->createNamedBuilder('name', 'Symfony\Component\Form\Extension\Core\Type\FormType', ['items' => ['one', 'two', 'three']]) + ->add('items', 'Symfony\Component\Form\Extension\Core\Type\CollectionType', ['allow_add' => true]) + ->getForm() + ->createView(); + + $html = $this->renderWidget($form); + + $this->assertMatchesXpath($html, + '//div[@id="name_items"][@data-prototype] + | + //table[@id="name_items"][@data-prototype]' + ); + } + + public function testEmptyRootFormName() + { + $form = $this->factory->createNamedBuilder('', 'Symfony\Component\Form\Extension\Core\Type\FormType') + ->add('child', 'Symfony\Component\Form\Extension\Core\Type\TextType') + ->getForm(); + + $this->assertMatchesXpath($this->renderWidget($form->createView()), + '//input[@type="hidden"][@id="_token"][@name="_token"] + | + //input[@type="text"][@id="child"][@name="child"]', 2); + } + + public function testButton() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ButtonType'); + + $this->assertWidgetMatchesXpath($form->createView(), [], + '/button[@type="button"][@name="name"][.="[trans]Name[/trans]"]' + ); + } + + public function testButtonLabelIsEmpty() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ButtonType'); + + $this->assertSame('', $this->renderLabel($form->createView())); + } + + public function testButtonlabelWithoutTranslation() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ButtonType', null, [ + 'translation_domain' => false, + ]); + + $this->assertWidgetMatchesXpath($form->createView(), [], + '/button[@type="button"][@name="name"][.="Name"]' + ); + } + + public function testSubmit() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\SubmitType'); + + $this->assertWidgetMatchesXpath($form->createView(), [], + '/button[@type="submit"][@name="name"]' + ); + } + + public function testReset() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ResetType'); + + $this->assertWidgetMatchesXpath($form->createView(), [], + '/button[@type="reset"][@name="name"]' + ); + } + + public function testStartTag() + { + $form = $this->factory->create('Symfony\Component\Form\Extension\Core\Type\FormType', null, [ + 'method' => 'get', + 'action' => 'http://example.com/directory', + ]); + + $html = $this->renderStart($form->createView()); + + $this->assertSame('
', $html); + } + + public function testStartTagForPutRequest() + { + $form = $this->factory->create('Symfony\Component\Form\Extension\Core\Type\FormType', null, [ + 'method' => 'put', + 'action' => 'http://example.com/directory', + ]); + + $html = $this->renderStart($form->createView()); + + $this->assertMatchesXpath($html.'
', + '/form + [./input[@type="hidden"][@name="_method"][@value="PUT"]] + [@method="post"] + [@action="http://example.com/directory"]' + ); + } + + public function testStartTagWithOverriddenVars() + { + $form = $this->factory->create('Symfony\Component\Form\Extension\Core\Type\FormType', null, [ + 'method' => 'put', + 'action' => 'http://example.com/directory', + ]); + + $html = $this->renderStart($form->createView(), [ + 'method' => 'post', + 'action' => 'http://foo.com/directory', + ]); + + $this->assertSame('
', $html); + } + + public function testStartTagForMultipartForm() + { + $form = $this->factory->createBuilder('Symfony\Component\Form\Extension\Core\Type\FormType', null, [ + 'method' => 'get', + 'action' => 'http://example.com/directory', + ]) + ->add('file', 'Symfony\Component\Form\Extension\Core\Type\FileType') + ->getForm(); + + $html = $this->renderStart($form->createView()); + + $this->assertSame('', $html); + } + + public function testStartTagWithExtraAttributes() + { + $form = $this->factory->create('Symfony\Component\Form\Extension\Core\Type\FormType', null, [ + 'method' => 'get', + 'action' => 'http://example.com/directory', + ]); + + $html = $this->renderStart($form->createView(), [ + 'attr' => ['class' => 'foobar'], + ]); + + $this->assertSame('', $html); + } + + public function testWidgetAttributes() + { + $form = $this->factory->createNamed('text', 'Symfony\Component\Form\Extension\Core\Type\TextType', 'value', [ + 'required' => true, + 'disabled' => true, + 'attr' => ['readonly' => true, 'maxlength' => 10, 'pattern' => '\d+', 'class' => 'foobar', 'data-foo' => 'bar'], + ]); + + $html = $this->renderWidget($form->createView()); + + // compare plain HTML to check the whitespace + $this->assertSame('', $html); + } + + public function testWidgetAttributeNameRepeatedIfTrue() + { + $form = $this->factory->createNamed('text', 'Symfony\Component\Form\Extension\Core\Type\TextType', 'value', [ + 'attr' => ['foo' => true], + ]); + + $html = $this->renderWidget($form->createView()); + + // foo="foo" + $this->assertSame('', $html); + } + + public function testWidgetAttributeHiddenIfFalse() + { + $form = $this->factory->createNamed('text', 'Symfony\Component\Form\Extension\Core\Type\TextType', 'value', [ + 'attr' => ['foo' => false], + ]); + + $html = $this->renderWidget($form->createView()); + + $this->assertStringNotContainsString('foo="', $html); + } + + public function testButtonAttributes() + { + $form = $this->factory->createNamed('button', 'Symfony\Component\Form\Extension\Core\Type\ButtonType', null, [ + 'disabled' => true, + 'attr' => ['class' => 'foobar', 'data-foo' => 'bar'], + ]); + + $html = $this->renderWidget($form->createView()); + + // compare plain HTML to check the whitespace + $this->assertSame('', $html); + } + + public function testButtonAttributeNameRepeatedIfTrue() + { + $form = $this->factory->createNamed('button', 'Symfony\Component\Form\Extension\Core\Type\ButtonType', null, [ + 'attr' => ['foo' => true], + ]); + + $html = $this->renderWidget($form->createView()); + + // foo="foo" + $this->assertSame('', $html); + } + + public function testButtonAttributeHiddenIfFalse() + { + $form = $this->factory->createNamed('button', 'Symfony\Component\Form\Extension\Core\Type\ButtonType', null, [ + 'attr' => ['foo' => false], + ]); + + $html = $this->renderWidget($form->createView()); + + $this->assertStringNotContainsString('foo="', $html); + } + + public function testTextareaWithWhitespaceOnlyContentRetainsValue() + { + $form = $this->factory->createNamed('textarea', 'Symfony\Component\Form\Extension\Core\Type\TextareaType', ' '); + + $html = $this->renderWidget($form->createView()); + + $this->assertStringContainsString('> ', $html); + } + + public function testTextareaWithWhitespaceOnlyContentRetainsValueWhenRenderingForm() + { + $form = $this->factory->createBuilder('Symfony\Component\Form\Extension\Core\Type\FormType', ['textarea' => ' ']) + ->add('textarea', 'Symfony\Component\Form\Extension\Core\Type\TextareaType') + ->getForm(); + + $html = $this->renderForm($form->createView()); + + $this->assertStringContainsString('> ', $html); + } + + public function testWidgetContainerAttributeHiddenIfFalse() + { + $form = $this->factory->createNamed('form', 'Symfony\Component\Form\Extension\Core\Type\FormType', null, [ + 'attr' => ['foo' => false], + ]); + + $html = $this->renderWidget($form->createView()); + + // no foo + $this->assertStringNotContainsString('foo="', $html); + } + + public function testTranslatedAttributes() + { + $view = $this->factory->createNamedBuilder('name', 'Symfony\Component\Form\Extension\Core\Type\FormType') + ->add('firstName', 'Symfony\Component\Form\Extension\Core\Type\TextType', ['attr' => ['title' => 'Foo']]) + ->add('lastName', 'Symfony\Component\Form\Extension\Core\Type\TextType', ['attr' => ['placeholder' => 'Bar']]) + ->getForm() + ->createView(); + + $html = $this->renderForm($view); + + $this->assertMatchesXpath($html, '/form//input[@title="[trans]Foo[/trans]"]'); + $this->assertMatchesXpath($html, '/form//input[@placeholder="[trans]Bar[/trans]"]'); + } + + public function testAttributesNotTranslatedWhenTranslationDomainIsFalse() + { + $view = $this->factory->createNamedBuilder('name', 'Symfony\Component\Form\Extension\Core\Type\FormType', null, [ + 'translation_domain' => false, + ]) + ->add('firstName', 'Symfony\Component\Form\Extension\Core\Type\TextType', ['attr' => ['title' => 'Foo']]) + ->add('lastName', 'Symfony\Component\Form\Extension\Core\Type\TextType', ['attr' => ['placeholder' => 'Bar']]) + ->getForm() + ->createView(); + + $html = $this->renderForm($view); + + $this->assertMatchesXpath($html, '/form//input[@title="Foo"]'); + $this->assertMatchesXpath($html, '/form//input[@placeholder="Bar"]'); + } + + public function testTel() + { + $tel = '0102030405'; + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\TelType', $tel); + + $this->assertWidgetMatchesXpath($form->createView(), [], + '/input + [@type="tel"] + [@name="name"] + [@value="0102030405"] +' + ); + } + + public function testColor() + { + $color = '#0000ff'; + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ColorType', $color); + + $this->assertWidgetMatchesXpath($form->createView(), [], + '/input + [@type="color"] + [@name="name"] + [@value="#0000ff"] +' + ); + } + + public function testLabelWithTranslationParameters() + { + $this->requiresFeatureSet(403); + + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\TextType'); + $html = $this->renderLabel($form->createView(), 'Address is %address%', [ + 'label_translation_parameters' => [ + '%address%' => 'Paris, rue de la Paix', + ], + ]); + + $this->assertMatchesXpath($html, + '/label + [@for="name"] + [.="[trans]Address is Paris, rue de la Paix[/trans]"] +' + ); + } + + public function testHelpWithTranslationParameters() + { + $this->requiresFeatureSet(403); + + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\TextType', null, [ + 'help' => 'for company %company%', + 'help_translation_parameters' => [ + '%company%' => 'ACME Ltd.', + ], + ]); + $html = $this->renderHelp($form->createView()); + + $this->assertMatchesXpath($html, + '/* + [@id="name_help"] + [.="[trans]for company ACME Ltd.[/trans]"] +' + ); + } + + public function testLabelWithTranslatableMessage() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\TextType', null, [ + 'label' => new TranslatableMessage('foo'), + ]); + $html = $this->renderLabel($form->createView()); + + $this->assertMatchesXpath($html, + '/label + [@for="name"] + [.="[trans]foo[/trans]"] +' + ); + } + + public function testHelpWithTranslatableMessage() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\TextType', null, [ + 'help' => new TranslatableMessage('foo'), + ]); + $html = $this->renderHelp($form->createView()); + + $this->assertMatchesXpath($html, + '/* + [@id="name_help"] + [.="[trans]foo[/trans]"] +' + ); + } + + public function testHelpWithTranslatableInterface() + { + $message = new class() implements TranslatableInterface { + public function trans(TranslatorInterface $translator, string $locale = null): string + { + return $translator->trans('foo'); + } + }; + + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\TextType', null, [ + 'help' => $message, + ]); + $html = $this->renderHelp($form->createView()); + + $this->assertMatchesXpath($html, + '/* + [@id="name_help"] + [.="[trans]foo[/trans]"] +' + ); + } + + public function testAttributesWithTranslationParameters() + { + $this->requiresFeatureSet(403); + + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\TextType', null, [ + 'attr' => [ + 'title' => 'Message to %company%', + 'placeholder' => 'Enter a message to %company%', + ], + 'attr_translation_parameters' => [ + '%company%' => 'ACME Ltd.', + ], + ]); + $html = $this->renderWidget($form->createView()); + + $this->assertMatchesXpath($html, + '/input + [@title="[trans]Message to ACME Ltd.[/trans]"] + [@placeholder="[trans]Enter a message to ACME Ltd.[/trans]"] +' + ); + } + + public function testButtonWithTranslationParameters() + { + $this->requiresFeatureSet(403); + + $form = $this->factory->createNamedBuilder('myform') + ->add('mybutton', 'Symfony\Component\Form\Extension\Core\Type\ButtonType', [ + 'label' => 'Submit to %company%', + 'label_translation_parameters' => [ + '%company%' => 'ACME Ltd.', + ], + ]) + ->getForm(); + $view = $form->get('mybutton')->createView(); + $html = $this->renderWidget($view, ['label_format' => 'form.%name%']); + + $this->assertMatchesXpath($html, + '/button + [.="[trans]Submit to ACME Ltd.[/trans]"] +' + ); + } + + /** + * @dataProvider submitFormNoValidateProvider + */ + public function testSubmitFormNoValidate(bool $validate) + { + $this->requiresFeatureSet(404); + + $form = $this->factory->create(SubmitType::class, null, [ + 'validate' => $validate, + ]); + + $html = $this->renderWidget($form->createView()); + + $xpath = '/button + [@type="submit"] + '; + + if (!$validate) { + $xpath .= '[@formnovalidate="formnovalidate"]'; + } else { + $xpath .= '[not(@formnovalidate="formnovalidate")]'; + } + + $this->assertMatchesXpath($html, $xpath); + } + + public static function submitFormNoValidateProvider() + { + return [ + [false], + [true], + ]; + } + + public function testWeekSingleText() + { + $this->requiresFeatureSet(404); + + $form = $this->factory->createNamed('holidays', 'Symfony\Component\Form\Extension\Core\Type\WeekType', '1970-W01', [ + 'input' => 'string', + 'widget' => 'single_text', + ]); + + $this->assertWidgetMatchesXpath($form->createView(), ['attr' => ['class' => 'my&class']], + '/input + [@type="week"] + [@name="holidays"] + [@class="my&class"] + [@value="1970-W01"] +' + ); + } + + public function testWeekSingleTextNoHtml5() + { + $this->requiresFeatureSet(404); + + $form = $this->factory->createNamed('holidays', 'Symfony\Component\Form\Extension\Core\Type\WeekType', '1970-W01', [ + 'input' => 'string', + 'widget' => 'single_text', + 'html5' => false, + ]); + + $this->assertWidgetMatchesXpath($form->createView(), ['attr' => ['class' => 'my&class']], + '/input + [@type="text"] + [@name="holidays"] + [@class="my&class"] + [@value="1970-W01"] +' + ); + } + + public function testWeekChoices() + { + $this->requiresFeatureSet(404); + + $data = ['year' => (int) date('Y'), 'week' => 1]; + + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\WeekType', $data, [ + 'input' => 'array', + 'widget' => 'choice', + ]); + + $this->assertWidgetMatchesXpath($form->createView(), ['attr' => ['class' => 'my&class']], + '/div + [@class="my&class"] + [ + ./select + [@id="name_year"] + [./option[@value="'.$data['year'].'"][@selected="selected"]] + /following-sibling::select + [@id="name_week"] + [./option[@value="'.$data['week'].'"][@selected="selected"]] + ] + [count(.//select)=2]' + ); + } + + public function testWeekText() + { + $this->requiresFeatureSet(404); + + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\WeekType', '2000-W01', [ + 'input' => 'string', + 'widget' => 'text', + ]); + + $this->assertWidgetMatchesXpath($form->createView(), ['attr' => ['class' => 'my&class']], + '/div + [@class="my&class"] + [ + ./input + [@id="name_year"] + [@type="number"] + [@value="2000"] + /following-sibling::input + [@id="name_week"] + [@type="number"] + [@value="1"] + ] + [count(./input)=2]' + ); + } +} diff --git a/Tests/Extension/AbstractTableLayoutTestCase.php b/Tests/Extension/AbstractTableLayoutTestCase.php new file mode 100644 index 00000000..c3cdb08e --- /dev/null +++ b/Tests/Extension/AbstractTableLayoutTestCase.php @@ -0,0 +1,536 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\Tests\Extension; + +use Symfony\Component\Form\FormError; +use Symfony\Component\Security\Csrf\CsrfToken; + +abstract class AbstractTableLayoutTestCase extends AbstractLayoutTestCase +{ + public function testRow() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\TextType'); + $form->addError(new FormError('[trans]Error![/trans]')); + $view = $form->createView(); + $html = $this->renderRow($view); + + $this->assertMatchesXpath($html, + '/tr + [ + ./td + [./label[@for="name"]] + /following-sibling::td + [ + ./ul + [./li[.="[trans]Error![/trans]"]] + [count(./li)=1] + /following-sibling::input[@id="name"] + ] + ] +' + ); + } + + public function testLabelIsNotRenderedWhenSetToFalse() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\TextType', null, [ + 'label' => false, + ]); + $html = $this->renderRow($form->createView()); + + $this->assertMatchesXpath($html, + '/tr + [ + ./td + [count(//label)=0] + /following-sibling::td + [./input[@id="name"]] + ] +' + ); + } + + public function testRepeatedRow() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\RepeatedType'); + $html = $this->renderRow($form->createView()); + + $this->assertMatchesXpath($html, + '/tr + [ + ./td + [./label[@for="name_first"]] + /following-sibling::td + [./input[@id="name_first"]] + ] +/following-sibling::tr + [ + ./td + [./label[@for="name_second"]] + /following-sibling::td + [./input[@id="name_second"]] + ] +/following-sibling::tr[@style="display: none"] + [./td[@colspan="2"]/input + [@type="hidden"] + [@id="name__token"] + ] + [count(../tr)=3] +' + ); + } + + public function testRepeatedRowWithErrors() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\RepeatedType'); + $form->addError(new FormError('[trans]Error![/trans]')); + $view = $form->createView(); + $html = $this->renderRow($view); + + // The errors of the form are not rendered by intention! + // In practice, repeated fields cannot have errors as all errors + // on them are mapped to the first child. + // (see RepeatedTypeValidatorExtension) + + $this->assertMatchesXpath($html, + '/tr + [ + ./td + [./label[@for="name_first"]] + /following-sibling::td + [./input[@id="name_first"]] + ] +/following-sibling::tr + [ + ./td + [./label[@for="name_second"]] + /following-sibling::td + [./input[@id="name_second"]] + ] +/following-sibling::tr[@style="display: none"] + [./td[@colspan="2"]/input + [@type="hidden"] + [@id="name__token"] + ] + [count(../tr)=3] +' + ); + } + + public function testButtonRow() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ButtonType'); + $view = $form->createView(); + $html = $this->renderRow($view); + + $this->assertMatchesXpath($html, + '/tr + [ + ./td + [.=""] + /following-sibling::td + [./button[@type="button"][@name="name"]] + ] + [count(//label)=0] +' + ); + } + + public function testRest() + { + $view = $this->factory->createNamedBuilder('name', 'Symfony\Component\Form\Extension\Core\Type\FormType') + ->add('field1', 'Symfony\Component\Form\Extension\Core\Type\TextType') + ->add('field2', 'Symfony\Component\Form\Extension\Core\Type\RepeatedType') + ->add('field3', 'Symfony\Component\Form\Extension\Core\Type\TextType') + ->add('field4', 'Symfony\Component\Form\Extension\Core\Type\TextType') + ->getForm() + ->createView(); + + // Render field2 row -> does not implicitly call renderWidget because + // it is a repeated field! + $this->renderRow($view['field2']); + + // Render field3 widget + $this->renderWidget($view['field3']); + + // Rest should only contain field1 and field4 + $html = $this->renderRest($view); + + $this->assertMatchesXpath($html, + '/tr + [ + ./td + [./label[@for="name_field1"]] + /following-sibling::td + [./input[@id="name_field1"]] + ] +/following-sibling::tr + [ + ./td + [./label[@for="name_field4"]] + /following-sibling::td + [./input[@id="name_field4"]] + ] + [count(../tr)=3] + [count(..//label)=2] + [count(..//input)=3] +/following-sibling::tr[@style="display: none"] + [./td[@colspan="2"]/input + [@type="hidden"] + [@id="name__token"] + ] +' + ); + } + + public function testCollection() + { + $form = $this->factory->createNamed('names', 'Symfony\Component\Form\Extension\Core\Type\CollectionType', ['a', 'b'], [ + 'entry_type' => 'Symfony\Component\Form\Extension\Core\Type\TextType', + ]); + + $this->assertWidgetMatchesXpath($form->createView(), [], + '/table + [ + ./tr[./td/input[@type="text"][@value="a"]] + /following-sibling::tr[./td/input[@type="text"][@value="b"]] + /following-sibling::tr[@style="display: none"][./td[@colspan="2"]/input[@type="hidden"][@id="names__token"]] + ] + [count(./tr[./td/input])=3] +' + ); + } + + public function testEmptyCollection() + { + $form = $this->factory->createNamed('names', 'Symfony\Component\Form\Extension\Core\Type\CollectionType', [], [ + 'entry_type' => 'Symfony\Component\Form\Extension\Core\Type\TextType', + ]); + + $this->assertWidgetMatchesXpath($form->createView(), [], + '/table + [./tr[@style="display: none"][./td[@colspan="2"]/input[@type="hidden"][@id="names__token"]]] + [count(./tr[./td/input])=1] +' + ); + } + + public function testForm() + { + $view = $this->factory->createNamedBuilder('name', 'Symfony\Component\Form\Extension\Core\Type\FormType') + ->setMethod('PUT') + ->setAction('http://example.com') + ->add('firstName', 'Symfony\Component\Form\Extension\Core\Type\TextType') + ->add('lastName', 'Symfony\Component\Form\Extension\Core\Type\TextType') + ->getForm() + ->createView(); + + $html = $this->renderForm($view, [ + 'id' => 'my&id', + 'attr' => ['class' => 'my&class'], + ]); + + $this->assertMatchesXpath($html, + '/form + [ + ./input[@type="hidden"][@name="_method"][@value="PUT"] + /following-sibling::table + [ + ./tr + [ + ./td + [./label[@for="name_firstName"]] + /following-sibling::td + [./input[@id="name_firstName"]] + ] + /following-sibling::tr + [ + ./td + [./label[@for="name_lastName"]] + /following-sibling::td + [./input[@id="name_lastName"]] + ] + /following-sibling::tr[@style="display: none"] + [./td[@colspan="2"]/input + [@type="hidden"] + [@id="name__token"] + ] + ] + [count(.//input)=3] + [@id="my&id"] + [@class="my&class"] + ] + [@method="post"] + [@action="http://example.com"] + [@class="my&class"] +' + ); + } + + public function testFormWidget() + { + $view = $this->factory->createNamedBuilder('name', 'Symfony\Component\Form\Extension\Core\Type\FormType') + ->add('firstName', 'Symfony\Component\Form\Extension\Core\Type\TextType') + ->add('lastName', 'Symfony\Component\Form\Extension\Core\Type\TextType') + ->getForm() + ->createView(); + + $this->assertWidgetMatchesXpath($view, [], + '/table + [ + ./tr + [ + ./td + [./label[@for="name_firstName"]] + /following-sibling::td + [./input[@id="name_firstName"]] + ] + /following-sibling::tr + [ + ./td + [./label[@for="name_lastName"]] + /following-sibling::td + [./input[@id="name_lastName"]] + ] + /following-sibling::tr[@style="display: none"] + [./td[@colspan="2"]/input + [@type="hidden"] + [@id="name__token"] + ] + ] + [count(.//input)=3] +' + ); + } + + // https://github.com/symfony/symfony/issues/2308 + public function testNestedFormError() + { + $form = $this->factory->createNamedBuilder('name', 'Symfony\Component\Form\Extension\Core\Type\FormType') + ->add($this->factory + ->createNamedBuilder('child', 'Symfony\Component\Form\Extension\Core\Type\FormType', null, ['error_bubbling' => false]) + ->add('grandChild', 'Symfony\Component\Form\Extension\Core\Type\FormType') + ) + ->getForm(); + + $form->get('child')->addError(new FormError('[trans]Error![/trans]')); + + $this->assertWidgetMatchesXpath($form->createView(), [], + '/table + [ + ./tr/td/ul[./li[.="[trans]Error![/trans]"]] + /following-sibling::table[@id="name_child"] + ] + [count(.//li[.="[trans]Error![/trans]"])=1] +' + ); + } + + public function testCsrf() + { + $this->csrfTokenManager->expects($this->any()) + ->method('getToken') + ->willReturn(new CsrfToken('token_id', 'foo&bar')); + + $form = $this->factory->createNamedBuilder('name', 'Symfony\Component\Form\Extension\Core\Type\FormType') + ->add($this->factory + // No CSRF protection on nested forms + ->createNamedBuilder('child', 'Symfony\Component\Form\Extension\Core\Type\FormType') + ->add($this->factory->createNamedBuilder('grandchild', 'Symfony\Component\Form\Extension\Core\Type\TextType')) + ) + ->getForm(); + + $this->assertWidgetMatchesXpath($form->createView(), [], + '/table + [ + ./tr[@style="display: none"] + [./td[@colspan="2"]/input + [@type="hidden"] + [@id="name__token"] + ] + ] + [count(.//input[@type="hidden"])=1] +' + ); + } + + public function testRepeated() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\RepeatedType', 'foobar', [ + 'type' => 'Symfony\Component\Form\Extension\Core\Type\TextType', + ]); + + $this->assertWidgetMatchesXpath($form->createView(), [], + '/table + [ + ./tr + [ + ./td + [./label[@for="name_first"]] + /following-sibling::td + [./input[@type="text"][@id="name_first"]] + ] + /following-sibling::tr + [ + ./td + [./label[@for="name_second"]] + /following-sibling::td + [./input[@type="text"][@id="name_second"]] + ] + /following-sibling::tr[@style="display: none"] + [./td[@colspan="2"]/input + [@type="hidden"] + [@id="name__token"] + ] + ] + [count(.//input)=3] +' + ); + } + + public function testRepeatedWithCustomOptions() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\RepeatedType', 'foobar', [ + 'type' => 'Symfony\Component\Form\Extension\Core\Type\PasswordType', + 'first_options' => ['label' => 'Test', 'required' => false], + 'second_options' => ['label' => 'Test2'], + ]); + + $this->assertWidgetMatchesXpath($form->createView(), [], + '/table + [ + ./tr + [ + ./td + [./label[@for="name_first"][.="[trans]Test[/trans]"]] + /following-sibling::td + [./input[@type="password"][@id="name_first"][@required="required"]] + ] + /following-sibling::tr + [ + ./td + [./label[@for="name_second"][.="[trans]Test2[/trans]"]] + /following-sibling::td + [./input[@type="password"][@id="name_second"][@required="required"]] + ] + /following-sibling::tr[@style="display: none"] + [./td[@colspan="2"]/input + [@type="hidden"] + [@id="name__token"] + ] + ] + [count(.//input)=3] +' + ); + } + + /** + * The block "_name_child_label" should be overridden in the theme of the + * implemented driver. + */ + public function testCollectionRowWithCustomBlock() + { + $collection = ['one', 'two', 'three']; + $form = $this->factory->createNamedBuilder('names', 'Symfony\Component\Form\Extension\Core\Type\CollectionType', $collection) + ->getForm(); + + $this->assertWidgetMatchesXpath($form->createView(), [], + '/table + [ + ./tr[./td/label[.="Custom label: [trans]0[/trans]"]] + /following-sibling::tr[./td/label[.="Custom label: [trans]1[/trans]"]] + /following-sibling::tr[./td/label[.="Custom label: [trans]2[/trans]"]] + ] +' + ); + } + + public function testFormEndWithRest() + { + $view = $this->factory->createNamedBuilder('name', 'Symfony\Component\Form\Extension\Core\Type\FormType') + ->add('field1', 'Symfony\Component\Form\Extension\Core\Type\TextType') + ->add('field2', 'Symfony\Component\Form\Extension\Core\Type\TextType') + ->getForm() + ->createView(); + + $this->renderWidget($view['field1']); + + // Rest should only contain field2 + $html = $this->renderEnd($view); + + // Insert the start tag, the end tag should be rendered by the helper + // Unfortunately this is not valid HTML, because the surrounding table + // tag is missing. If someone renders a form with table layout + // manually, they should call form_rest() explicitly within the + // tag. + $this->assertMatchesXpath(''.$html, + '/form + [ + ./tr + [ + ./td + [./label[@for="name_field2"]] + /following-sibling::td + [./input[@id="name_field2"]] + ] + /following-sibling::tr[@style="display: none"] + [./td[@colspan="2"]/input + [@type="hidden"] + [@id="name__token"] + ] + ] +' + ); + } + + public function testFormEndWithoutRest() + { + $view = $this->factory->createNamedBuilder('name', 'Symfony\Component\Form\Extension\Core\Type\FormType') + ->add('field1', 'Symfony\Component\Form\Extension\Core\Type\TextType') + ->add('field2', 'Symfony\Component\Form\Extension\Core\Type\TextType') + ->getForm() + ->createView(); + + $this->renderWidget($view['field1']); + + // Rest should only contain field2, but isn't rendered + $html = $this->renderEnd($view, ['render_rest' => false]); + + $this->assertEquals('', $html); + } + + public function testWidgetContainerAttributes() + { + $form = $this->factory->createNamed('form', 'Symfony\Component\Form\Extension\Core\Type\FormType', null, [ + 'attr' => ['class' => 'foobar', 'data-foo' => 'bar'], + ]); + + $form->add('text', 'Symfony\Component\Form\Extension\Core\Type\TextType'); + + $html = $this->renderWidget($form->createView()); + + // compare plain HTML to check the whitespace + $this->assertStringContainsString('
', $html); + } + + public function testWidgetContainerAttributeNameRepeatedIfTrue() + { + $form = $this->factory->createNamed('form', 'Symfony\Component\Form\Extension\Core\Type\FormType', null, [ + 'attr' => ['foo' => true], + ]); + + $html = $this->renderWidget($form->createView()); + + // foo="foo" + $this->assertStringContainsString('
', $html); + } +} diff --git a/Tests/Extension/FormExtensionDivLayoutTest.php b/Tests/Extension/FormExtensionDivLayoutTest.php index 9fa6a013..19efa945 100644 --- a/Tests/Extension/FormExtensionDivLayoutTest.php +++ b/Tests/Extension/FormExtensionDivLayoutTest.php @@ -18,7 +18,6 @@ use Symfony\Component\Form\ChoiceList\View\ChoiceView; use Symfony\Component\Form\FormRenderer; use Symfony\Component\Form\FormView; -use Symfony\Component\Form\Tests\AbstractDivLayoutTestCase; use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; use Twig\Environment; use Twig\Loader\FilesystemLoader; diff --git a/Tests/Extension/FormExtensionTableLayoutTest.php b/Tests/Extension/FormExtensionTableLayoutTest.php index c0a7a9e8..c94f9b5c 100644 --- a/Tests/Extension/FormExtensionTableLayoutTest.php +++ b/Tests/Extension/FormExtensionTableLayoutTest.php @@ -17,7 +17,6 @@ use Symfony\Bridge\Twig\Tests\Extension\Fixtures\StubTranslator; use Symfony\Component\Form\FormRenderer; use Symfony\Component\Form\FormView; -use Symfony\Component\Form\Tests\AbstractTableLayoutTestCase; use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; use Twig\Environment; use Twig\Loader\FilesystemLoader; From e32cbed3aac2221629e51e7697291997953f7cc2 Mon Sep 17 00:00:00 2001 From: Wouter de Jong Date: Sat, 22 Apr 2023 23:52:28 +0200 Subject: [PATCH 075/167] Add remaining missing return types to safe methods --- Command/DebugCommand.php | 2 +- Command/LintCommand.php | 8 ++++---- Extension/WorkflowExtension.php | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Command/DebugCommand.php b/Command/DebugCommand.php index fe581b59..43e4d9c9 100644 --- a/Command/DebugCommand.php +++ b/Command/DebugCommand.php @@ -305,7 +305,7 @@ private function getLoaderPaths(string $name = null): array return $loaderPaths; } - private function getMetadata(string $type, mixed $entity) + private function getMetadata(string $type, mixed $entity): mixed { if ('globals' === $type) { return $entity; diff --git a/Command/LintCommand.php b/Command/LintCommand.php index fa8effc1..e059740a 100644 --- a/Command/LintCommand.php +++ b/Command/LintCommand.php @@ -143,7 +143,7 @@ private function getFilesInfo(array $filenames): array return $filesInfo; } - protected function findFiles(string $filename) + protected function findFiles(string $filename): iterable { if (is_file($filename)) { return [$filename]; @@ -172,7 +172,7 @@ private function validate(string $template, string $file): array return ['template' => $template, 'file' => $file, 'valid' => true]; } - private function display(InputInterface $input, OutputInterface $output, SymfonyStyle $io, array $files) + private function display(InputInterface $input, OutputInterface $output, SymfonyStyle $io, array $files): int { return match ($this->format) { 'txt' => $this->displayTxt($output, $io, $files), @@ -205,7 +205,7 @@ private function displayTxt(OutputInterface $output, SymfonyStyle $io, array $fi return min($errors, 1); } - private function displayJson(OutputInterface $output, array $filesInfo) + private function displayJson(OutputInterface $output, array $filesInfo): int { $errors = 0; @@ -224,7 +224,7 @@ private function displayJson(OutputInterface $output, array $filesInfo) return min($errors, 1); } - private function renderException(SymfonyStyle $output, string $template, Error $exception, string $file = null, GithubActionReporter $githubReporter = null) + private function renderException(SymfonyStyle $output, string $template, Error $exception, string $file = null, GithubActionReporter $githubReporter = null): void { $line = $exception->getTemplateLine(); diff --git a/Extension/WorkflowExtension.php b/Extension/WorkflowExtension.php index 71f556ae..661a063f 100644 --- a/Extension/WorkflowExtension.php +++ b/Extension/WorkflowExtension.php @@ -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) + public function getMetadata(object $subject, string $key, string|Transition $metadataSubject = null, string $name = null): mixed { return $this ->workflowRegistry From 44c7677619c163fb7ee968b1d68d68c7d3a5f172 Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Thu, 20 Apr 2023 21:00:42 -0400 Subject: [PATCH 076/167] [Asset] [AssetMapper] New AssetMapper component: Map assets to publicly available, versioned paths --- Extension/ImportMapExtension.php | 28 +++++++++++++ Extension/ImportMapRuntime.php | 29 +++++++++++++ Tests/Extension/ImportMapExtensionTest.php | 49 ++++++++++++++++++++++ composer.json | 1 + 4 files changed, 107 insertions(+) create mode 100644 Extension/ImportMapExtension.php create mode 100644 Extension/ImportMapRuntime.php create mode 100644 Tests/Extension/ImportMapExtensionTest.php diff --git a/Extension/ImportMapExtension.php b/Extension/ImportMapExtension.php new file mode 100644 index 00000000..2156c74d --- /dev/null +++ b/Extension/ImportMapExtension.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\Extension; + +use Twig\Extension\AbstractExtension; +use Twig\TwigFunction; + +/** + * @author Kévin Dunglas + */ +final class ImportMapExtension extends AbstractExtension +{ + public function getFunctions(): array + { + return [ + new TwigFunction('importmap', [ImportMapRuntime::class, 'importmap'], ['is_safe' => ['html']]), + ]; + } +} diff --git a/Extension/ImportMapRuntime.php b/Extension/ImportMapRuntime.php new file mode 100644 index 00000000..aa5097d3 --- /dev/null +++ b/Extension/ImportMapRuntime.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\Extension; + +use Symfony\Component\AssetMapper\ImportMap\ImportMapRenderer; + +/** + * @author Kévin Dunglas + */ +class ImportMapRuntime +{ + public function __construct(private readonly ImportMapRenderer $importMapRenderer) + { + } + + public function importmap(?string $entryPoint = 'app'): string + { + return $this->importMapRenderer->render($entryPoint); + } +} diff --git a/Tests/Extension/ImportMapExtensionTest.php b/Tests/Extension/ImportMapExtensionTest.php new file mode 100644 index 00000000..26a572e1 --- /dev/null +++ b/Tests/Extension/ImportMapExtensionTest.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 Extension; + +use PHPUnit\Framework\TestCase; +use Symfony\Bridge\Twig\Extension\ImportMapExtension; +use Symfony\Bridge\Twig\Extension\ImportMapRuntime; +use Symfony\Component\AssetMapper\ImportMap\ImportMapRenderer; +use Twig\Environment; +use Twig\Loader\ArrayLoader; +use Twig\RuntimeLoader\RuntimeLoaderInterface; + +class ImportMapExtensionTest extends TestCase +{ + public function testItRendersTheImportmap() + { + $twig = new Environment(new ArrayLoader([ + 'template' => '{{ importmap("application") }}', + ]), ['debug' => true, 'cache' => false, 'autoescape' => 'html', 'optimizations' => 0]); + $twig->addExtension(new ImportMapExtension()); + $importMapRenderer = $this->createMock(ImportMapRenderer::class); + $expected = ''; + $importMapRenderer->expects($this->once()) + ->method('render') + ->with('application') + ->willReturn($expected); + $runtime = new ImportMapRuntime($importMapRenderer); + + $mockRuntimeLoader = $this->createMock(RuntimeLoaderInterface::class); + $mockRuntimeLoader + ->method('load') + ->willReturnMap([ + [ImportMapRuntime::class, $runtime], + ]) + ; + $twig->addRuntimeLoader($mockRuntimeLoader); + + $this->assertSame($expected, $twig->render('template')); + } +} diff --git a/composer.json b/composer.json index 8cf462fe..cac49224 100644 --- a/composer.json +++ b/composer.json @@ -26,6 +26,7 @@ "league/html-to-markdown": "^5.0", "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", "symfony/asset": "^5.4|^6.0", + "symfony/asset-mapper": "^6.3", "symfony/dependency-injection": "^5.4|^6.0", "symfony/finder": "^5.4|^6.0", "symfony/form": "^6.3", From 9b8eaa0f5e5302ee04164eadd7ff07336a67cc25 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 23 May 2023 17:38:00 +0200 Subject: [PATCH 077/167] [6.4] Allow 7.0 deps --- composer.json | 46 +++++++++++++++++++++++----------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/composer.json b/composer.json index cac49224..abe6f4d7 100644 --- a/composer.json +++ b/composer.json @@ -25,31 +25,31 @@ "egulias/email-validator": "^2.1.10|^3|^4", "league/html-to-markdown": "^5.0", "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", - "symfony/asset": "^5.4|^6.0", - "symfony/asset-mapper": "^6.3", - "symfony/dependency-injection": "^5.4|^6.0", - "symfony/finder": "^5.4|^6.0", - "symfony/form": "^6.3", - "symfony/html-sanitizer": "^6.1", - "symfony/http-foundation": "^5.4|^6.0", - "symfony/http-kernel": "^6.2", - "symfony/intl": "^5.4|^6.0", - "symfony/mime": "^6.2", + "symfony/asset": "^5.4|^6.0|^7.0", + "symfony/asset-mapper": "^6.3|^7.0", + "symfony/dependency-injection": "^5.4|^6.0|^7.0", + "symfony/finder": "^5.4|^6.0|^7.0", + "symfony/form": "^6.3|^7.0", + "symfony/html-sanitizer": "^6.1|^7.0", + "symfony/http-foundation": "^5.4|^6.0|^7.0", + "symfony/http-kernel": "^6.2|^7.0", + "symfony/intl": "^5.4|^6.0|^7.0", + "symfony/mime": "^6.2|^7.0", "symfony/polyfill-intl-icu": "~1.0", - "symfony/property-info": "^5.4|^6.0", - "symfony/routing": "^5.4|^6.0", - "symfony/translation": "^6.1", - "symfony/yaml": "^5.4|^6.0", + "symfony/property-info": "^5.4|^6.0|^7.0", + "symfony/routing": "^5.4|^6.0|^7.0", + "symfony/translation": "^6.1|^7.0", + "symfony/yaml": "^5.4|^6.0|^7.0", "symfony/security-acl": "^2.8|^3.0", - "symfony/security-core": "^5.4|^6.0", - "symfony/security-csrf": "^5.4|^6.0", - "symfony/security-http": "^5.4|^6.0", - "symfony/serializer": "^6.2", - "symfony/stopwatch": "^5.4|^6.0", - "symfony/console": "^5.4|^6.0", - "symfony/expression-language": "^5.4|^6.0", - "symfony/web-link": "^5.4|^6.0", - "symfony/workflow": "^5.4|^6.0", + "symfony/security-core": "^5.4|^6.0|^7.0", + "symfony/security-csrf": "^5.4|^6.0|^7.0", + "symfony/security-http": "^5.4|^6.0|^7.0", + "symfony/serializer": "^6.2|^7.0", + "symfony/stopwatch": "^5.4|^6.0|^7.0", + "symfony/console": "^5.4|^6.0|^7.0", + "symfony/expression-language": "^5.4|^6.0|^7.0", + "symfony/web-link": "^5.4|^6.0|^7.0", + "symfony/workflow": "^5.4|^6.0|^7.0", "twig/cssinliner-extra": "^2.12|^3", "twig/inky-extra": "^2.12|^3", "twig/markdown-extra": "^2.12|^3" From 5023dba18855c7bf66abf5f3e8b57d8bca6653fc Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 23 May 2023 17:24:39 +0200 Subject: [PATCH 078/167] [7.0] Bump to PHP 8.2 minimum --- composer.json | 62 +++++++++++++++++++++++++-------------------------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/composer.json b/composer.json index abe6f4d7..1526ba21 100644 --- a/composer.json +++ b/composer.json @@ -16,7 +16,7 @@ } ], "require": { - "php": ">=8.1", + "php": ">=8.2", "symfony/translation-contracts": "^2.5|^3", "twig/twig": "^2.13|^3.0.4" }, @@ -25,31 +25,31 @@ "egulias/email-validator": "^2.1.10|^3|^4", "league/html-to-markdown": "^5.0", "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", - "symfony/asset": "^5.4|^6.0|^7.0", - "symfony/asset-mapper": "^6.3|^7.0", - "symfony/dependency-injection": "^5.4|^6.0|^7.0", - "symfony/finder": "^5.4|^6.0|^7.0", - "symfony/form": "^6.3|^7.0", - "symfony/html-sanitizer": "^6.1|^7.0", - "symfony/http-foundation": "^5.4|^6.0|^7.0", - "symfony/http-kernel": "^6.2|^7.0", - "symfony/intl": "^5.4|^6.0|^7.0", - "symfony/mime": "^6.2|^7.0", + "symfony/asset": "^6.4|^7.0", + "symfony/asset-mapper": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/finder": "^6.4|^7.0", + "symfony/form": "^6.4|^7.0", + "symfony/html-sanitizer": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/intl": "^6.4|^7.0", + "symfony/mime": "^6.4|^7.0", "symfony/polyfill-intl-icu": "~1.0", - "symfony/property-info": "^5.4|^6.0|^7.0", - "symfony/routing": "^5.4|^6.0|^7.0", - "symfony/translation": "^6.1|^7.0", - "symfony/yaml": "^5.4|^6.0|^7.0", + "symfony/property-info": "^6.4|^7.0", + "symfony/routing": "^6.4|^7.0", + "symfony/translation": "^6.4|^7.0", + "symfony/yaml": "^6.4|^7.0", "symfony/security-acl": "^2.8|^3.0", - "symfony/security-core": "^5.4|^6.0|^7.0", - "symfony/security-csrf": "^5.4|^6.0|^7.0", - "symfony/security-http": "^5.4|^6.0|^7.0", - "symfony/serializer": "^6.2|^7.0", - "symfony/stopwatch": "^5.4|^6.0|^7.0", - "symfony/console": "^5.4|^6.0|^7.0", - "symfony/expression-language": "^5.4|^6.0|^7.0", - "symfony/web-link": "^5.4|^6.0|^7.0", - "symfony/workflow": "^5.4|^6.0|^7.0", + "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/stopwatch": "^6.4|^7.0", + "symfony/console": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/web-link": "^6.4|^7.0", + "symfony/workflow": "^6.4|^7.0", "twig/cssinliner-extra": "^2.12|^3", "twig/inky-extra": "^2.12|^3", "twig/markdown-extra": "^2.12|^3" @@ -57,13 +57,13 @@ "conflict": { "phpdocumentor/reflection-docblock": "<3.2.2", "phpdocumentor/type-resolver": "<1.4.0", - "symfony/console": "<5.4", - "symfony/form": "<6.3", - "symfony/http-foundation": "<5.4", - "symfony/http-kernel": "<6.2", - "symfony/mime": "<6.2", - "symfony/translation": "<5.4", - "symfony/workflow": "<5.4" + "symfony/console": "<6.4", + "symfony/form": "<6.4", + "symfony/http-foundation": "<6.4", + "symfony/http-kernel": "<6.4", + "symfony/mime": "<6.4", + "symfony/translation": "<6.4", + "symfony/workflow": "<6.4" }, "autoload": { "psr-4": { "Symfony\\Bridge\\Twig\\": "" }, From 67a33c71062d7d931fe9a8cb7be79cca986a6c09 Mon Sep 17 00:00:00 2001 From: Uladzimir Tsykun Date: Mon, 29 May 2023 15:12:36 +0200 Subject: [PATCH 079/167] Fix unable to use asset mapper with CSP --- Extension/ImportMapRuntime.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Extension/ImportMapRuntime.php b/Extension/ImportMapRuntime.php index aa5097d3..aa68111b 100644 --- a/Extension/ImportMapRuntime.php +++ b/Extension/ImportMapRuntime.php @@ -22,8 +22,8 @@ public function __construct(private readonly ImportMapRenderer $importMapRendere { } - public function importmap(?string $entryPoint = 'app'): string + public function importmap(?string $entryPoint = 'app', array $attributes = []): string { - return $this->importMapRenderer->render($entryPoint); + return $this->importMapRenderer->render($entryPoint, $attributes); } } From 5122a4141a0f6440c2add62603d35e07d392ce38 Mon Sep 17 00:00:00 2001 From: seb-jean Date: Sun, 25 Jun 2023 22:16:13 +0200 Subject: [PATCH 080/167] Allows to change element for form_help block --- Resources/views/Form/form_div_layout.html.twig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Resources/views/Form/form_div_layout.html.twig b/Resources/views/Form/form_div_layout.html.twig index 48ef5583..02628b5a 100644 --- a/Resources/views/Form/form_div_layout.html.twig +++ b/Resources/views/Form/form_div_layout.html.twig @@ -329,9 +329,9 @@ {% block form_help -%} {%- if help is not empty -%} {%- set help_attr = help_attr|merge({class: (help_attr.class|default('') ~ ' help-text')|trim}) -%} -
+ <{{ element|default('div') }} id="{{ id }}_help"{% with { attr: help_attr } %}{{ block('attributes') }}{% endwith %}> {{- block('form_help_content') -}} -
+ {%- endif -%} {%- endblock form_help %} From 0f57f2ea5d3a9c2ff012b8caaf17ded5147206f7 Mon Sep 17 00:00:00 2001 From: Wouter de Jong Date: Thu, 29 Jun 2023 12:39:19 +0200 Subject: [PATCH 081/167] Add missing return types and enforce return types on all methods --- DataCollector/TwigDataCollector.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DataCollector/TwigDataCollector.php b/DataCollector/TwigDataCollector.php index e261a050..e25158af 100644 --- a/DataCollector/TwigDataCollector.php +++ b/DataCollector/TwigDataCollector.php @@ -134,7 +134,7 @@ public function getProfile(): Profile return $this->profile ??= unserialize($this->data['profile'], ['allowed_classes' => ['Twig_Profiler_Profile', Profile::class]]); } - private function getComputedData(string $index) + private function getComputedData(string $index): mixed { $this->computed ??= $this->computeData($this->getProfile()); From b8cf690109339e26cf804d7c839979863e270cab Mon Sep 17 00:00:00 2001 From: Wouter de Jong Date: Sun, 2 Jul 2023 13:49:44 +0200 Subject: [PATCH 082/167] [Bridges][Bundles] Convert to native return types --- AppVariable.php | 20 ++++---------------- Command/DebugCommand.php | 5 +---- Command/LintCommand.php | 5 +---- EventListener/TemplateAttributeListener.php | 5 +---- Form/TwigRendererEngine.php | 4 +--- Translation/TwigExtractor.php | 15 +++------------ 6 files changed, 11 insertions(+), 43 deletions(-) diff --git a/AppVariable.php b/AppVariable.php index 8bfaa0a2..50b53d2c 100644 --- a/AppVariable.php +++ b/AppVariable.php @@ -32,34 +32,22 @@ class AppVariable private bool $debug; private LocaleSwitcher $localeSwitcher; - /** - * @return void - */ - public function setTokenStorage(TokenStorageInterface $tokenStorage) + public function setTokenStorage(TokenStorageInterface $tokenStorage): void { $this->tokenStorage = $tokenStorage; } - /** - * @return void - */ - public function setRequestStack(RequestStack $requestStack) + public function setRequestStack(RequestStack $requestStack): void { $this->requestStack = $requestStack; } - /** - * @return void - */ - public function setEnvironment(string $environment) + public function setEnvironment(string $environment): void { $this->environment = $environment; } - /** - * @return void - */ - public function setDebug(bool $debug) + public function setDebug(bool $debug): void { $this->debug = $debug; } diff --git a/Command/DebugCommand.php b/Command/DebugCommand.php index 43e4d9c9..5d153c21 100644 --- a/Command/DebugCommand.php +++ b/Command/DebugCommand.php @@ -59,10 +59,7 @@ public function __construct(Environment $twig, string $projectDir = null, array $this->fileLinkFormatter = $fileLinkFormatter; } - /** - * @return void - */ - protected function configure() + protected function configure(): void { $this ->setDefinition([ diff --git a/Command/LintCommand.php b/Command/LintCommand.php index e059740a..77427920 100644 --- a/Command/LintCommand.php +++ b/Command/LintCommand.php @@ -48,10 +48,7 @@ public function __construct( parent::__construct(); } - /** - * @return void - */ - protected function configure() + protected function configure(): void { $this ->addOption('format', null, InputOption::VALUE_REQUIRED, sprintf('The output format ("%s")', implode('", "', $this->getAvailableFormatOptions()))) diff --git a/EventListener/TemplateAttributeListener.php b/EventListener/TemplateAttributeListener.php index ef0f9ba9..f5962deb 100644 --- a/EventListener/TemplateAttributeListener.php +++ b/EventListener/TemplateAttributeListener.php @@ -28,10 +28,7 @@ public function __construct( ) { } - /** - * @return void - */ - public function onKernelView(ViewEvent $event) + public function onKernelView(ViewEvent $event): void { $parameters = $event->getControllerResult(); diff --git a/Form/TwigRendererEngine.php b/Form/TwigRendererEngine.php index fbe8e0b3..d07e6e1c 100644 --- a/Form/TwigRendererEngine.php +++ b/Form/TwigRendererEngine.php @@ -132,10 +132,8 @@ protected function loadResourceForBlockName(string $cacheKey, FormView $view, st * to initialize the theme first. Any changes made to * this variable will be kept and be available upon * further calls to this method using the same theme. - * - * @return void */ - protected function loadResourcesFromTheme(string $cacheKey, mixed &$theme) + protected function loadResourcesFromTheme(string $cacheKey, mixed &$theme): void { if (!$theme instanceof Template) { $theme = $this->environment->load($theme)->unwrap(); diff --git a/Translation/TwigExtractor.php b/Translation/TwigExtractor.php index 2b44c5ef..8a911ea0 100644 --- a/Translation/TwigExtractor.php +++ b/Translation/TwigExtractor.php @@ -45,10 +45,7 @@ public function __construct(Environment $twig) $this->twig = $twig; } - /** - * @return void - */ - public function extract($resource, MessageCatalogue $catalogue) + public function extract($resource, MessageCatalogue $catalogue): void { foreach ($this->extractFiles($resource) as $file) { try { @@ -59,18 +56,12 @@ public function extract($resource, MessageCatalogue $catalogue) } } - /** - * @return void - */ - public function setPrefix(string $prefix) + public function setPrefix(string $prefix): void { $this->prefix = $prefix; } - /** - * @return void - */ - protected function extractTemplate(string $template, MessageCatalogue $catalogue) + protected function extractTemplate(string $template, MessageCatalogue $catalogue): void { $visitor = $this->twig->getExtension(TranslationExtension::class)->getTranslationNodeVisitor(); $visitor->enable(); From 86af1633ba973f92d79bea6bcb6b91c3c7b9b8b3 Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Fri, 14 Jul 2023 20:02:50 +0200 Subject: [PATCH 083/167] [Serializer] Deprecate annotations in favor of attributes --- Tests/Extension/Fixtures/SerializerModelFixture.php | 4 +--- Tests/Extension/SerializerExtensionTest.php | 3 +-- composer.json | 2 +- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/Tests/Extension/Fixtures/SerializerModelFixture.php b/Tests/Extension/Fixtures/SerializerModelFixture.php index 07493ea9..6c0cc210 100644 --- a/Tests/Extension/Fixtures/SerializerModelFixture.php +++ b/Tests/Extension/Fixtures/SerializerModelFixture.php @@ -9,9 +9,7 @@ */ class SerializerModelFixture { - /** - * @Groups({"read"}) - */ + #[Groups(['read'])] public $name = 'howdy'; public $title = 'fixture'; diff --git a/Tests/Extension/SerializerExtensionTest.php b/Tests/Extension/SerializerExtensionTest.php index 0c36c8c6..0115f867 100644 --- a/Tests/Extension/SerializerExtensionTest.php +++ b/Tests/Extension/SerializerExtensionTest.php @@ -11,7 +11,6 @@ namespace Symfony\Bridge\Twig\Tests\Extension; -use Doctrine\Common\Annotations\AnnotationReader; use PHPUnit\Framework\TestCase; use Symfony\Bridge\Twig\Extension\SerializerExtension; use Symfony\Bridge\Twig\Extension\SerializerRuntime; @@ -50,7 +49,7 @@ public static function serializerDataProvider(): \Generator private function getTwig(string $template): Environment { - $meta = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + $meta = new ClassMetadataFactory(new AnnotationLoader()); $runtime = new SerializerRuntime(new Serializer([new ObjectNormalizer($meta)], [new JsonEncoder(), new YamlEncoder()])); $mockRuntimeLoader = $this->createMock(RuntimeLoaderInterface::class); diff --git a/composer.json b/composer.json index abe6f4d7..36014a59 100644 --- a/composer.json +++ b/composer.json @@ -21,7 +21,6 @@ "twig/twig": "^2.13|^3.0.4" }, "require-dev": { - "doctrine/annotations": "^1.12|^2", "egulias/email-validator": "^2.1.10|^3|^4", "league/html-to-markdown": "^5.0", "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", @@ -62,6 +61,7 @@ "symfony/http-foundation": "<5.4", "symfony/http-kernel": "<6.2", "symfony/mime": "<6.2", + "symfony/serializer": "<6.2", "symfony/translation": "<5.4", "symfony/workflow": "<5.4" }, From 4209b6f5ffee9b5b3dbe5d1d6fdc2333dcda9647 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Fri, 21 Jul 2023 15:28:24 +0200 Subject: [PATCH 084/167] Use typed properties in tests as much as possible --- Tests/AppVariableTest.php | 5 +---- Tests/Command/LintCommandTest.php | 2 +- Tests/Extension/AbstractLayoutTestCase.php | 10 +++++----- ...mExtensionBootstrap3HorizontalLayoutTest.php | 7 ++----- .../FormExtensionBootstrap3LayoutTest.php | 5 +---- ...mExtensionBootstrap4HorizontalLayoutTest.php | 4 ++-- .../FormExtensionBootstrap4LayoutTest.php | 6 ++---- ...mExtensionBootstrap5HorizontalLayoutTest.php | 4 ++-- .../FormExtensionBootstrap5LayoutTest.php | 5 +---- Tests/Extension/FormExtensionDivLayoutTest.php | 5 +---- .../Extension/FormExtensionFieldHelpersTest.php | 17 +++-------------- .../Extension/FormExtensionTableLayoutTest.php | 5 +---- Tests/Extension/WebLinkExtensionTest.php | 11 ++--------- Tests/Extension/WorkflowExtensionTest.php | 12 ++++++------ .../TranslationDefaultDomainNodeVisitorTest.php | 4 ++-- 15 files changed, 32 insertions(+), 70 deletions(-) diff --git a/Tests/AppVariableTest.php b/Tests/AppVariableTest.php index 764eade4..6dc75cdd 100644 --- a/Tests/AppVariableTest.php +++ b/Tests/AppVariableTest.php @@ -24,10 +24,7 @@ class AppVariableTest extends TestCase { - /** - * @var AppVariable - */ - protected $appVariable; + protected AppVariable $appVariable; protected function setUp(): void { diff --git a/Tests/Command/LintCommandTest.php b/Tests/Command/LintCommandTest.php index 75203f9c..649e2a0d 100644 --- a/Tests/Command/LintCommandTest.php +++ b/Tests/Command/LintCommandTest.php @@ -24,7 +24,7 @@ class LintCommandTest extends TestCase { - private $files; + private array $files; public function testLintCorrectFile() { diff --git a/Tests/Extension/AbstractLayoutTestCase.php b/Tests/Extension/AbstractLayoutTestCase.php index af1a013a..da7b0b86 100644 --- a/Tests/Extension/AbstractLayoutTestCase.php +++ b/Tests/Extension/AbstractLayoutTestCase.php @@ -11,7 +11,7 @@ namespace Symfony\Bridge\Twig\Tests\Extension; -use PHPUnit\Framework\SkippedTestError; +use PHPUnit\Framework\MockObject\MockObject; use Symfony\Component\Form\Extension\Core\Type\PercentType; use Symfony\Component\Form\Extension\Core\Type\SubmitType; use Symfony\Component\Form\Extension\Csrf\CsrfExtension; @@ -28,9 +28,10 @@ abstract class AbstractLayoutTestCase extends FormIntegrationTestCase { use VersionAwareTest; - protected $csrfTokenManager; - protected $testableFeatures = []; - private $defaultLocale; + protected MockObject&CsrfTokenManagerInterface $csrfTokenManager; + protected array $testableFeatures = []; + + private string $defaultLocale; protected function setUp(): void { @@ -55,7 +56,6 @@ protected function getExtensions() protected function tearDown(): void { - $this->csrfTokenManager = null; \Locale::setDefault($this->defaultLocale); parent::tearDown(); diff --git a/Tests/Extension/FormExtensionBootstrap3HorizontalLayoutTest.php b/Tests/Extension/FormExtensionBootstrap3HorizontalLayoutTest.php index e746a267..2f5289ce 100644 --- a/Tests/Extension/FormExtensionBootstrap3HorizontalLayoutTest.php +++ b/Tests/Extension/FormExtensionBootstrap3HorizontalLayoutTest.php @@ -25,14 +25,11 @@ class FormExtensionBootstrap3HorizontalLayoutTest extends AbstractBootstrap3Hori { use RuntimeLoaderProvider; - protected $testableFeatures = [ + protected array $testableFeatures = [ 'choice_attr', ]; - /** - * @var FormRenderer - */ - private $renderer; + private FormRenderer $renderer; protected function setUp(): void { diff --git a/Tests/Extension/FormExtensionBootstrap3LayoutTest.php b/Tests/Extension/FormExtensionBootstrap3LayoutTest.php index 2b7e186c..2e7ade2c 100644 --- a/Tests/Extension/FormExtensionBootstrap3LayoutTest.php +++ b/Tests/Extension/FormExtensionBootstrap3LayoutTest.php @@ -25,10 +25,7 @@ class FormExtensionBootstrap3LayoutTest extends AbstractBootstrap3LayoutTestCase { use RuntimeLoaderProvider; - /** - * @var FormRenderer - */ - private $renderer; + private FormRenderer $renderer; protected function setUp(): void { diff --git a/Tests/Extension/FormExtensionBootstrap4HorizontalLayoutTest.php b/Tests/Extension/FormExtensionBootstrap4HorizontalLayoutTest.php index f95e6784..a7d6c04e 100644 --- a/Tests/Extension/FormExtensionBootstrap4HorizontalLayoutTest.php +++ b/Tests/Extension/FormExtensionBootstrap4HorizontalLayoutTest.php @@ -30,11 +30,11 @@ class FormExtensionBootstrap4HorizontalLayoutTest extends AbstractBootstrap4Hori { use RuntimeLoaderProvider; - protected $testableFeatures = [ + protected array $testableFeatures = [ 'choice_attr', ]; - private $renderer; + private FormRenderer $renderer; protected function setUp(): void { diff --git a/Tests/Extension/FormExtensionBootstrap4LayoutTest.php b/Tests/Extension/FormExtensionBootstrap4LayoutTest.php index 2d0ca9fa..ccf9d65c 100644 --- a/Tests/Extension/FormExtensionBootstrap4LayoutTest.php +++ b/Tests/Extension/FormExtensionBootstrap4LayoutTest.php @@ -29,10 +29,8 @@ class FormExtensionBootstrap4LayoutTest extends AbstractBootstrap4LayoutTestCase { use RuntimeLoaderProvider; - /** - * @var FormRenderer - */ - private $renderer; + + private FormRenderer $renderer; protected function setUp(): void { diff --git a/Tests/Extension/FormExtensionBootstrap5HorizontalLayoutTest.php b/Tests/Extension/FormExtensionBootstrap5HorizontalLayoutTest.php index d6d2c3c2..9682129e 100644 --- a/Tests/Extension/FormExtensionBootstrap5HorizontalLayoutTest.php +++ b/Tests/Extension/FormExtensionBootstrap5HorizontalLayoutTest.php @@ -30,11 +30,11 @@ class FormExtensionBootstrap5HorizontalLayoutTest extends AbstractBootstrap5Hori { use RuntimeLoaderProvider; - protected $testableFeatures = [ + protected array $testableFeatures = [ 'choice_attr', ]; - private $renderer; + private FormRenderer $renderer; protected function setUp(): void { diff --git a/Tests/Extension/FormExtensionBootstrap5LayoutTest.php b/Tests/Extension/FormExtensionBootstrap5LayoutTest.php index 94174615..63d2dfa4 100644 --- a/Tests/Extension/FormExtensionBootstrap5LayoutTest.php +++ b/Tests/Extension/FormExtensionBootstrap5LayoutTest.php @@ -32,10 +32,7 @@ class FormExtensionBootstrap5LayoutTest extends AbstractBootstrap5LayoutTestCase { use RuntimeLoaderProvider; - /** - * @var FormRenderer - */ - private $renderer; + private FormRenderer $renderer; protected function setUp(): void { diff --git a/Tests/Extension/FormExtensionDivLayoutTest.php b/Tests/Extension/FormExtensionDivLayoutTest.php index 19efa945..f18c03ce 100644 --- a/Tests/Extension/FormExtensionDivLayoutTest.php +++ b/Tests/Extension/FormExtensionDivLayoutTest.php @@ -26,10 +26,7 @@ class FormExtensionDivLayoutTest extends AbstractDivLayoutTestCase { use RuntimeLoaderProvider; - /** - * @var FormRenderer - */ - private $renderer; + private FormRenderer $renderer; protected function setUp(): void { diff --git a/Tests/Extension/FormExtensionFieldHelpersTest.php b/Tests/Extension/FormExtensionFieldHelpersTest.php index ce3ee926..8e2c0298 100644 --- a/Tests/Extension/FormExtensionFieldHelpersTest.php +++ b/Tests/Extension/FormExtensionFieldHelpersTest.php @@ -22,20 +22,9 @@ class FormExtensionFieldHelpersTest extends FormIntegrationTestCase { - /** - * @var FormExtension - */ - private $rawExtension; - - /** - * @var FormExtension - */ - private $translatorExtension; - - /** - * @var FormView - */ - private $view; + private FormExtension $rawExtension; + private FormExtension $translatorExtension; + private FormView $view; protected function getTypes() { diff --git a/Tests/Extension/FormExtensionTableLayoutTest.php b/Tests/Extension/FormExtensionTableLayoutTest.php index c94f9b5c..751d2b1f 100644 --- a/Tests/Extension/FormExtensionTableLayoutTest.php +++ b/Tests/Extension/FormExtensionTableLayoutTest.php @@ -25,10 +25,7 @@ class FormExtensionTableLayoutTest extends AbstractTableLayoutTestCase { use RuntimeLoaderProvider; - /** - * @var FormRenderer - */ - private $renderer; + private FormRenderer $renderer; protected function setUp(): void { diff --git a/Tests/Extension/WebLinkExtensionTest.php b/Tests/Extension/WebLinkExtensionTest.php index 1739c1ee..c8167077 100644 --- a/Tests/Extension/WebLinkExtensionTest.php +++ b/Tests/Extension/WebLinkExtensionTest.php @@ -22,15 +22,8 @@ */ class WebLinkExtensionTest extends TestCase { - /** - * @var Request - */ - private $request; - - /** - * @var WebLinkExtension - */ - private $extension; + private Request $request; + private WebLinkExtension $extension; protected function setUp(): void { diff --git a/Tests/Extension/WorkflowExtensionTest.php b/Tests/Extension/WorkflowExtensionTest.php index 1252efb8..8a16e5b2 100644 --- a/Tests/Extension/WorkflowExtensionTest.php +++ b/Tests/Extension/WorkflowExtensionTest.php @@ -25,8 +25,8 @@ class WorkflowExtensionTest extends TestCase { - private $extension; - private $t1; + private WorkflowExtension $extension; + private Transition $t1; protected function setUp(): void { @@ -127,19 +127,19 @@ public function testbuildTransitionBlockerList() final class Subject { - private $marking; + private array $marking; - public function __construct($marking = null) + public function __construct(array $marking = []) { $this->marking = $marking; } - public function getMarking() + public function getMarking(): array { return $this->marking; } - public function setMarking($marking) + public function setMarking($marking): void { $this->marking = $marking; } diff --git a/Tests/NodeVisitor/TranslationDefaultDomainNodeVisitorTest.php b/Tests/NodeVisitor/TranslationDefaultDomainNodeVisitorTest.php index 25b8166e..40063c6b 100644 --- a/Tests/NodeVisitor/TranslationDefaultDomainNodeVisitorTest.php +++ b/Tests/NodeVisitor/TranslationDefaultDomainNodeVisitorTest.php @@ -21,8 +21,8 @@ class TranslationDefaultDomainNodeVisitorTest extends TestCase { - private static $message = 'message'; - private static $domain = 'domain'; + private static string $message = 'message'; + private static string $domain = 'domain'; /** @dataProvider getDefaultDomainAssignmentTestData */ public function testDefaultDomainAssignment(Node $node) From 6728cefd0a707c7e60591903bcd0c4a36e7c19fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Pineau?= Date: Tue, 25 Jul 2023 10:34:42 +0200 Subject: [PATCH 085/167] Use more "First class callable syntax" + Normalize set_error_handler/set_exception_handler calls --- Extension/HttpKernelExtension.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Extension/HttpKernelExtension.php b/Extension/HttpKernelExtension.php index 072d890d..4859f4dd 100644 --- a/Extension/HttpKernelExtension.php +++ b/Extension/HttpKernelExtension.php @@ -28,7 +28,7 @@ public function getFunctions(): array new TwigFunction('render', [HttpKernelRuntime::class, 'renderFragment'], ['is_safe' => ['html']]), new TwigFunction('render_*', [HttpKernelRuntime::class, 'renderFragmentStrategy'], ['is_safe' => ['html']]), new TwigFunction('fragment_uri', [HttpKernelRuntime::class, 'generateFragmentUri']), - new TwigFunction('controller', static::class.'::controller'), + new TwigFunction('controller', [self::class, 'controller']), ]; } From dcb5bb80aade1571fb28a4a57a0a65100c1252bd Mon Sep 17 00:00:00 2001 From: Thomas Landauer Date: Wed, 28 Sep 2022 16:54:47 +0200 Subject: [PATCH 086/167] [Form] Removing self-closing slash from `` --- Resources/views/Form/form_div_layout.html.twig | 10 +++++----- Tests/Extension/AbstractBootstrap3LayoutTestCase.php | 4 ++-- Tests/Extension/AbstractLayoutTestCase.php | 7 +++++-- .../Extension/Fixtures/templates/form/theme.html.twig | 2 +- .../Fixtures/templates/form/theme_extends.html.twig | 2 +- .../Fixtures/templates/form/theme_use.html.twig | 2 +- Tests/Extension/FormExtensionBootstrap3LayoutTest.php | 2 +- Tests/Extension/FormExtensionBootstrap4LayoutTest.php | 2 +- Tests/Extension/FormExtensionBootstrap5LayoutTest.php | 2 +- Tests/Extension/FormExtensionDivLayoutTest.php | 2 +- 10 files changed, 19 insertions(+), 16 deletions(-) diff --git a/Resources/views/Form/form_div_layout.html.twig b/Resources/views/Form/form_div_layout.html.twig index 02628b5a..29cfc2dc 100644 --- a/Resources/views/Form/form_div_layout.html.twig +++ b/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 -%} @@ -91,11 +91,11 @@ {%- endblock choice_widget_options -%} {%- block checkbox_widget -%} - + {%- endblock checkbox_widget -%} {%- block radio_widget -%} - + {%- endblock radio_widget -%} {%- block datetime_widget -%} @@ -402,7 +402,7 @@ {%- endif -%}
{%- if form_method != method -%} - + {%- endif -%} {%- endblock form_start -%} @@ -440,7 +440,7 @@ {%- endif -%} {%- if form_method != method -%} - + {%- endif -%} {% endif -%} {% endblock form_rest %} diff --git a/Tests/Extension/AbstractBootstrap3LayoutTestCase.php b/Tests/Extension/AbstractBootstrap3LayoutTestCase.php index 7f4a77e2..0982b2e0 100644 --- a/Tests/Extension/AbstractBootstrap3LayoutTestCase.php +++ b/Tests/Extension/AbstractBootstrap3LayoutTestCase.php @@ -2772,7 +2772,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() @@ -2784,7 +2784,7 @@ public function testWidgetAttributeNameRepeatedIfTrue() $html = $this->renderWidget($form->createView()); // foo="foo" - $this->assertSame('', $html); + $this->assertSame('', $html); } public function testButtonAttributes() diff --git a/Tests/Extension/AbstractLayoutTestCase.php b/Tests/Extension/AbstractLayoutTestCase.php index da7b0b86..d9bab6cd 100644 --- a/Tests/Extension/AbstractLayoutTestCase.php +++ b/Tests/Extension/AbstractLayoutTestCase.php @@ -72,6 +72,9 @@ protected function assertXpathNodeValue(\DOMElement $element, $expression, $node protected function assertMatchesXpath($html, $expression, $count = 1) { $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 @@ -2511,7 +2514,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() @@ -2523,7 +2526,7 @@ public function testWidgetAttributeNameRepeatedIfTrue() $html = $this->renderWidget($form->createView()); // foo="foo" - $this->assertSame('', $html); + $this->assertSame('', $html); } public function testWidgetAttributeHiddenIfFalse() diff --git a/Tests/Extension/Fixtures/templates/form/theme.html.twig b/Tests/Extension/Fixtures/templates/form/theme.html.twig index e8816be9..3ec513a4 100644 --- a/Tests/Extension/Fixtures/templates/form/theme.html.twig +++ b/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/Tests/Extension/Fixtures/templates/form/theme_extends.html.twig b/Tests/Extension/Fixtures/templates/form/theme_extends.html.twig index 501b555e..2b9118a2 100644 --- a/Tests/Extension/Fixtures/templates/form/theme_extends.html.twig +++ b/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/Tests/Extension/Fixtures/templates/form/theme_use.html.twig b/Tests/Extension/Fixtures/templates/form/theme_use.html.twig index 37150734..e05de5ac 100644 --- a/Tests/Extension/Fixtures/templates/form/theme_use.html.twig +++ b/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/Tests/Extension/FormExtensionBootstrap3LayoutTest.php b/Tests/Extension/FormExtensionBootstrap3LayoutTest.php index 2e7ade2c..bb0f5687 100644 --- a/Tests/Extension/FormExtensionBootstrap3LayoutTest.php +++ b/Tests/Extension/FormExtensionBootstrap3LayoutTest.php @@ -97,7 +97,7 @@ public function testMoneyWidgetInIso() $this->assertSame(<<<'HTML'
-
+ HTML , trim($this->renderWidget($view))); } diff --git a/Tests/Extension/FormExtensionBootstrap4LayoutTest.php b/Tests/Extension/FormExtensionBootstrap4LayoutTest.php index ccf9d65c..3aecccf1 100644 --- a/Tests/Extension/FormExtensionBootstrap4LayoutTest.php +++ b/Tests/Extension/FormExtensionBootstrap4LayoutTest.php @@ -102,7 +102,7 @@ public function testMoneyWidgetInIso() $this->assertSame(<<<'HTML'
-
+ HTML , trim($this->renderWidget($view))); } diff --git a/Tests/Extension/FormExtensionBootstrap5LayoutTest.php b/Tests/Extension/FormExtensionBootstrap5LayoutTest.php index 63d2dfa4..66f6bbb9 100644 --- a/Tests/Extension/FormExtensionBootstrap5LayoutTest.php +++ b/Tests/Extension/FormExtensionBootstrap5LayoutTest.php @@ -101,7 +101,7 @@ public function testMoneyWidgetInIso() ->createView(); self::assertSame(<<<'HTML' -
+
HTML , trim($this->renderWidget($view))); } diff --git a/Tests/Extension/FormExtensionDivLayoutTest.php b/Tests/Extension/FormExtensionDivLayoutTest.php index f18c03ce..c9dec6e2 100644 --- a/Tests/Extension/FormExtensionDivLayoutTest.php +++ b/Tests/Extension/FormExtensionDivLayoutTest.php @@ -184,7 +184,7 @@ public function testMoneyWidgetInIso() ->createView() ; - $this->assertSame('€ ', $this->renderWidget($view)); + $this->assertSame('€ ', $this->renderWidget($view)); } public function testHelpAttr() From e5a2bb7b521eddad8012f3cf1bdffd6dd5ffe86d Mon Sep 17 00:00:00 2001 From: "d.huethorst" Date: Tue, 27 Jun 2023 12:23:58 +0200 Subject: [PATCH 087/167] [TwigBridge] Change return type of Symfony\Bridge\Twig\AppVariable::getSession() --- AppVariable.php | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/AppVariable.php b/AppVariable.php index 8bfaa0a2..abcbff92 100644 --- a/AppVariable.php +++ b/AppVariable.php @@ -13,7 +13,7 @@ use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\RequestStack; -use Symfony\Component\HttpFoundation\Session\Session; +use Symfony\Component\HttpFoundation\Session\SessionInterface; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\User\UserInterface; @@ -112,7 +112,7 @@ public function getRequest(): ?Request /** * Returns the current session. */ - public function getSession(): ?Session + public function getSession(): ?SessionInterface { if (!isset($this->requestStack)) { throw new \RuntimeException('The "app.session" variable is not available.'); @@ -171,6 +171,12 @@ public function getFlashes(string|array $types = null): array return []; } + // In 7.0 (when symfony/http-foundation: 6.4 is required) this can be updated to + // check if the session is an instance of FlashBagAwareSessionInterface + if (!method_exists($session, 'getFlashBag')) { + return []; + } + if (null === $types || '' === $types || [] === $types) { return $session->getFlashBag()->all(); } From 2d4b152678ffd142c05289fc6827c0c31c588291 Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Sun, 10 Sep 2023 17:11:44 +0200 Subject: [PATCH 088/167] [TwigBridge] Remove obsolete Workflow feature detection --- Tests/Extension/WorkflowExtensionTest.php | 25 ++++++++--------------- 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/Tests/Extension/WorkflowExtensionTest.php b/Tests/Extension/WorkflowExtensionTest.php index 8a16e5b2..21f9e663 100644 --- a/Tests/Extension/WorkflowExtensionTest.php +++ b/Tests/Extension/WorkflowExtensionTest.php @@ -17,7 +17,6 @@ use Symfony\Component\Workflow\MarkingStore\MethodMarkingStore; use Symfony\Component\Workflow\Metadata\InMemoryMetadataStore; use Symfony\Component\Workflow\Registry; -use Symfony\Component\Workflow\SupportStrategy\ClassInstanceSupportStrategy; use Symfony\Component\Workflow\SupportStrategy\InstanceOfSupportStrategy; use Symfony\Component\Workflow\Transition; use Symfony\Component\Workflow\TransitionBlockerList; @@ -36,25 +35,19 @@ protected function setUp(): void new Transition('t2', 'waiting_for_payment', 'processed'), ]; - $metadataStore = null; - if (class_exists(InMemoryMetadataStore::class)) { - $transitionsMetadata = new \SplObjectStorage(); - $transitionsMetadata->attach($this->t1, ['title' => 't1 title']); - $metadataStore = new InMemoryMetadataStore( - ['title' => 'workflow title'], - ['orderer' => ['title' => 'ordered title']], - $transitionsMetadata - ); - } + $transitionsMetadata = new \SplObjectStorage(); + $transitionsMetadata->attach($this->t1, ['title' => 't1 title']); + $metadataStore = new InMemoryMetadataStore( + ['title' => 'workflow title'], + ['orderer' => ['title' => 'ordered title']], + $transitionsMetadata + ); $definition = new Definition($places, $transitions, null, $metadataStore); $workflow = new Workflow($definition, new MethodMarkingStore()); $registry = new Registry(); - $addWorkflow = method_exists($registry, 'addWorkflow') ? 'addWorkflow' : 'add'; - $supportStrategy = class_exists(InstanceOfSupportStrategy::class) - ? new InstanceOfSupportStrategy(Subject::class) - : new ClassInstanceSupportStrategy(Subject::class); - $registry->$addWorkflow($workflow, $supportStrategy); + $supportStrategy = new InstanceOfSupportStrategy(Subject::class); + $registry->addWorkflow($workflow, $supportStrategy); $this->extension = new WorkflowExtension($registry); } From 31b234385e61f845e684587aa9d25dcf0efac328 Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Sun, 10 Sep 2023 19:55:52 +0200 Subject: [PATCH 089/167] [TwigBridge] Remove duck typing from AppVariable::getFlashes() --- AppVariable.php | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/AppVariable.php b/AppVariable.php index bc3f611a..9d589124 100644 --- a/AppVariable.php +++ b/AppVariable.php @@ -13,6 +13,7 @@ use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\HttpFoundation\Session\FlashBagAwareSessionInterface; use Symfony\Component\HttpFoundation\Session\SessionInterface; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; @@ -152,16 +153,12 @@ public function getLocale(): string public function getFlashes(string|array $types = null): array { try { - if (null === $session = $this->getSession()) { - return []; - } + $session = $this->getSession(); } catch (\RuntimeException) { return []; } - // In 7.0 (when symfony/http-foundation: 6.4 is required) this can be updated to - // check if the session is an instance of FlashBagAwareSessionInterface - if (!method_exists($session, 'getFlashBag')) { + if (!$session instanceof FlashBagAwareSessionInterface) { return []; } From 34e9d77f152a8035cfafb27756930d898595b0c8 Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Mon, 11 Sep 2023 14:26:06 +0200 Subject: [PATCH 090/167] Drop support for Twig 2 --- CHANGELOG.md | 5 +++++ composer.json | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9613d9a3..d8b45c13 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +7.0 +--- + + * Drop support for Twig 2 + 6.3 --- diff --git a/composer.json b/composer.json index 3522e9ff..ebbbea2a 100644 --- a/composer.json +++ b/composer.json @@ -18,7 +18,7 @@ "require": { "php": ">=8.2", "symfony/translation-contracts": "^2.5|^3", - "twig/twig": "^2.13|^3.0.4" + "twig/twig": "^3.0.4" }, "require-dev": { "egulias/email-validator": "^2.1.10|^3|^4", From 63e9221f5c5b832176a36babf4a1b9d61d67b314 Mon Sep 17 00:00:00 2001 From: Arnaud De Abreu Date: Tue, 11 Jul 2023 11:43:37 +0200 Subject: [PATCH 091/167] [Form] Add `duplicate_preferred_choices` option to `ChoiceType` --- .../AbstractBootstrap3LayoutTestCase.php | 25 +++++++++++++++++++ .../AbstractBootstrap5LayoutTestCase.php | 25 +++++++++++++++++++ composer.json | 2 +- 3 files changed, 51 insertions(+), 1 deletion(-) diff --git a/Tests/Extension/AbstractBootstrap3LayoutTestCase.php b/Tests/Extension/AbstractBootstrap3LayoutTestCase.php index 0982b2e0..5b02b695 100644 --- a/Tests/Extension/AbstractBootstrap3LayoutTestCase.php +++ b/Tests/Extension/AbstractBootstrap3LayoutTestCase.php @@ -576,6 +576,31 @@ public function testSingleChoiceWithPreferred() ); } + public function testSingleChoiceWithPreferredIsNotDuplicated() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', '&a', [ + 'choices' => ['Choice&A' => '&a', 'Choice&B' => '&b'], + 'preferred_choices' => ['&b'], + 'duplicate_preferred_choices' => false, + 'multiple' => false, + 'expanded' => false, + ]); + + $this->assertWidgetMatchesXpath($form->createView(), ['separator' => '-- sep --', 'attr' => ['class' => 'my&class']], + '/select + [@name="name"] + [@class="my&class form-control"] + [not(@required)] + [ + ./option[@value="&b"][not(@selected)][.="[trans]Choice&B[/trans]"] + /following-sibling::option[@disabled="disabled"][not(@selected)][.="-- sep --"] + /following-sibling::option[@value="&a"][@selected="selected"][.="[trans]Choice&A[/trans]"] + ] + [count(./option)=3] +' + ); + } + public function testSingleChoiceWithSelectedPreferred() { $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', '&a', [ diff --git a/Tests/Extension/AbstractBootstrap5LayoutTestCase.php b/Tests/Extension/AbstractBootstrap5LayoutTestCase.php index 630663a6..576f2b18 100644 --- a/Tests/Extension/AbstractBootstrap5LayoutTestCase.php +++ b/Tests/Extension/AbstractBootstrap5LayoutTestCase.php @@ -584,6 +584,31 @@ public function testSingleChoiceWithPreferred() ); } + public function testSingleChoiceWithPreferredIsNotDuplicated() + { + $form = $this->factory->createNamed('name', ChoiceType::class, '&a', [ + 'choices' => ['Choice&A' => '&a', 'Choice&B' => '&b'], + 'preferred_choices' => ['&b'], + 'duplicate_preferred_choices' => false, + 'multiple' => false, + 'expanded' => false, + ]); + + $this->assertWidgetMatchesXpath($form->createView(), ['separator' => '-- sep --', 'attr' => ['class' => 'my&class']], + '/select + [@name="name"] + [@class="my&class form-select"] + [not(@required)] + [ + ./option[@value="&b"][not(@selected)][.="[trans]Choice&B[/trans]"] + /following-sibling::option[@disabled="disabled"][not(@selected)][.="-- sep --"] + /following-sibling::option[@value="&a"][@selected="selected"][.="[trans]Choice&A[/trans]"] + ] + [count(./option)=3] +' + ); + } + public function testSingleChoiceWithSelectedPreferred() { $form = $this->factory->createNamed('name', ChoiceType::class, '&a', [ diff --git a/composer.json b/composer.json index 36014a59..160e1074 100644 --- a/composer.json +++ b/composer.json @@ -28,7 +28,7 @@ "symfony/asset-mapper": "^6.3|^7.0", "symfony/dependency-injection": "^5.4|^6.0|^7.0", "symfony/finder": "^5.4|^6.0|^7.0", - "symfony/form": "^6.3|^7.0", + "symfony/form": "^6.4|^7.0", "symfony/html-sanitizer": "^6.1|^7.0", "symfony/http-foundation": "^5.4|^6.0|^7.0", "symfony/http-kernel": "^6.2|^7.0", From 13f9985c4b2d7766ed803ab3fd8f6bacfade5b49 Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Thu, 1 Jun 2023 13:35:20 -0400 Subject: [PATCH 092/167] [AssetMapper] Add support for CSS files in the importmap --- CHANGELOG.md | 5 +++++ Extension/ImportMapRuntime.php | 6 +++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9613d9a3..bb342c44 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +6.4 +--- + +* Allow an array to be passed as the first argument to the `importmap()` Twig function + 6.3 --- diff --git a/Extension/ImportMapRuntime.php b/Extension/ImportMapRuntime.php index aa68111b..a6d3fbc7 100644 --- a/Extension/ImportMapRuntime.php +++ b/Extension/ImportMapRuntime.php @@ -22,8 +22,12 @@ public function __construct(private readonly ImportMapRenderer $importMapRendere { } - public function importmap(?string $entryPoint = 'app', array $attributes = []): string + public function importmap(string|array|null $entryPoint = 'app', array $attributes = []): string { + if (null === $entryPoint) { + trigger_deprecation('symfony/twig-bridge', '6.4', 'Passing null as the first argument of the "importmap" Twig function is deprecated, pass an empty array if no entrypoints are desired.'); + } + return $this->importMapRenderer->render($entryPoint, $attributes); } } From 635666925b35fe539933096ffeb67f6c4e15888d Mon Sep 17 00:00:00 2001 From: Alexander Schranz Date: Tue, 19 Sep 2023 00:02:41 +0200 Subject: [PATCH 093/167] [Mime] Add `TemplatedEmail::locale()` to set the locale for the email rendering --- CHANGELOG.md | 3 +- Mime/BodyRenderer.php | 53 ++++++++++++++++++++----------- Mime/TemplatedEmail.php | 16 ++++++++++ Tests/Mime/BodyRendererTest.php | 22 +++++++++++-- Tests/Mime/TemplatedEmailTest.php | 2 ++ 5 files changed, 74 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bb342c44..89f3b00a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,8 @@ CHANGELOG 6.4 --- -* Allow an array to be passed as the first argument to the `importmap()` Twig function + * Allow an array to be passed as the first argument to the `importmap()` Twig function + * Add `TemplatedEmail::locale()` to set the locale for the email rendering 6.3 --- diff --git a/Mime/BodyRenderer.php b/Mime/BodyRenderer.php index d418ee2f..18e0eb1f 100644 --- a/Mime/BodyRenderer.php +++ b/Mime/BodyRenderer.php @@ -18,6 +18,7 @@ use Symfony\Component\Mime\HtmlToTextConverter\HtmlToTextConverterInterface; use Symfony\Component\Mime\HtmlToTextConverter\LeagueHtmlToMarkdownConverter; use Symfony\Component\Mime\Message; +use Symfony\Component\Translation\LocaleSwitcher; use Twig\Environment; /** @@ -28,12 +29,14 @@ final class BodyRenderer implements BodyRendererInterface private Environment $twig; private array $context; private HtmlToTextConverterInterface $converter; + private ?LocaleSwitcher $localeSwitcher = null; - public function __construct(Environment $twig, array $context = [], HtmlToTextConverterInterface $converter = null) + public function __construct(Environment $twig, array $context = [], HtmlToTextConverterInterface $converter = null, LocaleSwitcher $localeSwitcher = null) { $this->twig = $twig; $this->context = $context; $this->converter = $converter ?: (interface_exists(HtmlConverterInterface::class) ? new LeagueHtmlToMarkdownConverter() : new DefaultHtmlToTextConverter()); + $this->localeSwitcher = $localeSwitcher; } public function render(Message $message): void @@ -47,30 +50,42 @@ public function render(Message $message): void return; } - $messageContext = $message->getContext(); + $callback = function () use ($message) { + $messageContext = $message->getContext(); - if (isset($messageContext['email'])) { - throw new InvalidArgumentException(sprintf('A "%s" context cannot have an "email" entry as this is a reserved variable.', get_debug_type($message))); - } + if (isset($messageContext['email'])) { + throw new InvalidArgumentException(sprintf('A "%s" context cannot have an "email" entry as this is a reserved variable.', get_debug_type($message))); + } - $vars = array_merge($this->context, $messageContext, [ - 'email' => new WrappedTemplatedEmail($this->twig, $message), - ]); + $vars = array_merge($this->context, $messageContext, [ + 'email' => new WrappedTemplatedEmail($this->twig, $message), + ]); - if ($template = $message->getTextTemplate()) { - $message->text($this->twig->render($template, $vars)); - } + if ($template = $message->getTextTemplate()) { + $message->text($this->twig->render($template, $vars)); + } - if ($template = $message->getHtmlTemplate()) { - $message->html($this->twig->render($template, $vars)); - } + if ($template = $message->getHtmlTemplate()) { + $message->html($this->twig->render($template, $vars)); + } + + $message->markAsRendered(); - $message->markAsRendered(); + // if text body is empty, compute one from the HTML body + if (!$message->getTextBody() && null !== $html = $message->getHtmlBody()) { + $text = $this->converter->convert(\is_resource($html) ? stream_get_contents($html) : $html, $message->getHtmlCharset()); + $message->text($text, $message->getHtmlCharset()); + } + }; - // if text body is empty, compute one from the HTML body - if (!$message->getTextBody() && null !== $html = $message->getHtmlBody()) { - $text = $this->converter->convert(\is_resource($html) ? stream_get_contents($html) : $html, $message->getHtmlCharset()); - $message->text($text, $message->getHtmlCharset()); + $locale = $message->getLocale(); + + if ($locale && $this->localeSwitcher) { + $this->localeSwitcher->runWithLocale($locale, $callback); + + return; } + + $callback(); } } diff --git a/Mime/TemplatedEmail.php b/Mime/TemplatedEmail.php index 777cc06b..e5c990f3 100644 --- a/Mime/TemplatedEmail.php +++ b/Mime/TemplatedEmail.php @@ -20,6 +20,7 @@ class TemplatedEmail extends Email { private ?string $htmlTemplate = null; private ?string $textTemplate = null; + private ?string $locale = null; private array $context = []; /** @@ -42,6 +43,16 @@ public function htmlTemplate(?string $template): static return $this; } + /** + * @return $this + */ + public function locale(?string $locale): static + { + $this->locale = $locale; + + return $this; + } + public function getTextTemplate(): ?string { return $this->textTemplate; @@ -52,6 +63,11 @@ public function getHtmlTemplate(): ?string return $this->htmlTemplate; } + public function getLocale(): ?string + { + return $this->locale; + } + /** * @return $this */ diff --git a/Tests/Mime/BodyRendererTest.php b/Tests/Mime/BodyRendererTest.php index 6af152da..ddc6f46d 100644 --- a/Tests/Mime/BodyRendererTest.php +++ b/Tests/Mime/BodyRendererTest.php @@ -18,8 +18,10 @@ use Symfony\Component\Mime\HtmlToTextConverter\DefaultHtmlToTextConverter; use Symfony\Component\Mime\HtmlToTextConverter\HtmlToTextConverterInterface; use Symfony\Component\Mime\Part\Multipart\AlternativePart; +use Symfony\Component\Translation\LocaleSwitcher; use Twig\Environment; use Twig\Loader\ArrayLoader; +use Twig\TwigFunction; class BodyRendererTest extends TestCase { @@ -131,7 +133,16 @@ public function testRenderedOnceUnserializableContext() $this->assertEquals('Text', $email->getTextBody()); } - private function prepareEmail(?string $text, ?string $html, array $context = [], HtmlToTextConverterInterface $converter = null): TemplatedEmail + public function testRenderWithLocale() + { + $localeSwitcher = new LocaleSwitcher('en', []); + $email = $this->prepareEmail(null, 'Locale: {{ locale_switcher_locale() }}', [], new DefaultHtmlToTextConverter(), $localeSwitcher, 'fr'); + + $this->assertEquals('Locale: fr', $email->getTextBody()); + $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 { $twig = new Environment(new ArrayLoader([ 'text' => $text, @@ -139,12 +150,19 @@ private function prepareEmail(?string $text, ?string $html, array $context = [], 'document.txt' => 'Some text document...', 'image.jpg' => 'Some image data', ])); - $renderer = new BodyRenderer($twig, [], $converter); + + if ($localeSwitcher instanceof LocaleSwitcher) { + $twig->addFunction(new TwigFunction('locale_switcher_locale', [$localeSwitcher, 'getLocale'])); + } + + $renderer = new BodyRenderer($twig, [], $converter, $localeSwitcher); $email = (new TemplatedEmail()) ->to('fabien@symfony.com') ->from('helene@symfony.com') + ->locale($locale) ->context($context) ; + if (null !== $text) { $email->textTemplate('text'); } diff --git a/Tests/Mime/TemplatedEmailTest.php b/Tests/Mime/TemplatedEmailTest.php index 019be16f..f796c7a0 100644 --- a/Tests/Mime/TemplatedEmailTest.php +++ b/Tests/Mime/TemplatedEmailTest.php @@ -58,6 +58,7 @@ public function testSymfonySerialize() $e->to('you@example.com'); $e->textTemplate('email.txt.twig'); $e->htmlTemplate('email.html.twig'); + $e->locale('en'); $e->context(['foo' => 'bar']); $e->addPart(new DataPart('Some Text file', 'test.txt')); $expected = clone $e; @@ -66,6 +67,7 @@ public function testSymfonySerialize() { "htmlTemplate": "email.html.twig", "textTemplate": "email.txt.twig", + "locale": "en", "context": { "foo": "bar" }, From 30a7b74dcd0854e9eedb1f1a2387a361ba1d671e Mon Sep 17 00:00:00 2001 From: jmsche Date: Mon, 15 May 2023 09:27:21 +0200 Subject: [PATCH 094/167] [TwigBridge] Add `AppVariable::getEnabledLocales()` to retrieve the enabled locales --- AppVariable.php | 15 +++++++++++++++ CHANGELOG.md | 1 + Tests/AppVariableTest.php | 14 ++++++++++++++ 3 files changed, 30 insertions(+) diff --git a/AppVariable.php b/AppVariable.php index abcbff92..ea178a27 100644 --- a/AppVariable.php +++ b/AppVariable.php @@ -31,6 +31,7 @@ class AppVariable private string $environment; private bool $debug; private LocaleSwitcher $localeSwitcher; + private array $enabledLocales; /** * @return void @@ -69,6 +70,11 @@ public function setLocaleSwitcher(LocaleSwitcher $localeSwitcher): void $this->localeSwitcher = $localeSwitcher; } + public function setEnabledLocales(array $enabledLocales): void + { + $this->enabledLocales = $enabledLocales; + } + /** * Returns the current token. * @@ -155,6 +161,15 @@ public function getLocale(): string return $this->localeSwitcher->getLocale(); } + public function getEnabled_locales(): array + { + if (!isset($this->enabledLocales)) { + throw new \RuntimeException('The "app.enabled_locales" variable is not available.'); + } + + return $this->enabledLocales; + } + /** * Returns some or all the existing flash messages: * * getFlashes() returns all the flash messages diff --git a/CHANGELOG.md b/CHANGELOG.md index 89f3b00a..4e8ed131 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ CHANGELOG * Allow an array to be passed as the first argument to the `importmap()` Twig function * Add `TemplatedEmail::locale()` to set the locale for the email rendering + * Add `AppVariable::getEnabledLocales()` to retrieve the enabled locales 6.3 --- diff --git a/Tests/AppVariableTest.php b/Tests/AppVariableTest.php index 6dc75cdd..d7561cc9 100644 --- a/Tests/AppVariableTest.php +++ b/Tests/AppVariableTest.php @@ -112,6 +112,13 @@ public function testGetLocale() self::assertEquals('fr', $this->appVariable->getLocale()); } + public function testGetEnabledLocales() + { + $this->appVariable->setEnabledLocales(['en', 'fr']); + + self::assertSame(['en', 'fr'], $this->appVariable->getEnabled_locales()); + } + public function testGetTokenWithNoToken() { $tokenStorage = $this->createMock(TokenStorageInterface::class); @@ -171,6 +178,13 @@ public function testGetLocaleWithLocaleSwitcherNotSet() $this->appVariable->getLocale(); } + public function testGetEnabledLocalesWithEnabledLocalesNotSet() + { + $this->expectException(\RuntimeException::class); + $this->expectExceptionMessage('The "app.enabled_locales" variable is not available.'); + $this->appVariable->getEnabled_locales(); + } + public function testGetFlashesWithNoRequest() { $this->setRequestStack(null); From de78b537041d87e31d988f9ee388bed42a6e4db6 Mon Sep 17 00:00:00 2001 From: Romain Monteil Date: Sun, 23 Apr 2023 12:32:42 +0200 Subject: [PATCH 095/167] [TwigBridge] Add FormLayoutTestCase class --- Test/FormLayoutTestCase.php | 150 ++++++++++++++++++ .../Traits}/RuntimeLoaderProvider.php | 2 +- Tests/Extension/AbstractLayoutTestCase.php | 70 +------- ...xtensionBootstrap3HorizontalLayoutTest.php | 87 ++-------- .../FormExtensionBootstrap3LayoutTest.php | 86 ++-------- ...xtensionBootstrap4HorizontalLayoutTest.php | 87 ++-------- .../FormExtensionBootstrap4LayoutTest.php | 86 ++-------- ...xtensionBootstrap5HorizontalLayoutTest.php | 87 ++-------- .../FormExtensionBootstrap5LayoutTest.php | 86 ++-------- .../Extension/FormExtensionDivLayoutTest.php | 99 +++--------- .../FormExtensionTableLayoutTest.php | 93 +++-------- Tests/Node/FormThemeTest.php | 2 +- 12 files changed, 282 insertions(+), 653 deletions(-) create mode 100644 Test/FormLayoutTestCase.php rename {Tests/Extension => Test/Traits}/RuntimeLoaderProvider.php (94%) diff --git a/Test/FormLayoutTestCase.php b/Test/FormLayoutTestCase.php new file mode 100644 index 00000000..71a71530 --- /dev/null +++ b/Test/FormLayoutTestCase.php @@ -0,0 +1,150 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\Test; + +use Symfony\Bridge\Twig\Form\TwigRendererEngine; +use Symfony\Bridge\Twig\Test\Traits\RuntimeLoaderProvider; +use Symfony\Component\Form\FormRenderer; +use Symfony\Component\Form\FormRendererInterface; +use Symfony\Component\Form\FormView; +use Symfony\Component\Form\Test\FormIntegrationTestCase; +use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; +use Twig\Environment; +use Twig\Loader\FilesystemLoader; + +/** + * @author Romain Monteil + */ +abstract class FormLayoutTestCase extends FormIntegrationTestCase +{ + use RuntimeLoaderProvider; + + protected FormRendererInterface $renderer; + + protected function setUp(): void + { + parent::setUp(); + + $loader = new FilesystemLoader($this->getTemplatePaths()); + + $environment = new Environment($loader, ['strict_variables' => true]); + $environment->setExtensions($this->getTwigExtensions()); + + foreach ($this->getTwigGlobals() as $name => $value) { + $environment->addGlobal($name, $value); + } + + $rendererEngine = new TwigRendererEngine($this->getThemes(), $environment); + $this->renderer = new FormRenderer($rendererEngine, $this->createMock(CsrfTokenManagerInterface::class)); + $this->registerTwigRuntimeLoader($environment, $this->renderer); + } + + 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 + $dom->loadXML(''.$html.''); + } catch (\Exception $e) { + $this->fail(sprintf( + "Failed loading HTML:\n\n%s\n\nError: %s", + $html, + $e->getMessage() + )); + } + $xpath = new \DOMXPath($dom); + $nodeList = $xpath->evaluate('/root'.$expression); + + if ($nodeList->length != $count) { + $dom->formatOutput = true; + $this->fail(sprintf( + "Failed asserting that \n\n%s\n\nmatches exactly %s. Matches %s in \n\n%s", + $expression, + 1 == $count ? 'once' : $count.' times', + 1 == $nodeList->length ? 'once' : $nodeList->length.' times', + // strip away and + substr($dom->saveHTML(), 6, -8) + )); + } else { + $this->addToAssertionCount(1); + } + } + + abstract protected function getTemplatePaths(): array; + + abstract protected function getTwigExtensions(): array; + + protected function getTwigGlobals(): array + { + return []; + } + + abstract protected function getThemes(): array; + + protected function renderForm(FormView $view, array $vars = []): string + { + return $this->renderer->renderBlock($view, 'form', $vars); + } + + protected function renderLabel(FormView $view, $label = null, array $vars = []): string + { + if (null !== $label) { + $vars += ['label' => $label]; + } + + return $this->renderer->searchAndRenderBlock($view, 'label', $vars); + } + + protected function renderHelp(FormView $view): string + { + return $this->renderer->searchAndRenderBlock($view, 'help'); + } + + protected function renderErrors(FormView $view): string + { + return $this->renderer->searchAndRenderBlock($view, 'errors'); + } + + protected function renderWidget(FormView $view, array $vars = []): string + { + return $this->renderer->searchAndRenderBlock($view, 'widget', $vars); + } + + protected function renderRow(FormView $view, array $vars = []): string + { + return $this->renderer->searchAndRenderBlock($view, 'row', $vars); + } + + protected function renderRest(FormView $view, array $vars = []): string + { + return $this->renderer->searchAndRenderBlock($view, 'rest', $vars); + } + + protected function renderStart(FormView $view, array $vars = []): string + { + return $this->renderer->renderBlock($view, 'form_start', $vars); + } + + protected function renderEnd(FormView $view, array $vars = []): string + { + return $this->renderer->renderBlock($view, 'form_end', $vars); + } + + protected function setTheme(FormView $view, array $themes, $useDefaultThemes = true): void + { + $this->renderer->setTheme($view, $themes, $useDefaultThemes); + } +} diff --git a/Tests/Extension/RuntimeLoaderProvider.php b/Test/Traits/RuntimeLoaderProvider.php similarity index 94% rename from Tests/Extension/RuntimeLoaderProvider.php rename to Test/Traits/RuntimeLoaderProvider.php index dea14819..1025288b 100644 --- a/Tests/Extension/RuntimeLoaderProvider.php +++ b/Test/Traits/RuntimeLoaderProvider.php @@ -9,7 +9,7 @@ * file that was distributed with this source code. */ -namespace Symfony\Bridge\Twig\Tests\Extension; +namespace Symfony\Bridge\Twig\Test\Traits; use Symfony\Component\Form\FormRenderer; use Twig\Environment; diff --git a/Tests/Extension/AbstractLayoutTestCase.php b/Tests/Extension/AbstractLayoutTestCase.php index d9bab6cd..fc9eff09 100644 --- a/Tests/Extension/AbstractLayoutTestCase.php +++ b/Tests/Extension/AbstractLayoutTestCase.php @@ -12,19 +12,19 @@ namespace Symfony\Bridge\Twig\Tests\Extension; use PHPUnit\Framework\MockObject\MockObject; +use Symfony\Bridge\Twig\Test\FormLayoutTestCase; use Symfony\Component\Form\Extension\Core\Type\PercentType; use Symfony\Component\Form\Extension\Core\Type\SubmitType; use Symfony\Component\Form\Extension\Csrf\CsrfExtension; use Symfony\Component\Form\FormError; use Symfony\Component\Form\FormView; -use Symfony\Component\Form\Test\FormIntegrationTestCase; use Symfony\Component\Form\Tests\VersionAwareTest; use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; use Symfony\Component\Translation\TranslatableMessage; use Symfony\Contracts\Translation\TranslatableInterface; use Symfony\Contracts\Translation\TranslatorInterface; -abstract class AbstractLayoutTestCase extends FormIntegrationTestCase +abstract class AbstractLayoutTestCase extends FormLayoutTestCase { use VersionAwareTest; @@ -61,49 +61,6 @@ protected function tearDown(): void parent::tearDown(); } - protected function assertXpathNodeValue(\DOMElement $element, $expression, $nodeValue) - { - $xpath = new \DOMXPath($element->ownerDocument); - $nodeList = $xpath->evaluate($expression); - $this->assertEquals(1, $nodeList->length); - $this->assertEquals($nodeValue, $nodeList->item(0)->nodeValue); - } - - protected function assertMatchesXpath($html, $expression, $count = 1) - { - $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 - $dom->loadXML(''.$html.''); - } catch (\Exception $e) { - $this->fail(sprintf( - "Failed loading HTML:\n\n%s\n\nError: %s", - $html, - $e->getMessage() - )); - } - $xpath = new \DOMXPath($dom); - $nodeList = $xpath->evaluate('/root'.$expression); - - if ($nodeList->length != $count) { - $dom->formatOutput = true; - $this->fail(sprintf( - "Failed asserting that \n\n%s\n\nmatches exactly %s. Matches %s in \n\n%s", - $expression, - 1 == $count ? 'once' : $count.' times', - 1 == $nodeList->length ? 'once' : $nodeList->length.' times', - // strip away and - substr($dom->saveHTML(), 6, -8) - )); - } else { - $this->addToAssertionCount(1); - } - } - protected function assertWidgetMatchesXpath(FormView $view, array $vars, $xpath) { // include ampersands everywhere to validate escaping @@ -125,29 +82,6 @@ protected function assertWidgetMatchesXpath(FormView $view, array $vars, $xpath) $this->assertMatchesXpath($html, $xpath); } - abstract protected function renderForm(FormView $view, array $vars = []); - - abstract protected function renderLabel(FormView $view, $label = null, array $vars = []); - - protected function renderHelp(FormView $view) - { - $this->markTestSkipped(sprintf('%s::renderHelp() is not implemented.', static::class)); - } - - abstract protected function renderErrors(FormView $view); - - abstract protected function renderWidget(FormView $view, array $vars = []); - - abstract protected function renderRow(FormView $view, array $vars = []); - - abstract protected function renderRest(FormView $view, array $vars = []); - - abstract protected function renderStart(FormView $view, array $vars = []); - - abstract protected function renderEnd(FormView $view, array $vars = []); - - abstract protected function setTheme(FormView $view, array $themes, $useDefaultThemes = true); - public function testLabel() { $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\TextType'); diff --git a/Tests/Extension/FormExtensionBootstrap3HorizontalLayoutTest.php b/Tests/Extension/FormExtensionBootstrap3HorizontalLayoutTest.php index 2f5289ce..b15f4e68 100644 --- a/Tests/Extension/FormExtensionBootstrap3HorizontalLayoutTest.php +++ b/Tests/Extension/FormExtensionBootstrap3HorizontalLayoutTest.php @@ -13,96 +13,35 @@ use Symfony\Bridge\Twig\Extension\FormExtension; use Symfony\Bridge\Twig\Extension\TranslationExtension; -use Symfony\Bridge\Twig\Form\TwigRendererEngine; use Symfony\Bridge\Twig\Tests\Extension\Fixtures\StubTranslator; -use Symfony\Component\Form\FormRenderer; -use Symfony\Component\Form\FormView; -use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; -use Twig\Environment; -use Twig\Loader\FilesystemLoader; class FormExtensionBootstrap3HorizontalLayoutTest extends AbstractBootstrap3HorizontalLayoutTestCase { - use RuntimeLoaderProvider; - protected array $testableFeatures = [ 'choice_attr', ]; - private FormRenderer $renderer; - - protected function setUp(): void + protected function getTemplatePaths(): array { - parent::setUp(); - - $loader = new FilesystemLoader([ + return [ __DIR__.'/../../Resources/views/Form', __DIR__.'/Fixtures/templates/form', - ]); - - $environment = new Environment($loader, ['strict_variables' => true]); - $environment->addExtension(new TranslationExtension(new StubTranslator())); - $environment->addExtension(new FormExtension()); - - $rendererEngine = new TwigRendererEngine([ - 'bootstrap_3_horizontal_layout.html.twig', - 'custom_widgets.html.twig', - ], $environment); - $this->renderer = new FormRenderer($rendererEngine, $this->createMock(CsrfTokenManagerInterface::class)); - $this->registerTwigRuntimeLoader($environment, $this->renderer); - } - - protected function renderForm(FormView $view, array $vars = []) - { - return $this->renderer->renderBlock($view, 'form', $vars); - } - - protected function renderLabel(FormView $view, $label = null, array $vars = []) - { - if (null !== $label) { - $vars += ['label' => $label]; - } - - return $this->renderer->searchAndRenderBlock($view, 'label', $vars); - } - - protected function renderHelp(FormView $view) - { - return $this->renderer->searchAndRenderBlock($view, 'help'); + ]; } - protected function renderErrors(FormView $view) + protected function getTwigExtensions(): array { - return $this->renderer->searchAndRenderBlock($view, 'errors'); + return [ + new TranslationExtension(new StubTranslator()), + new FormExtension(), + ]; } - protected function renderWidget(FormView $view, array $vars = []) + protected function getThemes(): array { - return $this->renderer->searchAndRenderBlock($view, 'widget', $vars); - } - - protected function renderRow(FormView $view, array $vars = []) - { - return $this->renderer->searchAndRenderBlock($view, 'row', $vars); - } - - protected function renderRest(FormView $view, array $vars = []) - { - return $this->renderer->searchAndRenderBlock($view, 'rest', $vars); - } - - protected function renderStart(FormView $view, array $vars = []) - { - return $this->renderer->renderBlock($view, 'form_start', $vars); - } - - protected function renderEnd(FormView $view, array $vars = []) - { - return $this->renderer->renderBlock($view, 'form_end', $vars); - } - - protected function setTheme(FormView $view, array $themes, $useDefaultThemes = true) - { - $this->renderer->setTheme($view, $themes, $useDefaultThemes); + return [ + 'bootstrap_3_horizontal_layout.html.twig', + 'custom_widgets.html.twig', + ]; } } diff --git a/Tests/Extension/FormExtensionBootstrap3LayoutTest.php b/Tests/Extension/FormExtensionBootstrap3LayoutTest.php index bb0f5687..90a17563 100644 --- a/Tests/Extension/FormExtensionBootstrap3LayoutTest.php +++ b/Tests/Extension/FormExtensionBootstrap3LayoutTest.php @@ -16,38 +16,12 @@ use Symfony\Bridge\Twig\Form\TwigRendererEngine; use Symfony\Bridge\Twig\Tests\Extension\Fixtures\StubTranslator; use Symfony\Component\Form\FormRenderer; -use Symfony\Component\Form\FormView; use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; use Twig\Environment; use Twig\Loader\FilesystemLoader; class FormExtensionBootstrap3LayoutTest extends AbstractBootstrap3LayoutTestCase { - use RuntimeLoaderProvider; - - private FormRenderer $renderer; - - protected function setUp(): void - { - parent::setUp(); - - $loader = new FilesystemLoader([ - __DIR__.'/../../Resources/views/Form', - __DIR__.'/Fixtures/templates/form', - ]); - - $environment = new Environment($loader, ['strict_variables' => true]); - $environment->addExtension(new TranslationExtension(new StubTranslator())); - $environment->addExtension(new FormExtension()); - - $rendererEngine = new TwigRendererEngine([ - 'bootstrap_3_layout.html.twig', - 'custom_widgets.html.twig', - ], $environment); - $this->renderer = new FormRenderer($rendererEngine, $this->createMock(CsrfTokenManagerInterface::class)); - $this->registerTwigRuntimeLoader($environment, $this->renderer); - } - public function testStartTagHasNoActionAttributeWhenActionIsEmpty() { $form = $this->factory->create('Symfony\Component\Form\Extension\Core\Type\FormType', null, [ @@ -102,57 +76,27 @@ public function testMoneyWidgetInIso() , trim($this->renderWidget($view))); } - protected function renderForm(FormView $view, array $vars = []) + protected function getTemplatePaths(): array { - return $this->renderer->renderBlock($view, 'form', $vars); - } - - protected function renderLabel(FormView $view, $label = null, array $vars = []) - { - if (null !== $label) { - $vars += ['label' => $label]; - } - - return $this->renderer->searchAndRenderBlock($view, 'label', $vars); - } - - protected function renderHelp(FormView $view) - { - return $this->renderer->searchAndRenderBlock($view, 'help'); - } - - protected function renderErrors(FormView $view) - { - return $this->renderer->searchAndRenderBlock($view, 'errors'); - } - - protected function renderWidget(FormView $view, array $vars = []) - { - return $this->renderer->searchAndRenderBlock($view, 'widget', $vars); - } - - protected function renderRow(FormView $view, array $vars = []) - { - return $this->renderer->searchAndRenderBlock($view, 'row', $vars); - } - - protected function renderRest(FormView $view, array $vars = []) - { - return $this->renderer->searchAndRenderBlock($view, 'rest', $vars); - } - - protected function renderStart(FormView $view, array $vars = []) - { - return $this->renderer->renderBlock($view, 'form_start', $vars); + return [ + __DIR__.'/../../Resources/views/Form', + __DIR__.'/Fixtures/templates/form', + ]; } - protected function renderEnd(FormView $view, array $vars = []) + protected function getTwigExtensions(): array { - return $this->renderer->renderBlock($view, 'form_end', $vars); + return [ + new TranslationExtension(new StubTranslator()), + new FormExtension(), + ]; } - protected function setTheme(FormView $view, array $themes, $useDefaultThemes = true) + protected function getThemes(): array { - $this->renderer->setTheme($view, $themes, $useDefaultThemes); + return [ + 'bootstrap_3_layout.html.twig', + 'custom_widgets.html.twig', + ]; } } diff --git a/Tests/Extension/FormExtensionBootstrap4HorizontalLayoutTest.php b/Tests/Extension/FormExtensionBootstrap4HorizontalLayoutTest.php index a7d6c04e..dbc2827f 100644 --- a/Tests/Extension/FormExtensionBootstrap4HorizontalLayoutTest.php +++ b/Tests/Extension/FormExtensionBootstrap4HorizontalLayoutTest.php @@ -13,13 +13,7 @@ use Symfony\Bridge\Twig\Extension\FormExtension; use Symfony\Bridge\Twig\Extension\TranslationExtension; -use Symfony\Bridge\Twig\Form\TwigRendererEngine; use Symfony\Bridge\Twig\Tests\Extension\Fixtures\StubTranslator; -use Symfony\Component\Form\FormRenderer; -use Symfony\Component\Form\FormView; -use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; -use Twig\Environment; -use Twig\Loader\FilesystemLoader; /** * Class providing test cases for the Bootstrap 4 Twig form theme. @@ -28,86 +22,31 @@ */ class FormExtensionBootstrap4HorizontalLayoutTest extends AbstractBootstrap4HorizontalLayoutTestCase { - use RuntimeLoaderProvider; - protected array $testableFeatures = [ 'choice_attr', ]; - private FormRenderer $renderer; - - protected function setUp(): void + protected function getTemplatePaths(): array { - parent::setUp(); - - $loader = new FilesystemLoader([ + return [ __DIR__.'/../../Resources/views/Form', __DIR__.'/Fixtures/templates/form', - ]); - - $environment = new Environment($loader, ['strict_variables' => true]); - $environment->addExtension(new TranslationExtension(new StubTranslator())); - $environment->addExtension(new FormExtension()); - - $rendererEngine = new TwigRendererEngine([ - 'bootstrap_4_horizontal_layout.html.twig', - 'custom_widgets.html.twig', - ], $environment); - $this->renderer = new FormRenderer($rendererEngine, $this->createMock(CsrfTokenManagerInterface::class)); - $this->registerTwigRuntimeLoader($environment, $this->renderer); - } - - protected function renderForm(FormView $view, array $vars = []) - { - return $this->renderer->renderBlock($view, 'form', $vars); - } - - protected function renderLabel(FormView $view, $label = null, array $vars = []) - { - if (null !== $label) { - $vars += ['label' => $label]; - } - - return $this->renderer->searchAndRenderBlock($view, 'label', $vars); - } - - protected function renderHelp(FormView $view) - { - return $this->renderer->searchAndRenderBlock($view, 'help'); + ]; } - protected function renderErrors(FormView $view) + protected function getTwigExtensions(): array { - return $this->renderer->searchAndRenderBlock($view, 'errors'); + return [ + new TranslationExtension(new StubTranslator()), + new FormExtension(), + ]; } - protected function renderWidget(FormView $view, array $vars = []) + protected function getThemes(): array { - return $this->renderer->searchAndRenderBlock($view, 'widget', $vars); - } - - protected function renderRow(FormView $view, array $vars = []) - { - return $this->renderer->searchAndRenderBlock($view, 'row', $vars); - } - - protected function renderRest(FormView $view, array $vars = []) - { - return $this->renderer->searchAndRenderBlock($view, 'rest', $vars); - } - - protected function renderStart(FormView $view, array $vars = []) - { - return $this->renderer->renderBlock($view, 'form_start', $vars); - } - - protected function renderEnd(FormView $view, array $vars = []) - { - return $this->renderer->renderBlock($view, 'form_end', $vars); - } - - protected function setTheme(FormView $view, array $themes, $useDefaultThemes = true) - { - $this->renderer->setTheme($view, $themes, $useDefaultThemes); + return [ + 'bootstrap_4_horizontal_layout.html.twig', + 'custom_widgets.html.twig', + ]; } } diff --git a/Tests/Extension/FormExtensionBootstrap4LayoutTest.php b/Tests/Extension/FormExtensionBootstrap4LayoutTest.php index 3aecccf1..bffebe3f 100644 --- a/Tests/Extension/FormExtensionBootstrap4LayoutTest.php +++ b/Tests/Extension/FormExtensionBootstrap4LayoutTest.php @@ -16,7 +16,6 @@ use Symfony\Bridge\Twig\Form\TwigRendererEngine; use Symfony\Bridge\Twig\Tests\Extension\Fixtures\StubTranslator; use Symfony\Component\Form\FormRenderer; -use Symfony\Component\Form\FormView; use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; use Twig\Environment; use Twig\Loader\FilesystemLoader; @@ -28,31 +27,6 @@ */ class FormExtensionBootstrap4LayoutTest extends AbstractBootstrap4LayoutTestCase { - use RuntimeLoaderProvider; - - private FormRenderer $renderer; - - protected function setUp(): void - { - parent::setUp(); - - $loader = new FilesystemLoader([ - __DIR__.'/../../Resources/views/Form', - __DIR__.'/Fixtures/templates/form', - ]); - - $environment = new Environment($loader, ['strict_variables' => true]); - $environment->addExtension(new TranslationExtension(new StubTranslator())); - $environment->addExtension(new FormExtension()); - - $rendererEngine = new TwigRendererEngine([ - 'bootstrap_4_layout.html.twig', - 'custom_widgets.html.twig', - ], $environment); - $this->renderer = new FormRenderer($rendererEngine, $this->createMock(CsrfTokenManagerInterface::class)); - $this->registerTwigRuntimeLoader($environment, $this->renderer); - } - public function testStartTagHasNoActionAttributeWhenActionIsEmpty() { $form = $this->factory->create('Symfony\Component\Form\Extension\Core\Type\FormType', null, [ @@ -107,57 +81,27 @@ public function testMoneyWidgetInIso() , trim($this->renderWidget($view))); } - protected function renderForm(FormView $view, array $vars = []) + protected function getTemplatePaths(): array { - return $this->renderer->renderBlock($view, 'form', $vars); - } - - protected function renderLabel(FormView $view, $label = null, array $vars = []) - { - if (null !== $label) { - $vars += ['label' => $label]; - } - - return $this->renderer->searchAndRenderBlock($view, 'label', $vars); - } - - protected function renderHelp(FormView $view) - { - return $this->renderer->searchAndRenderBlock($view, 'help'); - } - - protected function renderErrors(FormView $view) - { - return $this->renderer->searchAndRenderBlock($view, 'errors'); - } - - protected function renderWidget(FormView $view, array $vars = []) - { - return $this->renderer->searchAndRenderBlock($view, 'widget', $vars); - } - - protected function renderRow(FormView $view, array $vars = []) - { - return $this->renderer->searchAndRenderBlock($view, 'row', $vars); - } - - protected function renderRest(FormView $view, array $vars = []) - { - return $this->renderer->searchAndRenderBlock($view, 'rest', $vars); - } - - protected function renderStart(FormView $view, array $vars = []) - { - return $this->renderer->renderBlock($view, 'form_start', $vars); + return [ + __DIR__.'/../../Resources/views/Form', + __DIR__.'/Fixtures/templates/form', + ]; } - protected function renderEnd(FormView $view, array $vars = []) + protected function getTwigExtensions(): array { - return $this->renderer->renderBlock($view, 'form_end', $vars); + return [ + new TranslationExtension(new StubTranslator()), + new FormExtension(), + ]; } - protected function setTheme(FormView $view, array $themes, $useDefaultThemes = true) + protected function getThemes(): array { - $this->renderer->setTheme($view, $themes, $useDefaultThemes); + return [ + 'bootstrap_4_layout.html.twig', + 'custom_widgets.html.twig', + ]; } } diff --git a/Tests/Extension/FormExtensionBootstrap5HorizontalLayoutTest.php b/Tests/Extension/FormExtensionBootstrap5HorizontalLayoutTest.php index 9682129e..54c8e5af 100644 --- a/Tests/Extension/FormExtensionBootstrap5HorizontalLayoutTest.php +++ b/Tests/Extension/FormExtensionBootstrap5HorizontalLayoutTest.php @@ -13,13 +13,7 @@ use Symfony\Bridge\Twig\Extension\FormExtension; use Symfony\Bridge\Twig\Extension\TranslationExtension; -use Symfony\Bridge\Twig\Form\TwigRendererEngine; use Symfony\Bridge\Twig\Tests\Extension\Fixtures\StubTranslator; -use Symfony\Component\Form\FormRenderer; -use Symfony\Component\Form\FormView; -use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; -use Twig\Environment; -use Twig\Loader\FilesystemLoader; /** * Class providing test cases for the Bootstrap 5 horizontal Twig form theme. @@ -28,86 +22,31 @@ */ class FormExtensionBootstrap5HorizontalLayoutTest extends AbstractBootstrap5HorizontalLayoutTestCase { - use RuntimeLoaderProvider; - protected array $testableFeatures = [ 'choice_attr', ]; - private FormRenderer $renderer; - - protected function setUp(): void + protected function getTemplatePaths(): array { - parent::setUp(); - - $loader = new FilesystemLoader([ + return [ __DIR__.'/../../Resources/views/Form', __DIR__.'/Fixtures/templates/form', - ]); - - $environment = new Environment($loader, ['strict_variables' => true]); - $environment->addExtension(new TranslationExtension(new StubTranslator())); - $environment->addExtension(new FormExtension()); - - $rendererEngine = new TwigRendererEngine([ - 'bootstrap_5_horizontal_layout.html.twig', - 'custom_widgets.html.twig', - ], $environment); - $this->renderer = new FormRenderer($rendererEngine, $this->getMockBuilder(CsrfTokenManagerInterface::class)->getMock()); - $this->registerTwigRuntimeLoader($environment, $this->renderer); - } - - protected function renderForm(FormView $view, array $vars = []): string - { - return $this->renderer->renderBlock($view, 'form', $vars); - } - - protected function renderLabel(FormView $view, $label = null, array $vars = []): string - { - if (null !== $label) { - $vars += ['label' => $label]; - } - - return $this->renderer->searchAndRenderBlock($view, 'label', $vars); - } - - protected function renderHelp(FormView $view): string - { - return $this->renderer->searchAndRenderBlock($view, 'help'); + ]; } - protected function renderErrors(FormView $view): string + protected function getTwigExtensions(): array { - return $this->renderer->searchAndRenderBlock($view, 'errors'); + return [ + new TranslationExtension(new StubTranslator()), + new FormExtension(), + ]; } - protected function renderWidget(FormView $view, array $vars = []): string + protected function getThemes(): array { - return $this->renderer->searchAndRenderBlock($view, 'widget', $vars); - } - - protected function renderRow(FormView $view, array $vars = []): string - { - return $this->renderer->searchAndRenderBlock($view, 'row', $vars); - } - - protected function renderRest(FormView $view, array $vars = []): string - { - return $this->renderer->searchAndRenderBlock($view, 'rest', $vars); - } - - protected function renderStart(FormView $view, array $vars = []): string - { - return $this->renderer->renderBlock($view, 'form_start', $vars); - } - - protected function renderEnd(FormView $view, array $vars = []): string - { - return $this->renderer->renderBlock($view, 'form_end', $vars); - } - - protected function setTheme(FormView $view, array $themes, $useDefaultThemes = true): void - { - $this->renderer->setTheme($view, $themes, $useDefaultThemes); + return [ + 'bootstrap_5_horizontal_layout.html.twig', + 'custom_widgets.html.twig', + ]; } } diff --git a/Tests/Extension/FormExtensionBootstrap5LayoutTest.php b/Tests/Extension/FormExtensionBootstrap5LayoutTest.php index 66f6bbb9..e7e537ac 100644 --- a/Tests/Extension/FormExtensionBootstrap5LayoutTest.php +++ b/Tests/Extension/FormExtensionBootstrap5LayoutTest.php @@ -18,7 +18,6 @@ use Symfony\Component\Form\Extension\Core\Type\FormType; use Symfony\Component\Form\Extension\Core\Type\MoneyType; use Symfony\Component\Form\FormRenderer; -use Symfony\Component\Form\FormView; use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; use Twig\Environment; use Twig\Loader\FilesystemLoader; @@ -30,31 +29,6 @@ */ class FormExtensionBootstrap5LayoutTest extends AbstractBootstrap5LayoutTestCase { - use RuntimeLoaderProvider; - - private FormRenderer $renderer; - - protected function setUp(): void - { - parent::setUp(); - - $loader = new FilesystemLoader([ - __DIR__.'/../../Resources/views/Form', - __DIR__.'/Fixtures/templates/form', - ]); - - $environment = new Environment($loader, ['strict_variables' => true]); - $environment->addExtension(new TranslationExtension(new StubTranslator())); - $environment->addExtension(new FormExtension()); - - $rendererEngine = new TwigRendererEngine([ - 'bootstrap_5_layout.html.twig', - 'custom_widgets.html.twig', - ], $environment); - $this->renderer = new FormRenderer($rendererEngine, $this->getMockBuilder(CsrfTokenManagerInterface::class)->getMock()); - $this->registerTwigRuntimeLoader($environment, $this->renderer); - } - public function testStartTagHasNoActionAttributeWhenActionIsEmpty() { $form = $this->factory->create(FormType::class, null, [ @@ -106,57 +80,27 @@ public function testMoneyWidgetInIso() , trim($this->renderWidget($view))); } - protected function renderForm(FormView $view, array $vars = []): string + protected function getTemplatePaths(): array { - return $this->renderer->renderBlock($view, 'form', $vars); - } - - protected function renderLabel(FormView $view, $label = null, array $vars = []): string - { - if (null !== $label) { - $vars += ['label' => $label]; - } - - return $this->renderer->searchAndRenderBlock($view, 'label', $vars); - } - - protected function renderHelp(FormView $view): string - { - return $this->renderer->searchAndRenderBlock($view, 'help'); - } - - protected function renderErrors(FormView $view): string - { - return $this->renderer->searchAndRenderBlock($view, 'errors'); - } - - protected function renderWidget(FormView $view, array $vars = []): string - { - return $this->renderer->searchAndRenderBlock($view, 'widget', $vars); - } - - protected function renderRow(FormView $view, array $vars = []): string - { - return $this->renderer->searchAndRenderBlock($view, 'row', $vars); - } - - protected function renderRest(FormView $view, array $vars = []): string - { - return $this->renderer->searchAndRenderBlock($view, 'rest', $vars); - } - - protected function renderStart(FormView $view, array $vars = []): string - { - return $this->renderer->renderBlock($view, 'form_start', $vars); + return [ + __DIR__.'/../../Resources/views/Form', + __DIR__.'/Fixtures/templates/form', + ]; } - protected function renderEnd(FormView $view, array $vars = []): string + protected function getTwigExtensions(): array { - return $this->renderer->renderBlock($view, 'form_end', $vars); + return [ + new TranslationExtension(new StubTranslator()), + new FormExtension(), + ]; } - protected function setTheme(FormView $view, array $themes, $useDefaultThemes = true): void + protected function getThemes(): array { - $this->renderer->setTheme($view, $themes, $useDefaultThemes); + return [ + 'bootstrap_5_layout.html.twig', + 'custom_widgets.html.twig', + ]; } } diff --git a/Tests/Extension/FormExtensionDivLayoutTest.php b/Tests/Extension/FormExtensionDivLayoutTest.php index c9dec6e2..fa0f1824 100644 --- a/Tests/Extension/FormExtensionDivLayoutTest.php +++ b/Tests/Extension/FormExtensionDivLayoutTest.php @@ -24,34 +24,6 @@ class FormExtensionDivLayoutTest extends AbstractDivLayoutTestCase { - use RuntimeLoaderProvider; - - private FormRenderer $renderer; - - protected function setUp(): void - { - parent::setUp(); - - $loader = new FilesystemLoader([ - __DIR__.'/../../Resources/views/Form', - __DIR__.'/Fixtures/templates/form', - ]); - - $environment = new Environment($loader, ['strict_variables' => true]); - $environment->addExtension(new TranslationExtension(new StubTranslator())); - $environment->addGlobal('global', ''); - // the value can be any template that exists - $environment->addGlobal('dynamic_template_name', 'child_label'); - $environment->addExtension(new FormExtension()); - - $rendererEngine = new TwigRendererEngine([ - 'form_div_layout.html.twig', - 'custom_widgets.html.twig', - ], $environment); - $this->renderer = new FormRenderer($rendererEngine, $this->createMock(CsrfTokenManagerInterface::class)); - $this->registerTwigRuntimeLoader($environment, $this->renderer); - } - public function testThemeBlockInheritanceUsingUse() { $view = $this->factory @@ -323,71 +295,50 @@ public function testLabelHtmlIsTrue() $this->assertMatchesXpath($html, '/label[@for="name"][@class="my&class required"]/b[.="Bolded label"]'); } - protected function renderForm(FormView $view, array $vars = []) - { - return $this->renderer->renderBlock($view, 'form', $vars); - } - - protected function renderLabel(FormView $view, $label = null, array $vars = []) - { - if (null !== $label) { - $vars += ['label' => $label]; - } - - return $this->renderer->searchAndRenderBlock($view, 'label', $vars); - } - - protected function renderHelp(FormView $view) - { - return $this->renderer->searchAndRenderBlock($view, 'help'); - } - - protected function renderErrors(FormView $view) - { - return $this->renderer->searchAndRenderBlock($view, 'errors'); - } - - protected function renderWidget(FormView $view, array $vars = []) - { - return $this->renderer->searchAndRenderBlock($view, 'widget', $vars); - } - - protected function renderRow(FormView $view, array $vars = []) - { - return $this->renderer->searchAndRenderBlock($view, 'row', $vars); - } - - protected function renderRest(FormView $view, array $vars = []) + public static function themeBlockInheritanceProvider() { - return $this->renderer->searchAndRenderBlock($view, 'rest', $vars); + return [ + [['theme.html.twig']], + ]; } - protected function renderStart(FormView $view, array $vars = []) + public static function themeInheritanceProvider() { - return $this->renderer->renderBlock($view, 'form_start', $vars); + return [ + [['parent_label.html.twig'], ['child_label.html.twig']], + ]; } - protected function renderEnd(FormView $view, array $vars = []) + protected function getTemplatePaths(): array { - return $this->renderer->renderBlock($view, 'form_end', $vars); + return [ + __DIR__.'/../../Resources/views/Form', + __DIR__.'/Fixtures/templates/form', + ]; } - protected function setTheme(FormView $view, array $themes, $useDefaultThemes = true) + protected function getTwigExtensions(): array { - $this->renderer->setTheme($view, $themes, $useDefaultThemes); + return [ + new TranslationExtension(new StubTranslator()), + new FormExtension(), + ]; } - public static function themeBlockInheritanceProvider() + protected function getTwigGlobals(): array { return [ - [['theme.html.twig']], + 'global' => '', + // the value can be any template that exists + 'dynamic_template_name' => 'child_label', ]; } - public static function themeInheritanceProvider() + protected function getThemes(): array { return [ - [['parent_label.html.twig'], ['child_label.html.twig']], + 'form_div_layout.html.twig', + 'custom_widgets.html.twig', ]; } } diff --git a/Tests/Extension/FormExtensionTableLayoutTest.php b/Tests/Extension/FormExtensionTableLayoutTest.php index 751d2b1f..2ab48be2 100644 --- a/Tests/Extension/FormExtensionTableLayoutTest.php +++ b/Tests/Extension/FormExtensionTableLayoutTest.php @@ -13,42 +13,10 @@ use Symfony\Bridge\Twig\Extension\FormExtension; use Symfony\Bridge\Twig\Extension\TranslationExtension; -use Symfony\Bridge\Twig\Form\TwigRendererEngine; use Symfony\Bridge\Twig\Tests\Extension\Fixtures\StubTranslator; -use Symfony\Component\Form\FormRenderer; -use Symfony\Component\Form\FormView; -use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; -use Twig\Environment; -use Twig\Loader\FilesystemLoader; class FormExtensionTableLayoutTest extends AbstractTableLayoutTestCase { - use RuntimeLoaderProvider; - - private FormRenderer $renderer; - - protected function setUp(): void - { - parent::setUp(); - - $loader = new FilesystemLoader([ - __DIR__.'/../../Resources/views/Form', - __DIR__.'/Fixtures/templates/form', - ]); - - $environment = new Environment($loader, ['strict_variables' => true]); - $environment->addExtension(new TranslationExtension(new StubTranslator())); - $environment->addGlobal('global', ''); - $environment->addExtension(new FormExtension()); - - $rendererEngine = new TwigRendererEngine([ - 'form_table_layout.html.twig', - 'custom_widgets.html.twig', - ], $environment); - $this->renderer = new FormRenderer($rendererEngine, $this->createMock(CsrfTokenManagerInterface::class)); - $this->registerTwigRuntimeLoader($environment, $this->renderer); - } - public function testStartTagHasNoActionAttributeWhenActionIsEmpty() { $form = $this->factory->create('Symfony\Component\Form\Extension\Core\Type\FormType', null, [ @@ -209,57 +177,34 @@ public function testLabelHtmlIsTrue() $this->assertMatchesXpath($html, '/label[@for="name"][@class="my&class required"]/b[.="Bolded label"]'); } - protected function renderForm(FormView $view, array $vars = []) - { - return $this->renderer->renderBlock($view, 'form', $vars); - } - - protected function renderLabel(FormView $view, $label = null, array $vars = []) - { - if (null !== $label) { - $vars += ['label' => $label]; - } - - return $this->renderer->searchAndRenderBlock($view, 'label', $vars); - } - - protected function renderHelp(FormView $view) - { - return $this->renderer->searchAndRenderBlock($view, 'help'); - } - - protected function renderErrors(FormView $view) - { - return $this->renderer->searchAndRenderBlock($view, 'errors'); - } - - protected function renderWidget(FormView $view, array $vars = []) - { - return $this->renderer->searchAndRenderBlock($view, 'widget', $vars); - } - - protected function renderRow(FormView $view, array $vars = []) - { - return $this->renderer->searchAndRenderBlock($view, 'row', $vars); - } - - protected function renderRest(FormView $view, array $vars = []) + protected function getTemplatePaths(): array { - return $this->renderer->searchAndRenderBlock($view, 'rest', $vars); + return [ + __DIR__.'/../../Resources/views/Form', + __DIR__.'/Fixtures/templates/form', + ]; } - protected function renderStart(FormView $view, array $vars = []) + protected function getTwigExtensions(): array { - return $this->renderer->renderBlock($view, 'form_start', $vars); + return [ + new TranslationExtension(new StubTranslator()), + new FormExtension(), + ]; } - protected function renderEnd(FormView $view, array $vars = []) + protected function getTwigGlobals(): array { - return $this->renderer->renderBlock($view, 'form_end', $vars); + return [ + 'global' => '', + ]; } - protected function setTheme(FormView $view, array $themes, $useDefaultThemes = true) + protected function getThemes(): array { - $this->renderer->setTheme($view, $themes, $useDefaultThemes); + return [ + 'form_table_layout.html.twig', + 'custom_widgets.html.twig', + ]; } } diff --git a/Tests/Node/FormThemeTest.php b/Tests/Node/FormThemeTest.php index cf981912..a54f2c14 100644 --- a/Tests/Node/FormThemeTest.php +++ b/Tests/Node/FormThemeTest.php @@ -13,7 +13,7 @@ use PHPUnit\Framework\TestCase; use Symfony\Bridge\Twig\Node\FormThemeNode; -use Symfony\Bridge\Twig\Tests\Extension\RuntimeLoaderProvider; +use Symfony\Bridge\Twig\Test\Traits\RuntimeLoaderProvider; use Symfony\Component\Form\FormRenderer; use Symfony\Component\Form\FormRendererEngineInterface; use Twig\Compiler; From f169c5a7307b4516f4be64dd7d885fc1a01d3fcf Mon Sep 17 00:00:00 2001 From: Alexandre Daubois Date: Mon, 2 Oct 2023 10:52:41 +0200 Subject: [PATCH 096/167] [Security] Make `impersonation_path()` argument mandatory and add `impersonation_url()` --- CHANGELOG.md | 1 + Extension/SecurityExtension.php | 12 +++++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e8ed131..9bb7aa0c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ CHANGELOG * Allow an array to be passed as the first argument to the `importmap()` Twig function * Add `TemplatedEmail::locale()` to set the locale for the email rendering * Add `AppVariable::getEnabledLocales()` to retrieve the enabled locales + * Add `impersonation_path()` and `impersonation_url()` Twig functions 6.3 --- diff --git a/Extension/SecurityExtension.php b/Extension/SecurityExtension.php index be222b05..3c3881ad 100644 --- a/Extension/SecurityExtension.php +++ b/Extension/SecurityExtension.php @@ -69,7 +69,16 @@ public function getImpersonateExitPath(string $exitTo = null): string return $this->impersonateUrlGenerator->generateExitPath($exitTo); } - public function getImpersonatePath(string $identifier = null): string + public function getImpersonateUrl(string $identifier): string + { + if (null === $this->impersonateUrlGenerator) { + return ''; + } + + return $this->impersonateUrlGenerator->generateImpersonationUrl($identifier); + } + + public function getImpersonatePath(string $identifier): string { if (null === $this->impersonateUrlGenerator) { return ''; @@ -84,6 +93,7 @@ public function getFunctions(): array new TwigFunction('is_granted', $this->isGranted(...)), new TwigFunction('impersonation_exit_url', $this->getImpersonateExitUrl(...)), new TwigFunction('impersonation_exit_path', $this->getImpersonateExitPath(...)), + new TwigFunction('impersonation_url', $this->getImpersonateUrl(...)), new TwigFunction('impersonation_path', $this->getImpersonatePath(...)), ]; } From c0391bdfbc19b94ace131332869b415ac70ad0c2 Mon Sep 17 00:00:00 2001 From: Alexander Schranz Date: Tue, 23 May 2023 01:51:38 +0200 Subject: [PATCH 097/167] Move UriSigner from HttpKernel to HttpFoundation package --- Tests/Extension/HttpKernelExtensionTest.php | 2 +- composer.json | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Tests/Extension/HttpKernelExtensionTest.php b/Tests/Extension/HttpKernelExtensionTest.php index a53e64a4..e7f58f4f 100644 --- a/Tests/Extension/HttpKernelExtensionTest.php +++ b/Tests/Extension/HttpKernelExtensionTest.php @@ -18,10 +18,10 @@ use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\UriSigner; use Symfony\Component\HttpKernel\Fragment\FragmentHandler; use Symfony\Component\HttpKernel\Fragment\FragmentRendererInterface; use Symfony\Component\HttpKernel\Fragment\FragmentUriGenerator; -use Symfony\Component\HttpKernel\UriSigner; use Twig\Environment; use Twig\Loader\ArrayLoader; use Twig\RuntimeLoader\RuntimeLoaderInterface; diff --git a/composer.json b/composer.json index 36014a59..843dbedb 100644 --- a/composer.json +++ b/composer.json @@ -31,7 +31,7 @@ "symfony/form": "^6.3|^7.0", "symfony/html-sanitizer": "^6.1|^7.0", "symfony/http-foundation": "^5.4|^6.0|^7.0", - "symfony/http-kernel": "^6.2|^7.0", + "symfony/http-kernel": "^6.4|^7.0", "symfony/intl": "^5.4|^6.0|^7.0", "symfony/mime": "^6.2|^7.0", "symfony/polyfill-intl-icu": "~1.0", @@ -59,7 +59,7 @@ "symfony/console": "<5.4", "symfony/form": "<6.3", "symfony/http-foundation": "<5.4", - "symfony/http-kernel": "<6.2", + "symfony/http-kernel": "<6.4", "symfony/mime": "<6.2", "symfony/serializer": "<6.2", "symfony/translation": "<5.4", From 251800357c1acb0da613d89432384f58266380ea Mon Sep 17 00:00:00 2001 From: Thomas Calvet Date: Fri, 13 Oct 2023 14:52:05 +0200 Subject: [PATCH 098/167] Fix CI --- Tests/Mime/BodyRendererTest.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Tests/Mime/BodyRendererTest.php b/Tests/Mime/BodyRendererTest.php index ddc6f46d..4ffea6b8 100644 --- a/Tests/Mime/BodyRendererTest.php +++ b/Tests/Mime/BodyRendererTest.php @@ -133,6 +133,9 @@ public function testRenderedOnceUnserializableContext() $this->assertEquals('Text', $email->getTextBody()); } + /** + * @requires extension intl + */ public function testRenderWithLocale() { $localeSwitcher = new LocaleSwitcher('en', []); From 4bc12ba02f9f24996310e57c2128b0a2342fe538 Mon Sep 17 00:00:00 2001 From: Nicolas Lemoine Date: Wed, 21 Jun 2023 21:09:57 +0200 Subject: [PATCH 099/167] [ErrorHandler] Improve fileLinkFormat handling - Avoid repeating file link format guessing (logic is already in FileLinkFormatter class) - Always set a fileLinkFormat to a FileLinkFormatter object to handle path mappings properly --- Command/DebugCommand.php | 2 +- Extension/CodeExtension.php | 2 +- Tests/Extension/CodeExtensionTest.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Command/DebugCommand.php b/Command/DebugCommand.php index 43e4d9c9..31edd0a5 100644 --- a/Command/DebugCommand.php +++ b/Command/DebugCommand.php @@ -22,8 +22,8 @@ use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Component\ErrorHandler\ErrorRenderer\FileLinkFormatter; use Symfony\Component\Finder\Finder; -use Symfony\Component\HttpKernel\Debug\FileLinkFormatter; use Twig\Environment; use Twig\Loader\ChainLoader; use Twig\Loader\FilesystemLoader; diff --git a/Extension/CodeExtension.php b/Extension/CodeExtension.php index d6bb18e4..2160b70d 100644 --- a/Extension/CodeExtension.php +++ b/Extension/CodeExtension.php @@ -11,7 +11,7 @@ namespace Symfony\Bridge\Twig\Extension; -use Symfony\Component\HttpKernel\Debug\FileLinkFormatter; +use Symfony\Component\ErrorHandler\ErrorRenderer\FileLinkFormatter; use Twig\Extension\AbstractExtension; use Twig\TwigFilter; diff --git a/Tests/Extension/CodeExtensionTest.php b/Tests/Extension/CodeExtensionTest.php index 38983cbd..ae7cf786 100644 --- a/Tests/Extension/CodeExtensionTest.php +++ b/Tests/Extension/CodeExtensionTest.php @@ -13,7 +13,7 @@ use PHPUnit\Framework\TestCase; use Symfony\Bridge\Twig\Extension\CodeExtension; -use Symfony\Component\HttpKernel\Debug\FileLinkFormatter; +use Symfony\Component\ErrorHandler\ErrorRenderer\FileLinkFormatter; class CodeExtensionTest extends TestCase { From bbe26f8effcd2e52b6e1eb64105bfd3bdeb84470 Mon Sep 17 00:00:00 2001 From: Alexandre Daubois Date: Tue, 22 Aug 2023 16:39:24 +0200 Subject: [PATCH 100/167] [FrameworkBundle][Serializer] Deprecate annotations --- Tests/Extension/SerializerExtensionTest.php | 4 ++-- composer.json | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Tests/Extension/SerializerExtensionTest.php b/Tests/Extension/SerializerExtensionTest.php index 0115f867..610030ce 100644 --- a/Tests/Extension/SerializerExtensionTest.php +++ b/Tests/Extension/SerializerExtensionTest.php @@ -18,7 +18,7 @@ use Symfony\Component\Serializer\Encoder\JsonEncoder; use Symfony\Component\Serializer\Encoder\YamlEncoder; use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory; -use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader; +use Symfony\Component\Serializer\Mapping\Loader\AttributeLoader; use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; use Symfony\Component\Serializer\Serializer; use Twig\Environment; @@ -49,7 +49,7 @@ public static function serializerDataProvider(): \Generator private function getTwig(string $template): Environment { - $meta = new ClassMetadataFactory(new AnnotationLoader()); + $meta = new ClassMetadataFactory(new AttributeLoader()); $runtime = new SerializerRuntime(new Serializer([new ObjectNormalizer($meta)], [new JsonEncoder(), new YamlEncoder()])); $mockRuntimeLoader = $this->createMock(RuntimeLoaderInterface::class); diff --git a/composer.json b/composer.json index a982bf4f..9a3449bd 100644 --- a/composer.json +++ b/composer.json @@ -43,7 +43,7 @@ "symfony/security-core": "^5.4|^6.0|^7.0", "symfony/security-csrf": "^5.4|^6.0|^7.0", "symfony/security-http": "^5.4|^6.0|^7.0", - "symfony/serializer": "^6.2|^7.0", + "symfony/serializer": "^6.4|^7.0", "symfony/stopwatch": "^5.4|^6.0|^7.0", "symfony/console": "^5.4|^6.0|^7.0", "symfony/expression-language": "^5.4|^6.0|^7.0", @@ -61,7 +61,7 @@ "symfony/http-foundation": "<5.4", "symfony/http-kernel": "<6.4", "symfony/mime": "<6.2", - "symfony/serializer": "<6.2", + "symfony/serializer": "<6.4", "symfony/translation": "<5.4", "symfony/workflow": "<5.4" }, From 49f5284a3166461bab237f6f872a2e9a0bd6b1e9 Mon Sep 17 00:00:00 2001 From: Thomas Calvet Date: Thu, 19 Oct 2023 17:30:50 +0200 Subject: [PATCH 101/167] [MonologBridge][TwigBridge] Fix requiring symfony/deprecation-contracts --- composer.json | 1 + 1 file changed, 1 insertion(+) diff --git a/composer.json b/composer.json index 9a3449bd..87c4e568 100644 --- a/composer.json +++ b/composer.json @@ -17,6 +17,7 @@ ], "require": { "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/translation-contracts": "^2.5|^3", "twig/twig": "^2.13|^3.0.4" }, From 3e2a47d0d73ac83c9275ab1775f4f306e5daa2e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20Andr=C3=A9?= Date: Sun, 22 Oct 2023 08:17:20 +0200 Subject: [PATCH 102/167] Update UndefinedCallableHandler with "importmap", "form" and "worflow" filters/functions --- UndefinedCallableHandler.php | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/UndefinedCallableHandler.php b/UndefinedCallableHandler.php index 9368f15b..ede634e1 100644 --- a/UndefinedCallableHandler.php +++ b/UndefinedCallableHandler.php @@ -24,6 +24,7 @@ class UndefinedCallableHandler { private const FILTER_COMPONENTS = [ 'humanize' => 'form', + 'form_encode_currency' => 'form', 'trans' => 'translation', 'sanitize_html' => 'html-sanitizer', 'yaml_encode' => 'yaml', @@ -33,6 +34,7 @@ class UndefinedCallableHandler private const FUNCTION_COMPONENTS = [ 'asset' => 'asset', 'asset_version' => 'asset', + 'importmap' => 'asset-mapper', 'dump' => 'debug-bundle', 'encore_entry_link_tags' => 'webpack-encore-bundle', 'encore_entry_script_tags' => 'webpack-encore-bundle', @@ -47,6 +49,13 @@ class UndefinedCallableHandler 'form_start' => 'form', 'form_end' => 'form', 'csrf_token' => 'form', + 'form_parent' => 'form', + 'field_name' => 'form', + 'field_value' => 'form', + 'field_label' => 'form', + 'field_help' => 'form', + 'field_errors' => 'form', + 'field_choices' => 'form', 'logout_url' => 'security-http', 'logout_path' => 'security-http', 'is_granted' => 'security-core', @@ -58,8 +67,11 @@ class UndefinedCallableHandler 'prerender' => 'web-link', 'workflow_can' => 'workflow', 'workflow_transitions' => 'workflow', + 'workflow_transition' => 'workflow', 'workflow_has_marked_place' => 'workflow', 'workflow_marked_places' => 'workflow', + 'workflow_metadata' => 'workflow', + 'workflow_transition_blockers' => 'workflow', ]; private const FULL_STACK_ENABLE = [ From 33fa61ebdec4b5b4145a4c5d2b92b646627bbff0 Mon Sep 17 00:00:00 2001 From: Wouter de Jong Date: Wed, 25 Oct 2023 16:05:35 +0200 Subject: [PATCH 103/167] Add annotation -> attribute aliases --- Tests/Extension/Fixtures/SerializerModelFixture.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/Extension/Fixtures/SerializerModelFixture.php b/Tests/Extension/Fixtures/SerializerModelFixture.php index 6c0cc210..e945a076 100644 --- a/Tests/Extension/Fixtures/SerializerModelFixture.php +++ b/Tests/Extension/Fixtures/SerializerModelFixture.php @@ -2,7 +2,7 @@ namespace Symfony\Bridge\Twig\Tests\Extension\Fixtures; -use Symfony\Component\Serializer\Annotation\Groups; +use Symfony\Component\Serializer\Attribute\Groups; /** * @author Jesse Rushlow From a20c7aa64902b01cd6dc4cdeee2ccd0370a86e2f Mon Sep 17 00:00:00 2001 From: Oskar Stark Date: Fri, 27 Oct 2023 13:50:26 +0200 Subject: [PATCH 104/167] [Tests] Move expectException closer to the place of the expectation to avoid false positives --- Tests/Extension/HttpKernelExtensionTest.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Tests/Extension/HttpKernelExtensionTest.php b/Tests/Extension/HttpKernelExtensionTest.php index e7f58f4f..5bce112d 100644 --- a/Tests/Extension/HttpKernelExtensionTest.php +++ b/Tests/Extension/HttpKernelExtensionTest.php @@ -30,9 +30,10 @@ class HttpKernelExtensionTest extends TestCase { public function testFragmentWithError() { - $this->expectException(\Twig\Error\RuntimeError::class); $renderer = $this->getFragmentHandler($this->throwException(new \Exception('foo'))); + $this->expectException(\Twig\Error\RuntimeError::class); + $this->renderTemplate($renderer); } From 0f192875af2151d2ddcd85472e549904b0103875 Mon Sep 17 00:00:00 2001 From: Oskar Stark Date: Tue, 31 Oct 2023 09:49:02 +0100 Subject: [PATCH 105/167] Fix tests --- .../Extension/FormExtensionDivLayoutTest.php | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Tests/Extension/FormExtensionDivLayoutTest.php b/Tests/Extension/FormExtensionDivLayoutTest.php index 8d7fd35b..cf76f9ee 100644 --- a/Tests/Extension/FormExtensionDivLayoutTest.php +++ b/Tests/Extension/FormExtensionDivLayoutTest.php @@ -295,12 +295,12 @@ public function testLabelHtmlIsTrue() $this->assertMatchesXpath($html, '/label[@for="name"][@class="my&class required"]/b[.="Bolded label"]'); } - protected function renderForm(FormView $view, array $vars = []) + protected function renderForm(FormView $view, array $vars = []): string { return $this->renderer->renderBlock($view, 'form', $vars); } - protected function renderLabel(FormView $view, $label = null, array $vars = []) + protected function renderLabel(FormView $view, $label = null, array $vars = []): string { if (null !== $label) { $vars += ['label' => $label]; @@ -309,42 +309,42 @@ protected function renderLabel(FormView $view, $label = null, array $vars = []) return $this->renderer->searchAndRenderBlock($view, 'label', $vars); } - protected function renderHelp(FormView $view) + protected function renderHelp(FormView $view): string { return $this->renderer->searchAndRenderBlock($view, 'help'); } - protected function renderErrors(FormView $view) + protected function renderErrors(FormView $view): string { return $this->renderer->searchAndRenderBlock($view, 'errors'); } - protected function renderWidget(FormView $view, array $vars = []) + protected function renderWidget(FormView $view, array $vars = []): string { return $this->renderer->searchAndRenderBlock($view, 'widget', $vars); } - protected function renderRow(FormView $view, array $vars = []) + protected function renderRow(FormView $view, array $vars = []): string { return $this->renderer->searchAndRenderBlock($view, 'row', $vars); } - protected function renderRest(FormView $view, array $vars = []) + protected function renderRest(FormView $view, array $vars = []): string { return $this->renderer->searchAndRenderBlock($view, 'rest', $vars); } - protected function renderStart(FormView $view, array $vars = []) + protected function renderStart(FormView $view, array $vars = []): string { return $this->renderer->renderBlock($view, 'form_start', $vars); } - protected function renderEnd(FormView $view, array $vars = []) + protected function renderEnd(FormView $view, array $vars = []): string { return $this->renderer->renderBlock($view, 'form_end', $vars); } - protected function setTheme(FormView $view, array $themes, $useDefaultThemes = true) + protected function setTheme(FormView $view, array $themes, $useDefaultThemes = true): void { $this->renderer->setTheme($view, $themes, $useDefaultThemes); } From 76b43934b95308231fc82a4905fb10eeac31b310 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Sat, 4 Nov 2023 10:00:54 +0100 Subject: [PATCH 106/167] [TwigBridge] Mark CodeExtension as @internal --- Extension/CodeExtension.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Extension/CodeExtension.php b/Extension/CodeExtension.php index 2160b70d..b47f35db 100644 --- a/Extension/CodeExtension.php +++ b/Extension/CodeExtension.php @@ -18,7 +18,12 @@ /** * Twig extension relate to PHP code and used by the profiler and the default exception templates. * + * This extension should only be used for debugging tools code + * that is never executed in a production environment. + * * @author Fabien Potencier + * + * @internal */ final class CodeExtension extends AbstractExtension { From 6da86c9d20806b18ce3469403ddc776c75b17fee Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 7 Nov 2023 13:59:50 +0100 Subject: [PATCH 107/167] [TwigBridge] Fix `@internal` annotation --- Extension/CodeExtension.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Extension/CodeExtension.php b/Extension/CodeExtension.php index b47f35db..96ded292 100644 --- a/Extension/CodeExtension.php +++ b/Extension/CodeExtension.php @@ -23,7 +23,7 @@ * * @author Fabien Potencier * - * @internal + * @internal since Symfony 6.4 */ final class CodeExtension extends AbstractExtension { From be445b22eb26a141fe1b97e5fd763cf7e81f3e50 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 7 Nov 2023 15:08:13 +0100 Subject: [PATCH 108/167] Move CodeExtension from TwigBridge to WebProfilerBundle --- Extension/CodeExtension.php | 246 ------------------------------------ 1 file changed, 246 deletions(-) delete mode 100644 Extension/CodeExtension.php diff --git a/Extension/CodeExtension.php b/Extension/CodeExtension.php deleted file mode 100644 index 96ded292..00000000 --- a/Extension/CodeExtension.php +++ /dev/null @@ -1,246 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Bridge\Twig\Extension; - -use Symfony\Component\ErrorHandler\ErrorRenderer\FileLinkFormatter; -use Twig\Extension\AbstractExtension; -use Twig\TwigFilter; - -/** - * Twig extension relate to PHP code and used by the profiler and the default exception templates. - * - * This extension should only be used for debugging tools code - * that is never executed in a production environment. - * - * @author Fabien Potencier - * - * @internal since Symfony 6.4 - */ -final class CodeExtension extends AbstractExtension -{ - private string|FileLinkFormatter|array|false $fileLinkFormat; - private string $charset; - private string $projectDir; - - public function __construct(string|FileLinkFormatter $fileLinkFormat, string $projectDir, string $charset) - { - $this->fileLinkFormat = $fileLinkFormat ?: \ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format'); - $this->projectDir = str_replace('\\', '/', $projectDir).'/'; - $this->charset = $charset; - } - - public function getFilters(): array - { - return [ - new TwigFilter('abbr_class', $this->abbrClass(...), ['is_safe' => ['html']]), - new TwigFilter('abbr_method', $this->abbrMethod(...), ['is_safe' => ['html']]), - new TwigFilter('format_args', $this->formatArgs(...), ['is_safe' => ['html']]), - new TwigFilter('format_args_as_text', $this->formatArgsAsText(...)), - new TwigFilter('file_excerpt', $this->fileExcerpt(...), ['is_safe' => ['html']]), - new TwigFilter('format_file', $this->formatFile(...), ['is_safe' => ['html']]), - new TwigFilter('format_file_from_text', $this->formatFileFromText(...), ['is_safe' => ['html']]), - new TwigFilter('format_log_message', $this->formatLogMessage(...), ['is_safe' => ['html']]), - new TwigFilter('file_link', $this->getFileLink(...)), - new TwigFilter('file_relative', $this->getFileRelative(...)), - ]; - } - - public function abbrClass(string $class): string - { - $parts = explode('\\', $class); - $short = array_pop($parts); - - return sprintf('%s', $class, $short); - } - - public function abbrMethod(string $method): string - { - if (str_contains($method, '::')) { - [$class, $method] = explode('::', $method, 2); - $result = sprintf('%s::%s()', $this->abbrClass($class), $method); - } elseif ('Closure' === $method) { - $result = sprintf('%1$s', $method); - } else { - $result = sprintf('%1$s()', $method); - } - - return $result; - } - - /** - * Formats an array as a string. - */ - public function formatArgs(array $args): string - { - $result = []; - foreach ($args as $key => $item) { - if ('object' === $item[0]) { - $parts = explode('\\', $item[1]); - $short = array_pop($parts); - $formattedValue = sprintf('object(%s)', $item[1], $short); - } elseif ('array' === $item[0]) { - $formattedValue = sprintf('array(%s)', \is_array($item[1]) ? $this->formatArgs($item[1]) : $item[1]); - } elseif ('null' === $item[0]) { - $formattedValue = 'null'; - } elseif ('boolean' === $item[0]) { - $formattedValue = ''.strtolower(var_export($item[1], true)).''; - } elseif ('resource' === $item[0]) { - $formattedValue = 'resource'; - } else { - $formattedValue = str_replace("\n", '', htmlspecialchars(var_export($item[1], true), \ENT_COMPAT | \ENT_SUBSTITUTE, $this->charset)); - } - - $result[] = \is_int($key) ? $formattedValue : sprintf("'%s' => %s", $key, $formattedValue); - } - - return implode(', ', $result); - } - - /** - * Formats an array as a string. - */ - public function formatArgsAsText(array $args): string - { - return strip_tags($this->formatArgs($args)); - } - - /** - * Returns an excerpt of a code file around the given line number. - */ - public function fileExcerpt(string $file, int $line, int $srcContext = 3): ?string - { - if (is_file($file) && is_readable($file)) { - // highlight_file could throw warnings - // see https://bugs.php.net/25725 - $code = @highlight_file($file, true); - if (\PHP_VERSION_ID >= 80300) { - // remove main pre/code tags - $code = preg_replace('#^\s*(.*)\s*#s', '\\1', $code); - // split multiline code tags - $code = preg_replace_callback('#]++)>((?:[^<]*+\\n)++[^<]*+)#', fn ($m) => "".str_replace("\n", "\n", $m[2]).'', $code); - // Convert spaces to html entities to preserve indentation when rendered - $code = str_replace(' ', ' ', $code); - $content = explode("\n", $code); - } else { - // remove main code/span tags - $code = preg_replace('#^\s*(.*)\s*#s', '\\1', $code); - // split multiline spans - $code = preg_replace_callback('#]++)>((?:[^<]*+
)++[^<]*+)
#', fn ($m) => "".str_replace('
', "

", $m[2]).'', $code); - $content = explode('
', $code); - } - - $lines = []; - if (0 > $srcContext) { - $srcContext = \count($content); - } - - for ($i = max($line - $srcContext, 1), $max = min($line + $srcContext, \count($content)); $i <= $max; ++$i) { - $lines[] = ''.self::fixCodeMarkup($content[$i - 1]).''; - } - - return '
    '.implode("\n", $lines).'
'; - } - - return null; - } - - /** - * Formats a file path. - */ - public function formatFile(string $file, int $line, string $text = null): string - { - $file = trim($file); - - if (null === $text) { - $text = $file; - if (null !== $rel = $this->getFileRelative($text)) { - $rel = explode('/', $rel, 2); - $text = sprintf('%s%s', $this->projectDir, $rel[0], '/'.($rel[1] ?? '')); - } - } - - if (0 < $line) { - $text .= ' at line '.$line; - } - - if (false !== $link = $this->getFileLink($file, $line)) { - return sprintf('%s', htmlspecialchars($link, \ENT_COMPAT | \ENT_SUBSTITUTE, $this->charset), $text); - } - - return $text; - } - - public function getFileLink(string $file, int $line): string|false - { - if ($fmt = $this->fileLinkFormat) { - return \is_string($fmt) ? strtr($fmt, ['%f' => $file, '%l' => $line]) : $fmt->format($file, $line); - } - - return false; - } - - public function getFileRelative(string $file): ?string - { - $file = str_replace('\\', '/', $file); - - if (null !== $this->projectDir && str_starts_with($file, $this->projectDir)) { - return ltrim(substr($file, \strlen($this->projectDir)), '/'); - } - - return null; - } - - public function formatFileFromText(string $text): string - { - return preg_replace_callback('/in ("|")?(.+?)\1(?: +(?:on|at))? +line (\d+)/s', fn ($match) => 'in '.$this->formatFile($match[2], $match[3]), $text); - } - - /** - * @internal - */ - public function formatLogMessage(string $message, array $context): string - { - if ($context && str_contains($message, '{')) { - $replacements = []; - foreach ($context as $key => $val) { - if (\is_scalar($val)) { - $replacements['{'.$key.'}'] = $val; - } - } - - if ($replacements) { - $message = strtr($message, $replacements); - } - } - - return htmlspecialchars($message, \ENT_COMPAT | \ENT_SUBSTITUTE, $this->charset); - } - - protected static function fixCodeMarkup(string $line): string - { - // ending tag from previous line - $opening = strpos($line, ''); - if (false !== $closing && (false === $opening || $closing < $opening)) { - $line = substr_replace($line, '', $closing, 7); - } - - // missing tag at the end of line - $opening = strpos($line, ''); - if (false !== $opening && (false === $closing || $closing > $opening)) { - $line .= ''; - } - - return trim($line); - } -} From 6bd24a13ba07492c2e012f5e8897bc266d26efff Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 7 Nov 2023 18:23:08 +0100 Subject: [PATCH 109/167] [WebProfilerBundle] Fix tests --- Tests/Extension/CodeExtensionTest.php | 69 --------------------------- 1 file changed, 69 deletions(-) delete mode 100644 Tests/Extension/CodeExtensionTest.php diff --git a/Tests/Extension/CodeExtensionTest.php b/Tests/Extension/CodeExtensionTest.php deleted file mode 100644 index ae7cf786..00000000 --- a/Tests/Extension/CodeExtensionTest.php +++ /dev/null @@ -1,69 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Bridge\Twig\Tests\Extension; - -use PHPUnit\Framework\TestCase; -use Symfony\Bridge\Twig\Extension\CodeExtension; -use Symfony\Component\ErrorHandler\ErrorRenderer\FileLinkFormatter; - -class CodeExtensionTest extends TestCase -{ - public function testFormatFile() - { - $expected = sprintf('%s at line 25', substr(__FILE__, 5), __FILE__); - $this->assertEquals($expected, $this->getExtension()->formatFile(__FILE__, 25)); - } - - public function testFileRelative() - { - $this->assertEquals('file.txt', $this->getExtension()->getFileRelative(\DIRECTORY_SEPARATOR.'project'.\DIRECTORY_SEPARATOR.'file.txt')); - } - - /** - * @dataProvider getClassNameProvider - */ - public function testGettingClassAbbreviation($class, $abbr) - { - $this->assertEquals($this->getExtension()->abbrClass($class), $abbr); - } - - /** - * @dataProvider getMethodNameProvider - */ - public function testGettingMethodAbbreviation($method, $abbr) - { - $this->assertEquals($this->getExtension()->abbrMethod($method), $abbr); - } - - public static function getClassNameProvider(): array - { - return [ - ['F\Q\N\Foo', 'Foo'], - ['Bare', 'Bare'], - ]; - } - - public static function getMethodNameProvider(): array - { - return [ - ['F\Q\N\Foo::Method', 'Foo::Method()'], - ['Bare::Method', 'Bare::Method()'], - ['Closure', 'Closure'], - ['Method', 'Method()'], - ]; - } - - protected function getExtension(): CodeExtension - { - return new CodeExtension(new FileLinkFormatter('proto://%f#&line=%l&'.substr(__FILE__, 0, 5).'>foobar'), \DIRECTORY_SEPARATOR.'project', 'UTF-8'); - } -} From 142bc3ad4a61d7eedf7cc21d8ef2bd8a8e7417bf Mon Sep 17 00:00:00 2001 From: Matthias Krauser Date: Fri, 24 Nov 2023 15:50:44 +0100 Subject: [PATCH 110/167] [Mime] Add `TemplatedEmail::$locale` to the serialized props --- Mime/TemplatedEmail.php | 3 ++- Tests/Mime/TemplatedEmailTest.php | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Mime/TemplatedEmail.php b/Mime/TemplatedEmail.php index e5c990f3..2d308947 100644 --- a/Mime/TemplatedEmail.php +++ b/Mime/TemplatedEmail.php @@ -100,7 +100,7 @@ public function markAsRendered(): void */ public function __serialize(): array { - return [$this->htmlTemplate, $this->textTemplate, $this->context, parent::__serialize()]; + return [$this->htmlTemplate, $this->textTemplate, $this->context, parent::__serialize(), $this->locale]; } /** @@ -109,6 +109,7 @@ public function __serialize(): array public function __unserialize(array $data): void { [$this->htmlTemplate, $this->textTemplate, $this->context, $parentData] = $data; + $this->locale = $data[4] ?? null; parent::__unserialize($parentData); } diff --git a/Tests/Mime/TemplatedEmailTest.php b/Tests/Mime/TemplatedEmailTest.php index f796c7a0..81f0edb6 100644 --- a/Tests/Mime/TemplatedEmailTest.php +++ b/Tests/Mime/TemplatedEmailTest.php @@ -43,12 +43,14 @@ public function testSerialize() ->textTemplate('text.txt.twig') ->htmlTemplate('text.html.twig') ->context($context = ['a' => 'b']) + ->locale($locale = 'fr_FR') ; $email = unserialize(serialize($email)); $this->assertEquals('text.txt.twig', $email->getTextTemplate()); $this->assertEquals('text.html.twig', $email->getHtmlTemplate()); $this->assertEquals($context, $email->getContext()); + $this->assertEquals($locale, $email->getLocale()); } public function testSymfonySerialize() From 883b7b035670b02e06f1dd6cbe71fa1dabcdec7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Sun, 26 Nov 2023 16:16:53 +0100 Subject: [PATCH 111/167] Remove legacy Twig_ namespace support --- DataCollector/TwigDataCollector.php | 2 +- Tests/Node/TransNodeTest.php | 6 +----- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/DataCollector/TwigDataCollector.php b/DataCollector/TwigDataCollector.php index e25158af..3994cbe3 100644 --- a/DataCollector/TwigDataCollector.php +++ b/DataCollector/TwigDataCollector.php @@ -131,7 +131,7 @@ public function getHtmlCallGraph(): Markup public function getProfile(): Profile { - return $this->profile ??= unserialize($this->data['profile'], ['allowed_classes' => ['Twig_Profiler_Profile', Profile::class]]); + return $this->profile ??= unserialize($this->data['profile'], ['allowed_classes' => [Profile::class]]); } private function getComputedData(string $index): mixed diff --git a/Tests/Node/TransNodeTest.php b/Tests/Node/TransNodeTest.php index c6d30646..daff6861 100644 --- a/Tests/Node/TransNodeTest.php +++ b/Tests/Node/TransNodeTest.php @@ -50,10 +50,6 @@ protected function getVariableGetterWithoutStrictCheck($name) protected function getVariableGetterWithStrictCheck($name) { - if (Environment::MAJOR_VERSION >= 2) { - return sprintf('(isset($context["%1$s"]) || array_key_exists("%1$s", $context) ? $context["%1$s"] : (function () { throw new %2$s(\'Variable "%1$s" does not exist.\', 0, $this->source); })())', $name, Environment::VERSION_ID >= 20700 ? 'RuntimeError' : 'Twig_Error_Runtime'); - } - - return sprintf('($context["%s"] ?? $this->getContext($context, "%1$s"))', $name); + return sprintf('(isset($context["%1$s"]) || array_key_exists("%1$s", $context) ? $context["%1$s"] : (function () { throw new RuntimeError(\'Variable "%1$s" does not exist.\', 0, $this->source); })())', $name); } } From 3309571f92cf92a00f9c46745f788c11e251ae98 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Mon, 11 Dec 2023 08:59:31 +0100 Subject: [PATCH 112/167] allow Twig 4 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 4c32ee9e..c4c0479a 100644 --- a/composer.json +++ b/composer.json @@ -18,7 +18,7 @@ "require": { "php": ">=8.2", "symfony/translation-contracts": "^2.5|^3", - "twig/twig": "^3.0.4" + "twig/twig": "^3.0.4|^4.0" }, "require-dev": { "egulias/email-validator": "^2.1.10|^3|^4", From 35e7e5114d9709a604b91e3db42cfc12f77c6727 Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Fri, 8 Dec 2023 16:21:59 +0100 Subject: [PATCH 113/167] Use faster hashing algorithms when possible --- NodeVisitor/TranslationDefaultDomainNodeVisitor.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NodeVisitor/TranslationDefaultDomainNodeVisitor.php b/NodeVisitor/TranslationDefaultDomainNodeVisitor.php index d0e3337a..6b023138 100644 --- a/NodeVisitor/TranslationDefaultDomainNodeVisitor.php +++ b/NodeVisitor/TranslationDefaultDomainNodeVisitor.php @@ -114,6 +114,6 @@ private function isNamedArguments(Node $arguments): bool private function getVarName(): string { - return sprintf('__internal_%s', hash('sha256', uniqid(mt_rand(), true), false)); + return sprintf('__internal_%s', hash('xxh128', uniqid(mt_rand(), true))); } } From 551ac865875c1de5cb1e8f2fb80877608ebc4a54 Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Mon, 18 Dec 2023 08:46:12 +0100 Subject: [PATCH 114/167] Code updates --- Command/DebugCommand.php | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/Command/DebugCommand.php b/Command/DebugCommand.php index b18100cb..1e1c446d 100644 --- a/Command/DebugCommand.php +++ b/Command/DebugCommand.php @@ -587,11 +587,7 @@ private function getFilesystemLoaders(): array private function getFileLink(string $absolutePath): string { - if (null === $this->fileLinkFormatter) { - return ''; - } - - return (string) $this->fileLinkFormatter->format($absolutePath, 1); + return (string) $this->fileLinkFormatter?->format($absolutePath, 1); } private function getAvailableFormatOptions(): array From 33657a87363eb1c2bccce1f4bdfae16625f919dc Mon Sep 17 00:00:00 2001 From: Nicolas Rigaud Date: Wed, 11 Oct 2023 11:46:30 +0200 Subject: [PATCH 115/167] [DoctrineBridge][TwigBridge] Add PHPDoc to attributes --- Attribute/Template.php | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/Attribute/Template.php b/Attribute/Template.php index f094f42a..e265e239 100644 --- a/Attribute/Template.php +++ b/Attribute/Template.php @@ -11,23 +11,20 @@ namespace Symfony\Bridge\Twig\Attribute; +/** + * Define the template to render in the controller. + */ #[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::TARGET_FUNCTION)] class Template { + /** + * @param string $template The name of the template to render + * @param string[]|null $vars The controller method arguments to pass to the template + * @param bool $stream Enables streaming the template + */ public function __construct( - /** - * The name of the template to render. - */ public string $template, - - /** - * The controller method arguments to pass to the template. - */ public ?array $vars = null, - - /** - * Enables streaming the template. - */ public bool $stream = false, ) { } From b603a37aeda360e435e2b8df7d76b77ac0452a24 Mon Sep 17 00:00:00 2001 From: mboultoureau Date: Fri, 3 Nov 2023 22:00:57 +0100 Subject: [PATCH 116/167] [Form] Add option `separator` to `ChoiceType` to use a custom separator after preferred choices --- Resources/views/Form/form_div_layout.html.twig | 6 +++++- Resources/views/Form/foundation_5_layout.html.twig | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/Resources/views/Form/form_div_layout.html.twig b/Resources/views/Form/form_div_layout.html.twig index 29cfc2dc..d57a4e11 100644 --- a/Resources/views/Form/form_div_layout.html.twig +++ b/Resources/views/Form/form_div_layout.html.twig @@ -68,7 +68,11 @@ {% set render_preferred_choices = true %} {{- block('choice_widget_options') -}} {%- if choices|length > 0 and separator is not none -%} - + {%- if separator_html is not defined or separator_html is same as(false) -%} + + {% else %} + {{ separator|raw }} + {% endif %} {%- endif -%} {%- endif -%} {%- set options = choices -%} diff --git a/Resources/views/Form/foundation_5_layout.html.twig b/Resources/views/Form/foundation_5_layout.html.twig index 4040f9a7..fe5af460 100644 --- a/Resources/views/Form/foundation_5_layout.html.twig +++ b/Resources/views/Form/foundation_5_layout.html.twig @@ -163,7 +163,11 @@ {% set render_preferred_choices = true %} {{- block('choice_widget_options') -}} {% if choices|length > 0 and separator is not none -%} - + {%- if separator_html is not defined or separator_html is same as(false) -%} + + {% else %} + {{ separator|raw }} + {% endif %} {%- endif %} {%- endif -%} {% set options = choices -%} From 07a4df458836acf07302121648426caad90182c0 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Wed, 10 Jan 2024 12:28:47 +0100 Subject: [PATCH 117/167] do not mock the RequestStack class --- Tests/AppVariableTest.php | 11 +++++++---- Tests/Extension/HttpKernelExtensionTest.php | 7 +++---- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/Tests/AppVariableTest.php b/Tests/AppVariableTest.php index beed252e..0367f770 100644 --- a/Tests/AppVariableTest.php +++ b/Tests/AppVariableTest.php @@ -291,12 +291,15 @@ public function testGetCurrentRouteParametersWithRequestStackNotSet() $this->appVariable->getCurrent_route_parameters(); } - protected function setRequestStack($request) + protected function setRequestStack(?Request $request) { - $requestStackMock = $this->createMock(RequestStack::class); - $requestStackMock->method('getCurrentRequest')->willReturn($request); + $requestStack = new RequestStack(); - $this->appVariable->setRequestStack($requestStackMock); + if (null !== $request) { + $requestStack->push($request); + } + + $this->appVariable->setRequestStack($requestStack); } protected function setTokenStorage($user) diff --git a/Tests/Extension/HttpKernelExtensionTest.php b/Tests/Extension/HttpKernelExtensionTest.php index 5bce112d..c214bcd8 100644 --- a/Tests/Extension/HttpKernelExtensionTest.php +++ b/Tests/Extension/HttpKernelExtensionTest.php @@ -48,8 +48,7 @@ public function testRenderFragment() public function testUnknownFragmentRenderer() { - $context = $this->createMock(RequestStack::class); - $renderer = new FragmentHandler($context); + $renderer = new FragmentHandler(new RequestStack()); $this->expectException(\InvalidArgumentException::class); $this->expectExceptionMessage('The "inline" renderer does not exist.'); @@ -90,9 +89,9 @@ protected function getFragmentHandler($return) $strategy->expects($this->once())->method('getName')->willReturn('inline'); $strategy->expects($this->once())->method('render')->will($return); - $context = $this->createMock(RequestStack::class); + $context = new RequestStack(); - $context->expects($this->any())->method('getCurrentRequest')->willReturn(Request::create('/')); + $context->push(Request::create('/')); return new FragmentHandler($context, [$strategy], false); } From 370d5be8c36642812719ebafa84d29ad4c78ce0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20Andr=C3=A9?= Date: Sat, 13 Jan 2024 23:00:11 +0100 Subject: [PATCH 118/167] [TwigBridge] Update the undefined callable list --- UndefinedCallableHandler.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/UndefinedCallableHandler.php b/UndefinedCallableHandler.php index ede634e1..0ae2893c 100644 --- a/UndefinedCallableHandler.php +++ b/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', From 935cb2c506edeb754a20d3c82e62445372bd854d Mon Sep 17 00:00:00 2001 From: Matthias Pigulla Date: Sun, 28 Jan 2024 16:49:46 +0100 Subject: [PATCH 119/167] Revert #47715 --- Resources/views/Form/form_div_layout.html.twig | 10 +++++----- Test/FormLayoutTestCase.php | 2 -- Tests/Extension/AbstractBootstrap3LayoutTestCase.php | 4 ++-- Tests/Extension/AbstractLayoutTestCase.php | 4 ++-- .../Extension/Fixtures/templates/form/theme.html.twig | 2 +- .../Fixtures/templates/form/theme_extends.html.twig | 2 +- .../Fixtures/templates/form/theme_use.html.twig | 2 +- Tests/Extension/FormExtensionBootstrap3LayoutTest.php | 2 +- Tests/Extension/FormExtensionBootstrap4LayoutTest.php | 2 +- Tests/Extension/FormExtensionBootstrap5LayoutTest.php | 2 +- Tests/Extension/FormExtensionDivLayoutTest.php | 2 +- 11 files changed, 16 insertions(+), 18 deletions(-) diff --git a/Resources/views/Form/form_div_layout.html.twig b/Resources/views/Form/form_div_layout.html.twig index 29cfc2dc..02628b5a 100644 --- a/Resources/views/Form/form_div_layout.html.twig +++ b/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 -%} @@ -91,11 +91,11 @@ {%- endblock choice_widget_options -%} {%- block checkbox_widget -%} - + {%- endblock checkbox_widget -%} {%- block radio_widget -%} - + {%- endblock radio_widget -%} {%- block datetime_widget -%} @@ -402,7 +402,7 @@ {%- endif -%} {%- if form_method != method -%} - + {%- endif -%} {%- endblock form_start -%} @@ -440,7 +440,7 @@ {%- endif -%} {%- if form_method != method -%} - + {%- endif -%} {% endif -%} {% endblock form_rest %} diff --git a/Test/FormLayoutTestCase.php b/Test/FormLayoutTestCase.php index 71a71530..1fdd83c9 100644 --- a/Test/FormLayoutTestCase.php +++ b/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/Tests/Extension/AbstractBootstrap3LayoutTestCase.php b/Tests/Extension/AbstractBootstrap3LayoutTestCase.php index 5b02b695..08a026fe 100644 --- a/Tests/Extension/AbstractBootstrap3LayoutTestCase.php +++ b/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/Tests/Extension/AbstractLayoutTestCase.php b/Tests/Extension/AbstractLayoutTestCase.php index 6d98620d..f340b066 100644 --- a/Tests/Extension/AbstractLayoutTestCase.php +++ b/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() diff --git a/Tests/Extension/Fixtures/templates/form/theme.html.twig b/Tests/Extension/Fixtures/templates/form/theme.html.twig index 3ec513a4..e8816be9 100644 --- a/Tests/Extension/Fixtures/templates/form/theme.html.twig +++ b/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/Tests/Extension/Fixtures/templates/form/theme_extends.html.twig b/Tests/Extension/Fixtures/templates/form/theme_extends.html.twig index 2b9118a2..501b555e 100644 --- a/Tests/Extension/Fixtures/templates/form/theme_extends.html.twig +++ b/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/Tests/Extension/Fixtures/templates/form/theme_use.html.twig b/Tests/Extension/Fixtures/templates/form/theme_use.html.twig index e05de5ac..37150734 100644 --- a/Tests/Extension/Fixtures/templates/form/theme_use.html.twig +++ b/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/Tests/Extension/FormExtensionBootstrap3LayoutTest.php b/Tests/Extension/FormExtensionBootstrap3LayoutTest.php index 90a17563..7c3742a7 100644 --- a/Tests/Extension/FormExtensionBootstrap3LayoutTest.php +++ b/Tests/Extension/FormExtensionBootstrap3LayoutTest.php @@ -71,7 +71,7 @@ public function testMoneyWidgetInIso() $this->assertSame(<<<'HTML'
-
+ HTML , trim($this->renderWidget($view))); } diff --git a/Tests/Extension/FormExtensionBootstrap4LayoutTest.php b/Tests/Extension/FormExtensionBootstrap4LayoutTest.php index bffebe3f..5fdec71d 100644 --- a/Tests/Extension/FormExtensionBootstrap4LayoutTest.php +++ b/Tests/Extension/FormExtensionBootstrap4LayoutTest.php @@ -76,7 +76,7 @@ public function testMoneyWidgetInIso() $this->assertSame(<<<'HTML'
-
+ HTML , trim($this->renderWidget($view))); } diff --git a/Tests/Extension/FormExtensionBootstrap5LayoutTest.php b/Tests/Extension/FormExtensionBootstrap5LayoutTest.php index e7e537ac..ced0fe60 100644 --- a/Tests/Extension/FormExtensionBootstrap5LayoutTest.php +++ b/Tests/Extension/FormExtensionBootstrap5LayoutTest.php @@ -75,7 +75,7 @@ public function testMoneyWidgetInIso() ->createView(); self::assertSame(<<<'HTML' -
+
HTML , trim($this->renderWidget($view))); } diff --git a/Tests/Extension/FormExtensionDivLayoutTest.php b/Tests/Extension/FormExtensionDivLayoutTest.php index cf76f9ee..ad2627a2 100644 --- a/Tests/Extension/FormExtensionDivLayoutTest.php +++ b/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() From 98e88cd0a9607d8e5c33778371e8c0508f75f86d Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Mon, 29 Jan 2024 16:11:35 +0100 Subject: [PATCH 120/167] Fix merge --- Tests/Mime/NotificationEmailTest.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Tests/Mime/NotificationEmailTest.php b/Tests/Mime/NotificationEmailTest.php index e7ac5b13..979f2791 100644 --- a/Tests/Mime/NotificationEmailTest.php +++ b/Tests/Mime/NotificationEmailTest.php @@ -142,7 +142,7 @@ public function testContext() 'action_url' => null, 'markdown' => false, 'raw' => false, - 'footer_text' => 'Notification e-mail sent by Symfony', + 'footer_text' => 'Notification email sent by Symfony', 'some' => 'context', ], $email->getContext()); @@ -158,7 +158,7 @@ public function testContext() 'action_url' => null, 'markdown' => false, 'raw' => false, - 'footer_text' => 'Notification e-mail sent by Symfony', + 'footer_text' => 'Notification email sent by Symfony', 'some' => 'context', 'foo' => 'bar', ], $email->getContext()); @@ -173,7 +173,7 @@ public function testContext() 'action_url' => 'Action URL', 'markdown' => false, 'raw' => false, - 'footer_text' => 'Notification e-mail sent by Symfony', + 'footer_text' => 'Notification email sent by Symfony', 'some' => 'context', 'foo' => 'bar', ], $email->getContext()); From 5c988028a11a1df023509cac57841fbd36611359 Mon Sep 17 00:00:00 2001 From: Antoine M Date: Mon, 3 Jul 2023 19:05:58 +0200 Subject: [PATCH 121/167] [TwigBridge] Allow `twig:lint` to excludes dirs --- Command/LintCommand.php | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/Command/LintCommand.php b/Command/LintCommand.php index e059740a..d58cf38d 100644 --- a/Command/LintCommand.php +++ b/Command/LintCommand.php @@ -57,6 +57,7 @@ protected function configure() ->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 directory', []) ->setHelp(<<<'EOF' The %command.name% command lints a template and outputs to STDOUT the first encountered syntax error. @@ -84,6 +85,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $io = new SymfonyStyle($input, $output); $filenames = $input->getArgument('filename'); $showDeprecations = $input->getOption('show-deprecations'); + $excludes = $input->getOption('excludes'); $this->format = $input->getOption('format') ?? (GithubActionReporter::isGithubActionEnvironment() ? 'github' : 'txt'); if (['-'] === $filenames) { @@ -121,7 +123,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int } try { - $filesInfo = $this->getFilesInfo($filenames); + $filesInfo = $this->getFilesInfo($filenames, $excludes); } finally { if ($showDeprecations) { restore_error_handler(); @@ -131,11 +133,11 @@ protected function execute(InputInterface $input, OutputInterface $output): int return $this->display($input, $output, $io, $filesInfo); } - private function getFilesInfo(array $filenames): array + private function getFilesInfo(array $filenames, array $excludes): array { $filesInfo = []; foreach ($filenames as $filename) { - foreach ($this->findFiles($filename) as $file) { + foreach ($this->findFiles($filename, $excludes) as $file) { $filesInfo[] = $this->validate(file_get_contents($file), $file); } } @@ -143,12 +145,12 @@ private function getFilesInfo(array $filenames): array return $filesInfo; } - protected function findFiles(string $filename): iterable + protected function findFiles(string $filename, array $excludes): 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($excludes); } throw new RuntimeException(sprintf('File or directory "%s" is not readable.', $filename)); From 52fee708079489e4423eef3e7f46a9c55719967a Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Sat, 3 Feb 2024 19:32:04 +0100 Subject: [PATCH 122/167] rework the way excluded directories are handled This change ensures that we not break BC by reverting the signature change made to the protected findFiles() method. --- Command/LintCommand.php | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/Command/LintCommand.php b/Command/LintCommand.php index e856b847..14c00ba1 100644 --- a/Command/LintCommand.php +++ b/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,7 +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 directory', []) + ->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. @@ -82,7 +83,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $io = new SymfonyStyle($input, $output); $filenames = $input->getArgument('filename'); $showDeprecations = $input->getOption('show-deprecations'); - $excludes = $input->getOption('excludes'); + $this->excludes = $input->getOption('excludes'); $this->format = $input->getOption('format') ?? (GithubActionReporter::isGithubActionEnvironment() ? 'github' : 'txt'); if (['-'] === $filenames) { @@ -120,7 +121,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int } try { - $filesInfo = $this->getFilesInfo($filenames, $excludes); + $filesInfo = $this->getFilesInfo($filenames); } finally { if ($showDeprecations) { restore_error_handler(); @@ -130,11 +131,11 @@ protected function execute(InputInterface $input, OutputInterface $output): int return $this->display($input, $output, $io, $filesInfo); } - private function getFilesInfo(array $filenames, array $excludes): array + private function getFilesInfo(array $filenames): array { $filesInfo = []; foreach ($filenames as $filename) { - foreach ($this->findFiles($filename, $excludes) as $file) { + foreach ($this->findFiles($filename) as $file) { $filesInfo[] = $this->validate(file_get_contents($file), $file); } } @@ -142,12 +143,12 @@ private function getFilesInfo(array $filenames, array $excludes): array return $filesInfo; } - protected function findFiles(string $filename, array $excludes): iterable + 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)->exclude($excludes); + return Finder::create()->files()->in($filename)->name($this->namePatterns)->exclude($this->excludes); } throw new RuntimeException(sprintf('File or directory "%s" is not readable.', $filename)); From db6ac9be79fe3b30d538d4fc9cbe6386edefdd9b Mon Sep 17 00:00:00 2001 From: wetternest Date: Tue, 13 Feb 2024 13:54:05 +0100 Subject: [PATCH 123/167] [TwigBridge] foundation 5 layout: use form_label_content block for checkbox and radio labels --- Resources/views/Form/foundation_5_layout.html.twig | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Resources/views/Form/foundation_5_layout.html.twig b/Resources/views/Form/foundation_5_layout.html.twig index 4040f9a7..78dbe0d8 100644 --- a/Resources/views/Form/foundation_5_layout.html.twig +++ b/Resources/views/Form/foundation_5_layout.html.twig @@ -269,7 +269,9 @@ {% endif %} {{ widget|raw }} - {{ translation_domain is same as(false) ? label : label|trans(label_translation_parameters, translation_domain) }} + {%- if label is not same as(false) -%} + {{- block('form_label_content') -}} + {%- endif -%} {%- endblock checkbox_radio_label %} From e1f9531501e422ee880b06b313672dbc357fcd16 Mon Sep 17 00:00:00 2001 From: connor Date: Tue, 12 Mar 2024 07:50:01 +0100 Subject: [PATCH 124/167] [TwigBridge] Use CPP --- Command/DebugCommand.php | 22 +++++++--------------- DataCollector/TwigDataCollector.php | 10 ++++------ ErrorRenderer/TwigErrorRenderer.php | 9 +++++---- Extension/AssetExtension.php | 8 +++----- Extension/CsrfRuntime.php | 8 +++----- Extension/DumpExtension.php | 11 ++++------- Extension/FormExtension.php | 8 +++----- Extension/HttpFoundationExtension.php | 8 +++----- Extension/HttpKernelRuntime.php | 11 ++++------- Extension/ImportMapRuntime.php | 5 +++-- Extension/LogoutUrlExtension.php | 8 +++----- Extension/ProfilerExtension.php | 9 ++++----- Extension/RoutingExtension.php | 8 +++----- Extension/SecurityExtension.php | 11 ++++------- Extension/SerializerRuntime.php | 8 +++----- Extension/StopwatchExtension.php | 11 ++++------- Extension/TranslationExtension.php | 11 ++++------- Extension/WebLinkExtension.php | 8 +++----- Extension/WorkflowExtension.php | 8 +++----- Form/TwigRendererEngine.php | 8 ++++---- Mime/BodyRenderer.php | 14 ++++++-------- Mime/WrappedTemplatedEmail.php | 11 ++++------- Node/DumpNode.php | 10 ++++++---- NodeVisitor/Scope.php | 7 +++---- TokenParser/StopwatchTokenParser.php | 8 +++----- Translation/TwigExtractor.php | 8 +++----- 26 files changed, 99 insertions(+), 149 deletions(-) diff --git a/Command/DebugCommand.php b/Command/DebugCommand.php index 43fab03c..21ede17a 100644 --- a/Command/DebugCommand.php +++ b/Command/DebugCommand.php @@ -36,27 +36,19 @@ #[AsCommand(name: 'debug:twig', description: 'Show a list of twig functions, filters, globals and tests')] class DebugCommand extends Command { - private Environment $twig; - private ?string $projectDir; - private array $bundlesMetadata; - private ?string $twigDefaultPath; - /** * @var FilesystemLoader[] */ private array $filesystemLoaders; - private ?FileLinkFormatter $fileLinkFormatter; - - public function __construct(Environment $twig, ?string $projectDir = null, array $bundlesMetadata = [], ?string $twigDefaultPath = null, ?FileLinkFormatter $fileLinkFormatter = null) - { + public function __construct( + private Environment $twig, + private ?string $projectDir = null, + private array $bundlesMetadata = [], + private ?string $twigDefaultPath = null, + private ?FileLinkFormatter $fileLinkFormatter = null, + ) { parent::__construct(); - - $this->twig = $twig; - $this->projectDir = $projectDir; - $this->bundlesMetadata = $bundlesMetadata; - $this->twigDefaultPath = $twigDefaultPath; - $this->fileLinkFormatter = $fileLinkFormatter; } protected function configure(): void diff --git a/DataCollector/TwigDataCollector.php b/DataCollector/TwigDataCollector.php index a5786d2f..f63d85a6 100644 --- a/DataCollector/TwigDataCollector.php +++ b/DataCollector/TwigDataCollector.php @@ -28,14 +28,12 @@ */ class TwigDataCollector extends DataCollector implements LateDataCollectorInterface { - private Profile $profile; - private ?Environment $twig; private array $computed; - public function __construct(Profile $profile, ?Environment $twig = null) - { - $this->profile = $profile; - $this->twig = $twig; + public function __construct( + private Profile $profile, + private ?Environment $twig = null, + ) { } public function collect(Request $request, Response $response, ?\Throwable $exception = null): void diff --git a/ErrorRenderer/TwigErrorRenderer.php b/ErrorRenderer/TwigErrorRenderer.php index 50d8b44d..0ea9b9aa 100644 --- a/ErrorRenderer/TwigErrorRenderer.php +++ b/ErrorRenderer/TwigErrorRenderer.php @@ -25,16 +25,17 @@ */ class TwigErrorRenderer implements ErrorRendererInterface { - private Environment $twig; private HtmlErrorRenderer $fallbackErrorRenderer; private \Closure|bool $debug; /** * @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) - { - $this->twig = $twig; + public function __construct( + private Environment $twig, + ?HtmlErrorRenderer $fallbackErrorRenderer = null, + bool|callable $debug = false, + ) { $this->fallbackErrorRenderer = $fallbackErrorRenderer ?? new HtmlErrorRenderer(); $this->debug = \is_bool($debug) ? $debug : $debug(...); } diff --git a/Extension/AssetExtension.php b/Extension/AssetExtension.php index 7a7aba0d..ce9fee72 100644 --- a/Extension/AssetExtension.php +++ b/Extension/AssetExtension.php @@ -22,11 +22,9 @@ */ final class AssetExtension extends AbstractExtension { - private Packages $packages; - - public function __construct(Packages $packages) - { - $this->packages = $packages; + public function __construct( + private Packages $packages, + ) { } public function getFunctions(): array diff --git a/Extension/CsrfRuntime.php b/Extension/CsrfRuntime.php index 216d9c92..29267116 100644 --- a/Extension/CsrfRuntime.php +++ b/Extension/CsrfRuntime.php @@ -19,11 +19,9 @@ */ final class CsrfRuntime { - private CsrfTokenManagerInterface $csrfTokenManager; - - public function __construct(CsrfTokenManagerInterface $csrfTokenManager) - { - $this->csrfTokenManager = $csrfTokenManager; + public function __construct( + private CsrfTokenManagerInterface $csrfTokenManager, + ) { } public function getCsrfToken(string $tokenId): string diff --git a/Extension/DumpExtension.php b/Extension/DumpExtension.php index 1bf2beee..a9006165 100644 --- a/Extension/DumpExtension.php +++ b/Extension/DumpExtension.php @@ -26,13 +26,10 @@ */ final class DumpExtension extends AbstractExtension { - private ClonerInterface $cloner; - private ?HtmlDumper $dumper; - - public function __construct(ClonerInterface $cloner, ?HtmlDumper $dumper = null) - { - $this->cloner = $cloner; - $this->dumper = $dumper; + public function __construct( + private ClonerInterface $cloner, + private ?HtmlDumper $dumper = null, + ) { } public function getFunctions(): array diff --git a/Extension/FormExtension.php b/Extension/FormExtension.php index 673f8199..01415414 100644 --- a/Extension/FormExtension.php +++ b/Extension/FormExtension.php @@ -33,11 +33,9 @@ */ final class FormExtension extends AbstractExtension { - private ?TranslatorInterface $translator; - - public function __construct(?TranslatorInterface $translator = null) - { - $this->translator = $translator; + public function __construct( + private ?TranslatorInterface $translator = null, + ) { } public function getTokenParsers(): array diff --git a/Extension/HttpFoundationExtension.php b/Extension/HttpFoundationExtension.php index 938d3dda..e06f1b39 100644 --- a/Extension/HttpFoundationExtension.php +++ b/Extension/HttpFoundationExtension.php @@ -23,11 +23,9 @@ */ final class HttpFoundationExtension extends AbstractExtension { - private UrlHelper $urlHelper; - - public function __construct(UrlHelper $urlHelper) - { - $this->urlHelper = $urlHelper; + public function __construct( + private UrlHelper $urlHelper, + ) { } public function getFunctions(): array diff --git a/Extension/HttpKernelRuntime.php b/Extension/HttpKernelRuntime.php index 5456de33..0aefed8f 100644 --- a/Extension/HttpKernelRuntime.php +++ b/Extension/HttpKernelRuntime.php @@ -22,13 +22,10 @@ */ final class HttpKernelRuntime { - private FragmentHandler $handler; - private ?FragmentUriGeneratorInterface $fragmentUriGenerator; - - public function __construct(FragmentHandler $handler, ?FragmentUriGeneratorInterface $fragmentUriGenerator = null) - { - $this->handler = $handler; - $this->fragmentUriGenerator = $fragmentUriGenerator; + public function __construct( + private FragmentHandler $handler, + private ?FragmentUriGeneratorInterface $fragmentUriGenerator = null, + ) { } /** diff --git a/Extension/ImportMapRuntime.php b/Extension/ImportMapRuntime.php index 97632a2b..902e0a42 100644 --- a/Extension/ImportMapRuntime.php +++ b/Extension/ImportMapRuntime.php @@ -18,8 +18,9 @@ */ class ImportMapRuntime { - public function __construct(private readonly ImportMapRenderer $importMapRenderer) - { + public function __construct( + private readonly ImportMapRenderer $importMapRenderer, + ) { } public function importmap(string|array $entryPoint = 'app', array $attributes = []): string diff --git a/Extension/LogoutUrlExtension.php b/Extension/LogoutUrlExtension.php index a576a6dd..15089d3c 100644 --- a/Extension/LogoutUrlExtension.php +++ b/Extension/LogoutUrlExtension.php @@ -22,11 +22,9 @@ */ final class LogoutUrlExtension extends AbstractExtension { - private LogoutUrlGenerator $generator; - - public function __construct(LogoutUrlGenerator $generator) - { - $this->generator = $generator; + public function __construct( + private LogoutUrlGenerator $generator, + ) { } public function getFunctions(): array diff --git a/Extension/ProfilerExtension.php b/Extension/ProfilerExtension.php index ab56f22a..2dbc4ec4 100644 --- a/Extension/ProfilerExtension.php +++ b/Extension/ProfilerExtension.php @@ -21,18 +21,17 @@ */ final class ProfilerExtension extends BaseProfilerExtension { - private ?Stopwatch $stopwatch; - /** * @var \SplObjectStorage */ private \SplObjectStorage $events; - public function __construct(Profile $profile, ?Stopwatch $stopwatch = null) - { + public function __construct( + Profile $profile, + private ?Stopwatch $stopwatch = null, + ) { parent::__construct($profile); - $this->stopwatch = $stopwatch; $this->events = new \SplObjectStorage(); } diff --git a/Extension/RoutingExtension.php b/Extension/RoutingExtension.php index 5827640d..eace5232 100644 --- a/Extension/RoutingExtension.php +++ b/Extension/RoutingExtension.php @@ -25,11 +25,9 @@ */ final class RoutingExtension extends AbstractExtension { - private UrlGeneratorInterface $generator; - - public function __construct(UrlGeneratorInterface $generator) - { - $this->generator = $generator; + public function __construct( + private UrlGeneratorInterface $generator, + ) { } public function getFunctions(): array diff --git a/Extension/SecurityExtension.php b/Extension/SecurityExtension.php index c94912e3..863df156 100644 --- a/Extension/SecurityExtension.php +++ b/Extension/SecurityExtension.php @@ -25,13 +25,10 @@ */ final class SecurityExtension extends AbstractExtension { - private ?AuthorizationCheckerInterface $securityChecker; - private ?ImpersonateUrlGenerator $impersonateUrlGenerator; - - public function __construct(?AuthorizationCheckerInterface $securityChecker = null, ?ImpersonateUrlGenerator $impersonateUrlGenerator = null) - { - $this->securityChecker = $securityChecker; - $this->impersonateUrlGenerator = $impersonateUrlGenerator; + public function __construct( + private ?AuthorizationCheckerInterface $securityChecker = null, + private ?ImpersonateUrlGenerator $impersonateUrlGenerator = null, + ) { } public function isGranted(mixed $role, mixed $object = null, ?string $field = null): bool diff --git a/Extension/SerializerRuntime.php b/Extension/SerializerRuntime.php index b48be3aa..22715733 100644 --- a/Extension/SerializerRuntime.php +++ b/Extension/SerializerRuntime.php @@ -19,11 +19,9 @@ */ final class SerializerRuntime implements RuntimeExtensionInterface { - private SerializerInterface $serializer; - - public function __construct(SerializerInterface $serializer) - { - $this->serializer = $serializer; + public function __construct( + private SerializerInterface $serializer, + ) { } public function serialize(mixed $data, string $format = 'json', array $context = []): string diff --git a/Extension/StopwatchExtension.php b/Extension/StopwatchExtension.php index 49df52cf..ba56d127 100644 --- a/Extension/StopwatchExtension.php +++ b/Extension/StopwatchExtension.php @@ -23,13 +23,10 @@ */ final class StopwatchExtension extends AbstractExtension { - private ?Stopwatch $stopwatch; - private bool $enabled; - - public function __construct(?Stopwatch $stopwatch = null, bool $enabled = true) - { - $this->stopwatch = $stopwatch; - $this->enabled = $enabled; + public function __construct( + private ?Stopwatch $stopwatch = null, + private bool $enabled = true, + ) { } public function getStopwatch(): Stopwatch diff --git a/Extension/TranslationExtension.php b/Extension/TranslationExtension.php index ba5758f3..bf8b81bd 100644 --- a/Extension/TranslationExtension.php +++ b/Extension/TranslationExtension.php @@ -34,13 +34,10 @@ class_exists(TranslatorTrait::class); */ final class TranslationExtension extends AbstractExtension { - private ?TranslatorInterface $translator; - private ?TranslationNodeVisitor $translationNodeVisitor; - - public function __construct(?TranslatorInterface $translator = null, ?TranslationNodeVisitor $translationNodeVisitor = null) - { - $this->translator = $translator; - $this->translationNodeVisitor = $translationNodeVisitor; + public function __construct( + private ?TranslatorInterface $translator = null, + private ?TranslationNodeVisitor $translationNodeVisitor = null, + ) { } public function getTranslator(): TranslatorInterface diff --git a/Extension/WebLinkExtension.php b/Extension/WebLinkExtension.php index 11eca517..9eeb305a 100644 --- a/Extension/WebLinkExtension.php +++ b/Extension/WebLinkExtension.php @@ -24,11 +24,9 @@ */ final class WebLinkExtension extends AbstractExtension { - private RequestStack $requestStack; - - public function __construct(RequestStack $requestStack) - { - $this->requestStack = $requestStack; + public function __construct( + private RequestStack $requestStack, + ) { } public function getFunctions(): array diff --git a/Extension/WorkflowExtension.php b/Extension/WorkflowExtension.php index b50130cc..0fcc9b3f 100644 --- a/Extension/WorkflowExtension.php +++ b/Extension/WorkflowExtension.php @@ -25,11 +25,9 @@ */ final class WorkflowExtension extends AbstractExtension { - private Registry $workflowRegistry; - - public function __construct(Registry $workflowRegistry) - { - $this->workflowRegistry = $workflowRegistry; + public function __construct( + private Registry $workflowRegistry, + ) { } public function getFunctions(): array diff --git a/Form/TwigRendererEngine.php b/Form/TwigRendererEngine.php index d07e6e1c..ff5568e0 100644 --- a/Form/TwigRendererEngine.php +++ b/Form/TwigRendererEngine.php @@ -21,13 +21,13 @@ */ class TwigRendererEngine extends AbstractRendererEngine { - private Environment $environment; private Template $template; - public function __construct(array $defaultThemes, Environment $environment) - { + public function __construct( + array $defaultThemes, + private Environment $environment, + ) { parent::__construct($defaultThemes); - $this->environment = $environment; } public function renderBlock(FormView $view, mixed $resource, string $blockName, array $variables = []): string diff --git a/Mime/BodyRenderer.php b/Mime/BodyRenderer.php index d5b6d14c..25d87353 100644 --- a/Mime/BodyRenderer.php +++ b/Mime/BodyRenderer.php @@ -26,17 +26,15 @@ */ final class BodyRenderer implements BodyRendererInterface { - private Environment $twig; - private array $context; private HtmlToTextConverterInterface $converter; - private ?LocaleSwitcher $localeSwitcher = null; - public function __construct(Environment $twig, array $context = [], ?HtmlToTextConverterInterface $converter = null, ?LocaleSwitcher $localeSwitcher = null) - { - $this->twig = $twig; - $this->context = $context; + public function __construct( + private Environment $twig, + private array $context = [], + ?HtmlToTextConverterInterface $converter = null, + private ?LocaleSwitcher $localeSwitcher = null, + ) { $this->converter = $converter ?: (interface_exists(HtmlConverterInterface::class) ? new LeagueHtmlToMarkdownConverter() : new DefaultHtmlToTextConverter()); - $this->localeSwitcher = $localeSwitcher; } public function render(Message $message): void diff --git a/Mime/WrappedTemplatedEmail.php b/Mime/WrappedTemplatedEmail.php index e72335a5..a327e94b 100644 --- a/Mime/WrappedTemplatedEmail.php +++ b/Mime/WrappedTemplatedEmail.php @@ -23,13 +23,10 @@ */ final class WrappedTemplatedEmail { - private Environment $twig; - private TemplatedEmail $message; - - public function __construct(Environment $twig, TemplatedEmail $message) - { - $this->twig = $twig; - $this->message = $message; + public function __construct( + private Environment $twig, + private TemplatedEmail $message, + ) { } public function toName(): string diff --git a/Node/DumpNode.php b/Node/DumpNode.php index 9b736da2..5c3ac6ca 100644 --- a/Node/DumpNode.php +++ b/Node/DumpNode.php @@ -21,10 +21,12 @@ #[YieldReady] final class DumpNode extends Node { - private string $varPrefix; - - public function __construct(string $varPrefix, ?Node $values, int $lineno, ?string $tag = null) - { + public function __construct( + private string $varPrefix, + ?Node $values, + int $lineno, + ?string $tag = null, + ) { $nodes = []; if (null !== $values) { $nodes['values'] = $values; diff --git a/NodeVisitor/Scope.php b/NodeVisitor/Scope.php index 66904b09..4914506f 100644 --- a/NodeVisitor/Scope.php +++ b/NodeVisitor/Scope.php @@ -16,13 +16,12 @@ */ class Scope { - private ?self $parent; private array $data = []; private bool $left = false; - public function __construct(?self $parent = null) - { - $this->parent = $parent; + public function __construct( + private ?self $parent = null, + ) { } /** diff --git a/TokenParser/StopwatchTokenParser.php b/TokenParser/StopwatchTokenParser.php index b332485d..810e7c27 100644 --- a/TokenParser/StopwatchTokenParser.php +++ b/TokenParser/StopwatchTokenParser.php @@ -24,11 +24,9 @@ */ final class StopwatchTokenParser extends AbstractTokenParser { - private bool $stopwatchIsAvailable; - - public function __construct(bool $stopwatchIsAvailable) - { - $this->stopwatchIsAvailable = $stopwatchIsAvailable; + public function __construct( + private bool $stopwatchIsAvailable, + ) { } public function parse(Token $token): Node diff --git a/Translation/TwigExtractor.php b/Translation/TwigExtractor.php index 8a911ea0..a4b4bbe5 100644 --- a/Translation/TwigExtractor.php +++ b/Translation/TwigExtractor.php @@ -38,11 +38,9 @@ class TwigExtractor extends AbstractFileExtractor implements ExtractorInterface */ private string $prefix = ''; - private Environment $twig; - - public function __construct(Environment $twig) - { - $this->twig = $twig; + public function __construct( + private Environment $twig, + ) { } public function extract($resource, MessageCatalogue $catalogue): void From 6461bac7a80cb21ef19636cd17068ab3ca8a8df3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Pineau?= Date: Thu, 28 Mar 2024 14:43:38 +0100 Subject: [PATCH 125/167] [TwigBridge] Add `emojify` twig filter --- CHANGELOG.md | 5 +++ Extension/EmojiExtension.php | 55 ++++++++++++++++++++++++++ Tests/Extension/EmojiExtensionTest.php | 32 +++++++++++++++ UndefinedCallableHandler.php | 1 + composer.json | 1 + 5 files changed, 94 insertions(+) create mode 100644 Extension/EmojiExtension.php create mode 100644 Tests/Extension/EmojiExtensionTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index cc2d3345..a3cbe342 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +7.1 +--- + + * Add `emojify` twig filter + 7.0 --- diff --git a/Extension/EmojiExtension.php b/Extension/EmojiExtension.php new file mode 100644 index 00000000..b98798da --- /dev/null +++ b/Extension/EmojiExtension.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\Bridge\Twig\Extension; + +use Symfony\Component\Emoji\EmojiTransliterator; +use Twig\Extension\AbstractExtension; +use Twig\TwigFilter; + +/** + * @author Grégoire Pineau + */ +final class EmojiExtension extends AbstractExtension +{ + private static array $transliterators = []; + + public function __construct( + private readonly string $defaultCatalog = 'text', + ) { + if (!class_exists(EmojiTransliterator::class)) { + throw new \LogicException('You cannot use the "emojify" filter as the "Emoji" component is not installed. Try running "composer require symfony/emoji".'); + } + } + + public function getFilters(): array + { + return [ + new TwigFilter('emojify', $this->emojify(...)), + ]; + } + + /** + * Converts emoji short code (:wave:) to real emoji (👋) + */ + public function emojify(string $string, ?string $catalog = null): string + { + $catalog ??= $this->defaultCatalog; + + try { + $tr = self::$transliterators[$catalog] ??= EmojiTransliterator::create($catalog, EmojiTransliterator::REVERSE); + } catch (\IntlException $e) { + throw new \LogicException(sprintf('The emoji catalog "%s" is not available.', $catalog), previous: $e); + } + + return (string) $tr->transliterate($string); + } +} diff --git a/Tests/Extension/EmojiExtensionTest.php b/Tests/Extension/EmojiExtensionTest.php new file mode 100644 index 00000000..492929a3 --- /dev/null +++ b/Tests/Extension/EmojiExtensionTest.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\Tests\Extension; + +use PHPUnit\Framework\TestCase; +use Symfony\Bridge\Twig\Extension\EmojiExtension; + +/** + * @requires extension intl + */ +class EmojiExtensionTest extends TestCase +{ + /** + * @testWith ["🅰️", ":a:"] + * ["🅰️", ":a:", "slack"] + * ["🅰", ":a:", "github"] + */ + public function testEmojify(string $expected, string $string, ?string $catalog = null) + { + $extension = new EmojiExtension(); + $this->assertSame($expected, $extension->emojify($string, $catalog)); + } +} diff --git a/UndefinedCallableHandler.php b/UndefinedCallableHandler.php index 0ae2893c..c9f502f6 100644 --- a/UndefinedCallableHandler.php +++ b/UndefinedCallableHandler.php @@ -37,6 +37,7 @@ class UndefinedCallableHandler 'asset_version' => 'asset', 'importmap' => 'asset-mapper', 'dump' => 'debug-bundle', + 'emojify' => 'emoji', 'encore_entry_link_tags' => 'webpack-encore-bundle', 'encore_entry_script_tags' => 'webpack-encore-bundle', 'expression' => 'expression-language', diff --git a/composer.json b/composer.json index 71fb6a2f..90eb69ed 100644 --- a/composer.json +++ b/composer.json @@ -27,6 +27,7 @@ "symfony/asset": "^6.4|^7.0", "symfony/asset-mapper": "^6.4|^7.0", "symfony/dependency-injection": "^6.4|^7.0", + "symfony/emoji": "^7.1", "symfony/finder": "^6.4|^7.0", "symfony/form": "^6.4|^7.0", "symfony/html-sanitizer": "^6.4|^7.0", From 63c0302788fe3c22a151121b51eb281db29aca02 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Tue, 9 Apr 2024 22:02:49 +0200 Subject: [PATCH 126/167] fix typo --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a3cbe342..df8f28f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ CHANGELOG 7.1 --- - * Add `emojify` twig filter + * Add `emojify` Twig filter 7.0 --- From ecf999c7f88c2219e997b382e1f992f7cc8d60a6 Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Thu, 2 May 2024 12:27:30 +0200 Subject: [PATCH 127/167] [TwigBridge][WebProfilerBundle] Require Twig 3.10 --- Node/TransNode.php | 4 +--- Tests/Node/TransNodeTest.php | 4 +--- composer.json | 2 +- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/Node/TransNode.php b/Node/TransNode.php index 0224d46a..f8dc1202 100644 --- a/Node/TransNode.php +++ b/Node/TransNode.php @@ -55,10 +55,8 @@ public function compile(Compiler $compiler): void $vars = null; } [$msg, $defaults] = $this->compileString($this->getNode('body'), $defaults, (bool) $vars); - $display = class_exists(YieldReady::class) ? 'yield' : 'echo'; - $compiler - ->write($display.' $this->env->getExtension(\'Symfony\Bridge\Twig\Extension\TranslationExtension\')->trans(') + ->write('yield $this->env->getExtension(\'Symfony\Bridge\Twig\Extension\TranslationExtension\')->trans(') ->subcompile($msg) ; diff --git a/Tests/Node/TransNodeTest.php b/Tests/Node/TransNodeTest.php index ee22531b..d1f1114f 100644 --- a/Tests/Node/TransNodeTest.php +++ b/Tests/Node/TransNodeTest.php @@ -13,7 +13,6 @@ use PHPUnit\Framework\TestCase; use Symfony\Bridge\Twig\Node\TransNode; -use Twig\Attribute\YieldReady; use Twig\Compiler; use Twig\Environment; use Twig\Loader\LoaderInterface; @@ -36,8 +35,7 @@ public function testCompileStrict() $this->assertEquals( sprintf( - '%s $this->env->getExtension(\'Symfony\Bridge\Twig\Extension\TranslationExtension\')->trans("trans %%var%%", array_merge(["%%var%%" => %s], %s), "messages");', - class_exists(YieldReady::class) ? 'yield' : 'echo', + 'yield $this->env->getExtension(\'Symfony\Bridge\Twig\Extension\TranslationExtension\')->trans("trans %%var%%", array_merge(["%%var%%" => %s], %s), "messages");', $this->getVariableGetterWithoutStrictCheck('var'), $this->getVariableGetterWithStrictCheck('foo') ), diff --git a/composer.json b/composer.json index 90eb69ed..f7f8d32d 100644 --- a/composer.json +++ b/composer.json @@ -18,7 +18,7 @@ "require": { "php": ">=8.2", "symfony/translation-contracts": "^2.5|^3", - "twig/twig": "^3.0.4" + "twig/twig": "^3.9" }, "require-dev": { "egulias/email-validator": "^2.1.10|^3|^4", From 3057ea5cc64547c3ff859b9112e2dd43672b09ef Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Fri, 24 May 2024 12:26:22 +0200 Subject: [PATCH 128/167] use constructor property promotion --- Node/DumpNode.php | 1 - 1 file changed, 1 deletion(-) diff --git a/Node/DumpNode.php b/Node/DumpNode.php index 5c3ac6ca..c96b4042 100644 --- a/Node/DumpNode.php +++ b/Node/DumpNode.php @@ -33,7 +33,6 @@ public function __construct( } parent::__construct($nodes, [], $lineno, $tag); - $this->varPrefix = $varPrefix; } public function compile(Compiler $compiler): void From bc841b3ce6898e55e419e9fb1435a48fcd201fce Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Thu, 20 Jun 2024 17:52:34 +0200 Subject: [PATCH 129/167] Prefix all sprintf() calls --- Command/DebugCommand.php | 30 +++++++++---------- Command/LintCommand.php | 22 +++++++------- ErrorRenderer/TwigErrorRenderer.php | 2 +- Extension/EmojiExtension.php | 2 +- Extension/HttpKernelRuntime.php | 2 +- Extension/TranslationExtension.php | 8 ++--- Mime/BodyRenderer.php | 2 +- Mime/NotificationEmail.php | 6 ++-- Node/DumpNode.php | 10 +++---- .../TranslationDefaultDomainNodeVisitor.php | 2 +- Test/FormLayoutTestCase.php | 4 +-- Tests/Extension/HttpKernelExtensionTest.php | 2 +- Tests/Node/FormThemeTest.php | 10 +++---- Tests/Node/SearchAndRenderBlockNodeTest.php | 22 +++++++------- Tests/Node/TransNodeTest.php | 6 ++-- UndefinedCallableHandler.php | 4 +-- 16 files changed, 67 insertions(+), 67 deletions(-) diff --git a/Command/DebugCommand.php b/Command/DebugCommand.php index 21ede17a..42b456c2 100644 --- a/Command/DebugCommand.php +++ b/Command/DebugCommand.php @@ -57,7 +57,7 @@ protected function configure(): void ->setDefinition([ new InputArgument('name', InputArgument::OPTIONAL, 'The template name'), new InputOption('filter', null, InputOption::VALUE_REQUIRED, 'Show details for all entries matching this filter'), - new InputOption('format', null, InputOption::VALUE_REQUIRED, sprintf('The output format ("%s")', implode('", "', $this->getAvailableFormatOptions())), 'text'), + new InputOption('format', null, InputOption::VALUE_REQUIRED, \sprintf('The output format ("%s")', implode('", "', $this->getAvailableFormatOptions())), 'text'), ]) ->setHelp(<<<'EOF' The %command.name% command outputs a list of twig functions, @@ -90,13 +90,13 @@ protected function execute(InputInterface $input, OutputInterface $output): int $filter = $input->getOption('filter'); if (null !== $name && [] === $this->getFilesystemLoaders()) { - throw new InvalidArgumentException(sprintf('Argument "name" not supported, it requires the Twig loader "%s".', FilesystemLoader::class)); + throw new InvalidArgumentException(\sprintf('Argument "name" not supported, it requires the Twig loader "%s".', FilesystemLoader::class)); } match ($input->getOption('format')) { 'text' => $name ? $this->displayPathsText($io, $name) : $this->displayGeneralText($io, $filter), 'json' => $name ? $this->displayPathsJson($io, $name) : $this->displayGeneralJson($io, $filter), - default => throw new InvalidArgumentException(sprintf('Supported formats are "%s".', implode('", "', $this->getAvailableFormatOptions()))), + default => throw new InvalidArgumentException(\sprintf('Supported formats are "%s".', implode('", "', $this->getAvailableFormatOptions()))), }; return 0; @@ -121,7 +121,7 @@ private function displayPathsText(SymfonyStyle $io, string $name): void $io->section('Matched File'); if ($file->valid()) { if ($fileLink = $this->getFileLink($file->key())) { - $io->block($file->current(), 'OK', sprintf('fg=black;bg=green;href=%s', $fileLink), ' ', true); + $io->block($file->current(), 'OK', \sprintf('fg=black;bg=green;href=%s', $fileLink), ' ', true); } else { $io->success($file->current()); } @@ -131,9 +131,9 @@ private function displayPathsText(SymfonyStyle $io, string $name): void $io->section('Overridden Files'); do { if ($fileLink = $this->getFileLink($file->key())) { - $io->text(sprintf('* %s', $fileLink, $file->current())); + $io->text(\sprintf('* %s', $fileLink, $file->current())); } else { - $io->text(sprintf('* %s', $file->current())); + $io->text(\sprintf('* %s', $file->current())); } $file->next(); } while ($file->valid()); @@ -158,7 +158,7 @@ private function displayPathsText(SymfonyStyle $io, string $name): void } } - $this->error($io, sprintf('Template name "%s" not found', $name), $alternatives); + $this->error($io, \sprintf('Template name "%s" not found', $name), $alternatives); } $io->section('Configured Paths'); @@ -171,7 +171,7 @@ private function displayPathsText(SymfonyStyle $io, string $name): void if (FilesystemLoader::MAIN_NAMESPACE === $namespace) { $message = 'No template paths configured for your application'; } else { - $message = sprintf('No template paths configured for "@%s" namespace', $namespace); + $message = \sprintf('No template paths configured for "@%s" namespace', $namespace); foreach ($this->getFilesystemLoaders() as $loader) { $namespaces = $loader->getNamespaces(); foreach ($this->findAlternatives($namespace, $namespaces) as $namespace) { @@ -199,7 +199,7 @@ private function displayPathsJson(SymfonyStyle $io, string $name): void $data['overridden_files'] = $files; } } else { - $data['matched_file'] = sprintf('Template name "%s" not found', $name); + $data['matched_file'] = \sprintf('Template name "%s" not found', $name); } $data['loader_paths'] = $paths; @@ -364,7 +364,7 @@ private function getPrettyMetadata(string $type, mixed $entity, bool $decorated) return '(unknown?)'; } } catch (\UnexpectedValueException $e) { - return sprintf(' %s', $decorated ? OutputFormatter::escape($e->getMessage()) : $e->getMessage()); + return \sprintf(' %s', $decorated ? OutputFormatter::escape($e->getMessage()) : $e->getMessage()); } if ('globals' === $type) { @@ -374,7 +374,7 @@ private function getPrettyMetadata(string $type, mixed $entity, bool $decorated) $description = substr(@json_encode($meta), 0, 50); - return sprintf(' = %s', $decorated ? OutputFormatter::escape($description) : $description); + return \sprintf(' = %s', $decorated ? OutputFormatter::escape($description) : $description); } if ('functions' === $type) { @@ -421,14 +421,14 @@ private function buildWarningMessages(array $wrongBundles): array { $messages = []; foreach ($wrongBundles as $path => $alternatives) { - $message = sprintf('Path "%s" not matching any bundle found', $path); + $message = \sprintf('Path "%s" not matching any bundle found', $path); if ($alternatives) { if (1 === \count($alternatives)) { - $message .= sprintf(", did you mean \"%s\"?\n", $alternatives[0]); + $message .= \sprintf(", did you mean \"%s\"?\n", $alternatives[0]); } else { $message .= ", did you mean one of these:\n"; foreach ($alternatives as $bundle) { - $message .= sprintf(" - %s\n", $bundle); + $message .= \sprintf(" - %s\n", $bundle); } } } @@ -481,7 +481,7 @@ private function parseTemplateName(string $name, string $default = FilesystemLoa { if (isset($name[0]) && '@' === $name[0]) { if (false === ($pos = strpos($name, '/')) || $pos === \strlen($name) - 1) { - throw new InvalidArgumentException(sprintf('Malformed namespaced template name "%s" (expecting "@namespace/template_name").', $name)); + throw new InvalidArgumentException(\sprintf('Malformed namespaced template name "%s" (expecting "@namespace/template_name").', $name)); } $namespace = substr($name, 1, $pos - 1); diff --git a/Command/LintCommand.php b/Command/LintCommand.php index 14c00ba1..456c1864 100644 --- a/Command/LintCommand.php +++ b/Command/LintCommand.php @@ -52,7 +52,7 @@ public function __construct( protected function configure(): void { $this - ->addOption('format', null, InputOption::VALUE_REQUIRED, sprintf('The output format ("%s")', implode('", "', $this->getAvailableFormatOptions()))) + ->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', []) @@ -151,7 +151,7 @@ protected function findFiles(string $filename): iterable return Finder::create()->files()->in($filename)->name($this->namePatterns)->exclude($this->excludes); } - throw new RuntimeException(sprintf('File or directory "%s" is not readable.', $filename)); + throw new RuntimeException(\sprintf('File or directory "%s" is not readable.', $filename)); } private function validate(string $template, string $file): array @@ -178,7 +178,7 @@ private function display(InputInterface $input, OutputInterface $output, Symfony 'txt' => $this->displayTxt($output, $io, $files), 'json' => $this->displayJson($output, $files), 'github' => $this->displayTxt($output, $io, $files, true), - default => throw new InvalidArgumentException(sprintf('Supported formats are "%s".', implode('", "', $this->getAvailableFormatOptions()))), + default => throw new InvalidArgumentException(\sprintf('Supported formats are "%s".', implode('", "', $this->getAvailableFormatOptions()))), }; } @@ -189,7 +189,7 @@ private function displayTxt(OutputInterface $output, SymfonyStyle $io, array $fi foreach ($filesInfo as $info) { if ($info['valid'] && $output->isVerbose()) { - $io->comment('OK'.($info['file'] ? sprintf(' in %s', $info['file']) : '')); + $io->comment('OK'.($info['file'] ? \sprintf(' in %s', $info['file']) : '')); } elseif (!$info['valid']) { ++$errors; $this->renderException($io, $info['template'], $info['exception'], $info['file'], $githubReporter); @@ -197,9 +197,9 @@ private function displayTxt(OutputInterface $output, SymfonyStyle $io, array $fi } if (0 === $errors) { - $io->success(sprintf('All %d Twig files contain valid syntax.', \count($filesInfo))); + $io->success(\sprintf('All %d Twig files contain valid syntax.', \count($filesInfo))); } else { - $io->warning(sprintf('%d Twig files have valid syntax and %d contain errors.', \count($filesInfo) - $errors, $errors)); + $io->warning(\sprintf('%d Twig files have valid syntax and %d contain errors.', \count($filesInfo) - $errors, $errors)); } return min($errors, 1); @@ -231,28 +231,28 @@ private function renderException(SymfonyStyle $output, string $template, Error $ $githubReporter?->error($exception->getRawMessage(), $file, $line <= 0 ? null : $line); if ($file) { - $output->text(sprintf(' ERROR in %s (line %s)', $file, $line)); + $output->text(\sprintf(' ERROR in %s (line %s)', $file, $line)); } else { - $output->text(sprintf(' ERROR (line %s)', $line)); + $output->text(\sprintf(' ERROR (line %s)', $line)); } // If the line is not known (this might happen for deprecations if we fail at detecting the line for instance), // we render the message without context, to ensure the message is displayed. if ($line <= 0) { - $output->text(sprintf(' >> %s ', $exception->getRawMessage())); + $output->text(\sprintf(' >> %s ', $exception->getRawMessage())); return; } foreach ($this->getContext($template, $line) as $lineNumber => $code) { - $output->text(sprintf( + $output->text(\sprintf( '%s %-6s %s', $lineNumber === $line ? ' >> ' : ' ', $lineNumber, $code )); if ($lineNumber === $line) { - $output->text(sprintf(' >> %s ', $exception->getRawMessage())); + $output->text(\sprintf(' >> %s ', $exception->getRawMessage())); } } } diff --git a/ErrorRenderer/TwigErrorRenderer.php b/ErrorRenderer/TwigErrorRenderer.php index 0ea9b9aa..f624720b 100644 --- a/ErrorRenderer/TwigErrorRenderer.php +++ b/ErrorRenderer/TwigErrorRenderer.php @@ -69,7 +69,7 @@ public static function isDebug(RequestStack $requestStack, bool $debug): \Closur private function findTemplate(int $statusCode): ?string { - $template = sprintf('@Twig/Exception/error%s.html.twig', $statusCode); + $template = \sprintf('@Twig/Exception/error%s.html.twig', $statusCode); if ($this->twig->getLoader()->exists($template)) { return $template; } diff --git a/Extension/EmojiExtension.php b/Extension/EmojiExtension.php index b98798da..dcc5a5a0 100644 --- a/Extension/EmojiExtension.php +++ b/Extension/EmojiExtension.php @@ -47,7 +47,7 @@ public function emojify(string $string, ?string $catalog = null): string try { $tr = self::$transliterators[$catalog] ??= EmojiTransliterator::create($catalog, EmojiTransliterator::REVERSE); } catch (\IntlException $e) { - throw new \LogicException(sprintf('The emoji catalog "%s" is not available.', $catalog), previous: $e); + throw new \LogicException(\sprintf('The emoji catalog "%s" is not available.', $catalog), previous: $e); } return (string) $tr->transliterate($string); diff --git a/Extension/HttpKernelRuntime.php b/Extension/HttpKernelRuntime.php index 0aefed8f..6c488ef7 100644 --- a/Extension/HttpKernelRuntime.php +++ b/Extension/HttpKernelRuntime.php @@ -54,7 +54,7 @@ public function renderFragmentStrategy(string $strategy, string|ControllerRefere public function generateFragmentUri(ControllerReference $controller, bool $absolute = false, bool $strict = true, bool $sign = true): string { if (null === $this->fragmentUriGenerator) { - throw new \LogicException(sprintf('An instance of "%s" must be provided to use "%s()".', FragmentUriGeneratorInterface::class, __METHOD__)); + throw new \LogicException(\sprintf('An instance of "%s" must be provided to use "%s()".', FragmentUriGeneratorInterface::class, __METHOD__)); } return $this->fragmentUriGenerator->generate($controller, null, $absolute, $strict, $sign); diff --git a/Extension/TranslationExtension.php b/Extension/TranslationExtension.php index bf8b81bd..1958aebe 100644 --- a/Extension/TranslationExtension.php +++ b/Extension/TranslationExtension.php @@ -44,7 +44,7 @@ public function getTranslator(): TranslatorInterface { if (null === $this->translator) { if (!interface_exists(TranslatorInterface::class)) { - throw new \LogicException(sprintf('You cannot use the "%s" if the Translation Contracts are not available. Try running "composer require symfony/translation".', __CLASS__)); + throw new \LogicException(\sprintf('You cannot use the "%s" if the Translation Contracts are not available. Try running "composer require symfony/translation".', __CLASS__)); } $this->translator = new class() implements TranslatorInterface { @@ -97,7 +97,7 @@ public function trans(string|\Stringable|TranslatableInterface|null $message, ar { if ($message instanceof TranslatableInterface) { if ([] !== $arguments && !\is_string($arguments)) { - throw new \TypeError(sprintf('Argument 2 passed to "%s()" must be a locale passed as a string when the message is a "%s", "%s" given.', __METHOD__, TranslatableInterface::class, get_debug_type($arguments))); + throw new \TypeError(\sprintf('Argument 2 passed to "%s()" must be a locale passed as a string when the message is a "%s", "%s" given.', __METHOD__, TranslatableInterface::class, get_debug_type($arguments))); } if ($message instanceof TranslatableMessage && '' === $message->getMessage()) { @@ -108,7 +108,7 @@ public function trans(string|\Stringable|TranslatableInterface|null $message, ar } if (!\is_array($arguments)) { - throw new \TypeError(sprintf('Unless the message is a "%s", argument 2 passed to "%s()" must be an array of parameters, "%s" given.', TranslatableInterface::class, __METHOD__, get_debug_type($arguments))); + throw new \TypeError(\sprintf('Unless the message is a "%s", argument 2 passed to "%s()" must be an array of parameters, "%s" given.', TranslatableInterface::class, __METHOD__, get_debug_type($arguments))); } if ('' === $message = (string) $message) { @@ -125,7 +125,7 @@ public function trans(string|\Stringable|TranslatableInterface|null $message, ar 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__)); + throw new \LogicException(\sprintf('You cannot use the "%s" as the Translation Component is not installed. Try running "composer require symfony/translation".', __CLASS__)); } return new TranslatableMessage($message, $parameters, $domain); diff --git a/Mime/BodyRenderer.php b/Mime/BodyRenderer.php index 25d87353..162f8c5f 100644 --- a/Mime/BodyRenderer.php +++ b/Mime/BodyRenderer.php @@ -52,7 +52,7 @@ public function render(Message $message): void $messageContext = $message->getContext(); if (isset($messageContext['email'])) { - throw new InvalidArgumentException(sprintf('A "%s" context cannot have an "email" entry as this is a reserved variable.', get_debug_type($message))); + throw new InvalidArgumentException(\sprintf('A "%s" context cannot have an "email" entry as this is a reserved variable.', get_debug_type($message))); } $vars = array_merge($this->context, $messageContext, [ diff --git a/Mime/NotificationEmail.php b/Mime/NotificationEmail.php index 6e33d33d..4b4e1b26 100644 --- a/Mime/NotificationEmail.php +++ b/Mime/NotificationEmail.php @@ -54,7 +54,7 @@ public function __construct(?Headers $headers = null, ?AbstractPart $body = null } if ($missingPackages) { - throw new \LogicException(sprintf('You cannot use "%s" if the "%s" Twig extension%s not available. Try running "%s".', static::class, implode('" and "', $missingPackages), \count($missingPackages) > 1 ? 's are' : ' is', 'composer require '.implode(' ', array_keys($missingPackages)))); + throw new \LogicException(\sprintf('You cannot use "%s" if the "%s" Twig extension%s not available. Try running "%s".', static::class, implode('" and "', $missingPackages), \count($missingPackages) > 1 ? 's are' : ' is', 'composer require '.implode(' ', array_keys($missingPackages)))); } parent::__construct($headers, $body); @@ -88,7 +88,7 @@ public function markAsPublic(): static public function markdown(string $content): static { if (!class_exists(MarkdownExtension::class)) { - throw new \LogicException(sprintf('You cannot use "%s" if the Markdown Twig extension is not available. Try running "composer require twig/markdown-extra".', __METHOD__)); + throw new \LogicException(\sprintf('You cannot use "%s" if the Markdown Twig extension is not available. Try running "composer require twig/markdown-extra".', __METHOD__)); } $this->context['markdown'] = true; @@ -218,7 +218,7 @@ public function getPreparedHeaders(): Headers $importance = $this->context['importance'] ?? self::IMPORTANCE_LOW; $this->priority($this->determinePriority($importance)); if ($this->context['importance']) { - $headers->setHeaderBody('Text', 'Subject', sprintf('[%s] %s', strtoupper($importance), $this->getSubject())); + $headers->setHeaderBody('Text', 'Subject', \sprintf('[%s] %s', strtoupper($importance), $this->getSubject())); } return $headers; diff --git a/Node/DumpNode.php b/Node/DumpNode.php index c96b4042..7c9c4a4a 100644 --- a/Node/DumpNode.php +++ b/Node/DumpNode.php @@ -44,18 +44,18 @@ public function compile(Compiler $compiler): void if (!$this->hasNode('values')) { // remove embedded templates (macros) from the context $compiler - ->write(sprintf('$%svars = [];'."\n", $this->varPrefix)) - ->write(sprintf('foreach ($context as $%1$skey => $%1$sval) {'."\n", $this->varPrefix)) + ->write(\sprintf('$%svars = [];'."\n", $this->varPrefix)) + ->write(\sprintf('foreach ($context as $%1$skey => $%1$sval) {'."\n", $this->varPrefix)) ->indent() - ->write(sprintf('if (!$%sval instanceof \Twig\Template) {'."\n", $this->varPrefix)) + ->write(\sprintf('if (!$%sval instanceof \Twig\Template) {'."\n", $this->varPrefix)) ->indent() - ->write(sprintf('$%1$svars[$%1$skey] = $%1$sval;'."\n", $this->varPrefix)) + ->write(\sprintf('$%1$svars[$%1$skey] = $%1$sval;'."\n", $this->varPrefix)) ->outdent() ->write("}\n") ->outdent() ->write("}\n") ->addDebugInfo($this) - ->write(sprintf('\Symfony\Component\VarDumper\VarDumper::dump($%svars);'."\n", $this->varPrefix)); + ->write(\sprintf('\Symfony\Component\VarDumper\VarDumper::dump($%svars);'."\n", $this->varPrefix)); } elseif (($values = $this->getNode('values')) && 1 === $values->count()) { $compiler ->addDebugInfo($this) diff --git a/NodeVisitor/TranslationDefaultDomainNodeVisitor.php b/NodeVisitor/TranslationDefaultDomainNodeVisitor.php index b071677d..a9266066 100644 --- a/NodeVisitor/TranslationDefaultDomainNodeVisitor.php +++ b/NodeVisitor/TranslationDefaultDomainNodeVisitor.php @@ -114,6 +114,6 @@ private function isNamedArguments(Node $arguments): bool private function getVarName(): string { - return sprintf('__internal_%s', hash('xxh128', uniqid(mt_rand(), true))); + return \sprintf('__internal_%s', hash('xxh128', uniqid(mt_rand(), true))); } } diff --git a/Test/FormLayoutTestCase.php b/Test/FormLayoutTestCase.php index 1fdd83c9..0c719efc 100644 --- a/Test/FormLayoutTestCase.php +++ b/Test/FormLayoutTestCase.php @@ -57,7 +57,7 @@ protected function assertMatchesXpath($html, $expression, $count = 1): void // the top level $dom->loadXML(''.$html.''); } catch (\Exception $e) { - $this->fail(sprintf( + $this->fail(\sprintf( "Failed loading HTML:\n\n%s\n\nError: %s", $html, $e->getMessage() @@ -68,7 +68,7 @@ protected function assertMatchesXpath($html, $expression, $count = 1): void if ($nodeList->length != $count) { $dom->formatOutput = true; - $this->fail(sprintf( + $this->fail(\sprintf( "Failed asserting that \n\n%s\n\nmatches exactly %s. Matches %s in \n\n%s", $expression, 1 == $count ? 'once' : $count.' times', diff --git a/Tests/Extension/HttpKernelExtensionTest.php b/Tests/Extension/HttpKernelExtensionTest.php index fdf3c4f4..6d4ea74d 100644 --- a/Tests/Extension/HttpKernelExtensionTest.php +++ b/Tests/Extension/HttpKernelExtensionTest.php @@ -67,7 +67,7 @@ public function testGenerateFragmentUri() $kernelRuntime = new HttpKernelRuntime($fragmentHandler, $fragmentUriGenerator); $loader = new ArrayLoader([ - 'index' => sprintf(<< \sprintf(<<assertEquals( - sprintf( + \sprintf( '$this->env->getRuntime("Symfony\\\\Component\\\\Form\\\\FormRenderer")->setTheme(%s, [1 => "tpl1", 0 => "tpl2"], true);', $this->getVariableGetter('form') ), @@ -71,7 +71,7 @@ public function testCompile() $node = new FormThemeNode($form, $resources, 0, null, true); $this->assertEquals( - sprintf( + \sprintf( '$this->env->getRuntime("Symfony\\\\Component\\\\Form\\\\FormRenderer")->setTheme(%s, [1 => "tpl1", 0 => "tpl2"], false);', $this->getVariableGetter('form') ), @@ -83,7 +83,7 @@ public function testCompile() $node = new FormThemeNode($form, $resources, 0); $this->assertEquals( - sprintf( + \sprintf( '$this->env->getRuntime("Symfony\\\\Component\\\\Form\\\\FormRenderer")->setTheme(%s, "tpl1", true);', $this->getVariableGetter('form') ), @@ -93,7 +93,7 @@ public function testCompile() $node = new FormThemeNode($form, $resources, 0, null, true); $this->assertEquals( - sprintf( + \sprintf( '$this->env->getRuntime("Symfony\\\\Component\\\\Form\\\\FormRenderer")->setTheme(%s, "tpl1", false);', $this->getVariableGetter('form') ), @@ -103,6 +103,6 @@ public function testCompile() protected function getVariableGetter($name) { - return sprintf('($context["%s"] ?? null)', $name); + return \sprintf('($context["%s"] ?? null)', $name); } } diff --git a/Tests/Node/SearchAndRenderBlockNodeTest.php b/Tests/Node/SearchAndRenderBlockNodeTest.php index b259990e..c8cff99c 100644 --- a/Tests/Node/SearchAndRenderBlockNodeTest.php +++ b/Tests/Node/SearchAndRenderBlockNodeTest.php @@ -36,7 +36,7 @@ public function testCompileWidget() $compiler = new Compiler(new Environment($this->createMock(LoaderInterface::class))); $this->assertEquals( - sprintf( + \sprintf( '$this->env->getRuntime(\'Symfony\Component\Form\FormRenderer\')->searchAndRenderBlock(%s, \'widget\')', $this->getVariableGetter('form') ), @@ -59,7 +59,7 @@ public function testCompileWidgetWithVariables() $compiler = new Compiler(new Environment($this->createMock(LoaderInterface::class))); $this->assertEquals( - sprintf( + \sprintf( '$this->env->getRuntime(\'Symfony\Component\Form\FormRenderer\')->searchAndRenderBlock(%s, \'widget\', ["foo" => "bar"])', $this->getVariableGetter('form') ), @@ -79,7 +79,7 @@ public function testCompileLabelWithLabel() $compiler = new Compiler(new Environment($this->createMock(LoaderInterface::class))); $this->assertEquals( - sprintf( + \sprintf( '$this->env->getRuntime(\'Symfony\Component\Form\FormRenderer\')->searchAndRenderBlock(%s, \'label\', ["label" => "my label"])', $this->getVariableGetter('form') ), @@ -101,7 +101,7 @@ public function testCompileLabelWithNullLabel() // "label" => null must not be included in the output! // Otherwise the default label is overwritten with null. $this->assertEquals( - sprintf( + \sprintf( '$this->env->getRuntime(\'Symfony\Component\Form\FormRenderer\')->searchAndRenderBlock(%s, \'label\')', $this->getVariableGetter('form') ), @@ -123,7 +123,7 @@ public function testCompileLabelWithEmptyStringLabel() // "label" => null must not be included in the output! // Otherwise the default label is overwritten with null. $this->assertEquals( - sprintf( + \sprintf( '$this->env->getRuntime(\'Symfony\Component\Form\FormRenderer\')->searchAndRenderBlock(%s, \'label\')', $this->getVariableGetter('form') ), @@ -142,7 +142,7 @@ public function testCompileLabelWithDefaultLabel() $compiler = new Compiler(new Environment($this->createMock(LoaderInterface::class))); $this->assertEquals( - sprintf( + \sprintf( '$this->env->getRuntime(\'Symfony\Component\Form\FormRenderer\')->searchAndRenderBlock(%s, \'label\')', $this->getVariableGetter('form') ), @@ -169,7 +169,7 @@ public function testCompileLabelWithAttributes() // Otherwise the default label is overwritten with null. // https://github.com/symfony/symfony/issues/5029 $this->assertEquals( - sprintf( + \sprintf( '$this->env->getRuntime(\'Symfony\Component\Form\FormRenderer\')->searchAndRenderBlock(%s, \'label\', ["foo" => "bar"])', $this->getVariableGetter('form') ), @@ -195,7 +195,7 @@ public function testCompileLabelWithLabelAndAttributes() $compiler = new Compiler(new Environment($this->createMock(LoaderInterface::class))); $this->assertEquals( - sprintf( + \sprintf( '$this->env->getRuntime(\'Symfony\Component\Form\FormRenderer\')->searchAndRenderBlock(%s, \'label\', ["foo" => "bar", "label" => "value in argument"])', $this->getVariableGetter('form') ), @@ -226,7 +226,7 @@ public function testCompileLabelWithLabelThatEvaluatesToNull() // Otherwise the default label is overwritten with null. // https://github.com/symfony/symfony/issues/5029 $this->assertEquals( - sprintf( + \sprintf( '$this->env->getRuntime(\'Symfony\Component\Form\FormRenderer\')->searchAndRenderBlock(%s, \'label\', (%s($_label_ = ((true) ? (null) : (null))) ? [] : ["label" => $_label_]))', $this->getVariableGetter('form'), method_exists(CoreExtension::class, 'testEmpty') ? 'CoreExtension::testEmpty' : 'twig_test_empty' @@ -264,7 +264,7 @@ public function testCompileLabelWithLabelThatEvaluatesToNullAndAttributes() // Otherwise the default label is overwritten with null. // https://github.com/symfony/symfony/issues/5029 $this->assertEquals( - sprintf( + \sprintf( '$this->env->getRuntime(\'Symfony\Component\Form\FormRenderer\')->searchAndRenderBlock(%s, \'label\', ["foo" => "bar", "label" => "value in attributes"] + (%s($_label_ = ((true) ? (null) : (null))) ? [] : ["label" => $_label_]))', $this->getVariableGetter('form'), method_exists(CoreExtension::class, 'testEmpty') ? 'CoreExtension::testEmpty' : 'twig_test_empty' @@ -275,6 +275,6 @@ public function testCompileLabelWithLabelThatEvaluatesToNullAndAttributes() protected function getVariableGetter($name) { - return sprintf('($context["%s"] ?? null)', $name); + return \sprintf('($context["%s"] ?? null)', $name); } } diff --git a/Tests/Node/TransNodeTest.php b/Tests/Node/TransNodeTest.php index d1f1114f..0b055cae 100644 --- a/Tests/Node/TransNodeTest.php +++ b/Tests/Node/TransNodeTest.php @@ -34,7 +34,7 @@ public function testCompileStrict() $compiler = new Compiler($env); $this->assertEquals( - sprintf( + \sprintf( 'yield $this->env->getExtension(\'Symfony\Bridge\Twig\Extension\TranslationExtension\')->trans("trans %%var%%", array_merge(["%%var%%" => %s], %s), "messages");', $this->getVariableGetterWithoutStrictCheck('var'), $this->getVariableGetterWithStrictCheck('foo') @@ -45,11 +45,11 @@ public function testCompileStrict() protected function getVariableGetterWithoutStrictCheck($name) { - return sprintf('($context["%s"] ?? null)', $name); + return \sprintf('($context["%s"] ?? null)', $name); } protected function getVariableGetterWithStrictCheck($name) { - return sprintf('(isset($context["%1$s"]) || array_key_exists("%1$s", $context) ? $context["%1$s"] : (function () { throw new RuntimeError(\'Variable "%1$s" does not exist.\', 0, $this->source); })())', $name); + return \sprintf('(isset($context["%1$s"]) || array_key_exists("%1$s", $context) ? $context["%1$s"] : (function () { throw new RuntimeError(\'Variable "%1$s" does not exist.\', 0, $this->source); })())', $name); } } diff --git a/UndefinedCallableHandler.php b/UndefinedCallableHandler.php index c9f502f6..4e63c283 100644 --- a/UndefinedCallableHandler.php +++ b/UndefinedCallableHandler.php @@ -115,7 +115,7 @@ public static function onUndefinedFunction(string $name): TwigFunction|false private static function onUndefined(string $name, string $type, string $component): string { if (class_exists(FullStack::class) && isset(self::FULL_STACK_ENABLE[$component])) { - return sprintf('Did you forget to %s? Unknown %s "%s".', self::FULL_STACK_ENABLE[$component], $type, $name); + return \sprintf('Did you forget to %s? Unknown %s "%s".', self::FULL_STACK_ENABLE[$component], $type, $name); } $missingPackage = 'symfony/'.$component; @@ -124,6 +124,6 @@ private static function onUndefined(string $name, string $type, string $componen $missingPackage = 'symfony/twig-bundle'; } - return sprintf('Did you forget to run "composer require %s"? Unknown %s "%s".', $missingPackage, $type, $name); + return \sprintf('Did you forget to run "composer require %s"? Unknown %s "%s".', $missingPackage, $type, $name); } } From a971fb90d6a700a449d81120486c6c686cff356a Mon Sep 17 00:00:00 2001 From: Dariusz Ruminski Date: Sun, 16 Jun 2024 17:17:26 +0200 Subject: [PATCH 130/167] chore: CS fixes --- Extension/EmojiExtension.php | 2 +- .../AbstractBootstrap3HorizontalLayoutTestCase.php | 6 +++--- .../AbstractBootstrap4HorizontalLayoutTestCase.php | 6 +++--- Tests/Extension/AbstractDivLayoutTestCase.php | 6 +++--- Tests/Extension/AbstractLayoutTestCase.php | 14 +++++++------- 5 files changed, 17 insertions(+), 17 deletions(-) diff --git a/Extension/EmojiExtension.php b/Extension/EmojiExtension.php index dcc5a5a0..c98a3aac 100644 --- a/Extension/EmojiExtension.php +++ b/Extension/EmojiExtension.php @@ -38,7 +38,7 @@ public function getFilters(): array } /** - * Converts emoji short code (:wave:) to real emoji (👋) + * Converts emoji short code (:wave:) to real emoji (👋). */ public function emojify(string $string, ?string $catalog = null): string { diff --git a/Tests/Extension/AbstractBootstrap3HorizontalLayoutTestCase.php b/Tests/Extension/AbstractBootstrap3HorizontalLayoutTestCase.php index 3a4104bb..db0789db 100644 --- a/Tests/Extension/AbstractBootstrap3HorizontalLayoutTestCase.php +++ b/Tests/Extension/AbstractBootstrap3HorizontalLayoutTestCase.php @@ -163,9 +163,9 @@ public function testStartTagWithOverriddenVars() public function testStartTagForMultipartForm() { $form = $this->factory->createBuilder('Symfony\Component\Form\Extension\Core\Type\FormType', null, [ - 'method' => 'get', - 'action' => 'http://example.com/directory', - ]) + 'method' => 'get', + 'action' => 'http://example.com/directory', + ]) ->add('file', 'Symfony\Component\Form\Extension\Core\Type\FileType') ->getForm(); diff --git a/Tests/Extension/AbstractBootstrap4HorizontalLayoutTestCase.php b/Tests/Extension/AbstractBootstrap4HorizontalLayoutTestCase.php index 723559ee..9b202e92 100644 --- a/Tests/Extension/AbstractBootstrap4HorizontalLayoutTestCase.php +++ b/Tests/Extension/AbstractBootstrap4HorizontalLayoutTestCase.php @@ -214,9 +214,9 @@ public function testStartTagWithOverriddenVars() public function testStartTagForMultipartForm() { $form = $this->factory->createBuilder('Symfony\Component\Form\Extension\Core\Type\FormType', null, [ - 'method' => 'get', - 'action' => 'http://example.com/directory', - ]) + 'method' => 'get', + 'action' => 'http://example.com/directory', + ]) ->add('file', 'Symfony\Component\Form\Extension\Core\Type\FileType') ->getForm(); diff --git a/Tests/Extension/AbstractDivLayoutTestCase.php b/Tests/Extension/AbstractDivLayoutTestCase.php index a02fca4b..ede4d695 100644 --- a/Tests/Extension/AbstractDivLayoutTestCase.php +++ b/Tests/Extension/AbstractDivLayoutTestCase.php @@ -691,9 +691,9 @@ public function testCollectionRowWithCustomBlock() public function testChoiceRowWithCustomBlock() { $form = $this->factory->createNamedBuilder('name_c', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', 'a', [ - 'choices' => ['ChoiceA' => 'a', 'ChoiceB' => 'b'], - 'expanded' => true, - ]) + 'choices' => ['ChoiceA' => 'a', 'ChoiceB' => 'b'], + 'expanded' => true, + ]) ->getForm(); $this->assertWidgetMatchesXpath($form->createView(), [], diff --git a/Tests/Extension/AbstractLayoutTestCase.php b/Tests/Extension/AbstractLayoutTestCase.php index f340b066..f61fefb3 100644 --- a/Tests/Extension/AbstractLayoutTestCase.php +++ b/Tests/Extension/AbstractLayoutTestCase.php @@ -313,8 +313,8 @@ public function testLabelFormatOverriddenOption() public function testLabelWithoutTranslationOnButton() { $form = $this->factory->createNamedBuilder('myform', 'Symfony\Component\Form\Extension\Core\Type\FormType', null, [ - 'translation_domain' => false, - ]) + 'translation_domain' => false, + ]) ->add('mybutton', 'Symfony\Component\Form\Extension\Core\Type\ButtonType') ->getForm(); $view = $form->get('mybutton')->createView(); @@ -2412,9 +2412,9 @@ public function testStartTagWithOverriddenVars() public function testStartTagForMultipartForm() { $form = $this->factory->createBuilder('Symfony\Component\Form\Extension\Core\Type\FormType', null, [ - 'method' => 'get', - 'action' => 'http://example.com/directory', - ]) + 'method' => 'get', + 'action' => 'http://example.com/directory', + ]) ->add('file', 'Symfony\Component\Form\Extension\Core\Type\FileType') ->getForm(); @@ -2559,8 +2559,8 @@ public function testTranslatedAttributes() public function testAttributesNotTranslatedWhenTranslationDomainIsFalse() { $view = $this->factory->createNamedBuilder('name', 'Symfony\Component\Form\Extension\Core\Type\FormType', null, [ - 'translation_domain' => false, - ]) + 'translation_domain' => false, + ]) ->add('firstName', 'Symfony\Component\Form\Extension\Core\Type\TextType', ['attr' => ['title' => 'Foo']]) ->add('lastName', 'Symfony\Component\Form\Extension\Core\Type\TextType', ['attr' => ['placeholder' => 'Bar']]) ->getForm() From 9d8c235ca99ea15cb5d5018e02751a133fbacedc Mon Sep 17 00:00:00 2001 From: Christophe Coevoet Date: Tue, 25 Jun 2024 14:58:00 +0200 Subject: [PATCH 131/167] Add more precise types in reusable test cases --- Test/FormLayoutTestCase.php | 13 +++++++++++++ Tests/Extension/AbstractLayoutTestCase.php | 4 ++++ Tests/Extension/FormExtensionFieldHelpersTest.php | 2 +- 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/Test/FormLayoutTestCase.php b/Test/FormLayoutTestCase.php index 0c719efc..bd8123a3 100644 --- a/Test/FormLayoutTestCase.php +++ b/Test/FormLayoutTestCase.php @@ -19,6 +19,7 @@ use Symfony\Component\Form\Test\FormIntegrationTestCase; use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; use Twig\Environment; +use Twig\Extension\ExtensionInterface; use Twig\Loader\FilesystemLoader; /** @@ -81,15 +82,27 @@ protected function assertMatchesXpath($html, $expression, $count = 1): void } } + /** + * @return string[] + */ abstract protected function getTemplatePaths(): array; + /** + * @return ExtensionInterface[] + */ abstract protected function getTwigExtensions(): array; + /** + * @return array + */ protected function getTwigGlobals(): array { return []; } + /** + * @return string[] + */ abstract protected function getThemes(): array; protected function renderForm(FormView $view, array $vars = []): string diff --git a/Tests/Extension/AbstractLayoutTestCase.php b/Tests/Extension/AbstractLayoutTestCase.php index f61fefb3..0053f7ca 100644 --- a/Tests/Extension/AbstractLayoutTestCase.php +++ b/Tests/Extension/AbstractLayoutTestCase.php @@ -17,6 +17,7 @@ use Symfony\Component\Form\Extension\Core\Type\SubmitType; use Symfony\Component\Form\Extension\Csrf\CsrfExtension; use Symfony\Component\Form\FormError; +use Symfony\Component\Form\FormExtensionInterface; use Symfony\Component\Form\FormView; use Symfony\Component\Form\Tests\VersionAwareTest; use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; @@ -47,6 +48,9 @@ protected function setUp(): void parent::setUp(); } + /** + * @return FormExtensionInterface[] + */ protected function getExtensions() { return [ diff --git a/Tests/Extension/FormExtensionFieldHelpersTest.php b/Tests/Extension/FormExtensionFieldHelpersTest.php index 8e2c0298..c463eaac 100644 --- a/Tests/Extension/FormExtensionFieldHelpersTest.php +++ b/Tests/Extension/FormExtensionFieldHelpersTest.php @@ -26,7 +26,7 @@ class FormExtensionFieldHelpersTest extends FormIntegrationTestCase private FormExtension $translatorExtension; private FormView $view; - protected function getTypes() + protected function getTypes(): array { return [new TextType(), new ChoiceType()]; } From 0f0769a91c7237419739b19f387e6a114742b342 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Tue, 25 Jun 2024 08:08:32 +0200 Subject: [PATCH 132/167] Unify how --format is handle by commands --- Command/DebugCommand.php | 15 +++++++++++---- Command/LintCommand.php | 5 ++++- Tests/Command/DebugCommandTest.php | 2 +- composer.json | 1 + 4 files changed, 17 insertions(+), 6 deletions(-) diff --git a/Command/DebugCommand.php b/Command/DebugCommand.php index 42b456c2..0e92e3e5 100644 --- a/Command/DebugCommand.php +++ b/Command/DebugCommand.php @@ -57,7 +57,7 @@ protected function configure(): void ->setDefinition([ new InputArgument('name', InputArgument::OPTIONAL, 'The template name'), new InputOption('filter', null, InputOption::VALUE_REQUIRED, 'Show details for all entries matching this filter'), - new InputOption('format', null, InputOption::VALUE_REQUIRED, \sprintf('The output format ("%s")', implode('", "', $this->getAvailableFormatOptions())), 'text'), + new InputOption('format', null, InputOption::VALUE_REQUIRED, \sprintf('The output format ("%s")', implode('", "', $this->getAvailableFormatOptions())), 'txt'), ]) ->setHelp(<<<'EOF' The %command.name% command outputs a list of twig functions, @@ -93,8 +93,14 @@ protected function execute(InputInterface $input, OutputInterface $output): int throw new InvalidArgumentException(\sprintf('Argument "name" not supported, it requires the Twig loader "%s".', FilesystemLoader::class)); } - match ($input->getOption('format')) { - 'text' => $name ? $this->displayPathsText($io, $name) : $this->displayGeneralText($io, $filter), + $format = $input->getOption('format'); + if ('text' === $format) { + trigger_deprecation('symfony/twig-bridge', '7.2', 'The "text" format is deprecated, use "txt" instead.'); + + $format = 'txt'; + } + match ($format) { + 'txt' => $name ? $this->displayPathsText($io, $name) : $this->displayGeneralText($io, $filter), 'json' => $name ? $this->displayPathsJson($io, $name) : $this->displayGeneralJson($io, $filter), default => throw new InvalidArgumentException(\sprintf('Supported formats are "%s".', implode('", "', $this->getAvailableFormatOptions()))), }; @@ -582,8 +588,9 @@ private function getFileLink(string $absolutePath): string return (string) $this->fileLinkFormatter?->format($absolutePath, 1); } + /** @return string[] */ private function getAvailableFormatOptions(): array { - return ['text', 'json']; + return ['txt', 'json']; } } diff --git a/Command/LintCommand.php b/Command/LintCommand.php index 456c1864..96b37c77 100644 --- a/Command/LintCommand.php +++ b/Command/LintCommand.php @@ -71,8 +71,10 @@ protected function configure(): void Or of a whole directory: php %command.full_name% dirname - php %command.full_name% dirname --format=json +The --format option specifies the format of the command output: + + php %command.full_name% dirname --format=json EOF ) ; @@ -280,6 +282,7 @@ public function complete(CompletionInput $input, CompletionSuggestions $suggesti } } + /** @return string[] */ private function getAvailableFormatOptions(): array { return ['txt', 'json', 'github']; diff --git a/Tests/Command/DebugCommandTest.php b/Tests/Command/DebugCommandTest.php index 8a67932f..7ba828c6 100644 --- a/Tests/Command/DebugCommandTest.php +++ b/Tests/Command/DebugCommandTest.php @@ -314,7 +314,7 @@ public function testComplete(array $input, array $expectedSuggestions) public static function provideCompletionSuggestions(): iterable { yield 'name' => [['email'], []]; - yield 'option --format' => [['--format', ''], ['text', 'json']]; + yield 'option --format' => [['--format', ''], ['txt', 'json']]; } private function createCommandTester(array $paths = [], array $bundleMetadata = [], ?string $defaultPath = null, bool $useChainLoader = false, array $globals = []): CommandTester diff --git a/composer.json b/composer.json index f7f8d32d..b707dab2 100644 --- a/composer.json +++ b/composer.json @@ -17,6 +17,7 @@ ], "require": { "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/translation-contracts": "^2.5|^3", "twig/twig": "^3.9" }, From 9a8b5f195826e92219416e1966b47682c2ec9d94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Mon, 1 Jul 2024 00:59:34 +0200 Subject: [PATCH 133/167] Remove random file name for lint:twig output in case of error Before: ERROR in sf_6681e2dd64a2e2.02015003 (line -1) After: ERROR in Standard Input (line -1) --- Command/LintCommand.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Command/LintCommand.php b/Command/LintCommand.php index 96b37c77..54720952 100644 --- a/Command/LintCommand.php +++ b/Command/LintCommand.php @@ -89,7 +89,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $this->format = $input->getOption('format') ?? (GithubActionReporter::isGithubActionEnvironment() ? 'github' : 'txt'); if (['-'] === $filenames) { - return $this->display($input, $output, $io, [$this->validate(file_get_contents('php://stdin'), uniqid('sf_', true))]); + return $this->display($input, $output, $io, [$this->validate(file_get_contents('php://stdin'), 'Standard Input')]); } if (!$filenames) { From 4b6c870e0705743e0877d4b3f1f9a6ce6449ff6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Mon, 1 Jul 2024 01:28:28 +0200 Subject: [PATCH 134/167] Use constant var name to cache trans_default_domain expression result --- NodeVisitor/TranslationDefaultDomainNodeVisitor.php | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/NodeVisitor/TranslationDefaultDomainNodeVisitor.php b/NodeVisitor/TranslationDefaultDomainNodeVisitor.php index a9266066..3faff33c 100644 --- a/NodeVisitor/TranslationDefaultDomainNodeVisitor.php +++ b/NodeVisitor/TranslationDefaultDomainNodeVisitor.php @@ -30,6 +30,8 @@ */ final class TranslationDefaultDomainNodeVisitor implements NodeVisitorInterface { + private const INTERNAL_VAR_NAME = '__internal_trans_default_domain'; + private Scope $scope; public function __construct() @@ -49,9 +51,8 @@ public function enterNode(Node $node, Environment $env): Node return $node; } else { - $var = $this->getVarName(); - $name = new AssignNameExpression($var, $node->getTemplateLine()); - $this->scope->set('domain', new NameExpression($var, $node->getTemplateLine())); + $name = new AssignNameExpression(self::INTERNAL_VAR_NAME, $node->getTemplateLine()); + $this->scope->set('domain', new NameExpression(self::INTERNAL_VAR_NAME, $node->getTemplateLine())); return new SetNode(false, new Node([$name]), new Node([$node->getNode('expr')]), $node->getTemplateLine()); } @@ -111,9 +112,4 @@ private function isNamedArguments(Node $arguments): bool return false; } - - private function getVarName(): string - { - return \sprintf('__internal_%s', hash('xxh128', uniqid(mt_rand(), true))); - } } From d09d344d9400a5f94e344d6fe2c4637b4b31035b Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Sat, 6 Jul 2024 09:57:16 +0200 Subject: [PATCH 135/167] Update .gitattributes --- .gitattributes | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.gitattributes b/.gitattributes index 84c7add0..14c3c359 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,4 +1,3 @@ /Tests export-ignore /phpunit.xml.dist export-ignore -/.gitattributes export-ignore -/.gitignore export-ignore +/.git* export-ignore From 04f930f07662f6e8889685f706e1427d684bbbc6 Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Fri, 26 Jul 2024 13:12:13 +0200 Subject: [PATCH 136/167] [Form] Deprecate VersionAwareTest trait --- .../AbstractBootstrap3LayoutTestCase.php | 8 ---- .../AbstractBootstrap5LayoutTestCase.php | 2 - Tests/Extension/AbstractLayoutTestCase.php | 37 ------------------- 3 files changed, 47 deletions(-) diff --git a/Tests/Extension/AbstractBootstrap3LayoutTestCase.php b/Tests/Extension/AbstractBootstrap3LayoutTestCase.php index 08a026fe..a25bd542 100644 --- a/Tests/Extension/AbstractBootstrap3LayoutTestCase.php +++ b/Tests/Extension/AbstractBootstrap3LayoutTestCase.php @@ -2869,8 +2869,6 @@ public function testColor() public function testWeekSingleText() { - $this->requiresFeatureSet(404); - $form = $this->factory->createNamed('holidays', 'Symfony\Component\Form\Extension\Core\Type\WeekType', '1970-W01', [ 'input' => 'string', 'widget' => 'single_text', @@ -2889,8 +2887,6 @@ public function testWeekSingleText() public function testWeekSingleTextNoHtml5() { - $this->requiresFeatureSet(404); - $form = $this->factory->createNamed('holidays', 'Symfony\Component\Form\Extension\Core\Type\WeekType', '1970-W01', [ 'input' => 'string', 'widget' => 'single_text', @@ -2910,8 +2906,6 @@ public function testWeekSingleTextNoHtml5() public function testWeekChoices() { - $this->requiresFeatureSet(404); - $data = ['year' => (int) date('Y'), 'week' => 1]; $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\WeekType', $data, [ @@ -2938,8 +2932,6 @@ public function testWeekChoices() public function testWeekText() { - $this->requiresFeatureSet(404); - $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\WeekType', '2000-W01', [ 'input' => 'string', 'widget' => 'text', diff --git a/Tests/Extension/AbstractBootstrap5LayoutTestCase.php b/Tests/Extension/AbstractBootstrap5LayoutTestCase.php index 576f2b18..4b3aa7fa 100644 --- a/Tests/Extension/AbstractBootstrap5LayoutTestCase.php +++ b/Tests/Extension/AbstractBootstrap5LayoutTestCase.php @@ -1842,8 +1842,6 @@ public function testTimezoneWithPlaceholder() public function testWeekChoices() { - $this->requiresFeatureSet(404); - $data = ['year' => (int) date('Y'), 'week' => 1]; $form = $this->factory->createNamed('name', WeekType::class, $data, [ diff --git a/Tests/Extension/AbstractLayoutTestCase.php b/Tests/Extension/AbstractLayoutTestCase.php index 0053f7ca..859566a7 100644 --- a/Tests/Extension/AbstractLayoutTestCase.php +++ b/Tests/Extension/AbstractLayoutTestCase.php @@ -19,7 +19,6 @@ use Symfony\Component\Form\FormError; use Symfony\Component\Form\FormExtensionInterface; use Symfony\Component\Form\FormView; -use Symfony\Component\Form\Tests\VersionAwareTest; use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; use Symfony\Component\Translation\TranslatableMessage; use Symfony\Contracts\Translation\TranslatableInterface; @@ -27,8 +26,6 @@ abstract class AbstractLayoutTestCase extends FormLayoutTestCase { - use VersionAwareTest; - protected MockObject&CsrfTokenManagerInterface $csrfTokenManager; protected array $testableFeatures = []; @@ -676,8 +673,6 @@ public function testSingleExpandedChoiceAttributesWithMainAttributes() public function testSingleChoiceWithPreferred() { - $this->requiresFeatureSet(404); - $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', '&a', [ 'choices' => ['Choice&A' => '&a', 'Choice&B' => '&b'], 'preferred_choices' => ['&b'], @@ -702,8 +697,6 @@ public function testSingleChoiceWithPreferred() public function testSingleChoiceWithPreferredAndNoSeparator() { - $this->requiresFeatureSet(404); - $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', '&a', [ 'choices' => ['Choice&A' => '&a', 'Choice&B' => '&b'], 'preferred_choices' => ['&b'], @@ -727,8 +720,6 @@ public function testSingleChoiceWithPreferredAndNoSeparator() public function testSingleChoiceWithPreferredAndBlankSeparator() { - $this->requiresFeatureSet(404); - $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', '&a', [ 'choices' => ['Choice&A' => '&a', 'Choice&B' => '&b'], 'preferred_choices' => ['&b'], @@ -753,8 +744,6 @@ public function testSingleChoiceWithPreferredAndBlankSeparator() public function testChoiceWithOnlyPreferred() { - $this->requiresFeatureSet(404); - $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', '&a', [ 'choices' => ['Choice&A' => '&a', 'Choice&B' => '&b'], 'preferred_choices' => ['&a', '&b'], @@ -1818,8 +1807,6 @@ public function testNumber() public function testRenderNumberWithHtml5NumberType() { - $this->requiresFeatureSet(403); - $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\NumberType', 1234.56, [ 'html5' => true, ]); @@ -1836,8 +1823,6 @@ public function testRenderNumberWithHtml5NumberType() public function testRenderNumberWithHtml5NumberTypeAndStepAttribute() { - $this->requiresFeatureSet(403); - $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\NumberType', 1234.56, [ 'html5' => true, 'attr' => ['step' => '0.1'], @@ -1912,8 +1897,6 @@ public function testPercent() public function testPercentNoSymbol() { - $this->requiresFeatureSet(403); - $form = $this->factory->createNamed('name', PercentType::class, 0.1, ['symbol' => false, 'rounding_mode' => \NumberFormatter::ROUND_CEILING]); $this->assertWidgetMatchesXpath($form->createView(), [], '/input @@ -1927,8 +1910,6 @@ public function testPercentNoSymbol() public function testPercentCustomSymbol() { - $this->requiresFeatureSet(403); - $form = $this->factory->createNamed('name', PercentType::class, 0.1, ['symbol' => '‱', 'rounding_mode' => \NumberFormatter::ROUND_CEILING]); $this->assertWidgetMatchesXpath($form->createView(), [], '/input @@ -2606,8 +2587,6 @@ public function testColor() public function testLabelWithTranslationParameters() { - $this->requiresFeatureSet(403); - $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\TextType'); $html = $this->renderLabel($form->createView(), 'Address is %address%', [ 'label_translation_parameters' => [ @@ -2625,8 +2604,6 @@ public function testLabelWithTranslationParameters() public function testHelpWithTranslationParameters() { - $this->requiresFeatureSet(403); - $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\TextType', null, [ 'help' => 'for company %company%', 'help_translation_parameters' => [ @@ -2697,8 +2674,6 @@ public function trans(TranslatorInterface $translator, ?string $locale = null): public function testAttributesWithTranslationParameters() { - $this->requiresFeatureSet(403); - $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\TextType', null, [ 'attr' => [ 'title' => 'Message to %company%', @@ -2720,8 +2695,6 @@ public function testAttributesWithTranslationParameters() public function testButtonWithTranslationParameters() { - $this->requiresFeatureSet(403); - $form = $this->factory->createNamedBuilder('myform') ->add('mybutton', 'Symfony\Component\Form\Extension\Core\Type\ButtonType', [ 'label' => 'Submit to %company%', @@ -2745,8 +2718,6 @@ public function testButtonWithTranslationParameters() */ public function testSubmitFormNoValidate(bool $validate) { - $this->requiresFeatureSet(404); - $form = $this->factory->create(SubmitType::class, null, [ 'validate' => $validate, ]); @@ -2776,8 +2747,6 @@ public static function submitFormNoValidateProvider() public function testWeekSingleText() { - $this->requiresFeatureSet(404); - $form = $this->factory->createNamed('holidays', 'Symfony\Component\Form\Extension\Core\Type\WeekType', '1970-W01', [ 'input' => 'string', 'widget' => 'single_text', @@ -2795,8 +2764,6 @@ public function testWeekSingleText() public function testWeekSingleTextNoHtml5() { - $this->requiresFeatureSet(404); - $form = $this->factory->createNamed('holidays', 'Symfony\Component\Form\Extension\Core\Type\WeekType', '1970-W01', [ 'input' => 'string', 'widget' => 'single_text', @@ -2815,8 +2782,6 @@ public function testWeekSingleTextNoHtml5() public function testWeekChoices() { - $this->requiresFeatureSet(404); - $data = ['year' => (int) date('Y'), 'week' => 1]; $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\WeekType', $data, [ @@ -2841,8 +2806,6 @@ public function testWeekChoices() public function testWeekText() { - $this->requiresFeatureSet(404); - $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\WeekType', '2000-W01', [ 'input' => 'string', 'widget' => 'text', From 38030470f1e02aaf1000851e62564bd6f29ab57a Mon Sep 17 00:00:00 2001 From: Alexandre Daubois Date: Wed, 31 Jul 2024 16:13:26 +0200 Subject: [PATCH 137/167] Remove unused code and unnecessary `else` branches --- Command/DebugCommand.php | 4 +--- NodeVisitor/TranslationDefaultDomainNodeVisitor.php | 10 +++++----- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/Command/DebugCommand.php b/Command/DebugCommand.php index 0e92e3e5..d0aded57 100644 --- a/Command/DebugCommand.php +++ b/Command/DebugCommand.php @@ -344,15 +344,13 @@ private function getMetadata(string $type, mixed $entity): mixed } // format args - $args = array_map(function (\ReflectionParameter $param) { + return array_map(function (\ReflectionParameter $param) { if ($param->isDefaultValueAvailable()) { return $param->getName().' = '.json_encode($param->getDefaultValue()); } return $param->getName(); }, $args); - - return $args; } return null; diff --git a/NodeVisitor/TranslationDefaultDomainNodeVisitor.php b/NodeVisitor/TranslationDefaultDomainNodeVisitor.php index 3faff33c..c76b7808 100644 --- a/NodeVisitor/TranslationDefaultDomainNodeVisitor.php +++ b/NodeVisitor/TranslationDefaultDomainNodeVisitor.php @@ -50,12 +50,12 @@ public function enterNode(Node $node, Environment $env): Node $this->scope->set('domain', $node->getNode('expr')); return $node; - } else { - $name = new AssignNameExpression(self::INTERNAL_VAR_NAME, $node->getTemplateLine()); - $this->scope->set('domain', new NameExpression(self::INTERNAL_VAR_NAME, $node->getTemplateLine())); - - return new SetNode(false, new Node([$name]), new Node([$node->getNode('expr')]), $node->getTemplateLine()); } + + $name = new AssignNameExpression(self::INTERNAL_VAR_NAME, $node->getTemplateLine()); + $this->scope->set('domain', new NameExpression(self::INTERNAL_VAR_NAME, $node->getTemplateLine())); + + return new SetNode(false, new Node([$name]), new Node([$node->getNode('expr')]), $node->getTemplateLine()); } if (!$this->scope->has('domain')) { From ac122b6d5423cb2463162a716eaa912993345fbd Mon Sep 17 00:00:00 2001 From: Roy de Vos Burchart Date: Thu, 1 Aug 2024 17:21:17 +0200 Subject: [PATCH 138/167] Code style change in `@PER-CS2.0` affecting `@Symfony` (parentheses for anonymous classes) --- Extension/TranslationExtension.php | 2 +- Tests/Extension/AbstractLayoutTestCase.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Extension/TranslationExtension.php b/Extension/TranslationExtension.php index 1958aebe..73c9ec85 100644 --- a/Extension/TranslationExtension.php +++ b/Extension/TranslationExtension.php @@ -47,7 +47,7 @@ public function getTranslator(): TranslatorInterface throw new \LogicException(\sprintf('You cannot use the "%s" if the Translation Contracts are not available. Try running "composer require symfony/translation".', __CLASS__)); } - $this->translator = new class() implements TranslatorInterface { + $this->translator = new class implements TranslatorInterface { use TranslatorTrait; }; } diff --git a/Tests/Extension/AbstractLayoutTestCase.php b/Tests/Extension/AbstractLayoutTestCase.php index 859566a7..a0c9ed40 100644 --- a/Tests/Extension/AbstractLayoutTestCase.php +++ b/Tests/Extension/AbstractLayoutTestCase.php @@ -2652,7 +2652,7 @@ public function testHelpWithTranslatableMessage() public function testHelpWithTranslatableInterface() { - $message = new class() implements TranslatableInterface { + $message = new class implements TranslatableInterface { public function trans(TranslatorInterface $translator, ?string $locale = null): string { return $translator->trans('foo'); From a0d160765e16aa6810c347866e48e3cde3247895 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20Andr=C3=A9?= Date: Sat, 17 Aug 2024 09:41:53 +0200 Subject: [PATCH 139/167] [TwigBridge] Render a `block` via the `#[Template]` attribute --- Attribute/Template.php | 2 ++ EventListener/TemplateAttributeListener.php | 12 ++++++-- .../TemplateAttributeListenerTest.php | 29 +++++++++++++++++++ 3 files changed, 41 insertions(+), 2 deletions(-) diff --git a/Attribute/Template.php b/Attribute/Template.php index e265e239..ef2f193b 100644 --- a/Attribute/Template.php +++ b/Attribute/Template.php @@ -21,11 +21,13 @@ class Template * @param string $template The name of the template to render * @param string[]|null $vars The controller method arguments to pass to the template * @param bool $stream Enables streaming the template + * @param string|null $block The name of the block to use in the template */ public function __construct( public string $template, public ?array $vars = null, public bool $stream = false, + public ?string $block = null, ) { } } diff --git a/EventListener/TemplateAttributeListener.php b/EventListener/TemplateAttributeListener.php index f5962deb..7220f4c4 100644 --- a/EventListener/TemplateAttributeListener.php +++ b/EventListener/TemplateAttributeListener.php @@ -55,8 +55,16 @@ public function onKernelView(ViewEvent $event): void } $event->setResponse($attribute->stream - ? new StreamedResponse(fn () => $this->twig->display($attribute->template, $parameters), $status) - : new Response($this->twig->render($attribute->template, $parameters), $status) + ? new StreamedResponse( + null !== $attribute->block + ? fn () => $this->twig->load($attribute->template)->displayBlock($attribute->block, $parameters) + : fn () => $this->twig->display($attribute->template, $parameters), + $status) + : new Response( + null !== $attribute->block + ? $this->twig->load($attribute->template)->renderBlock($attribute->block, $parameters) + : $this->twig->render($attribute->template, $parameters), + $status) ); } diff --git a/Tests/EventListener/TemplateAttributeListenerTest.php b/Tests/EventListener/TemplateAttributeListenerTest.php index e1fb7f95..478f285e 100644 --- a/Tests/EventListener/TemplateAttributeListenerTest.php +++ b/Tests/EventListener/TemplateAttributeListenerTest.php @@ -17,10 +17,12 @@ use Symfony\Bridge\Twig\Tests\Fixtures\TemplateAttributeController; use Symfony\Component\Form\FormInterface; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\StreamedResponse; use Symfony\Component\HttpKernel\Event\ControllerArgumentsEvent; use Symfony\Component\HttpKernel\Event\ViewEvent; use Symfony\Component\HttpKernel\HttpKernelInterface; use Twig\Environment; +use Twig\Loader\ArrayLoader; class TemplateAttributeListenerTest extends TestCase { @@ -65,6 +67,33 @@ public function testAttribute() $this->assertSame('Bar', $event->getResponse()->getContent()); } + public function testAttributeWithBlock() + { + $twig = new Environment(new ArrayLoader([ + 'foo.html.twig' => 'ERROR {% block bar %}FOOBAR{% endblock %}', + ])); + + $request = new Request(); + $kernel = $this->createMock(HttpKernelInterface::class); + $controllerArgumentsEvent = new ControllerArgumentsEvent($kernel, [new TemplateAttributeController(), 'foo'], ['Bar'], $request, null); + $listener = new TemplateAttributeListener($twig); + + $request->attributes->set('_template', new Template('foo.html.twig', [], false, 'bar')); + $event = new ViewEvent($kernel, $request, HttpKernelInterface::MAIN_REQUEST, ['foo' => 'bar'], $controllerArgumentsEvent); + $listener->onKernelView($event); + $this->assertSame('FOOBAR', $event->getResponse()->getContent()); + + $request->attributes->set('_template', new Template('foo.html.twig', [], true, 'bar')); + $event = new ViewEvent($kernel, $request, HttpKernelInterface::MAIN_REQUEST, ['foo' => 'bar'], $controllerArgumentsEvent); + $listener->onKernelView($event); + $this->assertInstanceOf(StreamedResponse::class, $event->getResponse()); + + $request->attributes->set('_template', new Template('foo.html.twig', [], false, 'not_a_block')); + $event = new ViewEvent($kernel, $request, HttpKernelInterface::MAIN_REQUEST, ['foo' => 'bar'], $controllerArgumentsEvent); + $this->expectExceptionMessage('Block "not_a_block" on template "foo.html.twig" does not exist in "foo.html.twig".'); + $listener->onKernelView($event); + } + public function testForm() { $request = new Request(); From 0f55794a3bf48087cd11837b3d8918c655d8ee78 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Thu, 29 Aug 2024 10:25:47 +0200 Subject: [PATCH 140/167] bump requirement for Twig to 3.12+ --- CHANGELOG.md | 5 ++ Node/DumpNode.php | 8 +-- Node/FormThemeNode.php | 17 ++++-- Node/StopwatchNode.php | 9 +-- Node/TransDefaultDomainNode.php | 9 +-- Node/TransNode.php | 9 +-- Tests/Node/FormThemeTest.php | 4 +- Tests/Node/SearchAndRenderBlockNodeTest.php | 61 +++---------------- .../TranslationNodeVisitorTest.php | 32 +++------- Tests/NodeVisitor/TwigNodeProvider.php | 10 --- .../TokenParser/FormThemeTokenParserTest.php | 22 ++----- TokenParser/FormThemeTokenParser.php | 2 +- composer.json | 2 +- 13 files changed, 52 insertions(+), 138 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index df8f28f0..b18e2745 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +7.2 +--- + + * Deprecate passing a tag to the constructor of `FormThemeNode` + 7.1 --- diff --git a/Node/DumpNode.php b/Node/DumpNode.php index 1367655c..23ebf361 100644 --- a/Node/DumpNode.php +++ b/Node/DumpNode.php @@ -11,7 +11,6 @@ namespace Symfony\Bridge\Twig\Node; -use Twig\Attribute\FirstClassTwigCallableReady; use Twig\Attribute\YieldReady; use Twig\Compiler; use Twig\Node\Node; @@ -26,18 +25,13 @@ public function __construct( private string $varPrefix, ?Node $values, int $lineno, - ?string $tag = null, ) { $nodes = []; if (null !== $values) { $nodes['values'] = $values; } - if (class_exists(FirstClassTwigCallableReady::class)) { - parent::__construct($nodes, [], $lineno); - } else { - parent::__construct($nodes, [], $lineno, $tag); - } + parent::__construct($nodes, [], $lineno); } public function compile(Compiler $compiler): void diff --git a/Node/FormThemeNode.php b/Node/FormThemeNode.php index 1d077097..b9ca29a5 100644 --- a/Node/FormThemeNode.php +++ b/Node/FormThemeNode.php @@ -12,7 +12,6 @@ namespace Symfony\Bridge\Twig\Node; use Symfony\Component\Form\FormRenderer; -use Twig\Attribute\FirstClassTwigCallableReady; use Twig\Attribute\YieldReady; use Twig\Compiler; use Twig\Node\Node; @@ -23,13 +22,19 @@ #[YieldReady] final class FormThemeNode extends Node { - public function __construct(Node $form, Node $resources, int $lineno, ?string $tag = null, bool $only = false) + /** + * @param bool $only + */ + public function __construct(Node $form, Node $resources, int $lineno, $only = false) { - if (class_exists(FirstClassTwigCallableReady::class)) { - parent::__construct(['form' => $form, 'resources' => $resources], ['only' => $only], $lineno); - } else { - parent::__construct(['form' => $form, 'resources' => $resources], ['only' => $only], $lineno, $tag); + if (null === $only || \is_string($only)) { + trigger_deprecation('twig/twig', '3.12', 'Passing a tag to %s() is deprecated.', __METHOD__); + $only = \func_num_args() > 4 ? func_get_arg(4) : true; + } elseif (!\is_bool($only)) { + throw new \TypeError(\sprintf('Argument 4 passed to "%s()" must be a boolean, "%s" given.', __METHOD__, get_debug_type($only))); } + + parent::__construct(['form' => $form, 'resources' => $resources], ['only' => $only], $lineno); } public function compile(Compiler $compiler): void diff --git a/Node/StopwatchNode.php b/Node/StopwatchNode.php index 239d1ca6..55085f2f 100644 --- a/Node/StopwatchNode.php +++ b/Node/StopwatchNode.php @@ -11,7 +11,6 @@ namespace Symfony\Bridge\Twig\Node; -use Twig\Attribute\FirstClassTwigCallableReady; use Twig\Attribute\YieldReady; use Twig\Compiler; use Twig\Node\Expression\AssignNameExpression; @@ -25,13 +24,9 @@ #[YieldReady] 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) { - if (class_exists(FirstClassTwigCallableReady::class)) { - parent::__construct(['body' => $body, 'name' => $name, 'var' => $var], [], $lineno); - } else { - parent::__construct(['body' => $body, 'name' => $name, 'var' => $var], [], $lineno, $tag); - } + parent::__construct(['body' => $body, 'name' => $name, 'var' => $var], [], $lineno); } public function compile(Compiler $compiler): void diff --git a/Node/TransDefaultDomainNode.php b/Node/TransDefaultDomainNode.php index 28cb6f1b..04349839 100644 --- a/Node/TransDefaultDomainNode.php +++ b/Node/TransDefaultDomainNode.php @@ -11,7 +11,6 @@ namespace Symfony\Bridge\Twig\Node; -use Twig\Attribute\FirstClassTwigCallableReady; use Twig\Attribute\YieldReady; use Twig\Compiler; use Twig\Node\Expression\AbstractExpression; @@ -23,13 +22,9 @@ #[YieldReady] final class TransDefaultDomainNode extends Node { - public function __construct(AbstractExpression $expr, int $lineno = 0, ?string $tag = null) + public function __construct(AbstractExpression $expr, int $lineno = 0) { - if (class_exists(FirstClassTwigCallableReady::class)) { - parent::__construct(['expr' => $expr], [], $lineno); - } else { - parent::__construct(['expr' => $expr], [], $lineno, $tag); - } + parent::__construct(['expr' => $expr], [], $lineno); } public function compile(Compiler $compiler): void diff --git a/Node/TransNode.php b/Node/TransNode.php index 42126399..525f2373 100644 --- a/Node/TransNode.php +++ b/Node/TransNode.php @@ -11,7 +11,6 @@ namespace Symfony\Bridge\Twig\Node; -use Twig\Attribute\FirstClassTwigCallableReady; use Twig\Attribute\YieldReady; use Twig\Compiler; use Twig\Node\Expression\AbstractExpression; @@ -27,7 +26,7 @@ #[YieldReady] 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) { $nodes = ['body' => $body]; if (null !== $domain) { @@ -43,11 +42,7 @@ public function __construct(Node $body, ?Node $domain = null, ?AbstractExpressio $nodes['locale'] = $locale; } - if (class_exists(FirstClassTwigCallableReady::class)) { - parent::__construct($nodes, [], $lineno); - } else { - parent::__construct($nodes, [], $lineno, $tag); - } + parent::__construct($nodes, [], $lineno); } public function compile(Compiler $compiler): void diff --git a/Tests/Node/FormThemeTest.php b/Tests/Node/FormThemeTest.php index ec95e352..ff1dad47 100644 --- a/Tests/Node/FormThemeTest.php +++ b/Tests/Node/FormThemeTest.php @@ -68,7 +68,7 @@ public function testCompile() trim($compiler->compile($node)->getSource()) ); - $node = new FormThemeNode($form, $resources, 0, null, true); + $node = new FormThemeNode($form, $resources, 0, true); $this->assertEquals( \sprintf( @@ -90,7 +90,7 @@ public function testCompile() trim($compiler->compile($node)->getSource()) ); - $node = new FormThemeNode($form, $resources, 0, null, true); + $node = new FormThemeNode($form, $resources, 0, true); $this->assertEquals( \sprintf( diff --git a/Tests/Node/SearchAndRenderBlockNodeTest.php b/Tests/Node/SearchAndRenderBlockNodeTest.php index a0da4317..cf960c09 100644 --- a/Tests/Node/SearchAndRenderBlockNodeTest.php +++ b/Tests/Node/SearchAndRenderBlockNodeTest.php @@ -13,7 +13,6 @@ use PHPUnit\Framework\TestCase; use Symfony\Bridge\Twig\Node\SearchAndRenderBlockNode; -use Twig\Attribute\FirstClassTwigCallableReady; use Twig\Compiler; use Twig\Environment; use Twig\Extension\CoreExtension; @@ -33,11 +32,7 @@ public function testCompileWidget() new NameExpression('form', 0), ]); - if (class_exists(FirstClassTwigCallableReady::class)) { - $node = new SearchAndRenderBlockNode(new TwigFunction('form_widget'), $arguments, 0); - } else { - $node = new SearchAndRenderBlockNode('form_widget', $arguments, 0); - } + $node = new SearchAndRenderBlockNode(new TwigFunction('form_widget'), $arguments, 0); $compiler = new Compiler(new Environment($this->createMock(LoaderInterface::class))); @@ -60,11 +55,7 @@ public function testCompileWidgetWithVariables() ], 0), ]); - if (class_exists(FirstClassTwigCallableReady::class)) { - $node = new SearchAndRenderBlockNode(new TwigFunction('form_widget'), $arguments, 0); - } else { - $node = new SearchAndRenderBlockNode('form_widget', $arguments, 0); - } + $node = new SearchAndRenderBlockNode(new TwigFunction('form_widget'), $arguments, 0); $compiler = new Compiler(new Environment($this->createMock(LoaderInterface::class))); @@ -84,11 +75,7 @@ public function testCompileLabelWithLabel() new ConstantExpression('my label', 0), ]); - if (class_exists(FirstClassTwigCallableReady::class)) { - $node = new SearchAndRenderBlockNode(new TwigFunction('form_label'), $arguments, 0); - } else { - $node = new SearchAndRenderBlockNode('form_label', $arguments, 0); - } + $node = new SearchAndRenderBlockNode(new TwigFunction('form_label'), $arguments, 0); $compiler = new Compiler(new Environment($this->createMock(LoaderInterface::class))); @@ -108,11 +95,7 @@ public function testCompileLabelWithNullLabel() new ConstantExpression(null, 0), ]); - if (class_exists(FirstClassTwigCallableReady::class)) { - $node = new SearchAndRenderBlockNode(new TwigFunction('form_label'), $arguments, 0); - } else { - $node = new SearchAndRenderBlockNode('form_label', $arguments, 0); - } + $node = new SearchAndRenderBlockNode(new TwigFunction('form_label'), $arguments, 0); $compiler = new Compiler(new Environment($this->createMock(LoaderInterface::class))); @@ -134,11 +117,7 @@ public function testCompileLabelWithEmptyStringLabel() new ConstantExpression('', 0), ]); - if (class_exists(FirstClassTwigCallableReady::class)) { - $node = new SearchAndRenderBlockNode(new TwigFunction('form_label'), $arguments, 0); - } else { - $node = new SearchAndRenderBlockNode('form_label', $arguments, 0); - } + $node = new SearchAndRenderBlockNode(new TwigFunction('form_label'), $arguments, 0); $compiler = new Compiler(new Environment($this->createMock(LoaderInterface::class))); @@ -159,11 +138,7 @@ public function testCompileLabelWithDefaultLabel() new NameExpression('form', 0), ]); - if (class_exists(FirstClassTwigCallableReady::class)) { - $node = new SearchAndRenderBlockNode(new TwigFunction('form_label'), $arguments, 0); - } else { - $node = new SearchAndRenderBlockNode('form_label', $arguments, 0); - } + $node = new SearchAndRenderBlockNode(new TwigFunction('form_label'), $arguments, 0); $compiler = new Compiler(new Environment($this->createMock(LoaderInterface::class))); @@ -187,11 +162,7 @@ public function testCompileLabelWithAttributes() ], 0), ]); - if (class_exists(FirstClassTwigCallableReady::class)) { - $node = new SearchAndRenderBlockNode(new TwigFunction('form_label'), $arguments, 0); - } else { - $node = new SearchAndRenderBlockNode('form_label', $arguments, 0); - } + $node = new SearchAndRenderBlockNode(new TwigFunction('form_label'), $arguments, 0); $compiler = new Compiler(new Environment($this->createMock(LoaderInterface::class))); @@ -220,11 +191,7 @@ public function testCompileLabelWithLabelAndAttributes() ], 0), ]); - if (class_exists(FirstClassTwigCallableReady::class)) { - $node = new SearchAndRenderBlockNode(new TwigFunction('form_label'), $arguments, 0); - } else { - $node = new SearchAndRenderBlockNode('form_label', $arguments, 0); - } + $node = new SearchAndRenderBlockNode(new TwigFunction('form_label'), $arguments, 0); $compiler = new Compiler(new Environment($this->createMock(LoaderInterface::class))); @@ -252,11 +219,7 @@ public function testCompileLabelWithLabelThatEvaluatesToNull() ), ]); - if (class_exists(FirstClassTwigCallableReady::class)) { - $node = new SearchAndRenderBlockNode(new TwigFunction('form_label'), $arguments, 0); - } else { - $node = new SearchAndRenderBlockNode('form_label', $arguments, 0); - } + $node = new SearchAndRenderBlockNode(new TwigFunction('form_label'), $arguments, 0); $compiler = new Compiler(new Environment($this->createMock(LoaderInterface::class))); @@ -294,11 +257,7 @@ public function testCompileLabelWithLabelThatEvaluatesToNullAndAttributes() ], 0), ]); - if (class_exists(FirstClassTwigCallableReady::class)) { - $node = new SearchAndRenderBlockNode(new TwigFunction('form_label'), $arguments, 0); - } else { - $node = new SearchAndRenderBlockNode('form_label', $arguments, 0); - } + $node = new SearchAndRenderBlockNode(new TwigFunction('form_label'), $arguments, 0); $compiler = new Compiler(new Environment($this->createMock(LoaderInterface::class))); diff --git a/Tests/NodeVisitor/TranslationNodeVisitorTest.php b/Tests/NodeVisitor/TranslationNodeVisitorTest.php index be26c9b4..873e7f36 100644 --- a/Tests/NodeVisitor/TranslationNodeVisitorTest.php +++ b/Tests/NodeVisitor/TranslationNodeVisitorTest.php @@ -13,7 +13,6 @@ use PHPUnit\Framework\TestCase; use Symfony\Bridge\Twig\NodeVisitor\TranslationNodeVisitor; -use Twig\Attribute\FirstClassTwigCallableReady; use Twig\Environment; use Twig\Loader\LoaderInterface; use Twig\Node\Expression\ArrayExpression; @@ -22,7 +21,6 @@ use Twig\Node\Expression\NameExpression; use Twig\Node\Node; use Twig\TwigFilter; -use Twig\TwigFunction; class TranslationNodeVisitorTest extends TestCase { @@ -41,27 +39,15 @@ public function testMessageExtractionWithInvalidDomainNode() { $message = 'new key'; - if (class_exists(FirstClassTwigCallableReady::class)) { - $node = new FilterExpression( - new ConstantExpression($message, 0), - new TwigFilter('trans'), - new Node([ - new ArrayExpression([], 0), - new NameExpression('variable', 0), - ]), - 0 - ); - } else { - $node = new FilterExpression( - new ConstantExpression($message, 0), - new ConstantExpression('trans', 0), - new Node([ - new ArrayExpression([], 0), - new NameExpression('variable', 0), - ]), - 0 - ); - } + $node = new FilterExpression( + new ConstantExpression($message, 0), + new TwigFilter('trans'), + new Node([ + new ArrayExpression([], 0), + new NameExpression('variable', 0), + ]), + 0 + ); $this->testMessagesExtraction($node, [[$message, TranslationNodeVisitor::UNDEFINED_DOMAIN]]); } diff --git a/Tests/NodeVisitor/TwigNodeProvider.php b/Tests/NodeVisitor/TwigNodeProvider.php index 7a79c341..d8e12988 100644 --- a/Tests/NodeVisitor/TwigNodeProvider.php +++ b/Tests/NodeVisitor/TwigNodeProvider.php @@ -13,7 +13,6 @@ use Symfony\Bridge\Twig\Node\TransDefaultDomainNode; use Symfony\Bridge\Twig\Node\TransNode; -use Twig\Attribute\FirstClassTwigCallableReady; use Twig\Node\BodyNode; use Twig\Node\Expression\ArrayExpression; use Twig\Node\Expression\ConstantExpression; @@ -47,15 +46,6 @@ public static function getTransFilter($message, $domain = null, $arguments = nul ] : []; } - if (!class_exists(FirstClassTwigCallableReady::class)) { - return new FilterExpression( - new ConstantExpression($message, 0), - new ConstantExpression('trans', 0), - new Node($arguments), - 0 - ); - } - return new FilterExpression( new ConstantExpression($message, 0), new TwigFilter('trans'), diff --git a/Tests/TokenParser/FormThemeTokenParserTest.php b/Tests/TokenParser/FormThemeTokenParserTest.php index c9c0ce80..35ac52ae 100644 --- a/Tests/TokenParser/FormThemeTokenParserTest.php +++ b/Tests/TokenParser/FormThemeTokenParserTest.php @@ -14,7 +14,6 @@ use PHPUnit\Framework\TestCase; use Symfony\Bridge\Twig\Node\FormThemeNode; use Symfony\Bridge\Twig\TokenParser\FormThemeTokenParser; -use Twig\Attribute\FirstClassTwigCallableReady; use Twig\Environment; use Twig\Loader\LoaderInterface; use Twig\Node\Expression\ArrayExpression; @@ -36,10 +35,7 @@ public function testCompile($source, $expected) $stream = $env->tokenize($source); $parser = new Parser($env); - if (class_exists(FirstClassTwigCallableReady::class)) { - $expected->setNodeTag('form_theme'); - } - + $expected->setNodeTag('form_theme'); $expected->setSourceContext($source); $this->assertEquals($expected, $parser->parse($stream)->getNode('body')->getNode(0)); @@ -56,8 +52,7 @@ public static function getTestsForFormTheme() new ConstantExpression(0, 1), new ConstantExpression('tpl1', 1), ], 1), - 1, - 'form_theme' + 1 ), ], [ @@ -70,8 +65,7 @@ public static function getTestsForFormTheme() new ConstantExpression(1, 1), new ConstantExpression('tpl2', 1), ], 1), - 1, - 'form_theme' + 1 ), ], [ @@ -79,8 +73,7 @@ public static function getTestsForFormTheme() new FormThemeNode( new NameExpression('form', 1), new ConstantExpression('tpl1', 1), - 1, - 'form_theme' + 1 ), ], [ @@ -91,8 +84,7 @@ public static function getTestsForFormTheme() new ConstantExpression(0, 1), new ConstantExpression('tpl1', 1), ], 1), - 1, - 'form_theme' + 1 ), ], [ @@ -105,8 +97,7 @@ public static function getTestsForFormTheme() new ConstantExpression(1, 1), new ConstantExpression('tpl2', 1), ], 1), - 1, - 'form_theme' + 1 ), ], [ @@ -120,7 +111,6 @@ public static function getTestsForFormTheme() new ConstantExpression('tpl2', 1), ], 1), 1, - 'form_theme', true ), ], diff --git a/TokenParser/FormThemeTokenParser.php b/TokenParser/FormThemeTokenParser.php index b95a2a05..413a8f51 100644 --- a/TokenParser/FormThemeTokenParser.php +++ b/TokenParser/FormThemeTokenParser.php @@ -48,7 +48,7 @@ public function parse(Token $token): Node $stream->expect(Token::BLOCK_END_TYPE); - return new FormThemeNode($form, $resources, $lineno, $this->getTag(), $only); + return new FormThemeNode($form, $resources, $lineno, $only); } public function getTag(): string diff --git a/composer.json b/composer.json index b707dab2..3af8ccbb 100644 --- a/composer.json +++ b/composer.json @@ -19,7 +19,7 @@ "php": ">=8.2", "symfony/deprecation-contracts": "^2.5|^3", "symfony/translation-contracts": "^2.5|^3", - "twig/twig": "^3.9" + "twig/twig": "^3.12" }, "require-dev": { "egulias/email-validator": "^2.1.10|^3|^4", From 36e7758e536e00e0594929277755811bffe9faec Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Mon, 2 Sep 2024 14:20:02 +0200 Subject: [PATCH 141/167] fix package name in deprecation --- Node/FormThemeNode.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Node/FormThemeNode.php b/Node/FormThemeNode.php index b9ca29a5..9d9bce1e 100644 --- a/Node/FormThemeNode.php +++ b/Node/FormThemeNode.php @@ -28,7 +28,7 @@ final class FormThemeNode extends Node public function __construct(Node $form, Node $resources, int $lineno, $only = false) { if (null === $only || \is_string($only)) { - trigger_deprecation('twig/twig', '3.12', 'Passing a tag to %s() is deprecated.', __METHOD__); + trigger_deprecation('symfony/twig-bridge', '3.12', 'Passing a tag to %s() is deprecated.', __METHOD__); $only = \func_num_args() > 4 ? func_get_arg(4) : true; } elseif (!\is_bool($only)) { throw new \TypeError(\sprintf('Argument 4 passed to "%s()" must be a boolean, "%s" given.', __METHOD__, get_debug_type($only))); From b29f131ce7a8d7f9103ec35a57435daf69d4b791 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Fri, 30 Aug 2024 09:35:32 +0200 Subject: [PATCH 142/167] allow Twig 4 --- Tests/Node/TransNodeTest.php | 6 ++++++ composer.json | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/Tests/Node/TransNodeTest.php b/Tests/Node/TransNodeTest.php index 0b055cae..35288174 100644 --- a/Tests/Node/TransNodeTest.php +++ b/Tests/Node/TransNodeTest.php @@ -18,6 +18,7 @@ use Twig\Loader\LoaderInterface; use Twig\Node\Expression\NameExpression; use Twig\Node\TextNode; +use Twig\Runtime\LoopIterator; /** * @author Asmir Mustafic @@ -50,6 +51,11 @@ protected function getVariableGetterWithoutStrictCheck($name) protected function getVariableGetterWithStrictCheck($name) { + if (class_exists(LoopIterator::class)) { + return \sprintf('(array_key_exists("%1$s", $context) ? $context["%1$s"] : throw new RuntimeError(\'Variable "%1$s" does not exist.\', 0, $this->source))', $name); + } + + // for Twig 3 and older, can be removed when support for Twig 3 is dropped return \sprintf('(isset($context["%1$s"]) || array_key_exists("%1$s", $context) ? $context["%1$s"] : (function () { throw new RuntimeError(\'Variable "%1$s" does not exist.\', 0, $this->source); })())', $name); } } diff --git a/composer.json b/composer.json index 3af8ccbb..9d94e1c1 100644 --- a/composer.json +++ b/composer.json @@ -19,7 +19,7 @@ "php": ">=8.2", "symfony/deprecation-contracts": "^2.5|^3", "symfony/translation-contracts": "^2.5|^3", - "twig/twig": "^3.12" + "twig/twig": "^3.12|^4.0" }, "require-dev": { "egulias/email-validator": "^2.1.10|^3|^4", From 3e12e7fe583d9ec53f8f5608d78d453d30fc7a3e Mon Sep 17 00:00:00 2001 From: Alexandre Daubois Date: Wed, 18 Sep 2024 13:33:46 +0200 Subject: [PATCH 143/167] Miscellaneous tests improvements --- Tests/Extension/AbstractDivLayoutTestCase.php | 14 ++++++++++++++ Tests/Extension/FormExtensionDivLayoutTest.php | 14 -------------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/Tests/Extension/AbstractDivLayoutTestCase.php b/Tests/Extension/AbstractDivLayoutTestCase.php index ede4d695..cfa2c5c6 100644 --- a/Tests/Extension/AbstractDivLayoutTestCase.php +++ b/Tests/Extension/AbstractDivLayoutTestCase.php @@ -619,6 +619,13 @@ public function testThemeBlockInheritance($theme) ); } + public static function themeBlockInheritanceProvider(): array + { + return [ + [['theme.html.twig']], + ]; + } + /** * @dataProvider themeInheritanceProvider */ @@ -663,6 +670,13 @@ public function testThemeInheritance($parentTheme, $childTheme) ); } + public static function themeInheritanceProvider(): array + { + return [ + [['parent_label.html.twig'], ['child_label.html.twig']], + ]; + } + /** * The block "_name_child_label" should be overridden in the theme of the * implemented driver. diff --git a/Tests/Extension/FormExtensionDivLayoutTest.php b/Tests/Extension/FormExtensionDivLayoutTest.php index ad2627a2..d0e90b1f 100644 --- a/Tests/Extension/FormExtensionDivLayoutTest.php +++ b/Tests/Extension/FormExtensionDivLayoutTest.php @@ -349,20 +349,6 @@ protected function setTheme(FormView $view, array $themes, $useDefaultThemes = t $this->renderer->setTheme($view, $themes, $useDefaultThemes); } - public static function themeBlockInheritanceProvider(): array - { - return [ - [['theme.html.twig']], - ]; - } - - public static function themeInheritanceProvider(): array - { - return [ - [['parent_label.html.twig'], ['child_label.html.twig']], - ]; - } - protected function getTemplatePaths(): array { return [ From 1b1fd833b3322227714f4d5d76a3727d2efdb889 Mon Sep 17 00:00:00 2001 From: Alexandre Daubois Date: Tue, 24 Sep 2024 13:28:07 +0200 Subject: [PATCH 144/167] Remove useless parent method calls in tests --- Tests/Extension/AbstractLayoutTestCase.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/Tests/Extension/AbstractLayoutTestCase.php b/Tests/Extension/AbstractLayoutTestCase.php index a0c9ed40..fa7fcc95 100644 --- a/Tests/Extension/AbstractLayoutTestCase.php +++ b/Tests/Extension/AbstractLayoutTestCase.php @@ -58,8 +58,6 @@ protected function getExtensions() protected function tearDown(): void { \Locale::setDefault($this->defaultLocale); - - parent::tearDown(); } protected function assertWidgetMatchesXpath(FormView $view, array $vars, $xpath) From 07d8ecfb02a6856d709766a88634bcb19791a042 Mon Sep 17 00:00:00 2001 From: Stanislau Kviatkouski <7zete7@gmail.com> Date: Thu, 26 Sep 2024 13:33:10 +0300 Subject: [PATCH 145/167] [TwigBridge] Fixed a parameterized choice label translation --- Extension/FormExtension.php | 12 ++-- .../FormExtensionFieldHelpersTest.php | 59 +++++++++++++++++++ 2 files changed, 67 insertions(+), 4 deletions(-) diff --git a/Extension/FormExtension.php b/Extension/FormExtension.php index 673f8199..01f65ec2 100644 --- a/Extension/FormExtension.php +++ b/Extension/FormExtension.php @@ -19,6 +19,7 @@ use Symfony\Component\Form\FormError; use Symfony\Component\Form\FormRenderer; use Symfony\Component\Form\FormView; +use Symfony\Contracts\Translation\TranslatableInterface; use Symfony\Contracts\Translation\TranslatorInterface; use Twig\Extension\AbstractExtension; use Twig\TwigFilter; @@ -149,23 +150,26 @@ public function getFieldChoices(FormView $view): iterable private function createFieldChoicesList(iterable $choices, string|false|null $translationDomain): iterable { foreach ($choices as $choice) { - $translatableLabel = $this->createFieldTranslation($choice->label, [], $translationDomain); - if ($choice instanceof ChoiceGroupView) { + $translatableLabel = $this->createFieldTranslation($choice->label, [], $translationDomain); yield $translatableLabel => $this->createFieldChoicesList($choice, $translationDomain); continue; } /* @var ChoiceView $choice */ + $translatableLabel = $this->createFieldTranslation($choice->label, $choice->labelTranslationParameters, $translationDomain); yield $translatableLabel => $choice->value; } } - private function createFieldTranslation(?string $value, array $parameters, string|false|null $domain): ?string + private function createFieldTranslation(TranslatableInterface|string|null $value, array $parameters, string|false|null $domain): ?string { if (!$this->translator || !$value || false === $domain) { - return $value; + return null !== $value ? (string) $value : null; + } + if ($value instanceof TranslatableInterface) { + return $value->trans($this->translator); } return $this->translator->trans($value, $parameters, $domain); diff --git a/Tests/Extension/FormExtensionFieldHelpersTest.php b/Tests/Extension/FormExtensionFieldHelpersTest.php index 8e2c0298..b65b53a0 100644 --- a/Tests/Extension/FormExtensionFieldHelpersTest.php +++ b/Tests/Extension/FormExtensionFieldHelpersTest.php @@ -19,6 +19,7 @@ use Symfony\Component\Form\FormError; use Symfony\Component\Form\FormView; use Symfony\Component\Form\Test\FormIntegrationTestCase; +use Symfony\Component\Translation\TranslatableMessage; class FormExtensionFieldHelpersTest extends FormIntegrationTestCase { @@ -81,6 +82,28 @@ protected function setUp(): void 'expanded' => true, 'label' => false, ]) + ->add('parametrized_choice_label', ChoiceType::class, [ + 'choices' => [ + (object) ['value' => 'yes', 'label' => 'parametrized.%yes%'], + (object) ['value' => 'no', 'label' => 'parametrized.%no%'], + ], + 'choice_value' => 'value', + 'choice_label' => 'label', + 'choice_translation_domain' => 'forms', + 'choice_translation_parameters' => [ + ['%yes%' => 'YES'], + ['%no%' => 'NO'], + ], + ]) + ->add('translatable_choice_label', ChoiceType::class, [ + 'choices' => [ + 'yes', + 'no', + ], + 'choice_label' => static function (string $choice) { + return new TranslatableMessage('parametrized.%value%', ['%value%' => $choice], 'forms'); + }, + ]) ->getForm() ; @@ -290,4 +313,40 @@ public function testFieldTranslatedChoicesMultiple() $this->assertSame('salt', $choicesArray[1]['value']); $this->assertSame('[trans]base.salt[/trans]', $choicesArray[1]['label']); } + + public function testChoiceParametrizedLabel() + { + $choices = $this->translatorExtension->getFieldChoices($this->view->children['parametrized_choice_label']); + + $choicesArray = []; + foreach ($choices as $label => $value) { + $choicesArray[] = ['label' => $label, 'value' => $value]; + } + + $this->assertCount(2, $choicesArray); + + $this->assertSame('yes', $choicesArray[0]['value']); + $this->assertSame('[trans]parametrized.YES[/trans]', $choicesArray[0]['label']); + + $this->assertSame('no', $choicesArray[1]['value']); + $this->assertSame('[trans]parametrized.NO[/trans]', $choicesArray[1]['label']); + } + + public function testChoiceTranslatableLabel() + { + $choices = $this->translatorExtension->getFieldChoices($this->view->children['translatable_choice_label']); + + $choicesArray = []; + foreach ($choices as $label => $value) { + $choicesArray[] = ['label' => $label, 'value' => $value]; + } + + $this->assertCount(2, $choicesArray); + + $this->assertSame('yes', $choicesArray[0]['value']); + $this->assertSame('[trans]parametrized.yes[/trans]', $choicesArray[0]['label']); + + $this->assertSame('no', $choicesArray[1]['value']); + $this->assertSame('[trans]parametrized.no[/trans]', $choicesArray[1]['label']); + } } From 9b11931a7f28216162a363f4dd94c141dfeb6af4 Mon Sep 17 00:00:00 2001 From: Alexandre Daubois Date: Sat, 28 Sep 2024 13:16:28 +0200 Subject: [PATCH 146/167] [TwigBridge] Remove `VersionAwareTest` from `AbstractLayoutTestCase` --- .../AbstractBootstrap3LayoutTestCase.php | 8 ---- .../AbstractBootstrap5LayoutTestCase.php | 2 - Tests/Extension/AbstractLayoutTestCase.php | 37 ------------------- 3 files changed, 47 deletions(-) diff --git a/Tests/Extension/AbstractBootstrap3LayoutTestCase.php b/Tests/Extension/AbstractBootstrap3LayoutTestCase.php index 08a026fe..a25bd542 100644 --- a/Tests/Extension/AbstractBootstrap3LayoutTestCase.php +++ b/Tests/Extension/AbstractBootstrap3LayoutTestCase.php @@ -2869,8 +2869,6 @@ public function testColor() public function testWeekSingleText() { - $this->requiresFeatureSet(404); - $form = $this->factory->createNamed('holidays', 'Symfony\Component\Form\Extension\Core\Type\WeekType', '1970-W01', [ 'input' => 'string', 'widget' => 'single_text', @@ -2889,8 +2887,6 @@ public function testWeekSingleText() public function testWeekSingleTextNoHtml5() { - $this->requiresFeatureSet(404); - $form = $this->factory->createNamed('holidays', 'Symfony\Component\Form\Extension\Core\Type\WeekType', '1970-W01', [ 'input' => 'string', 'widget' => 'single_text', @@ -2910,8 +2906,6 @@ public function testWeekSingleTextNoHtml5() public function testWeekChoices() { - $this->requiresFeatureSet(404); - $data = ['year' => (int) date('Y'), 'week' => 1]; $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\WeekType', $data, [ @@ -2938,8 +2932,6 @@ public function testWeekChoices() public function testWeekText() { - $this->requiresFeatureSet(404); - $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\WeekType', '2000-W01', [ 'input' => 'string', 'widget' => 'text', diff --git a/Tests/Extension/AbstractBootstrap5LayoutTestCase.php b/Tests/Extension/AbstractBootstrap5LayoutTestCase.php index 576f2b18..4b3aa7fa 100644 --- a/Tests/Extension/AbstractBootstrap5LayoutTestCase.php +++ b/Tests/Extension/AbstractBootstrap5LayoutTestCase.php @@ -1842,8 +1842,6 @@ public function testTimezoneWithPlaceholder() public function testWeekChoices() { - $this->requiresFeatureSet(404); - $data = ['year' => (int) date('Y'), 'week' => 1]; $form = $this->factory->createNamed('name', WeekType::class, $data, [ diff --git a/Tests/Extension/AbstractLayoutTestCase.php b/Tests/Extension/AbstractLayoutTestCase.php index f340b066..4c620213 100644 --- a/Tests/Extension/AbstractLayoutTestCase.php +++ b/Tests/Extension/AbstractLayoutTestCase.php @@ -18,7 +18,6 @@ use Symfony\Component\Form\Extension\Csrf\CsrfExtension; use Symfony\Component\Form\FormError; use Symfony\Component\Form\FormView; -use Symfony\Component\Form\Tests\VersionAwareTest; use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; use Symfony\Component\Translation\TranslatableMessage; use Symfony\Contracts\Translation\TranslatableInterface; @@ -26,8 +25,6 @@ abstract class AbstractLayoutTestCase extends FormLayoutTestCase { - use VersionAwareTest; - protected MockObject&CsrfTokenManagerInterface $csrfTokenManager; protected array $testableFeatures = []; @@ -672,8 +669,6 @@ public function testSingleExpandedChoiceAttributesWithMainAttributes() public function testSingleChoiceWithPreferred() { - $this->requiresFeatureSet(404); - $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', '&a', [ 'choices' => ['Choice&A' => '&a', 'Choice&B' => '&b'], 'preferred_choices' => ['&b'], @@ -698,8 +693,6 @@ public function testSingleChoiceWithPreferred() public function testSingleChoiceWithPreferredAndNoSeparator() { - $this->requiresFeatureSet(404); - $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', '&a', [ 'choices' => ['Choice&A' => '&a', 'Choice&B' => '&b'], 'preferred_choices' => ['&b'], @@ -723,8 +716,6 @@ public function testSingleChoiceWithPreferredAndNoSeparator() public function testSingleChoiceWithPreferredAndBlankSeparator() { - $this->requiresFeatureSet(404); - $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', '&a', [ 'choices' => ['Choice&A' => '&a', 'Choice&B' => '&b'], 'preferred_choices' => ['&b'], @@ -749,8 +740,6 @@ public function testSingleChoiceWithPreferredAndBlankSeparator() public function testChoiceWithOnlyPreferred() { - $this->requiresFeatureSet(404); - $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', '&a', [ 'choices' => ['Choice&A' => '&a', 'Choice&B' => '&b'], 'preferred_choices' => ['&a', '&b'], @@ -1814,8 +1803,6 @@ public function testNumber() public function testRenderNumberWithHtml5NumberType() { - $this->requiresFeatureSet(403); - $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\NumberType', 1234.56, [ 'html5' => true, ]); @@ -1832,8 +1819,6 @@ public function testRenderNumberWithHtml5NumberType() public function testRenderNumberWithHtml5NumberTypeAndStepAttribute() { - $this->requiresFeatureSet(403); - $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\NumberType', 1234.56, [ 'html5' => true, 'attr' => ['step' => '0.1'], @@ -1908,8 +1893,6 @@ public function testPercent() public function testPercentNoSymbol() { - $this->requiresFeatureSet(403); - $form = $this->factory->createNamed('name', PercentType::class, 0.1, ['symbol' => false, 'rounding_mode' => \NumberFormatter::ROUND_CEILING]); $this->assertWidgetMatchesXpath($form->createView(), [], '/input @@ -1923,8 +1906,6 @@ public function testPercentNoSymbol() public function testPercentCustomSymbol() { - $this->requiresFeatureSet(403); - $form = $this->factory->createNamed('name', PercentType::class, 0.1, ['symbol' => '‱', 'rounding_mode' => \NumberFormatter::ROUND_CEILING]); $this->assertWidgetMatchesXpath($form->createView(), [], '/input @@ -2602,8 +2583,6 @@ public function testColor() public function testLabelWithTranslationParameters() { - $this->requiresFeatureSet(403); - $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\TextType'); $html = $this->renderLabel($form->createView(), 'Address is %address%', [ 'label_translation_parameters' => [ @@ -2621,8 +2600,6 @@ public function testLabelWithTranslationParameters() public function testHelpWithTranslationParameters() { - $this->requiresFeatureSet(403); - $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\TextType', null, [ 'help' => 'for company %company%', 'help_translation_parameters' => [ @@ -2693,8 +2670,6 @@ public function trans(TranslatorInterface $translator, ?string $locale = null): public function testAttributesWithTranslationParameters() { - $this->requiresFeatureSet(403); - $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\TextType', null, [ 'attr' => [ 'title' => 'Message to %company%', @@ -2716,8 +2691,6 @@ public function testAttributesWithTranslationParameters() public function testButtonWithTranslationParameters() { - $this->requiresFeatureSet(403); - $form = $this->factory->createNamedBuilder('myform') ->add('mybutton', 'Symfony\Component\Form\Extension\Core\Type\ButtonType', [ 'label' => 'Submit to %company%', @@ -2741,8 +2714,6 @@ public function testButtonWithTranslationParameters() */ public function testSubmitFormNoValidate(bool $validate) { - $this->requiresFeatureSet(404); - $form = $this->factory->create(SubmitType::class, null, [ 'validate' => $validate, ]); @@ -2772,8 +2743,6 @@ public static function submitFormNoValidateProvider() public function testWeekSingleText() { - $this->requiresFeatureSet(404); - $form = $this->factory->createNamed('holidays', 'Symfony\Component\Form\Extension\Core\Type\WeekType', '1970-W01', [ 'input' => 'string', 'widget' => 'single_text', @@ -2791,8 +2760,6 @@ public function testWeekSingleText() public function testWeekSingleTextNoHtml5() { - $this->requiresFeatureSet(404); - $form = $this->factory->createNamed('holidays', 'Symfony\Component\Form\Extension\Core\Type\WeekType', '1970-W01', [ 'input' => 'string', 'widget' => 'single_text', @@ -2811,8 +2778,6 @@ public function testWeekSingleTextNoHtml5() public function testWeekChoices() { - $this->requiresFeatureSet(404); - $data = ['year' => (int) date('Y'), 'week' => 1]; $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\WeekType', $data, [ @@ -2837,8 +2802,6 @@ public function testWeekChoices() public function testWeekText() { - $this->requiresFeatureSet(404); - $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\WeekType', '2000-W01', [ 'input' => 'string', 'widget' => 'text', From 1b8dac6ac9e2a2b0dd08e48fa91d4f7ecea78d05 Mon Sep 17 00:00:00 2001 From: Alexandre Daubois Date: Thu, 3 Oct 2024 14:15:19 +0200 Subject: [PATCH 147/167] Various CS fix for consistency --- NodeVisitor/TranslationDefaultDomainNodeVisitor.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/NodeVisitor/TranslationDefaultDomainNodeVisitor.php b/NodeVisitor/TranslationDefaultDomainNodeVisitor.php index df5bdd9e..5788f539 100644 --- a/NodeVisitor/TranslationDefaultDomainNodeVisitor.php +++ b/NodeVisitor/TranslationDefaultDomainNodeVisitor.php @@ -59,9 +59,9 @@ public function enterNode(Node $node, Environment $env): Node if (class_exists(Nodes::class)) { return new SetNode(false, new Nodes([$name]), new Nodes([$node->getNode('expr')]), $node->getTemplateLine()); - } else { - return new SetNode(false, new Node([$name]), new Node([$node->getNode('expr')]), $node->getTemplateLine()); - } + } + + return new SetNode(false, new Node([$name]), new Node([$node->getNode('expr')]), $node->getTemplateLine()); } if (!$this->scope->has('domain')) { From d072d64871a7eedc9604cebd52682762776d575f Mon Sep 17 00:00:00 2001 From: PHAS Developer <110562019+phasdev@users.noreply.github.com> Date: Thu, 3 Oct 2024 17:23:13 +0000 Subject: [PATCH 148/167] [TwigBridge] Update main.css email stylesheet to latest foundation-emails release --- Resources/views/Email/zurb_2/main.css | 641 +++++++++++++++----------- 1 file changed, 380 insertions(+), 261 deletions(-) diff --git a/Resources/views/Email/zurb_2/main.css b/Resources/views/Email/zurb_2/main.css index dab0df58..7828ce78 100644 --- a/Resources/views/Email/zurb_2/main.css +++ b/Resources/views/Email/zurb_2/main.css @@ -1,7 +1,7 @@ /* * Copyright (c) 2017 ZURB, inc. -- MIT License * - * https://github.com/foundation/foundation-emails/blob/v2.2.1/dist/foundation-emails.css + * https://github.com/foundation/foundation-emails/blob/v2.4.0/dist/foundation-emails.css */ .wrapper { @@ -34,6 +34,7 @@ body { .ExternalClass span, .ExternalClass font, .ExternalClass td, +.ExternalClass th, .ExternalClass div { line-height: 100%; } @@ -58,34 +59,33 @@ img { center { width: 100%; - min-width: 580px; } a img { border: none; } -p { - margin: 0 0 0 10px; - Margin: 0 0 0 10px; -} - table { border-spacing: 0; border-collapse: collapse; } -td { +td, +th { word-wrap: break-word; -webkit-hyphens: auto; -moz-hyphens: auto; hyphens: auto; border-collapse: collapse !important; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + box-sizing: border-box; } table, tr, -td { +td, +th { padding: 0; vertical-align: top; text-align: left; @@ -140,27 +140,38 @@ th.column { padding-bottom: 16px; } -td.columns .column, -td.columns .columns, -td.column .column, -td.column .columns, -th.columns .column, -th.columns .columns, -th.column .column, -th.column .columns { +td.columns .column.first, +td.columns .columns.first, +td.column .column.first, +td.column .columns.first, +th.columns .column.first, +th.columns .columns.first, +th.column .column.first, +th.column .columns.first { padding-left: 0 !important; +} + +td.columns .column.last, +td.columns .columns.last, +td.column .column.last, +td.column .columns.last, +th.columns .column.last, +th.columns .columns.last, +th.column .column.last, +th.column .columns.last { padding-right: 0 !important; } -td.columns .column center, -td.columns .columns center, -td.column .column center, -td.column .columns center, -th.columns .column center, -th.columns .columns center, -th.column .column center, -th.column .columns center { - min-width: none !important; +td.columns .column:not([class*=large-offset]), +td.columns .columns:not([class*=large-offset]), +td.column .column:not([class*=large-offset]), +td.column .columns:not([class*=large-offset]), +th.columns .column:not([class*=large-offset]), +th.columns .columns:not([class*=large-offset]), +th.column .column:not([class*=large-offset]), +th.column .columns:not([class*=large-offset]) { + padding-left: 0 !important; + padding-right: 0 !important; } td.columns.last, @@ -170,16 +181,34 @@ th.column.last { padding-right: 16px; } -td.columns table:not(.button), -td.column table:not(.button), -th.columns table:not(.button), -th.column table:not(.button) { +td.columns table, +td.column table, +th.columns table, +th.column table { + width: 100%; +} + +td.columns table.button, +td.column table.button, +th.columns table.button, +th.column table.button { + width: auto; +} + +td.columns table.button.expand, +td.columns table.button.expanded, +td.column table.button.expand, +td.column table.button.expanded, +th.columns table.button.expand, +th.columns table.button.expanded, +th.column table.button.expand, +th.column table.button.expanded { width: 100%; } td.large-1, th.large-1 { - width: 32.33333px; + width: 32.3333333333px; padding-left: 8px; padding-right: 8px; } @@ -194,35 +223,30 @@ th.large-1.last { padding-right: 16px; } -.collapse>tbody>tr>td.large-1, -.collapse>tbody>tr>th.large-1 { +.collapse>tbody>tr>td.large-1:not([class*=large-offset]), +.collapse>tbody>tr>th.large-1:not([class*=large-offset]) { padding-right: 0; padding-left: 0; - width: 48.33333px; -} - -.collapse td.large-1.first, -.collapse th.large-1.first, -.collapse td.large-1.last, -.collapse th.large-1.last { - width: 56.33333px; + width: 48.3333333333px; } -td.large-1 center, -th.large-1 center { - min-width: 0.33333px; +.collapse>tbody>tr td.large-1.first, +.collapse>tbody>tr th.large-1.first, +.collapse>tbody>tr td.large-1.last, +.collapse>tbody>tr th.large-1.last { + width: 56.3333333333px; } .body .columns td.large-1, .body .column td.large-1, .body .columns th.large-1, .body .column th.large-1 { - width: 8.33333%; + width: 8.333333%; } td.large-2, th.large-2 { - width: 80.66667px; + width: 80.6666666667px; padding-left: 8px; padding-right: 8px; } @@ -237,30 +261,25 @@ th.large-2.last { padding-right: 16px; } -.collapse>tbody>tr>td.large-2, -.collapse>tbody>tr>th.large-2 { +.collapse>tbody>tr>td.large-2:not([class*=large-offset]), +.collapse>tbody>tr>th.large-2:not([class*=large-offset]) { padding-right: 0; padding-left: 0; - width: 96.66667px; + width: 96.6666666667px; } -.collapse td.large-2.first, -.collapse th.large-2.first, -.collapse td.large-2.last, -.collapse th.large-2.last { - width: 104.66667px; -} - -td.large-2 center, -th.large-2 center { - min-width: 48.66667px; +.collapse>tbody>tr td.large-2.first, +.collapse>tbody>tr th.large-2.first, +.collapse>tbody>tr td.large-2.last, +.collapse>tbody>tr th.large-2.last { + width: 104.6666666667px; } .body .columns td.large-2, .body .column td.large-2, .body .columns th.large-2, .body .column th.large-2 { - width: 16.66667%; + width: 16.666666%; } td.large-3, @@ -280,25 +299,20 @@ th.large-3.last { padding-right: 16px; } -.collapse>tbody>tr>td.large-3, -.collapse>tbody>tr>th.large-3 { +.collapse>tbody>tr>td.large-3:not([class*=large-offset]), +.collapse>tbody>tr>th.large-3:not([class*=large-offset]) { padding-right: 0; padding-left: 0; width: 145px; } -.collapse td.large-3.first, -.collapse th.large-3.first, -.collapse td.large-3.last, -.collapse th.large-3.last { +.collapse>tbody>tr td.large-3.first, +.collapse>tbody>tr th.large-3.first, +.collapse>tbody>tr td.large-3.last, +.collapse>tbody>tr th.large-3.last { width: 153px; } -td.large-3 center, -th.large-3 center { - min-width: 97px; -} - .body .columns td.large-3, .body .column td.large-3, .body .columns th.large-3, @@ -308,7 +322,7 @@ th.large-3 center { td.large-4, th.large-4 { - width: 177.33333px; + width: 177.3333333333px; padding-left: 8px; padding-right: 8px; } @@ -323,35 +337,30 @@ th.large-4.last { padding-right: 16px; } -.collapse>tbody>tr>td.large-4, -.collapse>tbody>tr>th.large-4 { +.collapse>tbody>tr>td.large-4:not([class*=large-offset]), +.collapse>tbody>tr>th.large-4:not([class*=large-offset]) { padding-right: 0; padding-left: 0; - width: 193.33333px; -} - -.collapse td.large-4.first, -.collapse th.large-4.first, -.collapse td.large-4.last, -.collapse th.large-4.last { - width: 201.33333px; + width: 193.3333333333px; } -td.large-4 center, -th.large-4 center { - min-width: 145.33333px; +.collapse>tbody>tr td.large-4.first, +.collapse>tbody>tr th.large-4.first, +.collapse>tbody>tr td.large-4.last, +.collapse>tbody>tr th.large-4.last { + width: 201.3333333333px; } .body .columns td.large-4, .body .column td.large-4, .body .columns th.large-4, .body .column th.large-4 { - width: 33.33333%; + width: 33.333333%; } td.large-5, th.large-5 { - width: 225.66667px; + width: 225.6666666667px; padding-left: 8px; padding-right: 8px; } @@ -366,30 +375,25 @@ th.large-5.last { padding-right: 16px; } -.collapse>tbody>tr>td.large-5, -.collapse>tbody>tr>th.large-5 { +.collapse>tbody>tr>td.large-5:not([class*=large-offset]), +.collapse>tbody>tr>th.large-5:not([class*=large-offset]) { padding-right: 0; padding-left: 0; - width: 241.66667px; + width: 241.6666666667px; } -.collapse td.large-5.first, -.collapse th.large-5.first, -.collapse td.large-5.last, -.collapse th.large-5.last { - width: 249.66667px; -} - -td.large-5 center, -th.large-5 center { - min-width: 193.66667px; +.collapse>tbody>tr td.large-5.first, +.collapse>tbody>tr th.large-5.first, +.collapse>tbody>tr td.large-5.last, +.collapse>tbody>tr th.large-5.last { + width: 249.6666666667px; } .body .columns td.large-5, .body .column td.large-5, .body .columns th.large-5, .body .column th.large-5 { - width: 41.66667%; + width: 41.666666%; } td.large-6, @@ -409,25 +413,20 @@ th.large-6.last { padding-right: 16px; } -.collapse>tbody>tr>td.large-6, -.collapse>tbody>tr>th.large-6 { +.collapse>tbody>tr>td.large-6:not([class*=large-offset]), +.collapse>tbody>tr>th.large-6:not([class*=large-offset]) { padding-right: 0; padding-left: 0; width: 290px; } -.collapse td.large-6.first, -.collapse th.large-6.first, -.collapse td.large-6.last, -.collapse th.large-6.last { +.collapse>tbody>tr td.large-6.first, +.collapse>tbody>tr th.large-6.first, +.collapse>tbody>tr td.large-6.last, +.collapse>tbody>tr th.large-6.last { width: 298px; } -td.large-6 center, -th.large-6 center { - min-width: 242px; -} - .body .columns td.large-6, .body .column td.large-6, .body .columns th.large-6, @@ -437,7 +436,7 @@ th.large-6 center { td.large-7, th.large-7 { - width: 322.33333px; + width: 322.3333333333px; padding-left: 8px; padding-right: 8px; } @@ -452,35 +451,30 @@ th.large-7.last { padding-right: 16px; } -.collapse>tbody>tr>td.large-7, -.collapse>tbody>tr>th.large-7 { +.collapse>tbody>tr>td.large-7:not([class*=large-offset]), +.collapse>tbody>tr>th.large-7:not([class*=large-offset]) { padding-right: 0; padding-left: 0; - width: 338.33333px; -} - -.collapse td.large-7.first, -.collapse th.large-7.first, -.collapse td.large-7.last, -.collapse th.large-7.last { - width: 346.33333px; + width: 338.3333333333px; } -td.large-7 center, -th.large-7 center { - min-width: 290.33333px; +.collapse>tbody>tr td.large-7.first, +.collapse>tbody>tr th.large-7.first, +.collapse>tbody>tr td.large-7.last, +.collapse>tbody>tr th.large-7.last { + width: 346.3333333333px; } .body .columns td.large-7, .body .column td.large-7, .body .columns th.large-7, .body .column th.large-7 { - width: 58.33333%; + width: 58.333333%; } td.large-8, th.large-8 { - width: 370.66667px; + width: 370.6666666667px; padding-left: 8px; padding-right: 8px; } @@ -495,30 +489,25 @@ th.large-8.last { padding-right: 16px; } -.collapse>tbody>tr>td.large-8, -.collapse>tbody>tr>th.large-8 { +.collapse>tbody>tr>td.large-8:not([class*=large-offset]), +.collapse>tbody>tr>th.large-8:not([class*=large-offset]) { padding-right: 0; padding-left: 0; - width: 386.66667px; -} - -.collapse td.large-8.first, -.collapse th.large-8.first, -.collapse td.large-8.last, -.collapse th.large-8.last { - width: 394.66667px; + width: 386.6666666667px; } -td.large-8 center, -th.large-8 center { - min-width: 338.66667px; +.collapse>tbody>tr td.large-8.first, +.collapse>tbody>tr th.large-8.first, +.collapse>tbody>tr td.large-8.last, +.collapse>tbody>tr th.large-8.last { + width: 394.6666666667px; } .body .columns td.large-8, .body .column td.large-8, .body .columns th.large-8, .body .column th.large-8 { - width: 66.66667%; + width: 66.666666%; } td.large-9, @@ -538,25 +527,20 @@ th.large-9.last { padding-right: 16px; } -.collapse>tbody>tr>td.large-9, -.collapse>tbody>tr>th.large-9 { +.collapse>tbody>tr>td.large-9:not([class*=large-offset]), +.collapse>tbody>tr>th.large-9:not([class*=large-offset]) { padding-right: 0; padding-left: 0; width: 435px; } -.collapse td.large-9.first, -.collapse th.large-9.first, -.collapse td.large-9.last, -.collapse th.large-9.last { +.collapse>tbody>tr td.large-9.first, +.collapse>tbody>tr th.large-9.first, +.collapse>tbody>tr td.large-9.last, +.collapse>tbody>tr th.large-9.last { width: 443px; } -td.large-9 center, -th.large-9 center { - min-width: 387px; -} - .body .columns td.large-9, .body .column td.large-9, .body .columns th.large-9, @@ -566,7 +550,7 @@ th.large-9 center { td.large-10, th.large-10 { - width: 467.33333px; + width: 467.3333333333px; padding-left: 8px; padding-right: 8px; } @@ -581,35 +565,30 @@ th.large-10.last { padding-right: 16px; } -.collapse>tbody>tr>td.large-10, -.collapse>tbody>tr>th.large-10 { +.collapse>tbody>tr>td.large-10:not([class*=large-offset]), +.collapse>tbody>tr>th.large-10:not([class*=large-offset]) { padding-right: 0; padding-left: 0; - width: 483.33333px; + width: 483.3333333333px; } -.collapse td.large-10.first, -.collapse th.large-10.first, -.collapse td.large-10.last, -.collapse th.large-10.last { - width: 491.33333px; -} - -td.large-10 center, -th.large-10 center { - min-width: 435.33333px; +.collapse>tbody>tr td.large-10.first, +.collapse>tbody>tr th.large-10.first, +.collapse>tbody>tr td.large-10.last, +.collapse>tbody>tr th.large-10.last { + width: 491.3333333333px; } .body .columns td.large-10, .body .column td.large-10, .body .columns th.large-10, .body .column th.large-10 { - width: 83.33333%; + width: 83.333333%; } td.large-11, th.large-11 { - width: 515.66667px; + width: 515.6666666667px; padding-left: 8px; padding-right: 8px; } @@ -624,30 +603,25 @@ th.large-11.last { padding-right: 16px; } -.collapse>tbody>tr>td.large-11, -.collapse>tbody>tr>th.large-11 { +.collapse>tbody>tr>td.large-11:not([class*=large-offset]), +.collapse>tbody>tr>th.large-11:not([class*=large-offset]) { padding-right: 0; padding-left: 0; - width: 531.66667px; -} - -.collapse td.large-11.first, -.collapse th.large-11.first, -.collapse td.large-11.last, -.collapse th.large-11.last { - width: 539.66667px; + width: 531.6666666667px; } -td.large-11 center, -th.large-11 center { - min-width: 483.66667px; +.collapse>tbody>tr td.large-11.first, +.collapse>tbody>tr th.large-11.first, +.collapse>tbody>tr td.large-11.last, +.collapse>tbody>tr th.large-11.last { + width: 539.6666666667px; } .body .columns td.large-11, .body .column td.large-11, .body .columns th.large-11, .body .column th.large-11 { - width: 91.66667%; + width: 91.666666%; } td.large-12, @@ -667,25 +641,20 @@ th.large-12.last { padding-right: 16px; } -.collapse>tbody>tr>td.large-12, -.collapse>tbody>tr>th.large-12 { +.collapse>tbody>tr>td.large-12:not([class*=large-offset]), +.collapse>tbody>tr>th.large-12:not([class*=large-offset]) { padding-right: 0; padding-left: 0; width: 580px; } -.collapse td.large-12.first, -.collapse th.large-12.first, -.collapse td.large-12.last, -.collapse th.large-12.last { +.collapse>tbody>tr td.large-12.first, +.collapse>tbody>tr th.large-12.first, +.collapse>tbody>tr td.large-12.last, +.collapse>tbody>tr th.large-12.last { width: 588px; } -td.large-12 center, -th.large-12 center { - min-width: 532px; -} - .body .columns td.large-12, .body .column td.large-12, .body .columns th.large-12, @@ -699,7 +668,7 @@ td.large-offset-1.last, th.large-offset-1, th.large-offset-1.first, th.large-offset-1.last { - padding-left: 64.33333px; + padding-left: 64.3333333333px; } td.large-offset-2, @@ -708,7 +677,7 @@ td.large-offset-2.last, th.large-offset-2, th.large-offset-2.first, th.large-offset-2.last { - padding-left: 112.66667px; + padding-left: 112.6666666667px; } td.large-offset-3, @@ -726,7 +695,7 @@ td.large-offset-4.last, th.large-offset-4, th.large-offset-4.first, th.large-offset-4.last { - padding-left: 209.33333px; + padding-left: 209.3333333333px; } td.large-offset-5, @@ -735,7 +704,7 @@ td.large-offset-5.last, th.large-offset-5, th.large-offset-5.first, th.large-offset-5.last { - padding-left: 257.66667px; + padding-left: 257.6666666667px; } td.large-offset-6, @@ -753,7 +722,7 @@ td.large-offset-7.last, th.large-offset-7, th.large-offset-7.first, th.large-offset-7.last { - padding-left: 354.33333px; + padding-left: 354.3333333333px; } td.large-offset-8, @@ -762,7 +731,7 @@ td.large-offset-8.last, th.large-offset-8, th.large-offset-8.first, th.large-offset-8.last { - padding-left: 402.66667px; + padding-left: 402.6666666667px; } td.large-offset-9, @@ -780,7 +749,7 @@ td.large-offset-10.last, th.large-offset-10, th.large-offset-10.first, th.large-offset-10.last { - padding-left: 499.33333px; + padding-left: 499.3333333333px; } td.large-offset-11, @@ -789,7 +758,7 @@ td.large-offset-11.last, th.large-offset-11, th.large-offset-11.first, th.large-offset-11.last { - padding-left: 547.66667px; + padding-left: 547.6666666667px; } td.expander, @@ -896,12 +865,15 @@ span.text-center { float: none !important; text-align: center !important; } + .small-text-center { text-align: center !important; } + .small-text-left { text-align: left !important; } + .small-text-right { text-align: right !important; } @@ -934,8 +906,22 @@ th.float-center { text-align: center; } +td.columns[valign=bottom], +td.column[valign=bottom], +th.columns[valign=bottom], +th.column[valign=bottom] { + vertical-align: bottom; +} + +td.columns[valign=middle], +td.column[valign=middle], +th.columns[valign=middle], +th.column[valign=middle] { + vertical-align: middle; +} + .hide-for-large { - display: none !important; + display: none; mso-hide: all; overflow: hidden; max-height: 0; @@ -960,6 +946,7 @@ table.body table.container .hide-for-large * { } @media only screen and (max-width: 596px) { + table.body table.container .hide-for-large, table.body table.container .row.hide-for-large { display: table !important; @@ -993,8 +980,7 @@ h5, h6, p, td, -th, -a { +th { color: #0a0a0a; font-family: Helvetica, Arial, sans-serif; font-weight: normal; @@ -1002,7 +988,7 @@ a { margin: 0; Margin: 0; text-align: left; - line-height: 1.3; + line-height: 130%; } h1, @@ -1036,7 +1022,7 @@ h4 { } h5 { - font-size: 20px; + font-size: 19px; } h6 { @@ -1049,7 +1035,7 @@ p, td, th { font-size: 16px; - line-height: 1.3; + line-height: 130%; } p { @@ -1059,7 +1045,7 @@ p { p.lead { font-size: 20px; - line-height: 1.6; + line-height: 160%; } p.subheader { @@ -1072,7 +1058,33 @@ p.subheader { color: #8a8a8a; } -small { +p a { + margin: default; + Margin: default; +} + +.text-xs { + font-size: 11.1111111111px; +} + +.text-sm { + font-size: 13.3333333333px; +} + +.text-lg { + font-size: 19.2px; +} + +.text-xl { + font-size: 23.04px; +} + +.text-xxl { + font-size: 27.648px; +} + +small, +.small { font-size: 80%; color: #cacaca; } @@ -1080,6 +1092,11 @@ small { a { color: #2199e8; text-decoration: none; + font-family: Helvetica, Arial, sans-serif; + font-weight: normal; + padding: 0; + text-align: left; + line-height: 130%; } a:hover { @@ -1129,20 +1146,42 @@ pre code span.callout-strong { font-weight: bold; } -table.hr { - width: 100%; +td.columns table.hr table, +td.column table.hr table, +th.columns table.hr table, +th.column table.hr table, +td.columns table.h-line table, +td.column table.h-line table, +th.columns table.h-line table, +th.column table.h-line table { + width: auto; +} + +table.hr th, +table.h-line th { + padding-bottom: 20px; + text-align: center; } -table.hr th { +table.hr table, +table.h-line table { + display: inline-block; + margin: 0; + Margin: 0; +} + +table.hr th, +table.h-line th { + width: 580px; height: 0; - max-width: 580px; + padding-top: 20px; + clear: both; border-top: 0; border-right: 0; border-bottom: 1px solid #0a0a0a; border-left: 0; - margin: 20px auto; - Margin: 20px auto; - clear: both; + font-size: 0; + line-height: 0; } .stat { @@ -1168,6 +1207,17 @@ span.preheader { overflow: hidden; } +@media only screen { + a[x-apple-data-detectors] { + color: inherit !important; + text-decoration: none !important; + font-size: inherit !important; + font-family: inherit !important; + font-weight: inherit !important; + line-height: inherit !important; + } +} + table.button { width: auto; margin: 0 0 16px 0; @@ -1187,6 +1237,7 @@ table.button table td a { font-weight: bold; color: #fefefe; text-decoration: none; + text-align: left; display: inline-block; padding: 8px 16px 8px 16px; border: 0 solid #2199e8; @@ -1203,6 +1254,10 @@ table.button.rounded table td { border: none; } +table.button:not(.expand):not(.expanded) table { + width: auto; +} + table.button:hover table tr td a, table.button:active table tr td a, table.button table tr td a:visited, @@ -1241,7 +1296,7 @@ table.button.large table a { table.button.expand, table.button.expanded { - width: 100% !important; + width: 100%; } table.button.expand table, @@ -1372,7 +1427,7 @@ th.callout-inner { th.callout-inner.primary { background: #def0fc; - border: 1px solid #444444; + border: 1px solid #0f5f94; color: #0a0a0a; } @@ -1385,19 +1440,19 @@ th.callout-inner.secondary { th.callout-inner.success { background: #e1faea; border: 1px solid #1b9448; - color: #fefefe; + color: #0a0a0a; } th.callout-inner.warning { background: #fff3d9; border: 1px solid #996800; - color: #fefefe; + color: #0a0a0a; } th.callout-inner.alert { background: #fce6e2; border: 1px solid #b42912; - color: #fefefe; + color: #0a0a0a; } .thumbnail { @@ -1422,8 +1477,10 @@ table.menu { table.menu td.menu-item, table.menu th.menu-item { - padding: 10px; + padding-top: 10px; padding-right: 10px; + padding-bottom: 10px; + padding-left: 10px; } table.menu td.menu-item a, @@ -1433,8 +1490,10 @@ table.menu th.menu-item a { table.menu.vertical td.menu-item, table.menu.vertical th.menu-item { - padding: 10px; + padding-top: 10px; padding-right: 0; + padding-bottom: 10px; + padding-left: 10px; display: block; } @@ -1454,8 +1513,32 @@ table.menu.text-center a { text-align: center; } -.menu[align="center"] { - width: auto !important; +.menu[align=center] { + width: auto; +} + +.menu[align=center] tr { + text-align: center; +} + +.menu:not(.float-center) .menu-item:first-child { + padding-left: 0 !important; +} + +.menu:not(.float-center) .menu-item:last-child { + padding-right: 0 !important; +} + +.menu.vertical .menu-item { + padding-left: 0 !important; + padding-right: 0 !important; +} + +@media only screen and (max-width: 596px) { + .menu.small-vertical .menu-item { + padding-left: 0 !important; + padding-right: 0 !important; + } } body.outlook p { @@ -1467,12 +1550,15 @@ body.outlook p { width: auto; height: auto; } + table.body center { min-width: 0 !important; } + table.body .container { width: 95% !important; } + table.body .columns, table.body .column { height: auto !important; @@ -1482,78 +1568,85 @@ body.outlook p { padding-left: 16px !important; padding-right: 16px !important; } - table.body .columns .column, - table.body .columns .columns, - table.body .column .column, - table.body .column .columns { - padding-left: 0 !important; - padding-right: 0 !important; - } - table.body .collapse .columns, - table.body .collapse .column { + + table.body .collapse>tbody>tr>.columns, + table.body .collapse>tbody>tr>.column { padding-left: 0 !important; padding-right: 0 !important; } + td.small-1, th.small-1 { display: inline-block !important; - width: 8.33333% !important; + width: 8.333333% !important; } + td.small-2, th.small-2 { display: inline-block !important; - width: 16.66667% !important; + width: 16.666666% !important; } + td.small-3, th.small-3 { display: inline-block !important; width: 25% !important; } + td.small-4, th.small-4 { display: inline-block !important; - width: 33.33333% !important; + width: 33.333333% !important; } + td.small-5, th.small-5 { display: inline-block !important; - width: 41.66667% !important; + width: 41.666666% !important; } + td.small-6, th.small-6 { display: inline-block !important; width: 50% !important; } + td.small-7, th.small-7 { display: inline-block !important; - width: 58.33333% !important; + width: 58.333333% !important; } + td.small-8, th.small-8 { display: inline-block !important; - width: 66.66667% !important; + width: 66.666666% !important; } + td.small-9, th.small-9 { display: inline-block !important; width: 75% !important; } + td.small-10, th.small-10 { display: inline-block !important; - width: 83.33333% !important; + width: 83.333333% !important; } + td.small-11, th.small-11 { display: inline-block !important; - width: 91.66667% !important; + width: 91.666666% !important; } + td.small-12, th.small-12 { display: inline-block !important; width: 100% !important; } + .columns td.small-12, .column td.small-12, .columns th.small-12, @@ -1561,98 +1654,119 @@ body.outlook p { display: block !important; width: 100% !important; } + table.body td.small-offset-1, table.body th.small-offset-1 { - margin-left: 8.33333% !important; - Margin-left: 8.33333% !important; + margin-left: 8.333333% !important; + Margin-left: 8.333333% !important; } + table.body td.small-offset-2, table.body th.small-offset-2 { - margin-left: 16.66667% !important; - Margin-left: 16.66667% !important; + margin-left: 16.666666% !important; + Margin-left: 16.666666% !important; } + table.body td.small-offset-3, table.body th.small-offset-3 { margin-left: 25% !important; Margin-left: 25% !important; } + table.body td.small-offset-4, table.body th.small-offset-4 { - margin-left: 33.33333% !important; - Margin-left: 33.33333% !important; + margin-left: 33.333333% !important; + Margin-left: 33.333333% !important; } + table.body td.small-offset-5, table.body th.small-offset-5 { - margin-left: 41.66667% !important; - Margin-left: 41.66667% !important; + margin-left: 41.666666% !important; + Margin-left: 41.666666% !important; } + table.body td.small-offset-6, table.body th.small-offset-6 { margin-left: 50% !important; Margin-left: 50% !important; } + table.body td.small-offset-7, table.body th.small-offset-7 { - margin-left: 58.33333% !important; - Margin-left: 58.33333% !important; + margin-left: 58.333333% !important; + Margin-left: 58.333333% !important; } + table.body td.small-offset-8, table.body th.small-offset-8 { - margin-left: 66.66667% !important; - Margin-left: 66.66667% !important; + margin-left: 66.666666% !important; + Margin-left: 66.666666% !important; } + table.body td.small-offset-9, table.body th.small-offset-9 { margin-left: 75% !important; Margin-left: 75% !important; } + table.body td.small-offset-10, table.body th.small-offset-10 { - margin-left: 83.33333% !important; - Margin-left: 83.33333% !important; + margin-left: 83.333333% !important; + Margin-left: 83.333333% !important; } + table.body td.small-offset-11, table.body th.small-offset-11 { - margin-left: 91.66667% !important; - Margin-left: 91.66667% !important; + margin-left: 91.666666% !important; + Margin-left: 91.666666% !important; } + table.body table.columns td.expander, table.body table.columns th.expander { display: none !important; } + table.body .right-text-pad, table.body .text-pad-right { padding-left: 10px !important; } + table.body .left-text-pad, table.body .text-pad-left { padding-right: 10px !important; } + table.menu { width: 100% !important; } + table.menu td, table.menu th { width: auto !important; display: inline-block !important; } + table.menu.vertical td, table.menu.vertical th, table.menu.small-vertical td, table.menu.small-vertical th { display: block !important; } - table.menu[align="center"] { + + table.menu[align=center] { width: auto !important; } + table.button.small-expand, table.button.small-expanded { width: 100% !important; } + table.button.small-expand table, table.button.small-expanded table { width: 100%; } + table.button.small-expand table a, table.button.small-expanded table a { text-align: center !important; @@ -1660,8 +1774,13 @@ body.outlook p { padding-left: 0 !important; padding-right: 0 !important; } + table.button.small-expand center, table.button.small-expanded center { min-width: 0; } -} + + th.callout-inner { + padding: 10px !important; + } +} \ No newline at end of file From f9c4f6527425da3413aad7c6dcae7e05aad7201c Mon Sep 17 00:00:00 2001 From: Dariusz Ruminski Date: Sun, 6 Oct 2024 18:45:39 +0200 Subject: [PATCH 149/167] CS: clean some whitespaces/indentation --- .../TranslationDefaultDomainNodeVisitor.php | 3 +- Tests/Node/SearchAndRenderBlockNodeTest.php | 66 +++++++++---------- 2 files changed, 34 insertions(+), 35 deletions(-) diff --git a/NodeVisitor/TranslationDefaultDomainNodeVisitor.php b/NodeVisitor/TranslationDefaultDomainNodeVisitor.php index 5788f539..122842b8 100644 --- a/NodeVisitor/TranslationDefaultDomainNodeVisitor.php +++ b/NodeVisitor/TranslationDefaultDomainNodeVisitor.php @@ -56,10 +56,9 @@ public function enterNode(Node $node, Environment $env): Node $name = new AssignNameExpression(self::INTERNAL_VAR_NAME, $node->getTemplateLine()); $this->scope->set('domain', new NameExpression(self::INTERNAL_VAR_NAME, $node->getTemplateLine())); - if (class_exists(Nodes::class)) { return new SetNode(false, new Nodes([$name]), new Nodes([$node->getNode('expr')]), $node->getTemplateLine()); - } + } return new SetNode(false, new Node([$name]), new Node([$node->getNode('expr')]), $node->getTemplateLine()); } diff --git a/Tests/Node/SearchAndRenderBlockNodeTest.php b/Tests/Node/SearchAndRenderBlockNodeTest.php index 2e790195..f587b3c9 100644 --- a/Tests/Node/SearchAndRenderBlockNodeTest.php +++ b/Tests/Node/SearchAndRenderBlockNodeTest.php @@ -57,9 +57,9 @@ public function testCompileWidgetWithVariables() if (class_exists(Nodes::class)) { $arguments = new Nodes([ new NameExpression('form', 0), - new ArrayExpression([ - new ConstantExpression('foo', 0), - new ConstantExpression('bar', 0), + new ArrayExpression([ + new ConstantExpression('foo', 0), + new ConstantExpression('bar', 0), ], 0), ]); } else { @@ -201,9 +201,9 @@ public function testCompileLabelWithAttributes() $arguments = new Nodes([ new NameExpression('form', 0), new ConstantExpression(null, 0), - new ArrayExpression([ - new ConstantExpression('foo', 0), - new ConstantExpression('bar', 0), + new ArrayExpression([ + new ConstantExpression('foo', 0), + new ConstantExpression('bar', 0), ], 0), ]); } else { @@ -239,11 +239,11 @@ public function testCompileLabelWithLabelAndAttributes() $arguments = new Nodes([ new NameExpression('form', 0), new ConstantExpression('value in argument', 0), - new ArrayExpression([ - new ConstantExpression('foo', 0), - new ConstantExpression('bar', 0), - new ConstantExpression('label', 0), - new ConstantExpression('value in attributes', 0), + new ArrayExpression([ + new ConstantExpression('foo', 0), + new ConstantExpression('bar', 0), + new ConstantExpression('label', 0), + new ConstantExpression('value in attributes', 0), ], 0), ]); } else { @@ -277,14 +277,14 @@ public function testCompileLabelWithLabelThatEvaluatesToNull() if (class_exists(Nodes::class)) { $arguments = new Nodes([ new NameExpression('form', 0), - new ConditionalExpression( - // if - new ConstantExpression(true, 0), - // then - new ConstantExpression(null, 0), - // else - new ConstantExpression(null, 0), - 0 + new ConditionalExpression( + // if + new ConstantExpression(true, 0), + // then + new ConstantExpression(null, 0), + // else + new ConstantExpression(null, 0), + 0 ), ]); } else { @@ -324,20 +324,20 @@ public function testCompileLabelWithLabelThatEvaluatesToNullAndAttributes() if (class_exists(Nodes::class)) { $arguments = new Nodes([ new NameExpression('form', 0), - new ConditionalExpression( - // if - new ConstantExpression(true, 0), - // then - new ConstantExpression(null, 0), - // else - new ConstantExpression(null, 0), - 0 - ), - new ArrayExpression([ - new ConstantExpression('foo', 0), - new ConstantExpression('bar', 0), - new ConstantExpression('label', 0), - new ConstantExpression('value in attributes', 0), + new ConditionalExpression( + // if + new ConstantExpression(true, 0), + // then + new ConstantExpression(null, 0), + // else + new ConstantExpression(null, 0), + 0 + ), + new ArrayExpression([ + new ConstantExpression('foo', 0), + new ConstantExpression('bar', 0), + new ConstantExpression('label', 0), + new ConstantExpression('value in attributes', 0), ], 0), ]); } else { From c825d4371be0a92e57a7f175d1a6c92768ba9cb5 Mon Sep 17 00:00:00 2001 From: Alexandre Daubois Date: Tue, 8 Oct 2024 14:43:47 +0200 Subject: [PATCH 150/167] [TwigBridge] Add missing return type --- Tests/Extension/AbstractLayoutTestCase.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/Extension/AbstractLayoutTestCase.php b/Tests/Extension/AbstractLayoutTestCase.php index fa7fcc95..5a541d7b 100644 --- a/Tests/Extension/AbstractLayoutTestCase.php +++ b/Tests/Extension/AbstractLayoutTestCase.php @@ -48,7 +48,7 @@ protected function setUp(): void /** * @return FormExtensionInterface[] */ - protected function getExtensions() + protected function getExtensions(): array { return [ new CsrfExtension($this->csrfTokenManager), From 170cf4436d8fb7e9edb1ec7b6473da7d9f230c6e Mon Sep 17 00:00:00 2001 From: Alexandre Daubois Date: Fri, 18 Oct 2024 16:04:52 +0200 Subject: [PATCH 151/167] Remove always true/false occurrences --- Command/DebugCommand.php | 1 - 1 file changed, 1 deletion(-) diff --git a/Command/DebugCommand.php b/Command/DebugCommand.php index d0aded57..1c2c1619 100644 --- a/Command/DebugCommand.php +++ b/Command/DebugCommand.php @@ -412,7 +412,6 @@ private function findWrongBundleOverrides(): array } if ($notFoundBundles = array_diff_key($bundleNames, $this->bundlesMetadata)) { - $alternatives = []; foreach ($notFoundBundles as $notFoundBundle => $path) { $alternatives[$path] = $this->findAlternatives($notFoundBundle, array_keys($this->bundlesMetadata)); } From 3e8cc6b655de5843c050fd60f6fa1598c3c2c7b1 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Wed, 23 Oct 2024 10:11:04 +0200 Subject: [PATCH 152/167] revert allowing Twig 4 As Twig 4 will not be released before Symfony 7.2 we should not claim compatibility before a stable Twig 4 release. --- Tests/Node/TransNodeTest.php | 6 ------ composer.json | 2 +- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/Tests/Node/TransNodeTest.php b/Tests/Node/TransNodeTest.php index 35288174..0b055cae 100644 --- a/Tests/Node/TransNodeTest.php +++ b/Tests/Node/TransNodeTest.php @@ -18,7 +18,6 @@ use Twig\Loader\LoaderInterface; use Twig\Node\Expression\NameExpression; use Twig\Node\TextNode; -use Twig\Runtime\LoopIterator; /** * @author Asmir Mustafic @@ -51,11 +50,6 @@ protected function getVariableGetterWithoutStrictCheck($name) protected function getVariableGetterWithStrictCheck($name) { - if (class_exists(LoopIterator::class)) { - return \sprintf('(array_key_exists("%1$s", $context) ? $context["%1$s"] : throw new RuntimeError(\'Variable "%1$s" does not exist.\', 0, $this->source))', $name); - } - - // for Twig 3 and older, can be removed when support for Twig 3 is dropped return \sprintf('(isset($context["%1$s"]) || array_key_exists("%1$s", $context) ? $context["%1$s"] : (function () { throw new RuntimeError(\'Variable "%1$s" does not exist.\', 0, $this->source); })())', $name); } } diff --git a/composer.json b/composer.json index 9d94e1c1..3af8ccbb 100644 --- a/composer.json +++ b/composer.json @@ -19,7 +19,7 @@ "php": ">=8.2", "symfony/deprecation-contracts": "^2.5|^3", "symfony/translation-contracts": "^2.5|^3", - "twig/twig": "^3.12|^4.0" + "twig/twig": "^3.12" }, "require-dev": { "egulias/email-validator": "^2.1.10|^3|^4", From a5d88e50935a998eb84495098f2d1e3aa75de627 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Fri, 25 Oct 2024 18:06:58 +0200 Subject: [PATCH 153/167] fix merge --- Node/StopwatchNode.php | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/Node/StopwatchNode.php b/Node/StopwatchNode.php index 6e584bd1..472b6280 100644 --- a/Node/StopwatchNode.php +++ b/Node/StopwatchNode.php @@ -27,11 +27,7 @@ final class StopwatchNode extends Node { public function __construct(Node $name, Node $body, AssignNameExpression|LocalVariable $var, int $lineno = 0) { - if (class_exists(FirstClassTwigCallableReady::class)) { - parent::__construct(['body' => $body, 'name' => $name, 'var' => $var], [], $lineno); - } else { - parent::__construct(['body' => $body, 'name' => $name, 'var' => $var], [], $lineno, $tag); - } + parent::__construct(['body' => $body, 'name' => $name, 'var' => $var], [], $lineno); } public function compile(Compiler $compiler): void From f2f472a9d9b329c95a221c5136cf102187eff701 Mon Sep 17 00:00:00 2001 From: Yi-Jyun Pan Date: Tue, 29 Oct 2024 10:53:53 +0800 Subject: [PATCH 154/167] [TwigBridge] Use INTERNAL_VAR_NAME instead of getVarName --- NodeVisitor/TranslationDefaultDomainNodeVisitor.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/NodeVisitor/TranslationDefaultDomainNodeVisitor.php b/NodeVisitor/TranslationDefaultDomainNodeVisitor.php index 7d50a2f6..858547c1 100644 --- a/NodeVisitor/TranslationDefaultDomainNodeVisitor.php +++ b/NodeVisitor/TranslationDefaultDomainNodeVisitor.php @@ -55,9 +55,8 @@ public function enterNode(Node $node, Environment $env): Node return $node; } - $var = $this->getVarName(); - $name = class_exists(AssignContextVariable::class) ? new AssignContextVariable($var, $node->getTemplateLine()) : new AssignNameExpression($var, $node->getTemplateLine()); - $this->scope->set('domain', class_exists(ContextVariable::class) ? new ContextVariable($var, $node->getTemplateLine()) : new NameExpression($var, $node->getTemplateLine())); + $name = class_exists(AssignContextVariable::class) ? new AssignContextVariable(self::INTERNAL_VAR_NAME, $node->getTemplateLine()) : new AssignNameExpression(self::INTERNAL_VAR_NAME, $node->getTemplateLine()); + $this->scope->set('domain', class_exists(ContextVariable::class) ? new ContextVariable(self::INTERNAL_VAR_NAME, $node->getTemplateLine()) : new NameExpression(self::INTERNAL_VAR_NAME, $node->getTemplateLine())); if (class_exists(Nodes::class)) { return new SetNode(false, new Nodes([$name]), new Nodes([$node->getNode('expr')]), $node->getTemplateLine()); From e34839ab413e1dc32bd2cb9f95186af1bacaee7e Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Fri, 25 Oct 2024 19:03:09 +0200 Subject: [PATCH 155/167] use reproducible variable names in the default domain node visitor --- .../TranslationDefaultDomainNodeVisitor.php | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/NodeVisitor/TranslationDefaultDomainNodeVisitor.php b/NodeVisitor/TranslationDefaultDomainNodeVisitor.php index 858547c1..ba93f2fe 100644 --- a/NodeVisitor/TranslationDefaultDomainNodeVisitor.php +++ b/NodeVisitor/TranslationDefaultDomainNodeVisitor.php @@ -20,8 +20,7 @@ use Twig\Node\Expression\ConstantExpression; use Twig\Node\Expression\FilterExpression; use Twig\Node\Expression\NameExpression; -use Twig\Node\Expression\Variable\AssignContextVariable; -use Twig\Node\Expression\Variable\ContextVariable; +use Twig\Node\Expression\Variable\LocalVariable; use Twig\Node\ModuleNode; use Twig\Node\Node; use Twig\Node\Nodes; @@ -33,9 +32,8 @@ */ final class TranslationDefaultDomainNodeVisitor implements NodeVisitorInterface { - private const INTERNAL_VAR_NAME = '__internal_trans_default_domain'; - private Scope $scope; + private int $nestingLevel = 0; public function __construct() { @@ -49,19 +47,25 @@ public function enterNode(Node $node, Environment $env): Node } if ($node instanceof TransDefaultDomainNode) { + ++$this->nestingLevel; + if ($node->getNode('expr') instanceof ConstantExpression) { $this->scope->set('domain', $node->getNode('expr')); return $node; } - $name = class_exists(AssignContextVariable::class) ? new AssignContextVariable(self::INTERNAL_VAR_NAME, $node->getTemplateLine()) : new AssignNameExpression(self::INTERNAL_VAR_NAME, $node->getTemplateLine()); - $this->scope->set('domain', class_exists(ContextVariable::class) ? new ContextVariable(self::INTERNAL_VAR_NAME, $node->getTemplateLine()) : new NameExpression(self::INTERNAL_VAR_NAME, $node->getTemplateLine())); - if (class_exists(Nodes::class)) { + $name = new LocalVariable(null, $node->getTemplateLine()); + $this->scope->set('domain', $name); + return new SetNode(false, new Nodes([$name]), new Nodes([$node->getNode('expr')]), $node->getTemplateLine()); } + $var = '__internal_trans_default_domain_'.$this->nestingLevel; + $name = new AssignNameExpression($var, $node->getTemplateLine()); + $this->scope->set('domain', new NameExpression($var, $node->getTemplateLine())); + return new SetNode(false, new Node([$name]), new Node([$node->getNode('expr')]), $node->getTemplateLine()); } @@ -94,6 +98,8 @@ public function enterNode(Node $node, Environment $env): Node public function leaveNode(Node $node, Environment $env): ?Node { if ($node instanceof TransDefaultDomainNode) { + --$this->nestingLevel; + return null; } From 535ab0be4fc563b2bc5fc0cc9e388626d226c63f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20Andr=C3=A9?= Date: Sun, 10 Nov 2024 03:47:09 +0100 Subject: [PATCH 156/167] [TwigBridge] Fix emojify as function in Undefined Handler `emojify` was registered as a function in UndefinedCallableHandler, instead of a filter. * https://symfony.com/doc/current/reference/twig_reference.html#emojify * https://twig.symfony.com/doc/3.x/#symfony-filters --- UndefinedCallableHandler.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/UndefinedCallableHandler.php b/UndefinedCallableHandler.php index c9f502f6..b9438be5 100644 --- a/UndefinedCallableHandler.php +++ b/UndefinedCallableHandler.php @@ -23,6 +23,7 @@ class UndefinedCallableHandler { private const FILTER_COMPONENTS = [ + 'emojify' => 'emoji', 'humanize' => 'form', 'form_encode_currency' => 'form', 'serialize' => 'serializer', @@ -37,7 +38,6 @@ class UndefinedCallableHandler 'asset_version' => 'asset', 'importmap' => 'asset-mapper', 'dump' => 'debug-bundle', - 'emojify' => 'emoji', 'encore_entry_link_tags' => 'webpack-encore-bundle', 'encore_entry_script_tags' => 'webpack-encore-bundle', 'expression' => 'expression-language', From b7efcdfbd484bbf37214a6ce6c921d9502a96c75 Mon Sep 17 00:00:00 2001 From: HypeMC Date: Thu, 14 Nov 2024 18:50:56 +0100 Subject: [PATCH 157/167] [TwigBridge] Remove leftover code --- TokenParser/TransTokenParser.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TokenParser/TransTokenParser.php b/TokenParser/TransTokenParser.php index e60263a4..67b92dd8 100644 --- a/TokenParser/TransTokenParser.php +++ b/TokenParser/TransTokenParser.php @@ -74,7 +74,7 @@ public function parse(Token $token): Node $stream->expect(Token::BLOCK_END_TYPE); - return new TransNode($body, $domain, $count, $vars, $locale, $lineno, $this->getTag()); + return new TransNode($body, $domain, $count, $vars, $locale, $lineno); } public function decideTransFork(Token $token): bool From 9958f5a5b6640734fe4b24c18897191f77a02c61 Mon Sep 17 00:00:00 2001 From: Dariusz Ruminski Date: Wed, 20 Nov 2024 00:05:41 +0100 Subject: [PATCH 158/167] CS: apply minor indentation fixes --- EventListener/TemplateAttributeListener.php | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/EventListener/TemplateAttributeListener.php b/EventListener/TemplateAttributeListener.php index 7220f4c4..45a4e9cc 100644 --- a/EventListener/TemplateAttributeListener.php +++ b/EventListener/TemplateAttributeListener.php @@ -55,16 +55,16 @@ public function onKernelView(ViewEvent $event): void } $event->setResponse($attribute->stream - ? new StreamedResponse( - null !== $attribute->block - ? fn () => $this->twig->load($attribute->template)->displayBlock($attribute->block, $parameters) - : fn () => $this->twig->display($attribute->template, $parameters), - $status) - : new Response( - null !== $attribute->block - ? $this->twig->load($attribute->template)->renderBlock($attribute->block, $parameters) - : $this->twig->render($attribute->template, $parameters), - $status) + ? new StreamedResponse( + null !== $attribute->block + ? fn () => $this->twig->load($attribute->template)->displayBlock($attribute->block, $parameters) + : fn () => $this->twig->display($attribute->template, $parameters), + $status) + : new Response( + null !== $attribute->block + ? $this->twig->load($attribute->template)->renderBlock($attribute->block, $parameters) + : $this->twig->render($attribute->template, $parameters), + $status) ); } From 6a0417fc0dff8cd055edfcaee167d4adcd367582 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Mon, 2 Dec 2024 07:48:00 +0100 Subject: [PATCH 159/167] fix Twig 3.17 compatibility --- Tests/Node/SearchAndRenderBlockNodeTest.php | 88 ++++++++++++--------- 1 file changed, 49 insertions(+), 39 deletions(-) diff --git a/Tests/Node/SearchAndRenderBlockNodeTest.php b/Tests/Node/SearchAndRenderBlockNodeTest.php index 5c2bacf1..47ec58ac 100644 --- a/Tests/Node/SearchAndRenderBlockNodeTest.php +++ b/Tests/Node/SearchAndRenderBlockNodeTest.php @@ -22,6 +22,7 @@ use Twig\Node\Expression\ConditionalExpression; use Twig\Node\Expression\ConstantExpression; use Twig\Node\Expression\NameExpression; +use Twig\Node\Expression\Ternary\ConditionalTernary; use Twig\Node\Expression\Variable\ContextVariable; use Twig\Node\Node; use Twig\Node\Nodes; @@ -308,32 +309,32 @@ public function testCompileLabelWithLabelAndAttributes() public function testCompileLabelWithLabelThatEvaluatesToNull() { + if (class_exists(ConditionalTernary::class)) { + $conditional = new ConditionalTernary( + // if + new ConstantExpression(true, 0), + // then + new ConstantExpression(null, 0), + // else + new ConstantExpression(null, 0), + 0 + ); + } else { + $conditional = new ConditionalExpression( + // if + new ConstantExpression(true, 0), + // then + new ConstantExpression(null, 0), + // else + new ConstantExpression(null, 0), + 0 + ); + } + if (class_exists(Nodes::class)) { - $arguments = new Nodes([ - new ContextVariable('form', 0), - new ConditionalExpression( - // if - new ConstantExpression(true, 0), - // then - new ConstantExpression(null, 0), - // else - new ConstantExpression(null, 0), - 0 - ), - ]); + $arguments = new Nodes([new ContextVariable('form', 0), $conditional]); } else { - $arguments = new Node([ - new NameExpression('form', 0), - new ConditionalExpression( - // if - new ConstantExpression(true, 0), - // then - new ConstantExpression(null, 0), - // else - new ConstantExpression(null, 0), - 0 - ), - ]); + $arguments = new Node([new NameExpression('form', 0), $conditional]); } if (class_exists(FirstClassTwigCallableReady::class)) { @@ -359,18 +360,32 @@ public function testCompileLabelWithLabelThatEvaluatesToNull() public function testCompileLabelWithLabelThatEvaluatesToNullAndAttributes() { + if (class_exists(ConditionalTernary::class)) { + $conditional = new ConditionalTernary( + // if + new ConstantExpression(true, 0), + // then + new ConstantExpression(null, 0), + // else + new ConstantExpression(null, 0), + 0 + ); + } else { + $conditional = new ConditionalExpression( + // if + new ConstantExpression(true, 0), + // then + new ConstantExpression(null, 0), + // else + new ConstantExpression(null, 0), + 0 + ); + } + if (class_exists(Nodes::class)) { $arguments = new Nodes([ new ContextVariable('form', 0), - new ConditionalExpression( - // if - new ConstantExpression(true, 0), - // then - new ConstantExpression(null, 0), - // else - new ConstantExpression(null, 0), - 0 - ), + $conditional, new ArrayExpression([ new ConstantExpression('foo', 0), new ConstantExpression('bar', 0), @@ -381,12 +396,7 @@ public function testCompileLabelWithLabelThatEvaluatesToNullAndAttributes() } else { $arguments = new Node([ new NameExpression('form', 0), - new ConditionalExpression( - new ConstantExpression(true, 0), - new ConstantExpression(null, 0), - new ConstantExpression(null, 0), - 0 - ), + $conditional, new ArrayExpression([ new ConstantExpression('foo', 0), new ConstantExpression('bar', 0), From 4b51036f31d510ee08e53ef90a232e7007870e32 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Mon, 2 Dec 2024 10:05:27 +0100 Subject: [PATCH 160/167] generate conflict-free variable names --- .../TranslationDefaultDomainNodeVisitor.php | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/NodeVisitor/TranslationDefaultDomainNodeVisitor.php b/NodeVisitor/TranslationDefaultDomainNodeVisitor.php index 9a0ecc9d..3b8196fa 100644 --- a/NodeVisitor/TranslationDefaultDomainNodeVisitor.php +++ b/NodeVisitor/TranslationDefaultDomainNodeVisitor.php @@ -21,7 +21,8 @@ use Twig\Node\Expression\ConstantExpression; use Twig\Node\Expression\FilterExpression; use Twig\Node\Expression\NameExpression; -use Twig\Node\Expression\Variable\LocalVariable; +use Twig\Node\Expression\Variable\AssignContextVariable; +use Twig\Node\Expression\Variable\ContextVariable; use Twig\Node\ModuleNode; use Twig\Node\Node; use Twig\Node\Nodes; @@ -34,7 +35,6 @@ final class TranslationDefaultDomainNodeVisitor implements NodeVisitorInterface { private Scope $scope; - private int $nestingLevel = 0; public function __construct() { @@ -48,22 +48,25 @@ public function enterNode(Node $node, Environment $env): Node } if ($node instanceof TransDefaultDomainNode) { - ++$this->nestingLevel; - if ($node->getNode('expr') instanceof ConstantExpression) { $this->scope->set('domain', $node->getNode('expr')); return $node; } + if (null === $templateName = $node->getTemplateName()) { + throw new \LogicException('Cannot traverse a node without a template name.'); + } + + $var = '__internal_trans_default_domain'.hash('xxh128', $templateName); + if (class_exists(Nodes::class)) { - $name = new LocalVariable(null, $node->getTemplateLine()); - $this->scope->set('domain', $name); + $name = new AssignContextVariable($var, $node->getTemplateLine()); + $this->scope->set('domain', new ContextVariable($var, $node->getTemplateLine())); return new SetNode(false, new Nodes([$name]), new Nodes([$node->getNode('expr')]), $node->getTemplateLine()); } - $var = '__internal_trans_default_domain_'.$this->nestingLevel; $name = new AssignNameExpression($var, $node->getTemplateLine()); $this->scope->set('domain', new NameExpression($var, $node->getTemplateLine())); @@ -105,8 +108,6 @@ public function enterNode(Node $node, Environment $env): Node public function leaveNode(Node $node, Environment $env): ?Node { if ($node instanceof TransDefaultDomainNode) { - --$this->nestingLevel; - return null; } From da55b3ef354c59692644221081f36ddbe9bfb9e3 Mon Sep 17 00:00:00 2001 From: Christophe Coevoet Date: Sat, 7 Dec 2024 10:09:39 +0100 Subject: [PATCH 161/167] Add tests covering `trans_default_domain` with dynamic expressions --- Tests/Extension/TranslationExtensionTest.php | 62 ++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/Tests/Extension/TranslationExtensionTest.php b/Tests/Extension/TranslationExtensionTest.php index 96f707cd..f6dd5f62 100644 --- a/Tests/Extension/TranslationExtensionTest.php +++ b/Tests/Extension/TranslationExtensionTest.php @@ -206,6 +206,68 @@ public function testDefaultTranslationDomainWithNamedArguments() $this->assertEquals('foo (custom)foo (foo)foo (custom)foo (custom)foo (fr)foo (custom)foo (fr)', trim($template->render([]))); } + public function testDefaultTranslationDomainWithExpression() + { + $templates = [ + 'index' => ' + {%- extends "base" %} + + {%- trans_default_domain custom_domain %} + + {%- block content %} + {{- "foo"|trans }} + {%- endblock %} + ', + + 'base' => ' + {%- block content "" %} + ', + ]; + + $translator = new Translator('en'); + $translator->addLoader('array', new ArrayLoader()); + $translator->addResource('array', ['foo' => 'foo (messages)'], 'en'); + $translator->addResource('array', ['foo' => 'foo (custom)'], 'en', 'custom'); + $translator->addResource('array', ['foo' => 'foo (foo)'], 'en', 'foo'); + + $template = $this->getTemplate($templates, $translator); + + $this->assertEquals('foo (foo)', trim($template->render(['custom_domain' => 'foo']))); + } + + public function testDefaultTranslationDomainWithExpressionAndInheritance() + { + $templates = [ + 'index' => ' + {%- extends "base" %} + + {%- trans_default_domain foo_domain %} + + {%- block content %} + {{- "foo"|trans }} + {%- endblock %} + ', + + 'base' => ' + {%- trans_default_domain custom_domain %} + + {{- "foo"|trans }} + {%- block content "" %} + {{- "foo"|trans }} + ', + ]; + + $translator = new Translator('en'); + $translator->addLoader('array', new ArrayLoader()); + $translator->addResource('array', ['foo' => 'foo (messages)'], 'en'); + $translator->addResource('array', ['foo' => 'foo (custom)'], 'en', 'custom'); + $translator->addResource('array', ['foo' => 'foo (foo)'], 'en', 'foo'); + + $template = $this->getTemplate($templates, $translator); + + $this->assertEquals('foo (custom)foo (foo)foo (custom)', trim($template->render(['foo_domain' => 'foo', 'custom_domain' => 'custom']))); + } + private function getTemplate($template, ?TranslatorInterface $translator = null): TemplateWrapper { $translator ??= new Translator('en'); From 238e1aac992b5231c66faf10131ace7bdba97065 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Thu, 19 Dec 2024 14:05:52 +0100 Subject: [PATCH 162/167] relax assertions on generated hashes --- Tests/Extension/HttpKernelExtensionTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/Extension/HttpKernelExtensionTest.php b/Tests/Extension/HttpKernelExtensionTest.php index 9b7eb0b1..a7057fda 100644 --- a/Tests/Extension/HttpKernelExtensionTest.php +++ b/Tests/Extension/HttpKernelExtensionTest.php @@ -81,7 +81,7 @@ public function testGenerateFragmentUri() ]); $twig->addRuntimeLoader($loader); - $this->assertSame('/_fragment?_hash=XCg0hX8QzSwik8Xuu9aMXhoCeI4oJOob7lUVacyOtyY%3D&_path=template%3Dfoo.html.twig%26_format%3Dhtml%26_locale%3Den%26_controller%3DSymfony%255CBundle%255CFrameworkBundle%255CController%255CTemplateController%253A%253AtemplateAction', $twig->render('index')); + $this->assertMatchesRegularExpression('#/_fragment\?_hash=.+&_path=template%3Dfoo.html.twig%26_format%3Dhtml%26_locale%3Den%26_controller%3DSymfony%255CBundle%255CFrameworkBundle%255CController%255CTemplateController%253A%253AtemplateAction$#', $twig->render('index')); } protected function getFragmentHandler($returnOrException): FragmentHandler From 99958cee806b4977373eb95dd8fc6131e0ec3859 Mon Sep 17 00:00:00 2001 From: Benjamin Ellis Date: Thu, 30 Jan 2025 09:40:15 +0100 Subject: [PATCH 163/167] [Mime] use isRendered method to avoid rendering an email twice --- Mime/BodyRenderer.php | 2 +- Tests/Mime/BodyRendererTest.php | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Mime/BodyRenderer.php b/Mime/BodyRenderer.php index d5b6d14c..b7ae05f4 100644 --- a/Mime/BodyRenderer.php +++ b/Mime/BodyRenderer.php @@ -45,7 +45,7 @@ public function render(Message $message): void return; } - if (null === $message->getTextTemplate() && null === $message->getHtmlTemplate()) { + if ($message->isRendered()) { // email has already been rendered return; } diff --git a/Tests/Mime/BodyRendererTest.php b/Tests/Mime/BodyRendererTest.php index f5d37e7d..cce8ee9a 100644 --- a/Tests/Mime/BodyRendererTest.php +++ b/Tests/Mime/BodyRendererTest.php @@ -105,10 +105,14 @@ public function testRenderedOnce() ; $email->textTemplate('text'); + $this->assertFalse($email->isRendered()); $renderer->render($email); + $this->assertTrue($email->isRendered()); + $this->assertEquals('Text', $email->getTextBody()); $email->text('reset'); + $this->assertTrue($email->isRendered()); $renderer->render($email); $this->assertEquals('reset', $email->getTextBody()); From d6aecb7196bf610e63ebb64f937c33878d5d03b1 Mon Sep 17 00:00:00 2001 From: Alexandre Daubois Date: Fri, 14 Feb 2025 10:47:54 +0100 Subject: [PATCH 164/167] [TwigBridge] Fix compatibility with Twig 3.21 --- TokenParser/DumpTokenParser.php | 18 +++++++++++++++++- TokenParser/FormThemeTokenParser.php | 10 +++++++--- TokenParser/StopwatchTokenParser.php | 4 +++- TokenParser/TransDefaultDomainTokenParser.php | 4 +++- TokenParser/TransTokenParser.php | 12 ++++++++---- 5 files changed, 38 insertions(+), 10 deletions(-) diff --git a/TokenParser/DumpTokenParser.php b/TokenParser/DumpTokenParser.php index e671f9ba..9c12dc23 100644 --- a/TokenParser/DumpTokenParser.php +++ b/TokenParser/DumpTokenParser.php @@ -14,6 +14,7 @@ use Symfony\Bridge\Twig\Node\DumpNode; use Twig\Node\Expression\Variable\LocalVariable; use Twig\Node\Node; +use Twig\Node\Nodes; use Twig\Token; use Twig\TokenParser\AbstractTokenParser; @@ -34,13 +35,28 @@ public function parse(Token $token): Node { $values = null; if (!$this->parser->getStream()->test(Token::BLOCK_END_TYPE)) { - $values = $this->parser->getExpressionParser()->parseMultitargetExpression(); + $values = method_exists($this->parser, 'parseExpression') ? + $this->parseMultitargetExpression() : + $this->parser->getExpressionParser()->parseMultitargetExpression(); } $this->parser->getStream()->expect(Token::BLOCK_END_TYPE); return new DumpNode(class_exists(LocalVariable::class) ? new LocalVariable(null, $token->getLine()) : $this->parser->getVarName(), $values, $token->getLine(), $this->getTag()); } + private function parseMultitargetExpression(): Node + { + $targets = []; + while (true) { + $targets[] = $this->parser->parseExpression(); + if (!$this->parser->getStream()->nextIf(Token::PUNCTUATION_TYPE, ',')) { + break; + } + } + + return new Nodes($targets); + } + public function getTag(): string { return 'dump'; diff --git a/TokenParser/FormThemeTokenParser.php b/TokenParser/FormThemeTokenParser.php index b95a2a05..c5fc2311 100644 --- a/TokenParser/FormThemeTokenParser.php +++ b/TokenParser/FormThemeTokenParser.php @@ -29,12 +29,16 @@ public function parse(Token $token): Node $lineno = $token->getLine(); $stream = $this->parser->getStream(); - $form = $this->parser->getExpressionParser()->parseExpression(); + $parseExpression = method_exists($this->parser, 'parseExpression') + ? $this->parser->parseExpression(...) + : $this->parser->getExpressionParser()->parseExpression(...); + + $form = $parseExpression(); $only = false; if ($this->parser->getStream()->test(Token::NAME_TYPE, 'with')) { $this->parser->getStream()->next(); - $resources = $this->parser->getExpressionParser()->parseExpression(); + $resources = $parseExpression(); if ($this->parser->getStream()->nextIf(Token::NAME_TYPE, 'only')) { $only = true; @@ -42,7 +46,7 @@ public function parse(Token $token): Node } else { $resources = new ArrayExpression([], $stream->getCurrent()->getLine()); do { - $resources->addElement($this->parser->getExpressionParser()->parseExpression()); + $resources->addElement($parseExpression()); } while (!$stream->test(Token::BLOCK_END_TYPE)); } diff --git a/TokenParser/StopwatchTokenParser.php b/TokenParser/StopwatchTokenParser.php index 324f9d45..c478d9e6 100644 --- a/TokenParser/StopwatchTokenParser.php +++ b/TokenParser/StopwatchTokenParser.php @@ -38,7 +38,9 @@ public function parse(Token $token): Node $stream = $this->parser->getStream(); // {% stopwatch 'bar' %} - $name = $this->parser->getExpressionParser()->parseExpression(); + $name = method_exists($this->parser, 'parseExpression') ? + $this->parser->parseExpression() : + $this->parser->getExpressionParser()->parseExpression(); $stream->expect(Token::BLOCK_END_TYPE); diff --git a/TokenParser/TransDefaultDomainTokenParser.php b/TokenParser/TransDefaultDomainTokenParser.php index c6d850d0..a64a2332 100644 --- a/TokenParser/TransDefaultDomainTokenParser.php +++ b/TokenParser/TransDefaultDomainTokenParser.php @@ -25,7 +25,9 @@ final class TransDefaultDomainTokenParser extends AbstractTokenParser { public function parse(Token $token): Node { - $expr = $this->parser->getExpressionParser()->parseExpression(); + $expr = method_exists($this->parser, 'parseExpression') ? + $this->parser->parseExpression() : + $this->parser->getExpressionParser()->parseExpression(); $this->parser->getStream()->expect(Token::BLOCK_END_TYPE); diff --git a/TokenParser/TransTokenParser.php b/TokenParser/TransTokenParser.php index e60263a4..2d17c9da 100644 --- a/TokenParser/TransTokenParser.php +++ b/TokenParser/TransTokenParser.php @@ -36,29 +36,33 @@ public function parse(Token $token): Node $vars = new ArrayExpression([], $lineno); $domain = null; $locale = null; + $parseExpression = method_exists($this->parser, 'parseExpression') + ? $this->parser->parseExpression(...) + : $this->parser->getExpressionParser()->parseExpression(...); + if (!$stream->test(Token::BLOCK_END_TYPE)) { if ($stream->test('count')) { // {% trans count 5 %} $stream->next(); - $count = $this->parser->getExpressionParser()->parseExpression(); + $count = $parseExpression(); } if ($stream->test('with')) { // {% trans with vars %} $stream->next(); - $vars = $this->parser->getExpressionParser()->parseExpression(); + $vars = $parseExpression(); } if ($stream->test('from')) { // {% trans from "messages" %} $stream->next(); - $domain = $this->parser->getExpressionParser()->parseExpression(); + $domain = $parseExpression(); } if ($stream->test('into')) { // {% trans into "fr" %} $stream->next(); - $locale = $this->parser->getExpressionParser()->parseExpression(); + $locale = $parseExpression(); } elseif (!$stream->test(Token::BLOCK_END_TYPE)) { throw new SyntaxError('Unexpected token. Twig was looking for the "with", "from", or "into" keyword.', $stream->getCurrent()->getLine(), $stream->getSourceContext()); } From 4d706f1c890608fb0b4715d489b86f4b29a5e247 Mon Sep 17 00:00:00 2001 From: Alexandre Daubois Date: Fri, 28 Feb 2025 18:45:24 +0100 Subject: [PATCH 165/167] [TwigBridge] Fix `ModuleNode` call in `TwigNodeProvider` --- Tests/NodeVisitor/TwigNodeProvider.php | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/Tests/NodeVisitor/TwigNodeProvider.php b/Tests/NodeVisitor/TwigNodeProvider.php index 69cf6bec..4fc96d8a 100644 --- a/Tests/NodeVisitor/TwigNodeProvider.php +++ b/Tests/NodeVisitor/TwigNodeProvider.php @@ -15,6 +15,7 @@ use Symfony\Bridge\Twig\Node\TransNode; use Twig\Attribute\FirstClassTwigCallableReady; use Twig\Node\BodyNode; +use Twig\Node\EmptyNode; use Twig\Node\Expression\ArrayExpression; use Twig\Node\Expression\ConstantExpression; use Twig\Node\Expression\FilterExpression; @@ -28,13 +29,15 @@ class TwigNodeProvider { public static function getModule($content) { + $emptyNodeExists = class_exists(EmptyNode::class); + return new ModuleNode( new BodyNode([new ConstantExpression($content, 0)]), null, - new ArrayExpression([], 0), - new ArrayExpression([], 0), - new ArrayExpression([], 0), - null, + $emptyNodeExists ? new EmptyNode() : new ArrayExpression([], 0), + $emptyNodeExists ? new EmptyNode() : new ArrayExpression([], 0), + $emptyNodeExists ? new EmptyNode() : new ArrayExpression([], 0), + $emptyNodeExists ? new EmptyNode() : null, new Source('', '') ); } From acf7783002561656c9583455c77c4a6ecd72040f Mon Sep 17 00:00:00 2001 From: Alexander Hofbauer Date: Wed, 26 Mar 2025 17:02:02 +0100 Subject: [PATCH 166/167] [Form] Use duplicate_preferred_choices to set value of ChoiceType When the preferred choices are not duplicated an option has to be selected in the group of preferred choices. Closes #58561 --- .../views/Form/form_div_layout.html.twig | 2 +- Tests/Extension/AbstractDivLayoutTestCase.php | 50 +++++++++++++++++++ 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/Resources/views/Form/form_div_layout.html.twig b/Resources/views/Form/form_div_layout.html.twig index 02628b5a..d43b40a0 100644 --- a/Resources/views/Form/form_div_layout.html.twig +++ b/Resources/views/Form/form_div_layout.html.twig @@ -85,7 +85,7 @@ {{- block('choice_widget_options') -}} {%- else -%} - + {%- endif -%} {% endfor %} {%- endblock choice_widget_options -%} diff --git a/Tests/Extension/AbstractDivLayoutTestCase.php b/Tests/Extension/AbstractDivLayoutTestCase.php index a02fca4b..bfbd458e 100644 --- a/Tests/Extension/AbstractDivLayoutTestCase.php +++ b/Tests/Extension/AbstractDivLayoutTestCase.php @@ -856,6 +856,56 @@ public function testMultipleChoiceExpandedWithLabelsSetFalseByCallable() ); } + public function testSingleChoiceWithoutDuplicatePreferredIsSelected() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', '&d', [ + 'choices' => ['Choice&A' => '&a', 'Choice&B' => '&b', 'Choice&C' => '&c', 'Choice&D' => '&d'], + 'preferred_choices' => ['&b', '&d'], + 'duplicate_preferred_choices' => false, + 'multiple' => false, + 'expanded' => false, + ]); + + $this->assertWidgetMatchesXpath($form->createView(), ['separator' => '-- sep --'], + '/select + [@name="name"] + [ + ./option[@value="&d"][@selected="selected"] + /following-sibling::option[@disabled="disabled"][.="-- sep --"] + /following-sibling::option[@value="&a"][not(@selected)] + /following-sibling::option[@value="&c"][not(@selected)] + ] + [count(./option)=5] +' + ); + } + + public function testSingleChoiceWithoutDuplicateNotPreferredIsSelected() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', '&d', [ + 'choices' => ['Choice&A' => '&a', 'Choice&B' => '&b', 'Choice&C' => '&c', 'Choice&D' => '&d'], + 'preferred_choices' => ['&b', '&d'], + 'duplicate_preferred_choices' => true, + 'multiple' => false, + 'expanded' => false, + ]); + + $this->assertWidgetMatchesXpath($form->createView(), ['separator' => '-- sep --'], + '/select + [@name="name"] + [ + ./option[@value="&d"][not(@selected)] + /following-sibling::option[@disabled="disabled"][.="-- sep --"] + /following-sibling::option[@value="&a"][not(@selected)] + /following-sibling::option[@value="&b"][not(@selected)] + /following-sibling::option[@value="&c"][not(@selected)] + /following-sibling::option[@value="&d"][@selected="selected"] + ] + [count(./option)=7] +' + ); + } + public function testFormEndWithRest() { $view = $this->factory->createNamedBuilder('name', 'Symfony\Component\Form\Extension\Core\Type\FormType') From bb423dfaa51b6d88b1d64197ae695a0c8ac73778 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Fri, 28 Mar 2025 14:07:35 +0100 Subject: [PATCH 167/167] [Twig] Fix tests --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 2b7a6a39..f663de11 100644 --- a/composer.json +++ b/composer.json @@ -29,7 +29,7 @@ "symfony/asset-mapper": "^6.3|^7.0", "symfony/dependency-injection": "^5.4|^6.0|^7.0", "symfony/finder": "^5.4|^6.0|^7.0", - "symfony/form": "^6.4|^7.0", + "symfony/form": "^6.4.20|^7.2.5", "symfony/html-sanitizer": "^6.1|^7.0", "symfony/http-foundation": "^5.4|^6.0|^7.0", "symfony/http-kernel": "^6.4|^7.0",