Form.php 26 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016
  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().
  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(). 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 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. * @return Boolean
  312. */
  313. public function hasAttribute($name)
  314. {
  315. return isset($this->attributes[$name]);
  316. }
  317. /**
  318. * Returns the value of the attributes with the given name.
  319. *
  320. * @param string $name The name of the attribute
  321. */
  322. public function getAttribute($name)
  323. {
  324. return $this->attributes[$name];
  325. }
  326. /**
  327. * Updates the field with default data.
  328. *
  329. * @param array $appData The data formatted as expected for the underlying object
  330. *
  331. * @return Form The current form
  332. */
  333. public function setData($appData)
  334. {
  335. $event = new DataEvent($this, $appData);
  336. $this->dispatcher->dispatch(FormEvents::PRE_SET_DATA, $event);
  337. // Hook to change content of the data
  338. $event = new FilterDataEvent($this, $appData);
  339. $this->dispatcher->dispatch(FormEvents::SET_DATA, $event);
  340. $appData = $event->getData();
  341. // Treat data as strings unless a value transformer exists
  342. if (!$this->clientTransformers && !$this->normTransformers && is_scalar($appData)) {
  343. $appData = (string) $appData;
  344. }
  345. // Synchronize representations - must not change the content!
  346. $normData = $this->appToNorm($appData);
  347. $clientData = $this->normToClient($normData);
  348. $this->appData = $appData;
  349. $this->normData = $normData;
  350. $this->clientData = $clientData;
  351. $this->synchronized = true;
  352. if ($this->dataMapper) {
  353. // Update child forms from the data
  354. $this->dataMapper->mapDataToForms($clientData, $this->children);
  355. }
  356. $event = new DataEvent($this, $appData);
  357. $this->dispatcher->dispatch(FormEvents::POST_SET_DATA, $event);
  358. return $this;
  359. }
  360. /**
  361. * Returns the data in the format needed for the underlying object.
  362. *
  363. * @return mixed
  364. */
  365. public function getData()
  366. {
  367. return $this->appData;
  368. }
  369. /**
  370. * Returns the data transformed by the value transformer.
  371. *
  372. * @return string
  373. */
  374. public function getClientData()
  375. {
  376. return $this->clientData;
  377. }
  378. /**
  379. * Returns the extra data.
  380. *
  381. * @return array The bound data which do not belong to a child
  382. */
  383. public function getExtraData()
  384. {
  385. return $this->extraData;
  386. }
  387. /**
  388. * Binds data to the field, transforms and validates it.
  389. *
  390. * @param string|array $clientData The data
  391. *
  392. * @return Form The current form
  393. *
  394. * @throws UnexpectedTypeException
  395. */
  396. public function bind($clientData)
  397. {
  398. if ($this->readOnly) {
  399. $this->bound = true;
  400. return $this;
  401. }
  402. if (is_scalar($clientData) || null === $clientData) {
  403. $clientData = (string) $clientData;
  404. }
  405. // Initialize errors in the very beginning so that we don't lose any
  406. // errors added during listeners
  407. $this->errors = array();
  408. $event = new DataEvent($this, $clientData);
  409. $this->dispatcher->dispatch(FormEvents::PRE_BIND, $event);
  410. $appData = null;
  411. $normData = null;
  412. $extraData = array();
  413. $synchronized = false;
  414. // Hook to change content of the data bound by the browser
  415. $event = new FilterDataEvent($this, $clientData);
  416. $this->dispatcher->dispatch(FormEvents::BIND_CLIENT_DATA, $event);
  417. $clientData = $event->getData();
  418. if (count($this->children) > 0) {
  419. if (null === $clientData || '' === $clientData) {
  420. $clientData = array();
  421. }
  422. if (!is_array($clientData)) {
  423. throw new UnexpectedTypeException($clientData, 'array');
  424. }
  425. foreach ($this->children as $name => $child) {
  426. if (!isset($clientData[$name])) {
  427. $clientData[$name] = null;
  428. }
  429. }
  430. foreach ($clientData as $name => $value) {
  431. if ($this->has($name)) {
  432. $this->children[$name]->bind($value);
  433. } else {
  434. $extraData[$name] = $value;
  435. }
  436. }
  437. // If we have a data mapper, use old client data and merge
  438. // data from the children into it later
  439. if ($this->dataMapper) {
  440. $clientData = $this->getClientData();
  441. }
  442. }
  443. if (null === $clientData || '' === $clientData) {
  444. $clientData = $this->emptyData;
  445. if ($clientData instanceof \Closure) {
  446. $clientData = $clientData($this);
  447. }
  448. }
  449. // Merge form data from children into existing client data
  450. if (count($this->children) > 0 && $this->dataMapper) {
  451. $this->dataMapper->mapFormsToData($this->children, $clientData);
  452. }
  453. try {
  454. // Normalize data to unified representation
  455. $normData = $this->clientToNorm($clientData);
  456. $synchronized = true;
  457. } catch (TransformationFailedException $e) {
  458. }
  459. if ($synchronized) {
  460. // Hook to change content of the data in the normalized
  461. // representation
  462. $event = new FilterDataEvent($this, $normData);
  463. $this->dispatcher->dispatch(FormEvents::BIND_NORM_DATA, $event);
  464. $normData = $event->getData();
  465. // Synchronize representations - must not change the content!
  466. $appData = $this->normToApp($normData);
  467. $clientData = $this->normToClient($normData);
  468. }
  469. $this->bound = true;
  470. $this->appData = $appData;
  471. $this->normData = $normData;
  472. $this->clientData = $clientData;
  473. $this->extraData = $extraData;
  474. $this->synchronized = $synchronized;
  475. $event = new DataEvent($this, $clientData);
  476. $this->dispatcher->dispatch(FormEvents::POST_BIND, $event);
  477. foreach ($this->validators as $validator) {
  478. $validator->validate($this);
  479. }
  480. return $this;
  481. }
  482. /**
  483. * Binds a request to the form.
  484. *
  485. * If the request method is POST, PUT or GET, the data is bound to the form,
  486. * transformed and written into the form data (an object or an array).
  487. *
  488. * @param Request $request The request to bind to the form
  489. *
  490. * @return Form This form
  491. *
  492. * @throws FormException if the method of the request is not one of GET, POST or PUT
  493. */
  494. public function bindRequest(Request $request)
  495. {
  496. // Store the bound data in case of a post request
  497. switch ($request->getMethod()) {
  498. case 'POST':
  499. case 'PUT':
  500. $data = array_replace_recursive(
  501. $request->request->get($this->getName(), array()),
  502. $request->files->get($this->getName(), array())
  503. );
  504. break;
  505. case 'GET':
  506. $data = $request->query->get($this->getName(), array());
  507. break;
  508. default:
  509. throw new FormException(sprintf('The request method "%s" is not supported', $request->getMethod()));
  510. }
  511. return $this->bind($data);
  512. }
  513. /**
  514. * Returns the normalized data of the field.
  515. *
  516. * @return mixed When the field is not bound, the default data is returned.
  517. * When the field is bound, the normalized bound data is
  518. * returned if the field is valid, null otherwise.
  519. */
  520. public function getNormData()
  521. {
  522. return $this->normData;
  523. }
  524. /**
  525. * Adds an error to this form.
  526. *
  527. * @param FormError $error
  528. *
  529. * @return Form The current form
  530. */
  531. public function addError(FormError $error)
  532. {
  533. if ($this->parent && $this->errorBubbling) {
  534. $this->parent->addError($error);
  535. } else {
  536. $this->errors[] = $error;
  537. }
  538. return $this;
  539. }
  540. /**
  541. * Returns whether errors bubble up to the parent.
  542. *
  543. * @return Boolean
  544. */
  545. public function getErrorBubbling()
  546. {
  547. return $this->errorBubbling;
  548. }
  549. /**
  550. * Returns whether the field is bound.
  551. *
  552. * @return Boolean true if the form is bound to input values, false otherwise
  553. */
  554. public function isBound()
  555. {
  556. return $this->bound;
  557. }
  558. /**
  559. * Returns whether the data in the different formats is synchronized.
  560. *
  561. * @return Boolean
  562. */
  563. public function isSynchronized()
  564. {
  565. return $this->synchronized;
  566. }
  567. /**
  568. * Returns whether the form is empty.
  569. *
  570. * @return Boolean
  571. */
  572. public function isEmpty()
  573. {
  574. foreach ($this->children as $child) {
  575. if (!$child->isEmpty()) {
  576. return false;
  577. }
  578. }
  579. return array() === $this->appData || null === $this->appData || '' === $this->appData;
  580. }
  581. /**
  582. * Returns whether the field is valid.
  583. *
  584. * @return Boolean
  585. */
  586. public function isValid()
  587. {
  588. if (!$this->isBound()) {
  589. throw new \LogicException('You cannot call isValid() on a form that is not bound.');
  590. }
  591. if ($this->hasErrors()) {
  592. return false;
  593. }
  594. if (!$this->readOnly) {
  595. foreach ($this->children as $child) {
  596. if (!$child->isValid()) {
  597. return false;
  598. }
  599. }
  600. }
  601. return true;
  602. }
  603. /**
  604. * Returns whether or not there are errors.
  605. *
  606. * @return Boolean true if form is bound and not valid
  607. */
  608. public function hasErrors()
  609. {
  610. // Don't call isValid() here, as its semantics are slightly different
  611. // Field groups are not valid if their children are invalid, but
  612. // hasErrors() returns only true if a field/field group itself has
  613. // errors
  614. return count($this->errors) > 0;
  615. }
  616. /**
  617. * Returns all errors.
  618. *
  619. * @return array An array of FormError instances that occurred during binding
  620. */
  621. public function getErrors()
  622. {
  623. return $this->errors;
  624. }
  625. /**
  626. * Returns the DataTransformers.
  627. *
  628. * @return array An array of DataTransformerInterface
  629. */
  630. public function getNormTransformers()
  631. {
  632. return $this->normTransformers;
  633. }
  634. /**
  635. * Returns the DataTransformers.
  636. *
  637. * @return array An array of DataTransformerInterface
  638. */
  639. public function getClientTransformers()
  640. {
  641. return $this->clientTransformers;
  642. }
  643. /**
  644. * Returns all children in this group.
  645. *
  646. * @return array
  647. */
  648. public function getChildren()
  649. {
  650. return $this->children;
  651. }
  652. /**
  653. * Return whether the form has children.
  654. *
  655. * @return Boolean
  656. */
  657. public function hasChildren()
  658. {
  659. return count($this->children) > 0;
  660. }
  661. /**
  662. * Adds a child to the form.
  663. *
  664. * @param FormInterface $child The FormInterface to add as a child
  665. *
  666. * @return Form the current form
  667. */
  668. public function add(FormInterface $child)
  669. {
  670. $this->children[$child->getName()] = $child;
  671. $child->setParent($this);
  672. if ($this->dataMapper) {
  673. $this->dataMapper->mapDataToForm($this->getClientData(), $child);
  674. }
  675. return $this;
  676. }
  677. /**
  678. * Removes a child from the form.
  679. *
  680. * @param string $name The name of the child to remove
  681. *
  682. * @return Form the current form
  683. */
  684. public function remove($name)
  685. {
  686. if (isset($this->children[$name])) {
  687. $this->children[$name]->setParent(null);
  688. unset($this->children[$name]);
  689. }
  690. return $this;
  691. }
  692. /**
  693. * Returns whether a child with the given name exists.
  694. *
  695. * @param string $name
  696. *
  697. * @return Boolean
  698. */
  699. public function has($name)
  700. {
  701. return isset($this->children[$name]);
  702. }
  703. /**
  704. * Returns the child with the given name.
  705. *
  706. * @param string $name
  707. *
  708. * @return FormInterface
  709. *
  710. * @throws \InvalidArgumentException if the child does not exist
  711. */
  712. public function get($name)
  713. {
  714. if (isset($this->children[$name])) {
  715. return $this->children[$name];
  716. }
  717. throw new \InvalidArgumentException(sprintf('Field "%s" does not exist.', $name));
  718. }
  719. /**
  720. * Returns true if the child exists (implements the \ArrayAccess interface).
  721. *
  722. * @param string $name The name of the child
  723. *
  724. * @return Boolean true if the widget exists, false otherwise
  725. */
  726. public function offsetExists($name)
  727. {
  728. return $this->has($name);
  729. }
  730. /**
  731. * Returns the form child associated with the name (implements the \ArrayAccess interface).
  732. *
  733. * @param string $name The offset of the value to get
  734. *
  735. * @return FormInterface A form instance
  736. */
  737. public function offsetGet($name)
  738. {
  739. return $this->get($name);
  740. }
  741. /**
  742. * Adds a child to the form (implements the \ArrayAccess interface).
  743. *
  744. * @param string $name Ignored. The name of the child is used.
  745. * @param FormInterface $child The child to be added
  746. */
  747. public function offsetSet($name, $child)
  748. {
  749. $this->add($child);
  750. }
  751. /**
  752. * Removes the child with the given name from the form (implements the \ArrayAccess interface).
  753. *
  754. * @param string $name The name of the child to be removed
  755. */
  756. public function offsetUnset($name)
  757. {
  758. $this->remove($name);
  759. }
  760. /**
  761. * Returns the iterator for this group.
  762. *
  763. * @return \ArrayIterator
  764. */
  765. public function getIterator()
  766. {
  767. return new \ArrayIterator($this->children);
  768. }
  769. /**
  770. * Returns the number of form children (implements the \Countable interface).
  771. *
  772. * @return integer The number of embedded form children
  773. */
  774. public function count()
  775. {
  776. return count($this->children);
  777. }
  778. /**
  779. * Creates a view.
  780. *
  781. * @param FormView $parent The parent view
  782. *
  783. * @return FormView The view
  784. */
  785. public function createView(FormView $parent = null)
  786. {
  787. if (null === $parent && $this->parent) {
  788. $parent = $this->parent->createView();
  789. }
  790. $view = new FormView();
  791. $view->setParent($parent);
  792. $types = (array) $this->types;
  793. foreach ($types as $type) {
  794. $type->buildView($view, $this);
  795. foreach ($type->getExtensions() as $typeExtension) {
  796. $typeExtension->buildView($view, $this);
  797. }
  798. }
  799. $childViews = array();
  800. foreach ($this->children as $key => $child) {
  801. $childViews[$key] = $child->createView($view);
  802. }
  803. $view->setChildren($childViews);
  804. foreach ($types as $type) {
  805. $type->buildViewBottomUp($view, $this);
  806. foreach ($type->getExtensions() as $typeExtension) {
  807. $typeExtension->buildViewBottomUp($view, $this);
  808. }
  809. }
  810. return $view;
  811. }
  812. /**
  813. * Normalizes the value if a normalization transformer is set.
  814. *
  815. * @param mixed $value The value to transform
  816. *
  817. * @return string
  818. */
  819. private function appToNorm($value)
  820. {
  821. foreach ($this->normTransformers as $transformer) {
  822. $value = $transformer->transform($value);
  823. }
  824. return $value;
  825. }
  826. /**
  827. * Reverse transforms a value if a normalization transformer is set.
  828. *
  829. * @param string $value The value to reverse transform
  830. *
  831. * @return mixed
  832. */
  833. private function normToApp($value)
  834. {
  835. for ($i = count($this->normTransformers) - 1; $i >= 0; --$i) {
  836. $value = $this->normTransformers[$i]->reverseTransform($value);
  837. }
  838. return $value;
  839. }
  840. /**
  841. * Transforms the value if a value transformer is set.
  842. *
  843. * @param mixed $value The value to transform
  844. *
  845. * @return string
  846. */
  847. private function normToClient($value)
  848. {
  849. if (!$this->clientTransformers) {
  850. // Scalar values should always be converted to strings to
  851. // facilitate differentiation between empty ("") and zero (0).
  852. return null === $value || is_scalar($value) ? (string) $value : $value;
  853. }
  854. foreach ($this->clientTransformers as $transformer) {
  855. $value = $transformer->transform($value);
  856. }
  857. return $value;
  858. }
  859. /**
  860. * Reverse transforms a value if a value transformer is set.
  861. *
  862. * @param string $value The value to reverse transform
  863. *
  864. * @return mixed
  865. */
  866. private function clientToNorm($value)
  867. {
  868. if (!$this->clientTransformers) {
  869. return '' === $value ? null : $value;
  870. }
  871. for ($i = count($this->clientTransformers) - 1; $i >= 0; --$i) {
  872. $value = $this->clientTransformers[$i]->reverseTransform($value);
  873. }
  874. return $value;
  875. }
  876. }