FormExtension.php 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. <?php
  2. /*
  3. * This file is part of the Symfony package.
  4. *
  5. * (c) Fabien Potencier <fabien@symfony.com>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Symfony\Bridge\Twig\Extension;
  11. use Symfony\Bridge\Twig\TokenParser\FormThemeTokenParser;
  12. use Symfony\Component\Form\ChoiceList\View\ChoiceGroupView;
  13. use Symfony\Component\Form\ChoiceList\View\ChoiceView;
  14. use Symfony\Component\Form\FormError;
  15. use Symfony\Component\Form\FormView;
  16. use Symfony\Contracts\Translation\TranslatorInterface;
  17. use Twig\Extension\AbstractExtension;
  18. use Twig\TwigFilter;
  19. use Twig\TwigFunction;
  20. use Twig\TwigTest;
  21. /**
  22. * FormExtension extends Twig with form capabilities.
  23. *
  24. * @author Fabien Potencier <fabien@symfony.com>
  25. * @author Bernhard Schussek <bschussek@gmail.com>
  26. */
  27. final class FormExtension extends AbstractExtension
  28. {
  29. private $translator;
  30. public function __construct(TranslatorInterface $translator = null)
  31. {
  32. $this->translator = $translator;
  33. }
  34. /**
  35. * {@inheritdoc}
  36. */
  37. public function getTokenParsers(): array
  38. {
  39. return [
  40. // {% form_theme form "SomeBundle::widgets.twig" %}
  41. new FormThemeTokenParser(),
  42. ];
  43. }
  44. /**
  45. * {@inheritdoc}
  46. */
  47. public function getFunctions(): array
  48. {
  49. return [
  50. new TwigFunction('form_widget', null, ['node_class' => 'Symfony\Bridge\Twig\Node\SearchAndRenderBlockNode', 'is_safe' => ['html']]),
  51. new TwigFunction('form_errors', null, ['node_class' => 'Symfony\Bridge\Twig\Node\SearchAndRenderBlockNode', 'is_safe' => ['html']]),
  52. new TwigFunction('form_label', null, ['node_class' => 'Symfony\Bridge\Twig\Node\SearchAndRenderBlockNode', 'is_safe' => ['html']]),
  53. new TwigFunction('form_help', null, ['node_class' => 'Symfony\Bridge\Twig\Node\SearchAndRenderBlockNode', 'is_safe' => ['html']]),
  54. new TwigFunction('form_row', null, ['node_class' => 'Symfony\Bridge\Twig\Node\SearchAndRenderBlockNode', 'is_safe' => ['html']]),
  55. new TwigFunction('form_rest', null, ['node_class' => 'Symfony\Bridge\Twig\Node\SearchAndRenderBlockNode', 'is_safe' => ['html']]),
  56. new TwigFunction('form', null, ['node_class' => 'Symfony\Bridge\Twig\Node\RenderBlockNode', 'is_safe' => ['html']]),
  57. new TwigFunction('form_start', null, ['node_class' => 'Symfony\Bridge\Twig\Node\RenderBlockNode', 'is_safe' => ['html']]),
  58. new TwigFunction('form_end', null, ['node_class' => 'Symfony\Bridge\Twig\Node\RenderBlockNode', 'is_safe' => ['html']]),
  59. new TwigFunction('csrf_token', ['Symfony\Component\Form\FormRenderer', 'renderCsrfToken']),
  60. new TwigFunction('form_parent', 'Symfony\Bridge\Twig\Extension\twig_get_form_parent'),
  61. new TwigFunction('field_name', [$this, 'getFieldName']),
  62. new TwigFunction('field_value', [$this, 'getFieldValue']),
  63. new TwigFunction('field_label', [$this, 'getFieldLabel']),
  64. new TwigFunction('field_help', [$this, 'getFieldHelp']),
  65. new TwigFunction('field_errors', [$this, 'getFieldErrors']),
  66. new TwigFunction('field_choices', [$this, 'getFieldChoices']),
  67. ];
  68. }
  69. /**
  70. * {@inheritdoc}
  71. */
  72. public function getFilters(): array
  73. {
  74. return [
  75. new TwigFilter('humanize', ['Symfony\Component\Form\FormRenderer', 'humanize']),
  76. new TwigFilter('form_encode_currency', ['Symfony\Component\Form\FormRenderer', 'encodeCurrency'], ['is_safe' => ['html'], 'needs_environment' => true]),
  77. ];
  78. }
  79. /**
  80. * {@inheritdoc}
  81. */
  82. public function getTests(): array
  83. {
  84. return [
  85. new TwigTest('selectedchoice', 'Symfony\Bridge\Twig\Extension\twig_is_selected_choice'),
  86. new TwigTest('rootform', 'Symfony\Bridge\Twig\Extension\twig_is_root_form'),
  87. ];
  88. }
  89. public function getFieldName(FormView $view): string
  90. {
  91. $view->setRendered();
  92. return $view->vars['full_name'];
  93. }
  94. /**
  95. * @return string|array
  96. */
  97. public function getFieldValue(FormView $view)
  98. {
  99. return $view->vars['value'];
  100. }
  101. public function getFieldLabel(FormView $view): ?string
  102. {
  103. if (false === $label = $view->vars['label']) {
  104. return null;
  105. }
  106. if (!$label && $labelFormat = $view->vars['label_format']) {
  107. $label = str_replace(['%id%', '%name%'], [$view->vars['id'], $view->vars['name']], $labelFormat);
  108. } elseif (!$label) {
  109. $label = ucfirst(strtolower(trim(preg_replace(['/([A-Z])/', '/[_\s]+/'], ['_$1', ' '], $view->vars['name']))));
  110. }
  111. return $this->createFieldTranslation(
  112. $label,
  113. $view->vars['label_translation_parameters'] ?: [],
  114. $view->vars['translation_domain']
  115. );
  116. }
  117. public function getFieldHelp(FormView $view): ?string
  118. {
  119. return $this->createFieldTranslation(
  120. $view->vars['help'],
  121. $view->vars['help_translation_parameters'] ?: [],
  122. $view->vars['translation_domain']
  123. );
  124. }
  125. /**
  126. * @return string[]
  127. */
  128. public function getFieldErrors(FormView $view): iterable
  129. {
  130. /** @var FormError $error */
  131. foreach ($view->vars['errors'] as $error) {
  132. yield $error->getMessage();
  133. }
  134. }
  135. /**
  136. * @return string[]|string[][]
  137. */
  138. public function getFieldChoices(FormView $view): iterable
  139. {
  140. yield from $this->createFieldChoicesList($view->vars['choices'], $view->vars['choice_translation_domain']);
  141. }
  142. private function createFieldChoicesList(iterable $choices, $translationDomain): iterable
  143. {
  144. foreach ($choices as $choice) {
  145. $translatableLabel = $this->createFieldTranslation($choice->label, [], $translationDomain);
  146. if ($choice instanceof ChoiceGroupView) {
  147. yield $translatableLabel => $this->createFieldChoicesList($choice, $translationDomain);
  148. continue;
  149. }
  150. /* @var ChoiceView $choice */
  151. yield $translatableLabel => $choice->value;
  152. }
  153. }
  154. private function createFieldTranslation(?string $value, array $parameters, $domain): ?string
  155. {
  156. if (!$this->translator || !$value || false === $domain) {
  157. return $value;
  158. }
  159. return $this->translator->trans($value, $parameters, $domain);
  160. }
  161. }
  162. /**
  163. * Returns whether a choice is selected for a given form value.
  164. *
  165. * This is a function and not callable due to performance reasons.
  166. *
  167. * @param string|array $selectedValue The selected value to compare
  168. *
  169. * @see ChoiceView::isSelected()
  170. */
  171. function twig_is_selected_choice(ChoiceView $choice, $selectedValue): bool
  172. {
  173. if (\is_array($selectedValue)) {
  174. return \in_array($choice->value, $selectedValue, true);
  175. }
  176. return $choice->value === $selectedValue;
  177. }
  178. /**
  179. * @internal
  180. */
  181. function twig_is_root_form(FormView $formView): bool
  182. {
  183. return null === $formView->parent;
  184. }
  185. /**
  186. * @internal
  187. */
  188. function twig_get_form_parent(FormView $formView): ?FormView
  189. {
  190. return $formView->parent;
  191. }