FormExtension.php 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273
  1. <?php
  2. namespace Symfony\Bundle\TwigBundle\Extension;
  3. use Symfony\Component\Form\Form;
  4. use Symfony\Component\Form\FieldGroupInterface;
  5. use Symfony\Component\Form\FieldInterface;
  6. use Symfony\Component\Form\CollectionField;
  7. use Symfony\Component\Form\HybridField;
  8. use Symfony\Bundle\TwigBundle\TokenParser\FormThemeTokenParser;
  9. /*
  10. * This file is part of the Symfony package.
  11. *
  12. * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
  13. *
  14. * For the full copyright and license information, please view the LICENSE
  15. * file that was distributed with this source code.
  16. */
  17. /**
  18. *
  19. * @author Fabien Potencier <fabien.potencier@symfony-project.com>
  20. * @author Bernhard Schussek <bernhard.schussek@symfony-project.com>
  21. */
  22. class FormExtension extends \Twig_Extension
  23. {
  24. static protected $cache = array();
  25. protected $resources;
  26. protected $templates;
  27. protected $environment;
  28. protected $themes;
  29. public function __construct(array $resources = array())
  30. {
  31. $this->themes = new \SplObjectStorage();
  32. $this->resources = $resources;
  33. }
  34. /**
  35. * {@inheritdoc}
  36. */
  37. public function initRuntime(\Twig_Environment $environment)
  38. {
  39. $this->environment = $environment;
  40. }
  41. public function setTheme(FieldGroupInterface $group, array $resources)
  42. {
  43. $this->themes->attach($group, $resources);
  44. }
  45. /**
  46. * Returns the token parser instance to add to the existing list.
  47. *
  48. * @return array An array of Twig_TokenParser instances
  49. */
  50. public function getTokenParsers()
  51. {
  52. return array(
  53. // {% form_theme form "SomeBungle::widgets.twig" %}
  54. new FormThemeTokenParser(),
  55. );
  56. }
  57. public function getFunctions()
  58. {
  59. return array(
  60. 'form_enctype' => new \Twig_Function_Method($this, 'renderEnctype', array('is_safe' => array('html'))),
  61. 'form_field' => new \Twig_Function_Method($this, 'renderField', array('is_safe' => array('html'))),
  62. 'form_hidden' => new \Twig_Function_Method($this, 'renderHidden', array('is_safe' => array('html'))),
  63. 'form_errors' => new \Twig_Function_Method($this, 'renderErrors', array('is_safe' => array('html'))),
  64. 'form_label' => new \Twig_Function_Method($this, 'renderLabel', array('is_safe' => array('html'))),
  65. 'form_data' => new \Twig_Function_Method($this, 'renderData', array('is_safe' => array('html'))),
  66. );
  67. }
  68. /**
  69. * Renders the HTML enctype in the form tag, if necessary
  70. *
  71. * Example usage in Twig templates:
  72. *
  73. * <form action="..." method="post" {{ render_enctype(form) }}>
  74. *
  75. * @param Form $form The form for which to render the encoding type
  76. */
  77. public function renderEnctype(Form $form)
  78. {
  79. return $form->isMultipart() ? 'enctype="multipart/form-data"' : '';
  80. }
  81. /**
  82. * Renders the HTML for an individual form field
  83. *
  84. * Example usage in Twig:
  85. *
  86. * {{ form_field(field) }}
  87. *
  88. * You can pass additional variables during the call:
  89. *
  90. * {{ form_field(field, {'param': 'value'}) }}
  91. *
  92. * @param FieldInterface $field The field to render
  93. * @param array $params Additional variables passed to the template
  94. * @param string $resources
  95. */
  96. public function renderField(FieldInterface $field, array $attributes = array(), array $parameters = array(), $resources = null)
  97. {
  98. if (null === $this->templates) {
  99. $this->templates = $this->resolveResources($this->resources);
  100. }
  101. if (null !== $resources) {
  102. // The developer provided a custom theme in the filter call
  103. $resources = array($resources);
  104. } else {
  105. // The default theme is used
  106. $parent = $field;
  107. $resources = array();
  108. while ($parent = $parent->getParent()) {
  109. if (isset($this->themes[$parent])) {
  110. $resources = $this->themes[$parent];
  111. }
  112. }
  113. }
  114. list($widget, $template) = $this->getWidget($field, $resources);
  115. return $template->renderBlock($widget, array(
  116. 'field' => $field,
  117. 'attr' => $attributes,
  118. 'params' => $parameters,
  119. ));
  120. }
  121. /**
  122. * Renders all hidden fields of the given field group
  123. *
  124. * @param FieldGroupInterface $group The field group
  125. * @param array $params Additional variables passed to the
  126. * template
  127. */
  128. public function renderHidden(FieldGroupInterface $group, array $parameters = array())
  129. {
  130. if (null === $this->templates) {
  131. $this->templates = $this->resolveResources($this->resources);
  132. }
  133. return $this->templates['hidden']->renderBlock('hidden', array(
  134. 'field' => $group,
  135. 'params' => $parameters,
  136. ));
  137. }
  138. /**
  139. * Renders the errors of the given field
  140. *
  141. * @param FieldInterface $field The field to render the errors for
  142. * @param array $params Additional variables passed to the template
  143. */
  144. public function renderErrors(FieldInterface $field, array $parameters = array())
  145. {
  146. if (null === $this->templates) {
  147. $this->templates = $this->resolveResources($this->resources);
  148. }
  149. return $this->templates['errors']->renderBlock('errors', array(
  150. 'field' => $field,
  151. 'params' => $parameters,
  152. ));
  153. }
  154. /**
  155. * Renders the label of the given field
  156. *
  157. * @param FieldInterface $field The field to render the label for
  158. * @param array $params Additional variables passed to the template
  159. */
  160. public function renderLabel(FieldInterface $field, $label = null, array $parameters = array())
  161. {
  162. if (null === $this->templates) {
  163. $this->templates = $this->resolveResources($this->resources);
  164. }
  165. return $this->templates['label']->renderBlock('label', array(
  166. 'field' => $field,
  167. 'params' => $parameters,
  168. 'label' => null !== $label ? $label : ucfirst(strtolower(str_replace('_', ' ', $field->getKey()))),
  169. ));
  170. }
  171. /**
  172. * Renders the widget data of the given field
  173. *
  174. * @param FieldInterface $field The field to render the data for
  175. */
  176. public function renderData(FieldInterface $field)
  177. {
  178. return $field->getData();
  179. }
  180. protected function getWidget(FieldInterface $field, array $resources = array())
  181. {
  182. $cacheable = true;
  183. $templates = array();
  184. if ($resources) {
  185. $templates = $this->resolveResources($resources);
  186. $cacheable = false;
  187. }
  188. // add "global" templates as fallback
  189. $templates = array_merge($this->templates, $templates);
  190. $class = get_class($field);
  191. if (true === $cacheable && isset(self::$cache[$class])) {
  192. return self::$cache[$class];
  193. }
  194. // find a template for the given class or one of its parents
  195. do {
  196. $parts = explode('\\', $class);
  197. $c = array_pop($parts);
  198. $underscore = strtolower(preg_replace(array('/([A-Z]+)([A-Z][a-z])/', '/([a-z\d])([A-Z])/'), array('\\1_\\2', '\\1_\\2'), strtr($c, '_', '.')));
  199. if (isset($templates[$underscore])) {
  200. if (true === $cacheable) {
  201. self::$cache[$class] = array($underscore, $templates[$underscore]);
  202. }
  203. return array($underscore, $templates[$underscore]);
  204. }
  205. } while (false !== $class = get_parent_class($class));
  206. throw new \RuntimeException(sprintf('Unable to render the "%s" field.', $field->getKey()));
  207. }
  208. protected function resolveResources(array $resources)
  209. {
  210. $templates = array();
  211. foreach ($resources as $resource)
  212. {
  213. $blocks = $this->resolveTemplate($this->environment->loadTemplate($resource));
  214. $templates = array_replace($templates, $blocks);
  215. }
  216. return $templates;
  217. }
  218. protected function resolveTemplate($template)
  219. {
  220. // an array of blockName => template
  221. $blocks = array();
  222. foreach ($template->getBlockNames() as $name) {
  223. $blocks[$name] = $template;
  224. }
  225. return $blocks;
  226. }
  227. /**
  228. * Returns the name of the extension.
  229. *
  230. * @return string The extension name
  231. */
  232. public function getName()
  233. {
  234. return 'form';
  235. }
  236. }