Form.php 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968
  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\HttpFoundation\Request;
  12. use Symfony\Component\HttpFoundation\FileBag;
  13. use Symfony\Component\Validator\ValidatorInterface;
  14. use Symfony\Component\Validator\ExecutionContext;
  15. use Symfony\Component\Form\Exception\FormException;
  16. use Symfony\Component\Form\Exception\MissingOptionsException;
  17. use Symfony\Component\Form\Exception\AlreadySubmittedException;
  18. use Symfony\Component\Form\Exception\UnexpectedTypeException;
  19. use Symfony\Component\Form\Exception\DanglingFieldException;
  20. use Symfony\Component\Form\Exception\FieldDefinitionException;
  21. use Symfony\Component\Form\CsrfProvider\CsrfProviderInterface;
  22. use Symfony\Component\Form\DataProcessor\DataProcessorInterface;
  23. use Symfony\Component\Form\FieldFactory\FieldFactoryInterface;
  24. /**
  25. * Form represents a form.
  26. *
  27. * A form is composed of a validator schema and a widget form schema.
  28. *
  29. * Form also takes care of CSRF protection by default.
  30. *
  31. * A CSRF secret can be any random string. If set to false, it disables the
  32. * CSRF protection, and if set to null, it forces the form to use the global
  33. * CSRF secret. If the global CSRF secret is also null, then a random one
  34. * is generated on the fly.
  35. *
  36. * @author Fabien Potencier <fabien.potencier@symfony-project.com>
  37. * @author Bernhard Schussek <bernhard.schussek@symfony-project.com>
  38. */
  39. class Form extends Field implements \IteratorAggregate, FormInterface
  40. {
  41. /**
  42. * Contains all the fields of this group
  43. * @var array
  44. */
  45. private $fields = array();
  46. /**
  47. * Contains the names of submitted values who don't belong to any fields
  48. * @var array
  49. */
  50. private $extraFields = array();
  51. /**
  52. * Stores the class that the data of this form must be instances of
  53. * @var string
  54. */
  55. private $dataClass;
  56. /**
  57. * Stores the constructor closure for creating new domain object instances
  58. * @var \Closure
  59. */
  60. private $dataConstructor;
  61. private $dataPreprocessor;
  62. private $modifyByReference = true;
  63. private $validator;
  64. private $validationGroups;
  65. private $virtual;
  66. private $csrfFieldName;
  67. private $csrfProvider;
  68. /**
  69. * Clones this group
  70. */
  71. public function __clone()
  72. {
  73. foreach ($this->fields as $name => $field) {
  74. $field = clone $field;
  75. // this condition is only to "bypass" a PHPUnit bug with mocks
  76. if (null !== $field->getParent()) {
  77. $field->setParent($this);
  78. }
  79. $this->fields[$name] = $field;
  80. }
  81. }
  82. /**
  83. * Adds a new field to this group. A field must have a unique name within
  84. * the group. Otherwise the existing field is overwritten.
  85. *
  86. * If you add a nested group, this group should also be represented in the
  87. * object hierarchy. If you want to add a group that operates on the same
  88. * hierarchy level, use merge().
  89. *
  90. * <code>
  91. * class Entity
  92. * {
  93. * public $location;
  94. * }
  95. *
  96. * class Location
  97. * {
  98. * public $longitude;
  99. * public $latitude;
  100. * }
  101. *
  102. * $entity = new Entity();
  103. * $entity->location = new Location();
  104. *
  105. * $form = new Form('entity', $entity, $validator);
  106. *
  107. * $locationGroup = new Form('location');
  108. * $locationGroup->add(new TextField('longitude'));
  109. * $locationGroup->add(new TextField('latitude'));
  110. *
  111. * $form->add($locationGroup);
  112. * </code>
  113. *
  114. * @param FieldInterface|string $field
  115. * @return FieldInterface
  116. */
  117. public function add($field)
  118. {
  119. if ($this->isSubmitted()) {
  120. throw new AlreadySubmittedException('You cannot add fields after submitting a form');
  121. }
  122. // if the field is given as string, ask the field factory of the form
  123. // to create a field
  124. if (!$field instanceof FieldInterface) {
  125. if (!is_string($field)) {
  126. throw new UnexpectedTypeException($field, 'FieldInterface or string');
  127. }
  128. $factory = $this->getFieldFactory();
  129. if (!$factory) {
  130. throw new FormException('A field factory must be set to automatically create fields');
  131. }
  132. $class = $this->getDataClass();
  133. if (!$class) {
  134. throw new FormException('The data class must be set to automatically create fields');
  135. }
  136. $options = func_num_args() > 1 ? func_get_arg(1) : array();
  137. $field = $factory->getInstance($class, $field, $options);
  138. // TODO throw exception if nothing was returned
  139. }
  140. if ('' === $field->getKey() || null === $field->getKey()) {
  141. throw new FieldDefinitionException('You cannot add anonymous fields');
  142. }
  143. $this->fields[$field->getKey()] = $field;
  144. $field->setParent($this);
  145. $data = $this->getTransformedData();
  146. // if the property "data" is NULL, getTransformedData() returns an empty
  147. // string
  148. if (!empty($data)) {
  149. $field->readProperty($data);
  150. }
  151. return $this;
  152. }
  153. /**
  154. * Removes the field with the given key.
  155. *
  156. * @param string $key
  157. */
  158. public function remove($key)
  159. {
  160. $this->fields[$key]->setParent(null);
  161. unset($this->fields[$key]);
  162. }
  163. /**
  164. * Returns whether a field with the given key exists.
  165. *
  166. * @param string $key
  167. * @return Boolean
  168. */
  169. public function has($key)
  170. {
  171. return isset($this->fields[$key]);
  172. }
  173. /**
  174. * Returns the field with the given key.
  175. *
  176. * @param string $key
  177. * @return FieldInterface
  178. */
  179. public function get($key)
  180. {
  181. if (isset($this->fields[$key])) {
  182. return $this->fields[$key];
  183. }
  184. throw new \InvalidArgumentException(sprintf('Field "%s" does not exist.', $key));
  185. }
  186. /**
  187. * Returns all fields in this group
  188. *
  189. * @return array
  190. */
  191. public function getFields()
  192. {
  193. return $this->fields;
  194. }
  195. /**
  196. * Initializes the field group with an object to operate on
  197. *
  198. * @see FieldInterface
  199. */
  200. public function setData($data)
  201. {
  202. parent::setData($data);
  203. // get transformed data and pass its values to child fields
  204. $data = $this->getTransformedData();
  205. if (!empty($data) && !is_array($data) && !is_object($data)) {
  206. throw new \InvalidArgumentException(sprintf('Expected argument of type object or array, %s given', gettype($data)));
  207. }
  208. if (!empty($data)) {
  209. if ($this->dataClass && !$data instanceof $this->dataClass) {
  210. throw new FormException(sprintf('Form data should be instance of %s', $this->dataClass));
  211. }
  212. $this->readObject($data);
  213. }
  214. return $this;
  215. }
  216. /**
  217. * {@inheritDoc}
  218. */
  219. protected function transform($value)
  220. {
  221. if (null === $this->getValueTransformer()) {
  222. // Empty values must be converted to objects or arrays so that
  223. // they can be read by PropertyPath in the child fields
  224. if (empty($value)) {
  225. if ($this->dataConstructor) {
  226. $constructor = $this->dataConstructor;
  227. return $constructor();
  228. } else if ($this->dataClass) {
  229. $class = $this->dataClass;
  230. return new $class();
  231. } else {
  232. return array();
  233. }
  234. }
  235. }
  236. return parent::transform($value);
  237. }
  238. /**
  239. * Returns the data of the field as it is displayed to the user.
  240. *
  241. * @see FieldInterface
  242. * @return array of field name => value
  243. */
  244. public function getDisplayedData()
  245. {
  246. $values = array();
  247. foreach ($this->fields as $key => $field) {
  248. $values[$key] = $field->getDisplayedData();
  249. }
  250. return $values;
  251. }
  252. /**
  253. * Binds POST data to the field, transforms and validates it.
  254. *
  255. * @param string|array $data The POST data
  256. */
  257. public function submit($data)
  258. {
  259. if (null === $data) {
  260. $data = array();
  261. }
  262. // might return an array, if $data isn't one already
  263. $data = $this->preprocessData($data);
  264. // remember for later
  265. $submittedData = $data;
  266. if (!is_array($data)) {
  267. throw new UnexpectedTypeException($data, 'array');
  268. }
  269. foreach ($this->fields as $key => $field) {
  270. if (!isset($data[$key])) {
  271. $data[$key] = null;
  272. }
  273. }
  274. foreach ($data as $key => $value) {
  275. if ($this->has($key)) {
  276. $this->fields[$key]->submit($value);
  277. }
  278. }
  279. $data = $this->getTransformedData();
  280. $this->writeObject($data);
  281. // set and reverse transform the data
  282. parent::submit($data);
  283. $this->extraFields = array();
  284. foreach ($submittedData as $key => $value) {
  285. if (!$this->has($key)) {
  286. $this->extraFields[] = $key;
  287. }
  288. }
  289. }
  290. /**
  291. * Updates the child fields from the properties of the given data
  292. *
  293. * This method calls readProperty() on all child fields that have a
  294. * property path set. If a child field has no property path set but
  295. * implements FormInterface, writeProperty() is called on its
  296. * children instead.
  297. *
  298. * @param array|object $objectOrArray
  299. */
  300. protected function readObject(&$objectOrArray)
  301. {
  302. $iterator = new RecursiveFieldIterator($this);
  303. $iterator = new \RecursiveIteratorIterator($iterator);
  304. foreach ($iterator as $field) {
  305. $field->readProperty($objectOrArray);
  306. }
  307. }
  308. /**
  309. * Updates all properties of the given data from the child fields
  310. *
  311. * This method calls writeProperty() on all child fields that have a property
  312. * path set. If a child field has no property path set but implements
  313. * FormInterface, writeProperty() is called on its children instead.
  314. *
  315. * @param array|object $objectOrArray
  316. */
  317. protected function writeObject(&$objectOrArray)
  318. {
  319. $iterator = new RecursiveFieldIterator($this);
  320. $iterator = new \RecursiveIteratorIterator($iterator);
  321. foreach ($iterator as $field) {
  322. $field->writeProperty($objectOrArray);
  323. }
  324. }
  325. /**
  326. * Processes the submitted data before it is passed to the individual fields
  327. *
  328. * The data is in the user format.
  329. *
  330. * @param array $data
  331. * @return array
  332. */
  333. protected function preprocessData($data)
  334. {
  335. if ($this->dataPreprocessor) {
  336. return $this->dataPreprocessor->processData($data);
  337. }
  338. return $data;
  339. }
  340. public function setVirtual($virtual)
  341. {
  342. $this->virtual = $virtual;
  343. return $this;
  344. }
  345. /**
  346. * @inheritDoc
  347. */
  348. public function isVirtual()
  349. {
  350. return $this->virtual;
  351. }
  352. /**
  353. * Returns whether this form was submitted with extra fields
  354. *
  355. * @return Boolean
  356. */
  357. public function isSubmittedWithExtraFields()
  358. {
  359. // TODO: integrate the field names in the error message
  360. return count($this->extraFields) > 0;
  361. }
  362. /**
  363. * Returns whether the field is valid.
  364. *
  365. * @return Boolean
  366. */
  367. public function isValid()
  368. {
  369. if (!parent::isValid()) {
  370. return false;
  371. }
  372. foreach ($this->fields as $field) {
  373. if (!$field->isValid()) {
  374. return false;
  375. }
  376. }
  377. return true;
  378. }
  379. /**
  380. * {@inheritDoc}
  381. */
  382. public function addError(Error $error, PropertyPathIterator $pathIterator = null)
  383. {
  384. if (null !== $pathIterator) {
  385. if ($error instanceof FieldError && $pathIterator->hasNext()) {
  386. $pathIterator->next();
  387. if ($pathIterator->isProperty() && $pathIterator->current() === 'fields') {
  388. $pathIterator->next();
  389. }
  390. if ($this->has($pathIterator->current())) {
  391. $this->get($pathIterator->current())->addError($error, $pathIterator);
  392. return;
  393. }
  394. } else if ($error instanceof DataError) {
  395. $iterator = new RecursiveFieldIterator($this);
  396. $iterator = new \RecursiveIteratorIterator($iterator);
  397. foreach ($iterator as $field) {
  398. if (null !== ($fieldPath = $field->getPropertyPath())) {
  399. if ($fieldPath->getElement(0) === $pathIterator->current()) {
  400. if ($pathIterator->hasNext()) {
  401. $pathIterator->next();
  402. }
  403. $field->addError($error, $pathIterator);
  404. return;
  405. }
  406. }
  407. }
  408. }
  409. }
  410. parent::addError($error);
  411. }
  412. /**
  413. * Returns whether the field requires a multipart form.
  414. *
  415. * @return Boolean
  416. */
  417. public function isMultipart()
  418. {
  419. foreach ($this->fields as $field) {
  420. if ($field->isMultipart()) {
  421. return true;
  422. }
  423. }
  424. return false;
  425. }
  426. /**
  427. * Returns true if the field exists (implements the \ArrayAccess interface).
  428. *
  429. * @param string $key The key of the field
  430. *
  431. * @return Boolean true if the widget exists, false otherwise
  432. */
  433. public function offsetExists($key)
  434. {
  435. return $this->has($key);
  436. }
  437. /**
  438. * Returns the form field associated with the name (implements the \ArrayAccess interface).
  439. *
  440. * @param string $key The offset of the value to get
  441. *
  442. * @return Field A form field instance
  443. */
  444. public function offsetGet($key)
  445. {
  446. return $this->get($key);
  447. }
  448. /**
  449. * Throws an exception saying that values cannot be set (implements the \ArrayAccess interface).
  450. *
  451. * @param string $offset (ignored)
  452. * @param string $value (ignored)
  453. *
  454. * @throws \LogicException
  455. */
  456. public function offsetSet($key, $field)
  457. {
  458. throw new \LogicException('Use the method add() to add fields');
  459. }
  460. /**
  461. * Throws an exception saying that values cannot be unset (implements the \ArrayAccess interface).
  462. *
  463. * @param string $key
  464. *
  465. * @throws \LogicException
  466. */
  467. public function offsetUnset($key)
  468. {
  469. return $this->remove($key);
  470. }
  471. /**
  472. * Returns the iterator for this group.
  473. *
  474. * @return \ArrayIterator
  475. */
  476. public function getIterator()
  477. {
  478. return new \ArrayIterator($this->fields);
  479. }
  480. /**
  481. * Returns the number of form fields (implements the \Countable interface).
  482. *
  483. * @return integer The number of embedded form fields
  484. */
  485. public function count()
  486. {
  487. return count($this->fields);
  488. }
  489. public function setValidator(ValidatorInterface $validator)
  490. {
  491. $this->validator = $validator;
  492. return $this;
  493. }
  494. /**
  495. * Returns the validator used by the form
  496. *
  497. * @return ValidatorInterface The validator instance
  498. */
  499. public function getValidator()
  500. {
  501. return $this->validator;
  502. }
  503. public function setValidationGroups($validationGroups)
  504. {
  505. $this->validationGroups = empty($validationGroups) ? null : (array)$validationGroups;
  506. return $this;
  507. }
  508. /**
  509. * Returns the validation groups validated by the form
  510. *
  511. * @return array A list of validation groups or null
  512. */
  513. public function getValidationGroups()
  514. {
  515. $groups = $this->validationGroups;
  516. if (!$groups && $this->hasParent()) {
  517. $groups = $this->getParent()->getValidationGroups();
  518. }
  519. return $groups;
  520. }
  521. public function enableCsrfProtection(CsrfProviderInterface $provider, $fieldName = '_token')
  522. {
  523. $this->csrfProvider = $provider;
  524. $this->csrfFieldName = $fieldName;
  525. $token = $provider->generateCsrfToken(get_class($this));
  526. // FIXME
  527. // $this->add(new HiddenField($fieldName, array('data' => $token)));
  528. }
  529. public function disableCsrfProtection()
  530. {
  531. if ($this->isCsrfProtected()) {
  532. $this->remove($this->csrfFieldName);
  533. $this->csrfProvider = null;
  534. $this->csrfFieldName = null;
  535. }
  536. }
  537. /**
  538. * Returns the name used for the CSRF protection field
  539. *
  540. * @return string The field name
  541. */
  542. public function getCsrfFieldName()
  543. {
  544. return $this->csrfFieldName;
  545. }
  546. /**
  547. * Returns the provider used for generating and validating CSRF tokens
  548. *
  549. * @return CsrfProviderInterface The provider instance
  550. */
  551. public function getCsrfProvider()
  552. {
  553. return $this->csrfProvider;
  554. }
  555. /**
  556. * @return true if this form is CSRF protected
  557. */
  558. public function isCsrfProtected()
  559. {
  560. return $this->csrfFieldName && $this->has($this->csrfFieldName);
  561. }
  562. /**
  563. * Returns whether the CSRF token is valid
  564. *
  565. * @return Boolean
  566. */
  567. public function isCsrfTokenValid()
  568. {
  569. if (!$this->isCsrfProtected()) {
  570. return true;
  571. } else {
  572. $token = $this->get($this->csrfFieldName)->getDisplayedData();
  573. return $this->csrfProvider->isCsrfTokenValid(get_class($this), $token);
  574. }
  575. }
  576. /**
  577. * Binds a request to the form
  578. *
  579. * If the request was a POST request, the data is submitted to the form,
  580. * transformed and written into the form data (an object or an array).
  581. * You can set the form data by passing it in the second parameter
  582. * of this method or by passing it in the "data" option of the form's
  583. * constructor.
  584. *
  585. * @param Request $request The request to bind to the form
  586. * @param array|object $data The data from which to read default values
  587. * and where to write submitted values
  588. */
  589. public function bind(Request $request, $data = null)
  590. {
  591. if (!$this->getKey()) {
  592. throw new FormException('You cannot bind anonymous forms. Please give this form a name');
  593. }
  594. // Store object from which to read the default values and where to
  595. // write the submitted values
  596. if (null !== $data) {
  597. $this->setData($data);
  598. }
  599. // Store the submitted data in case of a post request
  600. if ('POST' == $request->getMethod()) {
  601. $values = $request->request->get($this->getName(), array());
  602. $files = $request->files->get($this->getName(), array());
  603. $this->submit(self::deepArrayUnion($values, $files));
  604. $this->validate();
  605. }
  606. }
  607. /**
  608. * @deprecated
  609. */
  610. private function getName()
  611. {
  612. return null === $this->getParent() ? $this->getKey() : $this->getParent()->getName().'['.$this->key.']';
  613. }
  614. /**
  615. * @deprecated
  616. */
  617. private function getId()
  618. {
  619. return null === $this->getParent() ? $this->getKey() : $this->getParent()->getId().'_'.$this->key;
  620. }
  621. /**
  622. * Validates the form and its domain object
  623. *
  624. * @throws FormException If the option "validator" was not set
  625. */
  626. public function validate()
  627. {
  628. if (null === $this->validator) {
  629. throw new MissingOptionsException('A validator is required for validating', array('validator'));
  630. }
  631. // Validate the form in group "Default"
  632. // Validation of the data in the custom group is done by validateData(),
  633. // which is constrained by the Execute constraint
  634. if ($violations = $this->validator->validate($this)) {
  635. foreach ($violations as $violation) {
  636. $propertyPath = new PropertyPath($violation->getPropertyPath());
  637. $iterator = $propertyPath->getIterator();
  638. $template = $violation->getMessageTemplate();
  639. $parameters = $violation->getMessageParameters();
  640. if ($iterator->current() == 'data') {
  641. $iterator->next(); // point at the first data element
  642. $error = new DataError($template, $parameters);
  643. } else {
  644. $error = new FieldError($template, $parameters);
  645. }
  646. $this->addError($error, $iterator);
  647. }
  648. }
  649. }
  650. /**
  651. * Returns whether the maximum POST size was reached in this request.
  652. *
  653. * @return Boolean
  654. */
  655. public function isPostMaxSizeReached()
  656. {
  657. if ($this->isRoot() && isset($_SERVER['CONTENT_LENGTH'])) {
  658. $length = (int) $_SERVER['CONTENT_LENGTH'];
  659. $max = trim(ini_get('post_max_size'));
  660. switch (strtolower(substr($max, -1))) {
  661. // The 'G' modifier is available since PHP 5.1.0
  662. case 'g':
  663. $max *= 1024;
  664. case 'm':
  665. $max *= 1024;
  666. case 'k':
  667. $max *= 1024;
  668. }
  669. return $length > $max;
  670. } else {
  671. return false;
  672. }
  673. }
  674. /**
  675. * Sets the class that object bound to this form must be instances of
  676. *
  677. * @param string A fully qualified class name
  678. */
  679. public function setDataClass($class)
  680. {
  681. $this->dataClass = $class;
  682. return $this;
  683. }
  684. /**
  685. * Returns the class that object must have that are bound to this form
  686. *
  687. * @return string A fully qualified class name
  688. */
  689. public function getDataClass()
  690. {
  691. return $this->dataClass;
  692. }
  693. public function setDataConstructor($dataConstructor)
  694. {
  695. $this->dataConstructor = $dataConstructor;
  696. return $this;
  697. }
  698. public function getDataConstructor()
  699. {
  700. return $this->dataConstructor;
  701. }
  702. public function setFieldFactory(FieldFactoryInterface $fieldFactory = null)
  703. {
  704. $this->fieldFactory = $fieldFactory;
  705. return $this;
  706. }
  707. /**
  708. * Returns a factory for automatically creating fields based on metadata
  709. * available for a form's object
  710. *
  711. * @return FieldFactoryInterface The factory
  712. */
  713. public function getFieldFactory()
  714. {
  715. return $this->fieldFactory;
  716. }
  717. /**
  718. * Validates the data of this form
  719. *
  720. * This method is called automatically during the validation process.
  721. *
  722. * @param ExecutionContext $context The current validation context
  723. */
  724. public function validateData(ExecutionContext $context)
  725. {
  726. if (is_object($this->getData()) || is_array($this->getData())) {
  727. $groups = $this->getValidationGroups();
  728. $propertyPath = $context->getPropertyPath();
  729. $graphWalker = $context->getGraphWalker();
  730. if (null === $groups) {
  731. $groups = array(null);
  732. }
  733. // The Execute constraint is called on class level, so we need to
  734. // set the property manually
  735. $context->setCurrentProperty('data');
  736. // Adjust the property path accordingly
  737. if (!empty($propertyPath)) {
  738. $propertyPath .= '.';
  739. }
  740. $propertyPath .= 'data';
  741. foreach ($groups as $group) {
  742. $graphWalker->walkReference($this->getData(), $group, $propertyPath, true);
  743. }
  744. }
  745. }
  746. /**
  747. * {@inheritDoc}
  748. */
  749. public function writeProperty(&$objectOrArray)
  750. {
  751. $isReference = false;
  752. // If the data is identical to the value in $objectOrArray, we are
  753. // dealing with a reference
  754. if ($this->getPropertyPath() !== null) {
  755. $isReference = $this->getData() === $this->getPropertyPath()->getValue($objectOrArray);
  756. }
  757. // Don't write into $objectOrArray if $objectOrArray is an object,
  758. // $isReference is true (see above) and the option "by_reference" is
  759. // true as well
  760. if (!is_object($objectOrArray) || !$isReference || !$this->modifyByReference) {
  761. parent::writeProperty($objectOrArray);
  762. }
  763. }
  764. /**
  765. * {@inheritDoc}
  766. */
  767. public function isEmpty()
  768. {
  769. foreach ($this->fields as $field) {
  770. if (!$field->isEmpty()) {
  771. return false;
  772. }
  773. }
  774. return true;
  775. }
  776. /**
  777. * Sets the data preprocessor
  778. *
  779. * @param DataProcessorInterface $dataPreprocessor
  780. */
  781. public function setDataPreprocessor(DataProcessorInterface $dataPreprocessor)
  782. {
  783. $this->dataPreprocessor = $dataPreprocessor;
  784. return $this;
  785. }
  786. /**
  787. * Returns the data preprocessor
  788. *
  789. * @return DataPreprocessorInterface
  790. */
  791. public function getDataPreprocessor()
  792. {
  793. return $this->dataPreprocessor;
  794. }
  795. public function setModifyByReference($modifyByReference)
  796. {
  797. $this->modifyByReference = $modifyByReference;
  798. return $this;
  799. }
  800. public function isModifiedByReference()
  801. {
  802. return $this->modifyByReference;
  803. }
  804. /**
  805. * Merges two arrays without reindexing numeric keys.
  806. *
  807. * @param array $array1 An array to merge
  808. * @param array $array2 An array to merge
  809. *
  810. * @return array The merged array
  811. */
  812. static protected function deepArrayUnion($array1, $array2)
  813. {
  814. foreach ($array2 as $key => $value) {
  815. if (is_array($value) && isset($array1[$key]) && is_array($array1[$key])) {
  816. $array1[$key] = self::deepArrayUnion($array1[$key], $value);
  817. } else {
  818. $array1[$key] = $value;
  819. }
  820. }
  821. return $array1;
  822. }
  823. }