Skip to content

Commit 1dd1b83

Browse files
committed
[Form] Implement Twig helpers to get field variables
1 parent 9e4f511 commit 1dd1b83

File tree

4 files changed

+330
-0
lines changed

4 files changed

+330
-0
lines changed

src/Symfony/Bridge/Twig/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ CHANGELOG
99
* added support for translating `Translatable` objects
1010
* added the `t()` function to easily create `Translatable` objects
1111
* Added support for extracting messages from the `t()` function
12+
* Added `field_*` Twig functions to access string values from Form fields
1213

1314
5.0.0
1415
-----

src/Symfony/Bridge/Twig/Extension/FormExtension.php

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,12 @@
1212
namespace Symfony\Bridge\Twig\Extension;
1313

1414
use Symfony\Bridge\Twig\TokenParser\FormThemeTokenParser;
15+
use Symfony\Component\Form\ChoiceList\View\ChoiceGroupView;
1516
use Symfony\Component\Form\ChoiceList\View\ChoiceView;
17+
use Symfony\Component\Form\FormError;
1618
use Symfony\Component\Form\FormView;
19+
use Symfony\Component\Translation\Translatable;
20+
use Symfony\Contracts\Translation\TranslatorInterface;
1721
use Twig\Extension\AbstractExtension;
1822
use Twig\TwigFilter;
1923
use Twig\TwigFunction;
@@ -27,6 +31,13 @@
2731
*/
2832
final class FormExtension extends AbstractExtension
2933
{
34+
private $translator;
35+
36+
public function __construct(TranslatorInterface $translator = null)
37+
{
38+
$this->translator = $translator;
39+
}
40+
3041
/**
3142
* {@inheritdoc}
3243
*/
@@ -55,6 +66,12 @@ public function getFunctions(): array
5566
new TwigFunction('form_end', null, ['node_class' => 'Symfony\Bridge\Twig\Node\RenderBlockNode', 'is_safe' => ['html']]),
5667
new TwigFunction('csrf_token', ['Symfony\Component\Form\FormRenderer', 'renderCsrfToken']),
5768
new TwigFunction('form_parent', 'Symfony\Bridge\Twig\Extension\twig_get_form_parent'),
69+
new TwigFunction('field_name', [$this, 'getFieldName']),
70+
new TwigFunction('field_value', [$this, 'getFieldValue']),
71+
new TwigFunction('field_label', [$this, 'getFieldLabel']),
72+
new TwigFunction('field_help', [$this, 'getFieldHelp']),
73+
new TwigFunction('field_errors', [$this, 'getFieldErrors']),
74+
new TwigFunction('field_choices', [$this, 'getFieldChoices']),
5875
];
5976
}
6077

@@ -79,6 +96,80 @@ public function getTests(): array
7996
new TwigTest('rootform', 'Symfony\Bridge\Twig\Extension\twig_is_root_form'),
8097
];
8198
}
99+
100+
public function getFieldName(FormView $view): string
101+
{
102+
$view->setRendered();
103+
104+
return $view->vars['full_name'];
105+
}
106+
107+
public function getFieldValue(FormView $view): string
108+
{
109+
return $view->vars['value'];
110+
}
111+
112+
public function getFieldLabel(FormView $view): string
113+
{
114+
return $this->createFieldTranslation(
115+
$view->vars['label'],
116+
$view->vars['label_translation_parameters'] ?: [],
117+
$view->vars['translation_domain']
118+
);
119+
}
120+
121+
public function getFieldHelp(FormView $view): string
122+
{
123+
return $this->createFieldTranslation(
124+
$view->vars['help'],
125+
$view->vars['help_translation_parameters'] ?: [],
126+
$view->vars['translation_domain']
127+
);
128+
}
129+
130+
/**
131+
* @return string[]
132+
*/
133+
public function getFieldErrors(FormView $view): iterable
134+
{
135+
/** @var FormError $error */
136+
foreach ($view->vars['errors'] as $error) {
137+
yield $error->getMessage();
138+
}
139+
}
140+
141+
/**
142+
* @return Translatable[]|Translatable[][]
143+
*/
144+
public function getFieldChoices(FormView $view): iterable
145+
{
146+
yield from $this->createFieldChoicesList($view->vars['choices'], $view->vars['choice_translation_domain']);
147+
}
148+
149+
private function createFieldChoicesList(iterable $choices, $translationDomain): iterable
150+
{
151+
foreach ($choices as $choice) {
152+
$translatableLabel = $this->createFieldTranslation($choice->label, [], $translationDomain);
153+
154+
if ($choice instanceof ChoiceGroupView) {
155+
yield $translatableLabel => $this->createFieldChoicesList($choice, $translationDomain);
156+
157+
continue;
158+
}
159+
160+
/* @var ChoiceView $choice */
161+
yield $translatableLabel => $choice->value;
162+
}
163+
}
164+
165+
private function createFieldTranslation(?string $value, array $parameters, $domain): string
166+
{
167+
if (!$this->translator || !$value || false === $domain) {
168+
return $value;
169+
}
170+
171+
return $this->translator->trans($value, $parameters, $domain);
172+
}
82173
}
83174

