Form.php 27 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021
  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\Component\Form;
  11. use Symfony\Component\Form\Event\DataEvent;
  12. use Symfony\Component\Form\Event\FilterDataEvent;
  13. use Symfony\Component\Form\Exception\FormException;
  14. use Symfony\Component\Form\Exception\UnexpectedTypeException;
  15. use Symfony\Component\Form\Exception\TransformationFailedException;
  16. use Symfony\Component\HttpFoundation\Request;
  17. use Symfony\Component\EventDispatcher\EventDispatcherInterface;
  18. /**
  19. * Form represents a form.
  20. *
  21. * A form is composed of a validator schema and a widget form schema.
  22. *
  23. * To implement your own form fields, you need to have a thorough understanding
  24. * of the data flow within a form field. A form field stores its data in three
  25. * different representations:
  26. *
  27. * (1) the format required by the form's object
  28. * (2) a normalized format for internal processing
  29. * (3) the format used for display
  30. *
  31. * A date field, for example, may store a date as "Y-m-d" string (1) in the
  32. * object. To facilitate processing in the field, this value is normalized
  33. * to a DateTime object (2). In the HTML representation of your form, a
  34. * localized string (3) is presented to and modified by the user.
  35. *
  36. * In most cases, format (1) and format (2) will be the same. For example,
  37. * a checkbox field uses a Boolean value both for internal processing as for
  38. * storage in the object. In these cases you simply need to set a value
  39. * transformer to convert between formats (2) and (3). You can do this by
  40. * calling appendClientTransformer() in the configure() method.
  41. *
  42. * In some cases though it makes sense to make format (1) configurable. To
  43. * demonstrate this, let's extend our above date field to store the value
  44. * either as "Y-m-d" string or as timestamp. Internally we still want to
  45. * use a DateTime object for processing. To convert the data from string/integer
  46. * to DateTime you can set a normalization transformer by calling
  47. * appendNormTransformer() in configure(). The normalized data is then
  48. * converted to the displayed data as described before.
  49. *
  50. * @author Fabien Potencier <fabien@symfony.com>
  51. * @author Bernhard Schussek <bernhard.schussek@symfony.com>
  52. */
  53. class Form implements \IteratorAggregate, FormInterface
  54. {
  55. /**
  56. * The name of this form
  57. * @var string
  58. */
  59. private $name;
  60. /**
  61. * The parent of this form
  62. * @var FormInterface
  63. */
  64. private $parent;
  65. /**
  66. * The children of this form
  67. * @var array An array of FormInterface instances
  68. */
  69. private $children = array();
  70. /**
  71. * The mapper for mapping data to children and back
  72. * @var DataMapper\DataMapperInterface
  73. */
  74. private $dataMapper;
  75. /**
  76. * The errors of this form
  77. * @var array An array of FromError instances
  78. */
  79. private $errors = array();
  80. /**
  81. * Whether added errors should bubble up to the parent
  82. * @var Boolean
  83. */
  84. private $errorBubbling;
  85. /**
  86. * Whether this form is bound
  87. * @var Boolean
  88. */
  89. private $bound = false;
  90. /**
  91. * Whether this form may not be empty
  92. * @var Boolean
  93. */
  94. private $required;
  95. /**
  96. * The form data in application format
  97. * @var mixed
  98. */
  99. private $appData;
  100. /**
  101. * The form data in normalized format
  102. * @var mixed
  103. */
  104. private $normData;
  105. /**
  106. * The form data in client format
  107. * @var mixed
  108. */
  109. private $clientData;
  110. /**
  111. * Data used for the client data when no value is bound
  112. * @var mixed
  113. */
  114. private $emptyData = '';
  115. /**
  116. * The bound values that don't belong to any children
  117. * @var array
  118. */
  119. private $extraData = array();
  120. /**
  121. * The transformers for transforming from application to normalized format
  122. * and back
  123. * @var array An array of DataTransformerInterface
  124. */
  125. private $normTransformers;
  126. /**
  127. * The transformers for transforming from normalized to client format and
  128. * back
  129. * @var array An array of DataTransformerInterface
  130. */
  131. private $clientTransformers;
  132. /**
  133. * Whether the data in application, normalized and client format is
  134. * synchronized. Data may not be synchronized if transformation errors
  135. * occur.
  136. * @var Boolean
  137. */
  138. private $synchronized = true;
  139. /**
  140. * The validators attached to this form
  141. * @var array An array of FormValidatorInterface instances
  142. */
  143. private $validators;
  144. /**
  145. * Whether this form may only be read, but not bound
  146. * @var Boolean
  147. */
  148. private $readOnly = false;
  149. /**
  150. * The dispatcher for distributing events of this form
  151. * @var Symfony\Component\EventDispatcher\EventDispatcherInterface
  152. */
  153. private $dispatcher;
  154. /**
  155. * Key-value store for arbitrary attributes attached to this form
  156. * @var array
  157. */
  158. private $attributes;
  159. /**
  160. * The FormTypeInterface instances used to create this form
  161. * @var array An array of FormTypeInterface
  162. */
  163. private $types;
  164. public function __construct($name, EventDispatcherInterface $dispatcher,
  165. array $types = array(), array $clientTransformers = array(),
  166. array $normTransformers = array(),
  167. DataMapperInterface $dataMapper = null, array $validators = array(),
  168. $required = false, $readOnly = false, $errorBubbling = false,
  169. $emptyData = null, array $attributes = array())
  170. {
  171. foreach ($clientTransformers as $transformer) {
  172. if (!$transformer instanceof DataTransformerInterface) {
  173. throw new UnexpectedTypeException($transformer, 'Symfony\Component\Form\DataTransformerInterface');
  174. }
  175. }
  176. foreach ($normTransformers as $transformer) {
  177. if (!$transformer instanceof DataTransformerInterface) {
  178. throw new UnexpectedTypeException($transformer, 'Symfony\Component\Form\DataTransformerInterface');
  179. }
  180. }
  181. foreach ($validators as $validator) {
  182. if (!$validator instanceof FormValidatorInterface) {
  183. throw new UnexpectedTypeException($validator, 'Symfony\Component\Form\FormValidatorInterface');
  184. }
  185. }
  186. $this->name = (string) $name;
  187. $this->dispatcher = $dispatcher;
  188. $this->types = $types;
  189. $this->clientTransformers = $clientTransformers;
  190. $this->normTransformers = $normTransformers;
  191. $this->dataMapper = $dataMapper;
  192. $this->validators = $validators;
  193. $this->required = (Boolean) $required;
  194. $this->readOnly = (Boolean) $readOnly;
  195. $this->errorBubbling = (Boolean) $errorBubbling;
  196. $this->emptyData = $emptyData;
  197. $this->attributes = $attributes;
  198. $this->setData(null);
  199. }
  200. public function __clone()
  201. {
  202. foreach ($this->children as $key => $child) {
  203. $this->children[$key] = clone $child;
  204. }
  205. }
  206. /**
  207. * Returns the name by which the form is identified in forms.
  208. *
  209. * @return string The name of the form.
  210. */
  211. public function getName()
  212. {
  213. return $this->name;
  214. }
  215. /**
  216. * Returns the types used by this form.
  217. *
  218. * @return array An array of FormTypeInterface
  219. */
  220. public function getTypes()
  221. {
  222. return $this->types;
  223. }
  224. /**
  225. * Returns whether the form is required to be filled out.
  226. *
  227. * If the form has a parent and the parent is not required, this method
  228. * will always return false. Otherwise the value set with setRequired()
  229. * is returned.
  230. *
  231. * @return Boolean
  232. */
  233. public function isRequired()
  234. {
  235. if (null === $this->parent || $this->parent->isRequired()) {
  236. return $this->required;
  237. }
  238. return false;
  239. }
  240. /**
  241. * Returns whether this form is read only.
  242. *
  243. * The content of a read-only form is displayed, but not allowed to be
  244. * modified. The validation of modified read-only forms should fail.
  245. *
  246. * Fields whose parents are read-only are considered read-only regardless of
  247. * their own state.
  248. *
  249. * @return Boolean
  250. */
  251. public function isReadOnly()
  252. {
  253. if (null === $this->parent || !$this->parent->isReadOnly()) {
  254. return $this->readOnly;
  255. }
  256. return true;
  257. }
  258. /**
  259. * Sets the parent form.
  260. *
  261. * @param FormInterface $parent The parent form
  262. *
  263. * @return Form The current form
  264. */
  265. public function setParent(FormInterface $parent = null)
  266. {
  267. $this->parent = $parent;
  268. return $this;
  269. }
  270. /**
  271. * Returns the parent field.
  272. *
  273. * @return FormInterface The parent field
  274. */
  275. public function getParent()
  276. {
  277. return $this->parent;
  278. }
  279. /**
  280. * Returns whether the form has a parent.
  281. *
  282. * @return Boolean
  283. */
  284. public function hasParent()
  285. {
  286. return null !== $this->parent;
  287. }
  288. /**
  289. * Returns the root of the form tree.
  290. *
  291. * @return FormInterface The root of the tree
  292. */
  293. public function getRoot()
  294. {
  295. return $this->parent ? $this->parent->getRoot() : $this;
  296. }
  297. /**
  298. * Returns whether the field is the root of the form tree.
  299. *
  300. * @return Boolean
  301. */
  302. public function isRoot()
  303. {
  304. return !$this->hasParent();
  305. }
  306. /**
  307. * Returns whether the form has an attribute with the given name.
  308. *
  309. * @param string $name The name of the attribute
  310. */
  311. public function hasAttribute($name)
  312. {
  313. return isset($this->attributes[$name]);
  314. }
  315. /**
  316. * Returns the value of the attributes with the given name.
  317. *
  318. * @param string $name The name of the attribute
  319. */
  320. public function getAttribute($name)
  321. {
  322. return $this->attributes[$name];
  323. }
  324. /**
  325. * Updates the field with default data.
  326. *
  327. * @param array $appData The data formatted as expected for the underlying object
  328. *
  329. * @return Form The current form
  330. */
  331. public function setData($appData)
  332. {
  333. $event = new DataEvent($this, $appData);
  334. $this->dispatcher->dispatch(FormEvents::PRE_SET_DATA, $event);
  335. // Hook to change content of the data
  336. $event = new FilterDataEvent($this, $appData);
  337. $this->dispatcher->dispatch(FormEvents::SET_DATA, $event);
  338. $appData = $event->getData();
  339. // Treat data as strings unless a value transformer exists
  340. if (!$this->clientTransformers && !$this->normTransformers && is_scalar($appData)) {
  341. $appData = (string) $appData;
  342. }
  343. // Synchronize representations - must not change the content!
  344. $normData = $this->appToNorm($appData);
  345. $clientData = $this->normToClient($normData);
  346. $this->appData = $appData;
  347. $this->normData = $normData;
  348. $this->clientData = $clientData;
  349. $this->synchronized = true;
  350. if ($this->dataMapper) {
  351. // Update child forms from the data
  352. $this->dataMapper->mapDataToForms($clientData, $this->children);
  353. }
  354. $event = new DataEvent($this, $appData);
  355. $this->dispatcher->dispatch(FormEvents::POST_SET_DATA, $event);
  356. return $this;
  357. }
  358. /**
  359. * Returns the data in the format needed for the underlying object.
  360. *
  361. * @return mixed
  362. */
  363. public function getData()
  364. {
  365. return $this->appData;
  366. }
  367. /**
  368. * Returns the data transformed by the value transformer.
  369. *
  370. * @return string
  371. */
  372. public function getClientData()
  373. {
  374. return $this->clientData;
  375. }
  376. /**
  377. * Returns the extra data.
  378. *
  379. * @return array The bound data which do not belong to a child
  380. */
  381. public function getExtraData()
  382. {
  383. return $this->extraData;
  384. }
  385. /**
  386. * Binds data to the field, transforms and validates it.
  387. *
  388. * @param string|array $clientData The data
  389. *
  390. * @return Form The current form
  391. *
  392. * @throws UnexpectedTypeException
  393. */
  394. public function bind($clientData)
  395. {
  396. if ($this->readOnly) {
  397. $this->bound = true;
  398. return $this;
  399. }
  400. if (is_scalar($clientData) || null === $clientData) {
  401. $clientData = (string) $clientData;
  402. }
  403. // Initialize errors in the very beginning so that we don't lose any
  404. // errors added during listeners
  405. $this->errors = array();
  406. $event = new DataEvent($this, $clientData);
  407. $this->dispatcher->dispatch(FormEvents::PRE_BIND, $event);
  408. $appData = null;
  409. $normData = null;
  410. $extraData = array();
  411. $synchronized = false;
  412. // Hook to change content of the data bound by the browser
  413. $event = new FilterDataEvent($this, $clientData);
  414. $this->dispatcher->dispatch(FormEvents::BIND_CLIENT_DATA, $event);
  415. $clientData = $event->getData();
  416. if (count($this->children) > 0) {
  417. if (null === $clientData || '' === $clientData) {
  418. $clientData = array();
  419. }
  420. if (!is_array($clientData)) {
  421. throw new UnexpectedTypeException($clientData, 'array');
  422. }
  423. foreach ($this->children as $name => $child) {
  424. if (!isset($clientData[$name])) {
  425. $clientData[$name] = null;
  426. }
  427. }
  428. foreach ($clientData as $name => $value) {
  429. if ($this->has($name)) {
  430. $this->children[$name]->bind($value);
  431. } else {
  432. $extraData[$name] = $value;
  433. }
  434. }
  435. // If we have a data mapper, use old client data and merge
  436. // data from the children into it later
  437. if ($this->dataMapper) {
  438. $clientData = $this->getClientData();
  439. }
  440. }
  441. if (null === $clientData || '' === $clientData) {
  442. $clientData = $this->emptyData;
  443. if ($clientData instanceof \Closure) {
  444. $clientData = $clientData($this);
  445. }
  446. }
  447. // Merge form data from children into existing client data
  448. if (count($this->children) > 0 && $this->dataMapper) {
  449. $this->dataMapper->mapFormsToData($this->children, $clientData);
  450. }
  451. try {
  452. // Normalize data to unified representation
  453. $normData = $this->clientToNorm($clientData);
  454. $synchronized = true;
  455. } catch (TransformationFailedException $e) {
  456. }
  457. if ($synchronized) {
  458. // Hook to change content of the data in the normalized
  459. // representation
  460. $event = new FilterDataEvent($this, $normData);
  461. $this->dispatcher->dispatch(FormEvents::BIND_NORM_DATA, $event);
  462. $normData = $event->getData();
  463. // Synchronize representations - must not change the content!
  464. $appData = $this->normToApp($normData);
  465. $clientData = $this->normToClient($normData);
  466. }
  467. $this->bound = true;
  468. $this->appData = $appData;
  469. $this->normData = $normData;
  470. $this->clientData = $clientData;
  471. $this->extraData = $extraData;
  472. $this->synchronized = $synchronized;
  473. $event = new DataEvent($this, $clientData);
  474. $this->dispatcher->dispatch(FormEvents::POST_BIND, $event);
  475. foreach ($this->validators as $validator) {
  476. $validator->validate($this);
  477. }
  478. return $this;
  479. }
  480. /**
  481. * Binds a request to the form.
  482. *
  483. * If the request method is POST, PUT or GET, the data is bound to the form,
  484. * transformed and written into the form data (an object or an array).
  485. *
  486. * @param Request $request The request to bind to the form
  487. *
  488. * @return Form This form
  489. *
  490. * @throws FormException if the method of the request is not one of GET, POST or PUT
  491. */
  492. public function bindRequest(Request $request)
  493. {
  494. // Store the bound data in case of a post request
  495. switch ($request->getMethod()) {
  496. case 'POST':
  497. case 'PUT':
  498. $data = array_replace_recursive(
  499. $request->request->get($this->getName(), array()),
  500. $request->files->get($this->getName(), array())
  501. );
  502. break;
  503. case 'GET':
  504. $data = $request->query->get($this->getName(), array());
  505. break;
  506. default:
  507. throw new FormException(sprintf('The request method "%s" is not supported', $request->getMethod()));
  508. }
  509. return $this->bind($data);
  510. }
  511. /**
  512. * Returns the normalized data of the field.
  513. *
  514. * @return mixed When the field is not bound, the default data is returned.
  515. * When the field is bound, the normalized bound data is
  516. * returned if the field is valid, null otherwise.
  517. */
  518. public function getNormData()
  519. {
  520. return $this->normData;
  521. }
  522. /**
  523. * Adds an error to this form.
  524. *
  525. * @param FormError $error
  526. *
  527. * @return Form The current form
  528. */
  529. public function addError(FormError $error)
  530. {
  531. if ($this->parent && $this->errorBubbling) {
  532. $this->parent->addError($error);
  533. } else {
  534. $this->errors[] = $error;
  535. }
  536. return $this;
  537. }
  538. /**
  539. * Returns whether errors bubble up to the parent.
  540. *
  541. * @return Boolean
  542. */
  543. public function getErrorBubbling()
  544. {
  545. return $this->errorBubbling;
  546. }
  547. /**
  548. * Returns whether the field is bound.
  549. *
  550. * @return Boolean true if the form is bound to input values, false otherwise
  551. */
  552. public function isBound()
  553. {
  554. return $this->bound;
  555. }
  556. /**
  557. * Returns whether the data in the different formats is synchronized.
  558. *
  559. * @return Boolean
  560. */
  561. public function isSynchronized()
  562. {
  563. return $this->synchronized;
  564. }
  565. /**
  566. * Returns whether the form is empty.
  567. *
  568. * @return Boolean
  569. */
  570. public function isEmpty()
  571. {
  572. foreach ($this->children as $child) {
  573. if (!$child->isEmpty()) {
  574. return false;
  575. }
  576. }
  577. return array() === $this->appData || null === $this->appData || '' === $this->appData;
  578. }
  579. /**
  580. * Returns whether the field is valid.
  581. *
  582. * @return Boolean
  583. */
  584. public function isValid()
  585. {
  586. if (!$this->isBound() || $this->hasErrors()) {
  587. return false;
  588. }
  589. if (!$this->readOnly) {
  590. foreach ($this->children as $child) {
  591. if (!$child->isValid()) {
  592. return false;
  593. }
  594. }
  595. }
  596. return true;
  597. }
  598. /**
  599. * Returns whether or not there are errors.
  600. *
  601. * @return Boolean true if form is bound and not valid
  602. */
  603. public function hasErrors()
  604. {
  605. // Don't call isValid() here, as its semantics are slightly different
  606. // Field groups are not valid if their children are invalid, but
  607. // hasErrors() returns only true if a field/field group itself has
  608. // errors
  609. return count($this->errors) > 0;
  610. }
  611. /**
  612. * Returns all errors.
  613. *
  614. * @return array An array of FormError instances that occurred during binding
  615. */
  616. public function getErrors()
  617. {
  618. return $this->errors;
  619. }
  620. /**
  621. * Returns the DataTransformers.
  622. *
  623. * @return array An array of DataTransformerInterface
  624. */
  625. public function getNormTransformers()
  626. {
  627. return $this->normTransformers;
  628. }
  629. /**
  630. * Returns the DataTransformers.
  631. *
  632. * @return array An array of DataTransformerInterface
  633. */
  634. public function getClientTransformers()
  635. {
  636. return $this->clientTransformers;
  637. }
  638. /**
  639. * Returns all children in this group.
  640. *
  641. * @return array
  642. */
  643. public function getChildren()
  644. {
  645. return $this->children;
  646. }
  647. /**
  648. * Return whether the form has children.
  649. *
  650. * @return Boolean
  651. */
  652. public function hasChildren()
  653. {
  654. return count($this->children) > 0;
  655. }
  656. /**
  657. * Adds a child to the form.
  658. *
  659. * @param FormInterface $child The FormInterface to add as a child
  660. *
  661. * @return Form the current form
  662. */
  663. public function add(FormInterface $child)
  664. {
  665. $this->children[$child->getName()] = $child;
  666. $child->setParent($this);
  667. if ($this->dataMapper) {
  668. $this->dataMapper->mapDataToForm($this->getClientData(), $child);
  669. }
  670. return $this;
  671. }
  672. /**
  673. * Removes a child from the form.
  674. *
  675. * @param string $name The name of the child to remove
  676. *
  677. * @return Form the current form
  678. */
  679. public function remove($name)
  680. {
  681. if (isset($this->children[$name])) {
  682. $this->children[$name]->setParent(null);
  683. unset($this->children[$name]);
  684. }
  685. return $this;
  686. }
  687. /**
  688. * Returns whether a child with the given name exists.
  689. *
  690. * @param string $name
  691. *
  692. * @return Boolean
  693. */
  694. public function has($name)
  695. {
  696. return isset($this->children[$name]);
  697. }
  698. /**
  699. * Returns the child with the given name.
  700. *
  701. * @param string $name
  702. *
  703. * @return FormInterface
  704. *
  705. * @throws \InvalidArgumentException if the child does not exist
  706. */
  707. public function get($name)
  708. {
  709. if (isset($this->children[$name])) {
  710. return $this->children[$name];
  711. }
  712. throw new \InvalidArgumentException(sprintf('Field "%s" does not exist.', $name));
  713. }
  714. /**
  715. * Returns true if the child exists (implements the \ArrayAccess interface).
  716. *
  717. * @param string $name The name of the child
  718. *
  719. * @return Boolean true if the widget exists, false otherwise
  720. */
  721. public function offsetExists($name)
  722. {
  723. return $this->has($name);
  724. }
  725. /**
  726. * Returns the form child associated with the name (implements the \ArrayAccess interface).
  727. *
  728. * @param string $name The offset of the value to get
  729. *
  730. * @return FormInterface A form instance
  731. */
  732. public function offsetGet($name)
  733. {
  734. return $this->get($name);
  735. }
  736. /**
  737. * Adds a child to the form (implements the \ArrayAccess interface).
  738. *
  739. * @param string $name Ignored. The name of the child is used.
  740. * @param FormInterface $child The child to be added
  741. */
  742. public function offsetSet($name, $child)
  743. {
  744. $this->add($child);
  745. }
  746. /**
  747. * Removes the child with the given name from the form (implements the \ArrayAccess interface).
  748. *
  749. * @param string $name The name of the child to be removed
  750. */
  751. public function offsetUnset($name)
  752. {
  753. $this->remove($name);
  754. }
  755. /**
  756. * Returns the iterator for this group.
  757. *
  758. * @return \ArrayIterator
  759. */
  760. public function getIterator()
  761. {
  762. return new \ArrayIterator($this->children);
  763. }
  764. /**
  765. * Returns the number of form children (implements the \Countable interface).
  766. *
  767. * @return integer The number of embedded form children
  768. */
  769. public function count()
  770. {
  771. return count($this->children);
  772. }
  773. /**
  774. * Creates a view.
  775. *
  776. * @param FormView $parent The parent view
  777. *
  778. * @return FormView The view
  779. */
  780. public function createView(FormView $parent = null)
  781. {
  782. if (null === $parent && $this->parent) {
  783. $parent = $this->parent->createView();
  784. }
  785. $view = new FormView();
  786. $view->setParent($parent);
  787. $types = (array) $this->types;
  788. foreach ($types as $type) {
  789. $type->buildView($view, $this);
  790. foreach ($type->getExtensions() as $typeExtension) {
  791. $typeExtension->buildView($view, $this);
  792. }
  793. }
  794. $childViews = array();
  795. foreach ($this->children as $key => $child) {
  796. $childViews[$key] = $child->createView($view);
  797. }
  798. if (null !== $prototype = $view->get('prototype')) {
  799. $protoView = $prototype->getForm()->createView($view);
  800. $protoTypes = $protoView->get('types');
  801. $protoTypes[] = 'prototype';
  802. $childViews[$prototype->getName()] = $protoView
  803. ->set('types', $protoTypes)
  804. ->set('proto_id', $view->get('id').'_prototype');
  805. ;
  806. }
  807. $view->setChildren($childViews);
  808. foreach ($types as $type) {
  809. $type->buildViewBottomUp($view, $this);
  810. foreach ($type->getExtensions() as $typeExtension) {
  811. $typeExtension->buildViewBottomUp($view, $this);
  812. }
  813. }
  814. return $view;
  815. }
  816. /**
  817. * Normalizes the value if a normalization transformer is set.
  818. *
  819. * @param mixed $value The value to transform
  820. *
  821. * @return string
  822. */
  823. private function appToNorm($value)
  824. {
  825. foreach ($this->normTransformers as $transformer) {
  826. $value = $transformer->transform($value);
  827. }
  828. return $value;
  829. }
  830. /**
  831. * Reverse transforms a value if a normalization transformer is set.
  832. *
  833. * @param string $value The value to reverse transform
  834. *
  835. * @return mixed
  836. */
  837. private function normToApp($value)
  838. {
  839. for ($i = count($this->normTransformers) - 1; $i >= 0; --$i) {
  840. $value = $this->normTransformers[$i]->reverseTransform($value);
  841. }
  842. return $value;
  843. }
  844. /**
  845. * Transforms the value if a value transformer is set.
  846. *
  847. * @param mixed $value The value to transform
  848. *
  849. * @return string
  850. */
  851. private function normToClient($value)
  852. {
  853. if (!$this->clientTransformers) {
  854. // Scalar values should always be converted to strings to
  855. // facilitate differentiation between empty ("") and zero (0).
  856. return null === $value || is_scalar($value) ? (string) $value : $value;
  857. }
  858. foreach ($this->clientTransformers as $transformer) {
  859. $value = $transformer->transform($value);
  860. }
  861. return $value;
  862. }
  863. /**
  864. * Reverse transforms a value if a value transformer is set.
  865. *
  866. * @param string $value The value to reverse transform
  867. *
  868. * @return mixed
  869. */
  870. private function clientToNorm($value)
  871. {
  872. if (!$this->clientTransformers) {
  873. return '' === $value ? null : $value;
  874. }
  875. for ($i = count($this->clientTransformers) - 1; $i >= 0; --$i) {
  876. $value = $this->clientTransformers[$i]->reverseTransform($value);
  877. }
  878. return $value;
  879. }
  880. }