Form.php 26 KB

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