diff --git a/src/Symfony/Bridge/Twig/CHANGELOG.md b/src/Symfony/Bridge/Twig/CHANGELOG.md index e0cd19afd52ae..1d8689806912c 100644 --- a/src/Symfony/Bridge/Twig/CHANGELOG.md +++ b/src/Symfony/Bridge/Twig/CHANGELOG.md @@ -9,6 +9,7 @@ CHANGELOG * added support for translating `Translatable` objects * added the `t()` function to easily create `Translatable` objects * Added support for extracting messages from the `t()` function + * Added `field_*` Twig functions to access string values from Form fields 5.0.0 ----- diff --git a/src/Symfony/Bridge/Twig/Extension/FormExtension.php b/src/Symfony/Bridge/Twig/Extension/FormExtension.php index 0f4076db53275..080a5eb06e57b 100644 --- a/src/Symfony/Bridge/Twig/Extension/FormExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/FormExtension.php @@ -12,8 +12,11 @@ namespace Symfony\Bridge\Twig\Extension; 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\FormView; +use Symfony\Contracts\Translation\TranslatorInterface; use Twig\Extension\AbstractExtension; use Twig\TwigFilter; use Twig\TwigFunction; @@ -27,6 +30,13 @@ */ final class FormExtension extends AbstractExtension { + private $translator; + + public function __construct(TranslatorInterface $translator = null) + { + $this->translator = $translator; + } + /** * {@inheritdoc} */ @@ -55,6 +65,12 @@ public function getFunctions(): array 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_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']), ]; } @@ -79,6 +95,80 @@ public function getTests(): array new TwigTest('rootform', 'Symfony\Bridge\Twig\Extension\twig_is_root_form'), ]; } + + public function getFieldName(FormView $view): string + { + $view->setRendered(); + + return $view->vars['full_name']; + } + + public function getFieldValue(FormView $view): string + { + return $view->vars['value']; + } + + public function getFieldLabel(FormView $view): string + { + return $this->createFieldTranslation( + $view->vars['label'], + $view->vars['label_translation_parameters'] ?: [], + $view->vars['translation_domain'] + ); + } + + public function getFieldHelp(FormView $view): string + { + return $this->createFieldTranslation( + $view->vars['help'], + $view->vars['help_translation_parameters'] ?: [], + $view->vars['translation_domain'] + ); + } + + /** + * @return string[] + */ + public function getFieldErrors(FormView $view): iterable + { + /** @var FormError $error */ + foreach ($view->vars['errors'] as $error) { + yield $error->getMessage(); + } + } + + /** + * @return string[]|string[][] + */ + 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 + { + foreach ($choices as $choice) { + $translatableLabel = $this->createFieldTranslation($choice->label, [], $translationDomain); + + if ($choice instanceof ChoiceGroupView) { + yield $translatableLabel => $this->createFieldChoicesList($choice, $translationDomain); + + continue; + } + + /* @var ChoiceView $choice */ + yield $translatableLabel => $choice->value; + } + } + + private function createFieldTranslation(?string $value, array $parameters, $domain): string + { + if (!$this->translator || !$value || false === $domain) { + return $value; + } + + return $this->translator->trans($value, $parameters, $domain); + } } /** diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionFieldHelpersTest.php b/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionFieldHelpersTest.php new file mode 100644 index 0000000000000..f773c1b430918 --- /dev/null +++ b/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionFieldHelpersTest.php @@ -0,0 +1,237 @@ + + * + * 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\Bridge\Twig\Extension\FormExtension; +use Symfony\Bridge\Twig\Tests\Extension\Fixtures\StubTranslator; +use Symfony\Component\Form\Extension\Core\Type\ChoiceType; +use Symfony\Component\Form\Extension\Core\Type\FormType; +use Symfony\Component\Form\Extension\Core\Type\TextType; +use Symfony\Component\Form\FormError; +use Symfony\Component\Form\FormView; +use Symfony\Component\Form\Test\FormIntegrationTestCase; + +class FormExtensionFieldHelpersTest extends FormIntegrationTestCase +{ + /** + * @var FormExtension + */ + private $rawExtension; + + /** + * @var FormExtension + */ + private $translatorExtension; + + /** + * @var FormView + */ + private $view; + + protected function getTypes() + { + return [new TextType(), new ChoiceType()]; + } + + protected function setUp(): void + { + parent::setUp(); + + $this->rawExtension = new FormExtension(); + $this->translatorExtension = new FormExtension(new StubTranslator()); + + $form = $this->factory->createNamedBuilder('register', FormType::class, ['username' => 'tgalopin']) + ->add('username', TextType::class, [ + 'label' => 'base.username', + 'label_translation_parameters' => ['%label_brand%' => 'Symfony'], + 'help' => 'base.username_help', + 'help_translation_parameters' => ['%help_brand%' => 'Symfony'], + 'translation_domain' => 'forms', + ]) + ->add('choice_flat', ChoiceType::class, [ + 'choices' => [ + 'base.yes' => 'yes', + 'base.no' => 'no', + ], + 'choice_translation_domain' => 'forms', + ]) + ->add('choice_grouped', ChoiceType::class, [ + 'choices' => [ + 'base.europe' => [ + 'base.fr' => 'fr', + 'base.de' => 'de', + ], + 'base.asia' => [ + 'base.cn' => 'cn', + 'base.jp' => 'jp', + ], + ], + 'choice_translation_domain' => 'forms', + ]) + ->getForm() + ; + + $form->get('username')->addError(new FormError('username.max_length')); + + $this->view = $form->createView(); + } + + public function testFieldName() + { + $this->assertFalse($this->view->children['username']->isRendered()); + $this->assertSame('register[username]', $this->rawExtension->getFieldName($this->view->children['username'])); + $this->assertTrue($this->view->children['username']->isRendered()); + } + + public function testFieldValue() + { + $this->assertSame('tgalopin', $this->rawExtension->getFieldValue($this->view->children['username'])); + } + + public function testFieldLabel() + { + $this->assertSame('base.username', $this->rawExtension->getFieldLabel($this->view->children['username'])); + } + + public function testFieldTranslatedLabel() + { + $this->assertSame('[trans]base.username[/trans]', $this->translatorExtension->getFieldLabel($this->view->children['username'])); + } + + public function testFieldHelp() + { + $this->assertSame('base.username_help', $this->rawExtension->getFieldHelp($this->view->children['username'])); + } + + public function testFieldTranslatedHelp() + { + $this->assertSame('[trans]base.username_help[/trans]', $this->translatorExtension->getFieldHelp($this->view->children['username'])); + } + + public function testFieldErrors() + { + $errors = $this->rawExtension->getFieldErrors($this->view->children['username']); + $this->assertSame(['username.max_length'], iterator_to_array($errors)); + } + + public function testFieldTranslatedErrors() + { + $errors = $this->translatorExtension->getFieldErrors($this->view->children['username']); + $this->assertSame(['username.max_length'], iterator_to_array($errors)); + } + + public function testFieldChoicesFlat() + { + $choices = $this->rawExtension->getFieldChoices($this->view->children['choice_flat']); + + $choicesArray = []; + foreach ($choices as $label => $value) { + $choicesArray[] = ['label' => $label, 'value' => $value]; + } + + $this->assertCount(2, $choicesArray); + + $this->assertSame('yes', $choicesArray[0]['value']); + $this->assertSame('base.yes', $choicesArray[0]['label']); + + $this->assertSame('no', $choicesArray[1]['value']); + $this->assertSame('base.no', $choicesArray[1]['label']); + } + + public function testFieldTranslatedChoicesFlat() + { + $choices = $this->translatorExtension->getFieldChoices($this->view->children['choice_flat']); + + $choicesArray = []; + foreach ($choices as $label => $value) { + $choicesArray[] = ['label' => $label, 'value' => $value]; + } + + $this->assertCount(2, $choicesArray); + + $this->assertSame('yes', $choicesArray[0]['value']); + $this->assertSame('[trans]base.yes[/trans]', $choicesArray[0]['label']); + + $this->assertSame('no', $choicesArray[1]['value']); + $this->assertSame('[trans]base.no[/trans]', $choicesArray[1]['label']); + } + + public function testFieldChoicesGrouped() + { + $choices = $this->rawExtension->getFieldChoices($this->view->children['choice_grouped']); + + $choicesArray = []; + foreach ($choices as $groupLabel => $groupChoices) { + $groupChoicesArray = []; + foreach ($groupChoices as $label => $value) { + $groupChoicesArray[] = ['label' => $label, 'value' => $value]; + } + + $choicesArray[] = ['label' => $groupLabel, 'choices' => $groupChoicesArray]; + } + + $this->assertCount(2, $choicesArray); + + $this->assertCount(2, $choicesArray[0]['choices']); + $this->assertSame('base.europe', $choicesArray[0]['label']); + + $this->assertSame('fr', $choicesArray[0]['choices'][0]['value']); + $this->assertSame('base.fr', $choicesArray[0]['choices'][0]['label']); + + $this->assertSame('de', $choicesArray[0]['choices'][1]['value']); + $this->assertSame('base.de', $choicesArray[0]['choices'][1]['label']); + + $this->assertCount(2, $choicesArray[1]['choices']); + $this->assertSame('base.asia', $choicesArray[1]['label']); + + $this->assertSame('cn', $choicesArray[1]['choices'][0]['value']); + $this->assertSame('base.cn', $choicesArray[1]['choices'][0]['label']); + + $this->assertSame('jp', $choicesArray[1]['choices'][1]['value']); + $this->assertSame('base.jp', $choicesArray[1]['choices'][1]['label']); + } + + public function testFieldTranslatedChoicesGrouped() + { + $choices = $this->translatorExtension->getFieldChoices($this->view->children['choice_grouped']); + + $choicesArray = []; + foreach ($choices as $groupLabel => $groupChoices) { + $groupChoicesArray = []; + foreach ($groupChoices as $label => $value) { + $groupChoicesArray[] = ['label' => $label, 'value' => $value]; + } + + $choicesArray[] = ['label' => $groupLabel, 'choices' => $groupChoicesArray]; + } + + $this->assertCount(2, $choicesArray); + + $this->assertCount(2, $choicesArray[0]['choices']); + $this->assertSame('[trans]base.europe[/trans]', $choicesArray[0]['label']); + + $this->assertSame('fr', $choicesArray[0]['choices'][0]['value']); + $this->assertSame('[trans]base.fr[/trans]', $choicesArray[0]['choices'][0]['label']); + + $this->assertSame('de', $choicesArray[0]['choices'][1]['value']); + $this->assertSame('[trans]base.de[/trans]', $choicesArray[0]['choices'][1]['label']); + + $this->assertCount(2, $choicesArray[1]['choices']); + $this->assertSame('[trans]base.asia[/trans]', $choicesArray[1]['label']); + + $this->assertSame('cn', $choicesArray[1]['choices'][0]['value']); + $this->assertSame('[trans]base.cn[/trans]', $choicesArray[1]['choices'][0]['label']); + + $this->assertSame('jp', $choicesArray[1]['choices'][1]['value']); + $this->assertSame('[trans]base.jp[/trans]', $choicesArray[1]['choices'][1]['label']); + } +} diff --git a/src/Symfony/Bundle/TwigBundle/Resources/config/form.php b/src/Symfony/Bundle/TwigBundle/Resources/config/form.php index bbc1b51a9c296..9f2efdf94105c 100644 --- a/src/Symfony/Bundle/TwigBundle/Resources/config/form.php +++ b/src/Symfony/Bundle/TwigBundle/Resources/config/form.php @@ -18,6 +18,7 @@ return static function (ContainerConfigurator $container) { $container->services() ->set('twig.extension.form', FormExtension::class) + ->args([service('translator')->nullOnInvalid()]) ->set('twig.form.engine', TwigRendererEngine::class) ->args([param('twig.form.resources'), service('twig')]) pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy