FormFactory.php 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398
  1. <?php
  2. /*
  3. * This file is part of the Symfony package.
  4. *
  5. * (c) Fabien Potencier <fabien.potencier@symfony-project.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\Component\Form;
  11. use Symfony\Component\Form\ValueTransformer\BooleanToStringTransformer;
  12. use Symfony\Component\Form\ChoiceList\ChoiceListInterface;
  13. use Symfony\Component\Form\ChoiceList\DefaultChoiceList;
  14. use Symfony\Component\Form\ChoiceList\PaddedChoiceList;
  15. use Symfony\Component\Form\ChoiceList\MonthChoiceList;
  16. use Symfony\Component\Form\DataProcessor\RadioToArrayConverter;
  17. use Symfony\Component\Form\Renderer\DefaultRenderer;
  18. use Symfony\Component\Form\Renderer\Theme\ThemeInterface;
  19. use Symfony\Component\Form\Renderer\Plugin\IdPlugin;
  20. use Symfony\Component\Form\Renderer\Plugin\NamePlugin;
  21. use Symfony\Component\Form\Renderer\Plugin\ParameterPlugin;
  22. use Symfony\Component\Form\Renderer\Plugin\ChoicePlugin;
  23. use Symfony\Component\Form\Renderer\Plugin\ParentNamePlugin;
  24. use Symfony\Component\Form\Renderer\Plugin\DatePatternPlugin;
  25. use Symfony\Component\Form\ValueTransformer\ScalarToChoicesTransformer;
  26. use Symfony\Component\Form\ValueTransformer\DateTimeToArrayTransformer;
  27. class FormFactory
  28. {
  29. private $theme;
  30. public function __construct(ThemeInterface $theme)
  31. {
  32. $this->setTheme($theme);
  33. }
  34. public function setTheme(ThemeInterface $theme)
  35. {
  36. $this->theme = $theme;
  37. }
  38. public function getTheme()
  39. {
  40. return $this->theme;
  41. }
  42. protected function getField($key, $template)
  43. {
  44. $field = new Field($key);
  45. return $field
  46. ->setRenderer(new DefaultRenderer($this->theme, $template))
  47. ->addRendererPlugin(new IdPlugin($field))
  48. ->addRendererPlugin(new NamePlugin($field));
  49. }
  50. protected function getForm($key, $template)
  51. {
  52. $field = new Form($key);
  53. return $field
  54. ->setRenderer(new DefaultRenderer($this->theme, $template))
  55. ->addRendererPlugin(new IdPlugin($field))
  56. ->addRendererPlugin(new NamePlugin($field));
  57. }
  58. public function getTextField($key, array $options = array())
  59. {
  60. $options = array_merge(array(
  61. 'max_length' => null,
  62. ), $options);
  63. return $this->getField($key, 'text')
  64. ->addRendererPlugin(new ParameterPlugin('max_length', $options['max_length']));
  65. }
  66. public function getCheckboxField($key, array $options = array())
  67. {
  68. $options = array_merge(array(
  69. 'value' => '1',
  70. ), $options);
  71. return $this->getField($key, 'checkbox')
  72. ->setValueTransformer(new BooleanToStringTransformer())
  73. ->addRendererPlugin(new ParameterPlugin('value', $options['value']));
  74. }
  75. public function getRadioField($key, array $options = array())
  76. {
  77. $options = array_merge(array(
  78. 'value' => null,
  79. ), $options);
  80. $field = $this->getField($key, 'radio');
  81. return $field
  82. ->setValueTransformer(new BooleanToStringTransformer())
  83. ->addRendererPlugin(new ParentNamePlugin($field))
  84. ->addRendererPlugin(new ParameterPlugin('value', $options['value']));
  85. }
  86. protected function getChoiceFieldForList($key, ChoiceListInterface $choiceList, array $options = array())
  87. {
  88. $options = array_merge(array(
  89. 'multiple' => false,
  90. 'expanded' => false,
  91. ), $options);
  92. if (!$options['expanded']) {
  93. $field = $this->getField($key, 'choice');
  94. } else {
  95. $field = $this->getForm($key, 'choice');
  96. $choices = array_merge($choiceList->getPreferredChoices(), $choiceList->getOtherChoices());
  97. foreach ($choices as $choice => $value) {
  98. if ($options['multiple']) {
  99. $field->add($this->getCheckboxField($choice, array(
  100. 'value' => $choice,
  101. )));
  102. } else {
  103. $field->add($this->getRadioField($choice, array(
  104. 'value' => $choice,
  105. )));
  106. }
  107. }
  108. }
  109. $field->addRendererPlugin(new ChoicePlugin($choiceList))
  110. ->addRendererPlugin(new ParameterPlugin('multiple', $options['multiple']))
  111. ->addRendererPlugin(new ParameterPlugin('expanded', $options['expanded']));
  112. if ($options['multiple'] && $options['expanded']) {
  113. $field->setValueTransformer(new ArrayToChoicesTransformer($choiceList));
  114. }
  115. if (!$options['multiple'] && $options['expanded']) {
  116. $field->setValueTransformer(new ScalarToChoicesTransformer($choiceList));
  117. $field->setDataPreprocessor(new RadioToArrayConverter());
  118. }
  119. if ($options['multiple'] && !$options['expanded']) {
  120. $field->addRendererPlugin(new SelectMultipleNamePlugin($field));
  121. }
  122. return $field;
  123. }
  124. public function getChoiceField($key, array $options = array())
  125. {
  126. $options = array_merge(array(
  127. 'choices' => array(),
  128. 'preferred_choices' => array(),
  129. ), $options);
  130. $choiceList = new DefaultChoiceList(
  131. $options['choices'],
  132. $options['preferred_choices']
  133. );
  134. return $this->getChoiceFieldForList($key, $choiceList, $options);
  135. }
  136. protected function getDayField($key, array $options = array())
  137. {
  138. $options = array_merge(array(
  139. 'days' => range(1, 31),
  140. 'preferred_choices' => array(),
  141. ), $options);
  142. $choiceList = new PaddedChoiceList(
  143. $options['days'], 2, '0', STR_PAD_LEFT, $options['preferred_choices']
  144. );
  145. return $this->getChoiceFieldForList($key, $choiceList, $options);
  146. }
  147. protected function getMonthField($key, \IntlDateFormatter $formatter, array $options = array())
  148. {
  149. $options = array_merge(array(
  150. 'months' => range(1, 12),
  151. 'preferred_choices' => array(),
  152. ), $options);
  153. $choiceList = new MonthChoiceList(
  154. $formatter, $options['months'], $options['preferred_choices']
  155. );
  156. return $this->getChoiceFieldForList($key, $choiceList, $options);
  157. }
  158. protected function getYearField($key, array $options = array())
  159. {
  160. $options = array_merge(array(
  161. 'years' => range(date('Y') - 5, date('Y') + 5),
  162. 'preferred_choices' => array(),
  163. ), $options);
  164. $choiceList = new PaddedChoiceList(
  165. $options['years'], 4, '0', STR_PAD_LEFT, $options['preferred_choices']
  166. );
  167. return $this->getChoiceFieldForList($key, $choiceList, $options);
  168. }
  169. protected function getHourField($key, array $options = array())
  170. {
  171. $options = array_merge(array(
  172. 'widget' => 'choice',
  173. 'hours' => range(0, 23),
  174. 'preferred_choices' => array(),
  175. ), $options);
  176. if ($options['widget'] == 'text') {
  177. return $this->getTextField($key, array('max_length' => 2));
  178. } else {
  179. $choiceList = new PaddedChoiceList(
  180. $options['hours'], 2, '0', STR_PAD_LEFT, $options['preferred_choices']
  181. );
  182. return $this->getChoiceFieldForList($key, $choiceList, $options);
  183. }
  184. }
  185. protected function getMinuteField($key, array $options = array())
  186. {
  187. $options = array_merge(array(
  188. 'widget' => 'choice',
  189. 'minutes' => range(0, 59),
  190. 'preferred_choices' => array(),
  191. ), $options);
  192. if ($options['widget'] == 'text') {
  193. return $this->getTextField($key, array('max_length' => 2));
  194. } else {
  195. $choiceList = new PaddedChoiceList(
  196. $options['minutes'], 2, '0', STR_PAD_LEFT, $options['preferred_choices']
  197. );
  198. return $this->getChoiceFieldForList($key, $choiceList, $options);
  199. }
  200. }
  201. protected function getSecondField($key, array $options = array())
  202. {
  203. $options = array_merge(array(
  204. 'widget' => 'choice',
  205. 'seconds' => range(0, 59),
  206. 'preferred_choices' => array(),
  207. ), $options);
  208. if ($options['widget'] == 'text') {
  209. return $this->getTextField($key, array('max_length' => 2));
  210. } else {
  211. $choiceList = new PaddedChoiceList(
  212. $options['seconds'], 2, '0', STR_PAD_LEFT, $options['preferred_choices']
  213. );
  214. return $this->getChoiceFieldForList($key, $choiceList, $options);
  215. }
  216. }
  217. public function getDateField($key, array $options = array())
  218. {
  219. $options = array_merge(array(
  220. 'widget' => 'choice',
  221. 'type' => 'datetime',
  222. 'pattern' => null,
  223. 'format' => \IntlDateFormatter::MEDIUM,
  224. 'data_timezone' => date_default_timezone_get(),
  225. 'user_timezone' => date_default_timezone_get(),
  226. ), $options);
  227. $formatter = new \IntlDateFormatter(
  228. \Locale::getDefault(),
  229. $options['format'],
  230. \IntlDateFormatter::NONE
  231. );
  232. if ($options['widget'] === 'text') {
  233. $field = $this->getField($key, 'date')
  234. ->setValueTransformer(new DateTimeToLocalizedStringTransformer(array(
  235. 'date_format' => $options['format'],
  236. 'time_format' => DateTimeToLocalizedStringTransformer::NONE,
  237. 'input_timezone' => $options['data_timezone'],
  238. 'output_timezone' => $options['user_timezone'],
  239. )));
  240. } else {
  241. $field = $this->getForm($key, 'date')
  242. ->add($this->getYearField('year', $options))
  243. ->add($this->getMonthField('month', $formatter, $options))
  244. ->add($this->getDayField('day', $options))
  245. ->setValueTransformer(new DateTimeToArrayTransformer(array(
  246. 'input_timezone' => $options['data_timezone'],
  247. 'output_timezone' => $options['user_timezone'],
  248. )))
  249. ->addRendererPlugin(new DatePatternPlugin($formatter))
  250. // Don't modify \DateTime classes by reference, we treat
  251. // them like immutable value objects
  252. ->setModifyByReference(false);
  253. }
  254. if ($options['type'] === 'string') {
  255. $field->setNormalizationTransformer(new ReversedTransformer(
  256. new DateTimeToStringTransformer(array(
  257. 'input_timezone' => $options['data_timezone'],
  258. 'output_timezone' => $options['data_timezone'],
  259. 'format' => 'Y-m-d',
  260. ))
  261. ));
  262. } else if ($options['type'] === 'timestamp') {
  263. $field->setNormalizationTransformer(new ReversedTransformer(
  264. new DateTimeToTimestampTransformer(array(
  265. 'output_timezone' => $options['data_timezone'],
  266. 'input_timezone' => $options['data_timezone'],
  267. ))
  268. ));
  269. } else if ($options['type'] === 'raw') {
  270. $field->setNormalizationTransformer(new ReversedTransformer(
  271. new DateTimeToArrayTransformer(array(
  272. 'input_timezone' => $options['data_timezone'],
  273. 'output_timezone' => $options['data_timezone'],
  274. 'fields' => array('year', 'month', 'day'),
  275. ))
  276. ));
  277. }
  278. $field->addRendererPlugin(new ParameterPlugin('widget', $options['widget']));
  279. return $field;
  280. }
  281. public function getTimeField($key, array $options = array())
  282. {
  283. $options = array_merge(array(
  284. 'widget' => 'choice',
  285. 'type' => 'datetime',
  286. 'with_seconds' => false,
  287. 'pattern' => null,
  288. 'data_timezone' => date_default_timezone_get(),
  289. 'user_timezone' => date_default_timezone_get(),
  290. ), $options);
  291. $children = array('hour', 'minute');
  292. $field = $this->getForm($key, 'time')
  293. ->add($this->getHourField('hour', $options))
  294. ->add($this->getMinuteField('minute', $options))
  295. // Don't modify \DateTime classes by reference, we treat
  296. // them like immutable value objects
  297. ->setModifyByReference(false);
  298. if ($options['with_seconds']) {
  299. $children[] = 'second';
  300. $field->add($this->getSecondField('second', $options));
  301. }
  302. if ($options['type'] == 'string') {
  303. $field->setNormalizationTransformer(new ReversedTransformer(
  304. new DateTimeToStringTransformer(array(
  305. 'format' => 'H:i:s',
  306. 'input_timezone' => $options['data_timezone'],
  307. 'output_timezone' => $options['data_timezone'],
  308. ))
  309. ));
  310. } else if ($options['type'] == 'timestamp') {
  311. $field->setNormalizationTransformer(new ReversedTransformer(
  312. new DateTimeToTimestampTransformer(array(
  313. 'input_timezone' => $options['data_timezone'],
  314. 'output_timezone' => $options['data_timezone'],
  315. ))
  316. ));
  317. } else if ($options['type'] === 'raw') {
  318. $field->setNormalizationTransformer(new ReversedTransformer(
  319. new DateTimeToArrayTransformer(array(
  320. 'input_timezone' => $options['data_timezone'],
  321. 'output_timezone' => $options['data_timezone'],
  322. 'fields' => $children,
  323. ))
  324. ));
  325. }
  326. $field
  327. ->setValueTransformer(new DateTimeToArrayTransformer(array(
  328. 'input_timezone' => $options['data_timezone'],
  329. 'output_timezone' => $options['user_timezone'],
  330. // if the field is rendered as choice field, the values should be trimmed
  331. // of trailing zeros to render the selected choices correctly
  332. 'pad' => $options['widget'] === 'text',
  333. 'fields' => $children,
  334. )))
  335. ->addRendererPlugin(new ParameterPlugin('widget', $options['widget']))
  336. ->addRendererPlugin(new ParameterPlugin('with_seconds', $options['with_seconds']));
  337. return $field;
  338. }
  339. }