84175
/**
Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Bridge\Twig\Tests\Extension;
13+
14+
use Symfony\Bridge\Twig\Extension\FormExtension;
15+
use Symfony\Bridge\Twig\Tests\Extension\Fixtures\StubTranslator;
16+
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
17+
use Symfony\Component\Form\Extension\Core\Type\FormType;
18+
use Symfony\Component\Form\Extension\Core\Type\TextType;
19+
use Symfony\Component\Form\FormError;
20+
use Symfony\Component\Form\FormView;
21+
use Symfony\Component\Form\Test\FormIntegrationTestCase;
22+
23+
class FormExtensionFieldHelpersTest extends FormIntegrationTestCase
24+
{
25+
/**
26+
* @var FormExtension
27+
*/
28+
private $rawExtension;
29+
30+
/**
31+
* @var FormExtension
32+
*/
33+
private $translatorExtension;
34+
35+
/**
36+
* @var FormView
37+
*/
38+
private $view;
39+
40+
protected function getTypes()
41+
{
42+
return [new TextType(), new ChoiceType()];
43+
}
44+
45+
protected function setUp(): void
46+
{
47+
parent::setUp();
48+
49+
$this->rawExtension = new FormExtension();
50+
$this->translatorExtension = new FormExtension(new StubTranslator());
51+
52+
$form = $this->factory->createNamedBuilder('register', FormType::class, ['username' => 'tgalopin'])
53+
->add('username', TextType::class, [
54+
'label' => 'base.username',
55+
'label_translation_parameters' => ['%label_brand%' => 'Symfony'],
56+
'help' => 'base.username_help',
57+
'help_translation_parameters' => ['%help_brand%' => 'Symfony'],
58+
'translation_domain' => 'forms',
59+
])
60+
->add('choice_flat', ChoiceType::class, [
61+
'choices' => [
62+
'base.yes' => 'yes',
63+
'base.no' => 'no',
64+
],
65+
'choice_translation_domain' => 'forms',
66+
])
67+
->add('choice_grouped', ChoiceType::class, [
68+
'choices' => [
69+
'base.europe' => [
70+
'base.fr' => 'fr',
71+
'base.de' => 'de',
72+
],
73+
'base.asia' => [
74+
'base.cn' => 'cn',
75+
'base.jp' => 'jp',
76+
],
77+
],
78+
'choice_translation_domain' => 'forms',
79+
])
80+
->getForm()
81+
;
82+
83+
$form->get('username')->addError(new FormError('username.max_length'));
84+
85+
$this->view = $form->createView();
86+
}
87+
88+
public function testFieldName()
89+
{
90+
$this->assertFalse($this->view->children['username']->isRendered());
91+
$this->assertSame('register[username]', $this->rawExtension->getFieldName($this->view->children['username']));
92+
$this->assertTrue($this->view->children['username']->isRendered());
93+
}
94+
95+
public function testFieldValue()
96+
{
97+
$this->assertSame('tgalopin', $this->rawExtension->getFieldValue($this->view->children['username']));
98+
}
99+
100+
public function testFieldLabel()
101+
{
102+
$this->assertSame('base.username', $this->rawExtension->getFieldLabel($this->view->children['username']));
103+
}
104+
105+
public function testFieldTranslatedLabel()
106+
{
107+
$this->assertSame('[trans]base.username[/trans]', $this->translatorExtension->getFieldLabel($this->view->children['username']));
108+
}
109+
110+
public function testFieldHelp()
111+
{
112+
$this->assertSame('base.username_help', $this->rawExtension->getFieldHelp($this->view->children['username']));
113+
}
114+
115+
public function testFieldTranslatedHelp()
116+
{
117+
$this->assertSame('[trans]base.username_help[/trans]', $this->translatorExtension->getFieldHelp($this->view->children['username']));
118+
}
119+
120+
public function testFieldErrors()
121+
{
122+
$errors = $this->rawExtension->getFieldErrors($this->view->children['username']);
123+
$this->assertSame(['username.max_length'], iterator_to_array($errors));
124+
}
125+
126+
public function testFieldTranslatedErrors()
127+
{
128+
$errors = $this->translatorExtension->getFieldErrors($this->view->children['username']);
129+
$this->assertSame(['username.max_length'], iterator_to_array($errors));
130+
}
131+
132+
public function testFieldChoicesFlat()
133+
{
134+
$choices = $this->rawExtension->getFieldChoices($this->view->children['choice_flat']);
135+
136+
$choicesArray = [];
137+
foreach ($choices as $label => $value) {
138+
$choicesArray[] = ['label' => $label, 'value' => $value];
139+
}
140+
141+
$this->assertCount(2, $choicesArray);
142+
143+
$this->assertSame('yes', $choicesArray[0]['value']);
144+
$this->assertSame('base.yes', $choicesArray[0]['label']);
145+
146+
$this->assertSame('no', $choicesArray[1]['value']);
147+
$this->assertSame('base.no', $choicesArray[1]['label']);
148+
}
149+
150+
public function testFieldTranslatedChoicesFlat()
151+
{
152+
$choices = $this->translatorExtension->getFieldChoices($this->view->children['choice_flat']);
153+
154+
$choicesArray = [];
155+
foreach ($choices as $label => $value) {
156+
$choicesArray[] = ['label' => $label, 'value' => $value];
157+
}
158+
159+
$this->assertCount(2, $choicesArray);
160+
161+
$this->assertSame('yes', $choicesArray[0]['value']);
162+
$this->assertSame('[trans]base.yes[/trans]', $choicesArray[0]['label']);
163+
164+
$this->assertSame('no', $choicesArray[1]['value']);
165+
$this->assertSame('[trans]base.no[/trans]', $choicesArray[1]['label']);
166+
}
167+
168+
public function testFieldChoicesGrouped()
169+
{
170+
$choices = $this->rawExtension->getFieldChoices($this->view->children['choice_grouped']);
171+
172+
$choicesArray = [];
173+
foreach ($choices as $groupLabel => $groupChoices) {
174+
$groupChoicesArray = [];
175+
foreach ($groupChoices as $label => $value) {
176+
$groupChoicesArray[] = ['label' => $label, 'value' => $value];
177+
}
178+
179+
$choicesArray[] = ['label' => $groupLabel, 'choices' => $groupChoicesArray];
180+
}
181+
182+
$this->assertCount(2, $choicesArray);
183+
184+
$this->assertCount(2, $choicesArray[0]['choices']);
185+
$this->assertSame('base.europe', $choicesArray[0]['label']);
186+
187+
$this->assertSame('fr', $choicesArray[0]['choices'][0]['value']);
188+
$this->assertSame('base.fr', $choicesArray[0]['choices'][0]['label']);
189+
190+
$this->assertSame('de', $choicesArray[0]['choices'][1]['value']);
191+
$this->assertSame('base.de', $choicesArray[0]['choices'][1]['label']);
192+
193+
$this->assertCount(2, $choicesArray[1]['choices']);
194+
$this->assertSame('base.asia', $choicesArray[1]['label']);
195+
196+
$this->assertSame('cn', $choicesArray[1]['choices'][0]['value']);
197+
$this->assertSame('base.cn', $choicesArray[1]['choices'][0]['label']);
198+
199+
$this->assertSame('jp', $choicesArray[1]['choices'][1]['value']);
200+
$this->assertSame('base.jp', $choicesArray[1]['choices'][1]['label']);
201+
}
202+
203+
public function testFieldTranslatedChoicesGrouped()
204+
{
205+
$choices = $this->translatorExtension->getFieldChoices($this->view->children['choice_grouped']);
206+
207+
$choicesArray = [];
208+
foreach ($choices as $groupLabel => $groupChoices) {
209+
$groupChoicesArray = [];
210+
foreach ($groupChoices as $label => $value) {
211+
$groupChoicesArray[] = ['label' => $label, 'value' => $value];
212+
}
213+
214+
$choicesArray[] = ['label' => $groupLabel, 'choices' => $groupChoicesArray];
215+
}
216+
217+
$this->assertCount(2, $choicesArray);
218+
219+
$this->assertCount(2, $choicesArray[0]['choices']);
220+
$this->assertSame('[trans]base.europe[/trans]', $choicesArray[0]['label']);
221+
222+
$this->assertSame('fr', $choicesArray[0]['choices'][0]['value']);
223+
$this->assertSame('[trans]base.fr[/trans]', $choicesArray[0]['choices'][0]['label']);
224+
225+
$this->assertSame('de', $choicesArray[0]['choices'][1]['value']);
226+
$this->assertSame('[trans]base.de[/trans]', $choicesArray[0]['choices'][1]['label']);
227+
228+
$this->assertCount(2, $choicesArray[1]['choices']);
229+
$this->assertSame('[trans]base.asia[/trans]', $choicesArray[1]['label']);
230+
231+
$this->assertSame('cn', $choicesArray[1]['choices'][0]['value']);
232+
$this->assertSame('[trans]base.cn[/trans]', $choicesArray[1]['choices'][0]['label']);
233+
234+
$this->assertSame('jp', $choicesArray[1]['choices'][1]['value']);
235+
$this->assertSame('[trans]base.jp[/trans]', $choicesArray[1]['choices'][1]['label']);
236+
}
237+
}

src/Symfony/Bundle/TwigBundle/Resources/config/form.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
return static function (ContainerConfigurator $container) {
1919
$container->services()
2020
->set('twig.extension.form', FormExtension::class)
21+
->args([service('translator')->nullOnInvalid()])
2122

2223
->set('twig.form.engine', TwigRendererEngine::class)
2324
->args([param('twig.form.resources'), service('twig')])

0 commit comments

Comments
 (0)
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