FormFactory.php 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733
  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\ChoiceList\ChoiceListInterface;
  12. use Symfony\Component\Form\ChoiceList\DefaultChoiceList;
  13. use Symfony\Component\Form\ChoiceList\PaddedChoiceList;
  14. use Symfony\Component\Form\ChoiceList\MonthChoiceList;
  15. use Symfony\Component\Form\ChoiceList\TimeZoneChoiceList;
  16. use Symfony\Component\Form\ChoiceList\EntityChoiceList;
  17. use Symfony\Component\Form\CsrfProvider\CsrfProviderInterface;
  18. use Symfony\Component\Form\DataProcessor\RadioToArrayConverter;
  19. use Symfony\Component\Form\DataProcessor\UrlProtocolFixer;
  20. use Symfony\Component\Form\DataProcessor\CollectionMerger;
  21. use Symfony\Component\Form\FieldFactory\FieldFactoryInterface;
  22. use Symfony\Component\Form\Renderer\DefaultRenderer;
  23. use Symfony\Component\Form\Renderer\Theme\ThemeInterface;
  24. use Symfony\Component\Form\Renderer\Plugin\IdPlugin;
  25. use Symfony\Component\Form\Renderer\Plugin\NamePlugin;
  26. use Symfony\Component\Form\Renderer\Plugin\ParameterPlugin;
  27. use Symfony\Component\Form\Renderer\Plugin\ChoicePlugin;
  28. use Symfony\Component\Form\Renderer\Plugin\ParentNamePlugin;
  29. use Symfony\Component\Form\Renderer\Plugin\DatePatternPlugin;
  30. use Symfony\Component\Form\Renderer\Plugin\MoneyPatternPlugin;
  31. use Symfony\Component\Form\Renderer\Plugin\ValuePlugin;
  32. use Symfony\Component\Form\Renderer\Plugin\PasswordValuePlugin;
  33. use Symfony\Component\Form\Renderer\Plugin\SelectMultipleNamePlugin;
  34. use Symfony\Component\Form\ValueTransformer\BooleanToStringTransformer;
  35. use Symfony\Component\Form\ValueTransformer\NumberToLocalizedStringTransformer;
  36. use Symfony\Component\Form\ValueTransformer\IntegerToLocalizedStringTransformer;
  37. use Symfony\Component\Form\ValueTransformer\MoneyToLocalizedStringTransformer;
  38. use Symfony\Component\Form\ValueTransformer\PercentToLocalizedStringTransformer;
  39. use Symfony\Component\Form\ValueTransformer\ScalarToChoicesTransformer;
  40. use Symfony\Component\Form\ValueTransformer\DateTimeToArrayTransformer;
  41. use Symfony\Component\Form\ValueTransformer\DateTimeToStringTransformer;
  42. use Symfony\Component\Form\ValueTransformer\DateTimeToLocalizedStringTransformer;
  43. use Symfony\Component\Form\ValueTransformer\DateTimeToTimestampTransformer;
  44. use Symfony\Component\Form\ValueTransformer\ReversedTransformer;
  45. use Symfony\Component\Form\ValueTransformer\EntityToIdTransformer;
  46. use Symfony\Component\Form\ValueTransformer\EntitiesToArrayTransformer;
  47. use Symfony\Component\Form\ValueTransformer\ValueTransformerChain;
  48. use Symfony\Component\Form\ValueTransformer\ArrayToChoicesTransformer;
  49. use Symfony\Component\Validator\ValidatorInterface;
  50. use Symfony\Component\Locale\Locale;
  51. class FormFactory
  52. {
  53. private $theme;
  54. private $csrfProvider;
  55. private $validator;
  56. private $fieldFactory;
  57. public function __construct(ThemeInterface $theme, CsrfProviderInterface $csrfProvider, ValidatorInterface $validator, FieldFactoryInterface $fieldFactory)
  58. {
  59. $this->theme = $theme;
  60. $this->csrfProvider = $csrfProvider;
  61. $this->validator = $validator;
  62. $this->fieldFactory = $fieldFactory;
  63. }
  64. protected function getTheme()
  65. {
  66. return $this->theme;
  67. }
  68. protected function getCsrfProvider()
  69. {
  70. return $this->csrfProvider;
  71. }
  72. protected function getValidator()
  73. {
  74. return $this->validator;
  75. }
  76. protected function getFieldFactory()
  77. {
  78. return $this->fieldFactory;
  79. }
  80. protected function initField(FieldInterface $field, array $options = array())
  81. {
  82. $options = array_merge(array(
  83. 'template' => 'text',
  84. 'data' => null,
  85. 'property_path' => (string)$field->getKey(),
  86. 'trim' => true,
  87. 'required' => true,
  88. 'disabled' => false,
  89. 'value_transformer' => null,
  90. 'normalization_transformer' => null,
  91. ), $options);
  92. return $field
  93. ->setData($options['data'])
  94. ->setPropertyPath($options['property_path'])
  95. ->setTrim($options['trim'])
  96. ->setRequired($options['required'])
  97. ->setDisabled($options['disabled'])
  98. ->setValueTransformer($options['value_transformer'])
  99. ->setNormalizationTransformer($options['normalization_transformer'])
  100. ->setRenderer(new DefaultRenderer($this->theme, $options['template']))
  101. ->addRendererPlugin(new IdPlugin($field))
  102. ->addRendererPlugin(new NamePlugin($field))
  103. ->addRendererPlugin(new ValuePlugin($field))
  104. ->setRendererVar('field', $field)
  105. ->setRendererVar('class', null)
  106. ->setRendererVar('max_length', null)
  107. ->setRendererVar('size', null)
  108. ->setRendererVar('label', ucfirst(strtolower(str_replace('_', ' ', $field->getKey()))));
  109. }
  110. protected function initForm(FormInterface $form, array $options = array())
  111. {
  112. $options = array_merge(array(
  113. 'template' => 'form',
  114. 'data_class' => null,
  115. 'data_constructor' => null,
  116. 'csrf_protection' => true,
  117. 'csrf_field_name' => '_token',
  118. 'csrf_provider' => $this->csrfProvider,
  119. 'field_factory' => $this->fieldFactory,
  120. 'validation_groups' => null,
  121. 'virtual' => false,
  122. 'validator' => $this->validator,
  123. ), $options);
  124. $this->initField($form, $options);
  125. if ($options['csrf_protection']) {
  126. $form->enableCsrfProtection($options['csrf_provider'], $options['csrf_field_name']);
  127. }
  128. return $form
  129. ->setDataClass($options['data_class'])
  130. ->setDataConstructor($options['data_constructor'])
  131. ->setFieldFactory($options['field_factory'])
  132. ->setValidationGroups($options['validation_groups'])
  133. ->setVirtual($options['virtual'])
  134. ->setValidator($options['validator']);
  135. }
  136. public function getField($key, array $options = array())
  137. {
  138. $field = new Field($key);
  139. $this->initField($field, $options);
  140. return $field;
  141. }
  142. public function getForm($key, array $options = array())
  143. {
  144. $form = new Form($key);
  145. $this->initForm($form, $options);
  146. return $form;
  147. }
  148. public function getTextField($key, array $options = array())
  149. {
  150. $options = array_merge(array(
  151. 'template' => 'text',
  152. 'max_length' => null,
  153. ), $options);
  154. return $this->getField($key, $options)
  155. ->setRendererVar('max_length', $options['max_length']);
  156. }
  157. public function getTextareaField($key, array $options = array())
  158. {
  159. $options = array_merge(array(
  160. 'template' => 'textarea',
  161. ), $options);
  162. return $this->getField($key, $options);
  163. }
  164. public function getPasswordField($key, array $options = array())
  165. {
  166. $options = array_merge(array(
  167. 'template' => 'password',
  168. 'always_empty' => true,
  169. ), $options);
  170. $field = $this->getTextField($key, $options);
  171. return $field
  172. ->addRendererPlugin(new PasswordValuePlugin($field, $options['always_empty']));
  173. }
  174. public function getHiddenField($key, array $options = array())
  175. {
  176. $options = array_merge(array(
  177. 'template' => 'hidden',
  178. ), $options);
  179. return $this->getField($key, $options)
  180. ->setHidden(true);
  181. }
  182. public function getNumberField($key, array $options = array())
  183. {
  184. $options = array_merge(array(
  185. 'template' => 'number',
  186. // default precision is locale specific (usually around 3)
  187. 'precision' => null,
  188. 'grouping' => false,
  189. 'rounding_mode' => NumberToLocalizedStringTransformer::ROUND_HALFUP,
  190. ), $options);
  191. return $this->getField($key, $options)
  192. ->setValueTransformer(new NumberToLocalizedStringTransformer(array(
  193. 'precision' => $options['precision'],
  194. 'grouping' => $options['grouping'],
  195. 'rounding-mode' => $options['rounding_mode'],
  196. )));
  197. }
  198. public function getIntegerField($key, array $options = array())
  199. {
  200. $options = array_merge(array(
  201. 'template' => 'integer',
  202. // default precision is locale specific (usually around 3)
  203. 'precision' => null,
  204. 'grouping' => false,
  205. // Integer cast rounds towards 0, so do the same when displaying fractions
  206. 'rounding_mode' => IntegerToLocalizedStringTransformer::ROUND_DOWN,
  207. ), $options);
  208. return $this->getField($key, $options)
  209. ->setValueTransformer(new IntegerToLocalizedStringTransformer(array(
  210. 'precision' => $options['precision'],
  211. 'grouping' => $options['grouping'],
  212. 'rounding-mode' => $options['rounding_mode'],
  213. )));
  214. }
  215. public function getMoneyField($key, array $options = array())
  216. {
  217. $options = array_merge(array(
  218. 'template' => 'money',
  219. 'precision' => 2,
  220. 'grouping' => false,
  221. 'divisor' => 1,
  222. 'currency' => 'EUR',
  223. ), $options);
  224. return $this->getField($key, $options)
  225. ->setValueTransformer(new MoneyToLocalizedStringTransformer(array(
  226. 'precision' => $options['precision'],
  227. 'grouping' => $options['grouping'],
  228. 'divisor' => $options['divisor'],
  229. )))
  230. ->addRendererPlugin(new MoneyPatternPlugin($options['currency']));
  231. }
  232. public function getPercentField($key, array $options = array())
  233. {
  234. $options = array_merge(array(
  235. 'template' => 'percent',
  236. 'precision' => 0,
  237. 'type' => 'fractional',
  238. ), $options);
  239. return $this->getField($key, $options)
  240. ->setValueTransformer(new PercentToLocalizedStringTransformer(array(
  241. 'precision' => $options['precision'],
  242. 'type' => $options['type'],
  243. )));
  244. }
  245. public function getCheckboxField($key, array $options = array())
  246. {
  247. $options = array_merge(array(
  248. 'template' => 'checkbox',
  249. 'value' => '1',
  250. ), $options);
  251. return $this->getField($key, $options)
  252. ->setValueTransformer(new BooleanToStringTransformer())
  253. ->setRendererVar('value', $options['value']);
  254. }
  255. public function getRadioField($key, array $options = array())
  256. {
  257. $options = array_merge(array(
  258. 'template' => 'radio',
  259. 'value' => null,
  260. ), $options);
  261. $field = $this->getField($key, $options);
  262. return $field
  263. ->setValueTransformer(new BooleanToStringTransformer())
  264. ->addRendererPlugin(new ParentNamePlugin($field))
  265. ->setRendererVar('value', $options['value']);
  266. }
  267. public function getUrlField($key, array $options = array())
  268. {
  269. $options = array_merge(array(
  270. 'default_protocol' => 'http',
  271. ), $options);
  272. return $this->getTextField($key, $options)
  273. ->setDataProcessor(new UrlProtocolFixer($options['default_protocol']));
  274. }
  275. protected function getChoiceFieldForList($key, ChoiceListInterface $choiceList, array $options = array())
  276. {
  277. $options = array_merge(array(
  278. 'template' => 'choice',
  279. 'multiple' => false,
  280. 'expanded' => false,
  281. ), $options);
  282. if (!$options['expanded']) {
  283. $field = $this->getField($key, $options);
  284. } else {
  285. $field = $this->getForm($key, $options);
  286. $choices = array_replace($choiceList->getPreferredChoices(), $choiceList->getOtherChoices());
  287. foreach ($choices as $choice => $value) {
  288. if ($options['multiple']) {
  289. $field->add($this->getCheckboxField($choice, array(
  290. 'value' => $choice,
  291. )));
  292. } else {
  293. $field->add($this->getRadioField($choice, array(
  294. 'value' => $choice,
  295. )));
  296. }
  297. }
  298. }
  299. $field->addRendererPlugin(new ChoicePlugin($choiceList))
  300. ->setRendererVar('multiple', $options['multiple'])
  301. ->setRendererVar('expanded', $options['expanded']);
  302. if ($options['multiple'] && $options['expanded']) {
  303. $field->setValueTransformer(new ArrayToChoicesTransformer($choiceList));
  304. }
  305. if (!$options['multiple'] && $options['expanded']) {
  306. $field->setValueTransformer(new ScalarToChoicesTransformer($choiceList));
  307. $field->setDataPreprocessor(new RadioToArrayConverter());
  308. }
  309. if ($options['multiple'] && !$options['expanded']) {
  310. $field->addRendererPlugin(new SelectMultipleNamePlugin($field));
  311. }
  312. return $field;
  313. }
  314. public function getChoiceField($key, array $options = array())
  315. {
  316. $options = array_merge(array(
  317. 'choices' => array(),
  318. 'preferred_choices' => array(),
  319. ), $options);
  320. $choiceList = new DefaultChoiceList(
  321. $options['choices'],
  322. $options['preferred_choices']
  323. );
  324. return $this->getChoiceFieldForList($key, $choiceList, $options);
  325. }
  326. public function getEntityChoiceField($key, array $options = array())
  327. {
  328. $options = array_merge(array(
  329. 'em' => null,
  330. 'class' => null,
  331. 'property' => null,
  332. 'query_builder' => null,
  333. 'choices' => array(),
  334. 'preferred_choices' => array(),
  335. 'multiple' => false,
  336. 'expanded' => false,
  337. ), $options);
  338. $choiceList = new EntityChoiceList(
  339. $options['em'],
  340. $options['class'],
  341. $options['property'],
  342. $options['query_builder'],
  343. $options['choices'],
  344. $options['preferred_choices']
  345. );
  346. $field = $this->getChoiceFieldForList($key, $choiceList, $options);
  347. $transformers = array();
  348. if ($options['multiple']) {
  349. $field->setDataProcessor(new CollectionMerger($field));
  350. $transformers[] = new EntitiesToArrayTransformer($choiceList);
  351. if ($options['expanded']) {
  352. $transformers[] = new ArrayToChoicesTransformer($choiceList);
  353. }
  354. } else {
  355. $transformers[] = new EntityToIdTransformer($choiceList);
  356. if ($options['expanded']) {
  357. $transformers[] = new ScalarToChoicesTransformer($choiceList);
  358. }
  359. }
  360. if (count($transformers) > 1) {
  361. $field->setValueTransformer(new ValueTransformerChain($transformers));
  362. } else {
  363. $field->setValueTransformer(current($transformers));
  364. }
  365. return $field;
  366. }
  367. public function getCountryField($key, array $options = array())
  368. {
  369. $options = array_merge(array(
  370. 'choices' => Locale::getDisplayCountries(\Locale::getDefault()),
  371. ), $options);
  372. return $this->getChoiceField($key, $options);
  373. }
  374. public function getLanguageField($key, array $options = array())
  375. {
  376. $options = array_merge(array(
  377. 'choices' => Locale::getDisplayLanguages(\Locale::getDefault()),
  378. ), $options);
  379. return $this->getChoiceField($key, $options);
  380. }
  381. public function getLocaleField($key, array $options = array())
  382. {
  383. $options = array_merge(array(
  384. 'choices' => Locale::getDisplayLocales(\Locale::getDefault()),
  385. ), $options);
  386. return $this->getChoiceField($key, $options);
  387. }
  388. public function getTimeZoneField($key, array $options = array())
  389. {
  390. $options = array_merge(array(
  391. 'preferred_choices' => array(),
  392. ), $options);
  393. $choiceList = new TimeZoneChoiceList($options['preferred_choices']);
  394. return $this->getChoiceFieldForList($key, $choiceList, $options);
  395. }
  396. protected function getDayField($key, array $options = array())
  397. {
  398. $options = array_merge(array(
  399. 'days' => range(1, 31),
  400. 'preferred_choices' => array(),
  401. ), $options);
  402. $choiceList = new PaddedChoiceList(
  403. $options['days'], 2, '0', STR_PAD_LEFT, $options['preferred_choices']
  404. );
  405. return $this->getChoiceFieldForList($key, $choiceList, $options);
  406. }
  407. protected function getMonthField($key, \IntlDateFormatter $formatter, array $options = array())
  408. {
  409. $options = array_merge(array(
  410. 'months' => range(1, 12),
  411. 'preferred_choices' => array(),
  412. ), $options);
  413. $choiceList = new MonthChoiceList(
  414. $formatter, $options['months'], $options['preferred_choices']
  415. );
  416. return $this->getChoiceFieldForList($key, $choiceList, $options);
  417. }
  418. protected function getYearField($key, array $options = array())
  419. {
  420. $options = array_merge(array(
  421. 'years' => range(date('Y') - 5, date('Y') + 5),
  422. 'preferred_choices' => array(),
  423. ), $options);
  424. $choiceList = new PaddedChoiceList(
  425. $options['years'], 4, '0', STR_PAD_LEFT, $options['preferred_choices']
  426. );
  427. return $this->getChoiceFieldForList($key, $choiceList, $options);
  428. }
  429. protected function getHourField($key, array $options = array())
  430. {
  431. $options = array_merge(array(
  432. 'widget' => 'choice',
  433. 'hours' => range(0, 23),
  434. 'preferred_choices' => array(),
  435. ), $options);
  436. if ($options['widget'] == 'text') {
  437. return $this->getTextField($key, array('max_length' => 2));
  438. } else {
  439. $choiceList = new PaddedChoiceList(
  440. $options['hours'], 2, '0', STR_PAD_LEFT, $options['preferred_choices']
  441. );
  442. return $this->getChoiceFieldForList($key, $choiceList, $options);
  443. }
  444. }
  445. protected function getMinuteField($key, array $options = array())
  446. {
  447. $options = array_merge(array(
  448. 'widget' => 'choice',
  449. 'minutes' => range(0, 59),
  450. 'preferred_choices' => array(),
  451. ), $options);
  452. if ($options['widget'] == 'text') {
  453. return $this->getTextField($key, array('max_length' => 2));
  454. } else {
  455. $choiceList = new PaddedChoiceList(
  456. $options['minutes'], 2, '0', STR_PAD_LEFT, $options['preferred_choices']
  457. );
  458. return $this->getChoiceFieldForList($key, $choiceList, $options);
  459. }
  460. }
  461. protected function getSecondField($key, array $options = array())
  462. {
  463. $options = array_merge(array(
  464. 'widget' => 'choice',
  465. 'seconds' => range(0, 59),
  466. 'preferred_choices' => array(),
  467. ), $options);
  468. if ($options['widget'] == 'text') {
  469. return $this->getTextField($key, array('max_length' => 2));
  470. } else {
  471. $choiceList = new PaddedChoiceList(
  472. $options['seconds'], 2, '0', STR_PAD_LEFT, $options['preferred_choices']
  473. );
  474. return $this->getChoiceFieldForList($key, $choiceList, $options);
  475. }
  476. }
  477. public function getDateField($key, array $options = array())
  478. {
  479. $options = array_merge(array(
  480. 'template' => 'date',
  481. 'widget' => 'choice',
  482. 'type' => 'datetime',
  483. 'pattern' => null,
  484. 'format' => \IntlDateFormatter::MEDIUM,
  485. 'data_timezone' => date_default_timezone_get(),
  486. 'user_timezone' => date_default_timezone_get(),
  487. ), $options);
  488. $formatter = new \IntlDateFormatter(
  489. \Locale::getDefault(),
  490. $options['format'],
  491. \IntlDateFormatter::NONE
  492. );
  493. if ($options['widget'] === 'text') {
  494. $field = $this->getField($key, $options)
  495. ->setValueTransformer(new DateTimeToLocalizedStringTransformer(array(
  496. 'date_format' => $options['format'],
  497. 'time_format' => \IntlDateFormatter::NONE,
  498. 'input_timezone' => $options['data_timezone'],
  499. 'output_timezone' => $options['user_timezone'],
  500. )));
  501. } else {
  502. // Only pass a subset of the options to children
  503. $childOptions = array_intersect_key($options, array_flip(array(
  504. 'years',
  505. 'months',
  506. 'days',
  507. )));
  508. $field = $this->getForm($key, $options)
  509. ->add($this->getYearField('year', $childOptions))
  510. ->add($this->getMonthField('month', $formatter, $childOptions))
  511. ->add($this->getDayField('day', $childOptions))
  512. ->setValueTransformer(new DateTimeToArrayTransformer(array(
  513. 'input_timezone' => $options['data_timezone'],
  514. 'output_timezone' => $options['user_timezone'],
  515. )))
  516. ->addRendererPlugin(new DatePatternPlugin($formatter))
  517. // Don't modify \DateTime classes by reference, we treat
  518. // them like immutable value objects
  519. ->setModifyByReference(false);
  520. }
  521. if ($options['type'] === 'string') {
  522. $field->setNormalizationTransformer(new ReversedTransformer(
  523. new DateTimeToStringTransformer(array(
  524. 'input_timezone' => $options['data_timezone'],
  525. 'output_timezone' => $options['data_timezone'],
  526. 'format' => 'Y-m-d',
  527. ))
  528. ));
  529. } else if ($options['type'] === 'timestamp') {
  530. $field->setNormalizationTransformer(new ReversedTransformer(
  531. new DateTimeToTimestampTransformer(array(
  532. 'output_timezone' => $options['data_timezone'],
  533. 'input_timezone' => $options['data_timezone'],
  534. ))
  535. ));
  536. } else if ($options['type'] === 'array') {
  537. $field->setNormalizationTransformer(new ReversedTransformer(
  538. new DateTimeToArrayTransformer(array(
  539. 'input_timezone' => $options['data_timezone'],
  540. 'output_timezone' => $options['data_timezone'],
  541. 'fields' => array('year', 'month', 'day'),
  542. ))
  543. ));
  544. }
  545. $field->setRendererVar('widget', $options['widget']);
  546. return $field;
  547. }
  548. public function getBirthdayField($key, array $options = array())
  549. {
  550. $options = array_merge(array(
  551. 'years' => range($currentYear-120, $currentYear),
  552. ), $options);
  553. return $this->getDateField($key, $options);
  554. }
  555. public function getTimeField($key, array $options = array())
  556. {
  557. $options = array_merge(array(
  558. 'template' => 'time',
  559. 'widget' => 'choice',
  560. 'type' => 'datetime',
  561. 'with_seconds' => false,
  562. 'pattern' => null,
  563. 'data_timezone' => date_default_timezone_get(),
  564. 'user_timezone' => date_default_timezone_get(),
  565. ), $options);
  566. // Only pass a subset of the options to children
  567. $childOptions = array_intersect_key($options, array_flip(array(
  568. 'hours',
  569. 'minutes',
  570. 'seconds',
  571. 'widget',
  572. )));
  573. $children = array('hour', 'minute');
  574. $field = $this->getForm($key, $options)
  575. ->add($this->getHourField('hour', $childOptions))
  576. ->add($this->getMinuteField('minute', $childOptions))
  577. // Don't modify \DateTime classes by reference, we treat
  578. // them like immutable value objects
  579. ->setModifyByReference(false);
  580. if ($options['with_seconds']) {
  581. $children[] = 'second';
  582. $field->add($this->getSecondField('second', $childOptions));
  583. }
  584. if ($options['type'] == 'string') {
  585. $field->setNormalizationTransformer(new ReversedTransformer(
  586. new DateTimeToStringTransformer(array(
  587. 'format' => 'H:i:s',
  588. 'input_timezone' => $options['data_timezone'],
  589. 'output_timezone' => $options['data_timezone'],
  590. ))
  591. ));
  592. } else if ($options['type'] == 'timestamp') {
  593. $field->setNormalizationTransformer(new ReversedTransformer(
  594. new DateTimeToTimestampTransformer(array(
  595. 'input_timezone' => $options['data_timezone'],
  596. 'output_timezone' => $options['data_timezone'],
  597. ))
  598. ));
  599. } else if ($options['type'] === 'array') {
  600. $field->setNormalizationTransformer(new ReversedTransformer(
  601. new DateTimeToArrayTransformer(array(
  602. 'input_timezone' => $options['data_timezone'],
  603. 'output_timezone' => $options['data_timezone'],
  604. 'fields' => $children,
  605. ))
  606. ));
  607. }
  608. $field
  609. ->setValueTransformer(new DateTimeToArrayTransformer(array(
  610. 'input_timezone' => $options['data_timezone'],
  611. 'output_timezone' => $options['user_timezone'],
  612. // if the field is rendered as choice field, the values should be trimmed
  613. // of trailing zeros to render the selected choices correctly
  614. 'pad' => $options['widget'] === 'text',
  615. 'fields' => $children,
  616. )))
  617. ->setRendererVar('widget', $options['widget'])
  618. ->setRendererVar('with_seconds', $options['with_seconds']);
  619. return $field;
  620. }
  621. }