Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit c95592f

Browse filesBrowse files
committed
[Form] Implement Twig helpers to get field variables
1 parent 9e4f511 commit c95592f
Copy full SHA for c95592f

File tree

4 files changed

+333
-0
lines changed
Filter options

4 files changed

+333
-0
lines changed

‎src/Symfony/Bridge/Twig/CHANGELOG.md

Copy file name to clipboardExpand all lines: src/Symfony/Bridge/Twig/CHANGELOG.md
+1Lines 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

Copy file name to clipboardExpand all lines: src/Symfony/Bridge/Twig/Extension/FormExtension.php
+94Lines changed: 94 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,83 @@ 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(
147+
$view->vars['choices'],
148+
$view->vars['choice_translation_domain'] ?: 'messages'
149+
);
150+
}
151+
152+
private function createFieldChoicesList(iterable $choices, string $translationDomain): iterable
153+
{
154+
foreach ($choices as $choice) {
155+
$translatableLabel = $this->createFieldTranslation($choice->label, [], $translationDomain);
156+
157+
if ($choice instanceof ChoiceGroupView) {
158+
yield $translatableLabel => $this->createFieldChoicesList($choice, $translationDomain);
159+
160+
continue;
161+
}
162+
163+
/* @var ChoiceView $choice */
164+
yield $translatableLabel => $choice->value;
165+
}
166+
}
167+
168+
private function createFieldTranslation(?string $value, array $parameters, ?string $domain): string
169+
{
170+
if (!$this->translator || !$value) {
171+
return $value;
172+
}
173+
174+
return $this->translator->trans($value, $parameters, $domain ?: 'messages');
175+
}
82176
}
83177

84178
/**
+237Lines 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

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/TwigBundle/Resources/config/form.php
+1Lines 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 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.