Field.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604
  1. <?php
  2. namespace Symfony\Component\Form;
  3. /*
  4. * This file is part of the Symfony package.
  5. *
  6. * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
  7. *
  8. * For the full copyright and license information, please view the LICENSE
  9. * file that was distributed with this source code.
  10. */
  11. use Symfony\Component\Form\ValueTransformer\ValueTransformerInterface;
  12. use Symfony\Component\Form\ValueTransformer\TransformationFailedException;
  13. use Symfony\Component\Form\Renderer\RendererInterface;
  14. use Symfony\Component\Form\Renderer\Plugin\PluginInterface;
  15. use Symfony\Component\Form\Filter\FilterChainInterface;
  16. use Symfony\Component\Form\Filter\FilterChain;
  17. use Symfony\Component\Form\Filter\FilterInterface;
  18. /**
  19. * Base class for form fields
  20. *
  21. * To implement your own form fields, you need to have a thorough understanding
  22. * of the data flow within a form field. A form field stores its data in three
  23. * different representations:
  24. *
  25. * (1) the format required by the form's object
  26. * (2) a normalized format for internal processing
  27. * (3) the format used for display
  28. *
  29. * A date field, for example, may store a date as "Y-m-d" string (1) in the
  30. * object. To facilitate processing in the field, this value is normalized
  31. * to a DateTime object (2). In the HTML representation of your form, a
  32. * localized string (3) is presented to and modified by the user.
  33. *
  34. * In most cases, format (1) and format (2) will be the same. For example,
  35. * a checkbox field uses a Boolean value both for internal processing as for
  36. * storage in the object. In these cases you simply need to set a value
  37. * transformer to convert between formats (2) and (3). You can do this by
  38. * calling setValueTransformer() in the configure() method.
  39. *
  40. * In some cases though it makes sense to make format (1) configurable. To
  41. * demonstrate this, let's extend our above date field to store the value
  42. * either as "Y-m-d" string or as timestamp. Internally we still want to
  43. * use a DateTime object for processing. To convert the data from string/integer
  44. * to DateTime you can set a normalization transformer by calling
  45. * setNormalizationTransformer() in configure(). The normalized data is then
  46. * converted to the displayed data as described before.
  47. *
  48. * @author Bernhard Schussek <bernhard.schussek@symfony-project.com>
  49. */
  50. class Field implements FieldInterface
  51. {
  52. private $errors = array();
  53. private $key = '';
  54. private $parent;
  55. private $submitted = false;
  56. private $required;
  57. private $data;
  58. private $normalizedData;
  59. private $transformedData = '';
  60. private $normalizationTransformer;
  61. private $valueTransformer;
  62. private $propertyPath;
  63. private $transformationSuccessful = true;
  64. private $renderer;
  65. private $hidden = false;
  66. private $trim = true;
  67. private $disabled = false;
  68. private $filterChain;
  69. public function __construct($key = null)
  70. {
  71. $this->key = (string)$key;
  72. // TODO should be injected instead
  73. $this->filterChain = new FilterChain(Filters::$all);
  74. }
  75. /**
  76. * @deprecated
  77. */
  78. public function prependFilter(FilterInterface $filter)
  79. {
  80. $this->filterChain->prependFilter($filter);
  81. return $this;
  82. }
  83. /**
  84. * @deprecated
  85. */
  86. public function appendFilter(FilterInterface $filter)
  87. {
  88. $this->filterChain->appendFilter($filter);
  89. return $this;
  90. }
  91. /**
  92. * Clones this field.
  93. */
  94. public function __clone()
  95. {
  96. // TODO
  97. }
  98. /**
  99. * Returns the data of the field as it is displayed to the user.
  100. *
  101. * @return string|array When the field is not submitted, the transformed
  102. * default data is returned. When the field is submitted,
  103. * the submitted data is returned.
  104. */
  105. public function getDisplayedData()
  106. {
  107. return $this->getTransformedData();
  108. }
  109. /**
  110. * Returns the data transformed by the value transformer
  111. *
  112. * @return string
  113. */
  114. public function getTransformedData()
  115. {
  116. return $this->transformedData;
  117. }
  118. /**
  119. * {@inheritDoc}
  120. */
  121. public function setPropertyPath($propertyPath)
  122. {
  123. $this->propertyPath = null === $propertyPath || '' === $propertyPath ? null : new PropertyPath($propertyPath);
  124. return $this;
  125. }
  126. /**
  127. * {@inheritDoc}
  128. */
  129. public function getPropertyPath()
  130. {
  131. return $this->propertyPath;
  132. }
  133. /**
  134. * {@inheritDoc}
  135. */
  136. public function setKey($key)
  137. {
  138. $this->key = (string)$key;
  139. }
  140. /**
  141. * {@inheritDoc}
  142. */
  143. public function getKey()
  144. {
  145. return $this->key;
  146. }
  147. /**
  148. * {@inheritDoc}
  149. */
  150. public function setRequired($required)
  151. {
  152. $this->required = $required;
  153. return $this;
  154. }
  155. /**
  156. * {@inheritDoc}
  157. */
  158. public function isRequired()
  159. {
  160. if (null === $this->parent || $this->parent->isRequired()) {
  161. return $this->required;
  162. }
  163. return false;
  164. }
  165. public function setDisabled($disabled)
  166. {
  167. $this->disabled = $disabled;
  168. return $this;
  169. }
  170. /**
  171. * {@inheritDoc}
  172. */
  173. public function isDisabled()
  174. {
  175. if (null === $this->parent || !$this->parent->isDisabled()) {
  176. return $this->disabled;
  177. }
  178. return true;
  179. }
  180. /**
  181. * {@inheritDoc}
  182. */
  183. public function isMultipart()
  184. {
  185. return false;
  186. }
  187. /**
  188. * {@inheritDoc}
  189. */
  190. public function setParent(FieldInterface $parent = null)
  191. {
  192. $this->parent = $parent;
  193. return $this;
  194. }
  195. /**
  196. * Returns the parent field.
  197. *
  198. * @return FieldInterface The parent field
  199. */
  200. public function getParent()
  201. {
  202. return $this->parent;
  203. }
  204. /**
  205. * Returns whether the field has a parent.
  206. *
  207. * @return Boolean
  208. */
  209. public function hasParent()
  210. {
  211. return null !== $this->parent;
  212. }
  213. /**
  214. * Returns the root of the form tree
  215. *
  216. * @return FieldInterface The root of the tree
  217. */
  218. public function getRoot()
  219. {
  220. return $this->parent ? $this->parent->getRoot() : $this;
  221. }
  222. /**
  223. * Returns whether the field is the root of the form tree
  224. *
  225. * @return Boolean
  226. */
  227. public function isRoot()
  228. {
  229. return !$this->hasParent();
  230. }
  231. /**
  232. * Updates the field with default data
  233. *
  234. * @see FieldInterface
  235. */
  236. public function setData($appData)
  237. {
  238. // Hook to change content of the data
  239. $appData = $this->filterChain->filter(Filters::filterSetData, $appData);
  240. // Treat data as strings unless a value transformer exists
  241. if (null === $this->valueTransformer && is_scalar($appData)) {
  242. $appData = (string)$appData;
  243. }
  244. // Synchronize representations - must not change the content!
  245. $normData = $this->normalize($appData);
  246. $clientData = $this->transform($normData);
  247. $this->data = $appData;
  248. $this->normalizedData = $normData;
  249. $this->transformedData = $clientData;
  250. return $this;
  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($clientData)
  258. {
  259. $this->submitted = true;
  260. $this->errors = array();
  261. if (is_scalar($clientData) || null === $clientData) {
  262. $clientData = (string)$clientData;
  263. }
  264. if (is_string($clientData) && $this->trim) {
  265. $clientData = trim($clientData);
  266. }
  267. $appData = null;
  268. $normData = null;
  269. // Hook to change content of the data submitted by the browser
  270. $clientData = $this->filterChain->filter(Filters::filterBoundDataFromClient, $clientData);
  271. try {
  272. // Normalize data to unified representation
  273. $normData = $this->reverseTransform($clientData);
  274. $this->transformationSuccessful = true;
  275. } catch (TransformationFailedException $e) {
  276. $this->transformationSuccessful = false;
  277. }
  278. if ($this->transformationSuccessful) {
  279. // Hook to change content of the data in the normalized
  280. // representation
  281. $normData = $this->filterChain->filter(Filters::filterBoundData, $normData);
  282. // Synchronize representations - must not change the content!
  283. $appData = $this->denormalize($normData);
  284. $clientData = $this->transform($normData);
  285. }
  286. $this->data = $appData;
  287. $this->normalizedData = $normData;
  288. $this->transformedData = $clientData;
  289. }
  290. /**
  291. * Returns the data in the format needed for the underlying object.
  292. *
  293. * @return mixed
  294. */
  295. public function getData()
  296. {
  297. return $this->data;
  298. }
  299. /**
  300. * Returns the normalized data of the field.
  301. *
  302. * @return mixed When the field is not submitted, the default data is returned.
  303. * When the field is submitted, the normalized submitted data is
  304. * returned if the field is valid, null otherwise.
  305. */
  306. public function getNormalizedData()
  307. {
  308. return $this->normalizedData;
  309. }
  310. /**
  311. * Adds an error to the field.
  312. *
  313. * @see FieldInterface
  314. */
  315. public function addError(Error $error, PropertyPathIterator $pathIterator = null)
  316. {
  317. $this->errors[] = $error;
  318. }
  319. /**
  320. * Returns whether the field is submitted.
  321. *
  322. * @return Boolean true if the form is submitted to input values, false otherwise
  323. */
  324. public function isSubmitted()
  325. {
  326. return $this->submitted;
  327. }
  328. /**
  329. * Returns whether the submitted value could be reverse transformed correctly
  330. *
  331. * @return Boolean
  332. */
  333. public function isTransformationSuccessful()
  334. {
  335. return $this->transformationSuccessful;
  336. }
  337. /**
  338. * Returns whether the field is valid.
  339. *
  340. * @return Boolean
  341. */
  342. public function isValid()
  343. {
  344. return $this->isSubmitted() && !$this->hasErrors(); // TESTME
  345. }
  346. /**
  347. * Returns whether or not there are errors.
  348. *
  349. * @return Boolean true if form is submitted and not valid
  350. */
  351. public function hasErrors()
  352. {
  353. // Don't call isValid() here, as its semantics are slightly different
  354. // Field groups are not valid if their children are invalid, but
  355. // hasErrors() returns only true if a field/field group itself has
  356. // errors
  357. return count($this->errors) > 0;
  358. }
  359. /**
  360. * Returns all errors
  361. *
  362. * @return array An array of FieldError instances that occurred during submitting
  363. */
  364. public function getErrors()
  365. {
  366. return $this->errors;
  367. }
  368. /**
  369. * Sets the ValueTransformer.
  370. *
  371. * @param ValueTransformerInterface $valueTransformer
  372. */
  373. public function setNormalizationTransformer(ValueTransformerInterface $normalizationTransformer = null)
  374. {
  375. $this->normalizationTransformer = $normalizationTransformer;
  376. return $this;
  377. }
  378. /**
  379. * Returns the ValueTransformer.
  380. *
  381. * @return ValueTransformerInterface
  382. */
  383. public function getNormalizationTransformer()
  384. {
  385. return $this->normalizationTransformer;
  386. }
  387. /**
  388. * Sets the ValueTransformer.
  389. *
  390. * @param ValueTransformerInterface $valueTransformer
  391. */
  392. public function setValueTransformer(ValueTransformerInterface $valueTransformer = null)
  393. {
  394. $this->valueTransformer = $valueTransformer;
  395. return $this;
  396. }
  397. /**
  398. * Returns the ValueTransformer.
  399. *
  400. * @return ValueTransformerInterface
  401. */
  402. public function getValueTransformer()
  403. {
  404. return $this->valueTransformer;
  405. }
  406. public function setTrim($trim)
  407. {
  408. $this->trim = $trim;
  409. return $this;
  410. }
  411. public function getTrim()
  412. {
  413. return $this->trim;
  414. }
  415. /**
  416. * Sets the renderer
  417. *
  418. * @param RendererInterface $renderer
  419. */
  420. public function setRenderer(RendererInterface $renderer)
  421. {
  422. $this->renderer = $renderer;
  423. return $this;
  424. }
  425. /**
  426. * Returns the renderer
  427. *
  428. * @return RendererInterface
  429. */
  430. public function getRenderer()
  431. {
  432. return $this->renderer;
  433. }
  434. public function addRendererPlugin(PluginInterface $plugin)
  435. {
  436. $this->renderer->addPlugin($plugin);
  437. return $this;
  438. }
  439. public function setRendererVar($name, $value)
  440. {
  441. $this->renderer->setVar($name, $value);
  442. return $this;
  443. }
  444. /**
  445. * Normalizes the value if a normalization transformer is set
  446. *
  447. * @param mixed $value The value to transform
  448. * @return string
  449. */
  450. protected function normalize($value)
  451. {
  452. if (null === $this->normalizationTransformer) {
  453. return $value;
  454. }
  455. return $this->normalizationTransformer->transform($value);
  456. }
  457. /**
  458. * Reverse transforms a value if a normalization transformer is set.
  459. *
  460. * @param string $value The value to reverse transform
  461. * @return mixed
  462. */
  463. protected function denormalize($value)
  464. {
  465. if (null === $this->normalizationTransformer) {
  466. return $value;
  467. }
  468. return $this->normalizationTransformer->reverseTransform($value, $this->data);
  469. }
  470. /**
  471. * Transforms the value if a value transformer is set.
  472. *
  473. * @param mixed $value The value to transform
  474. * @return string
  475. */
  476. protected function transform($value)
  477. {
  478. if (null === $this->valueTransformer) {
  479. // Scalar values should always be converted to strings to
  480. // facilitate differentiation between empty ("") and zero (0).
  481. return null === $value || is_scalar($value) ? (string)$value : $value;
  482. }
  483. return $this->valueTransformer->transform($value);
  484. }
  485. /**
  486. * Reverse transforms a value if a value transformer is set.
  487. *
  488. * @param string $value The value to reverse transform
  489. * @return mixed
  490. */
  491. protected function reverseTransform($value)
  492. {
  493. if (null === $this->valueTransformer) {
  494. return '' === $value ? null : $value;
  495. }
  496. return $this->valueTransformer->reverseTransform($value, $this->data);
  497. }
  498. /**
  499. * {@inheritDoc}
  500. */
  501. public function readProperty(&$objectOrArray)
  502. {
  503. // TODO throw exception if not object or array
  504. if ($this->propertyPath !== null) {
  505. $this->setData($this->propertyPath->getValue($objectOrArray));
  506. }
  507. }
  508. /**
  509. * {@inheritDoc}
  510. */
  511. public function writeProperty(&$objectOrArray)
  512. {
  513. // TODO throw exception if not object or array
  514. if ($this->propertyPath !== null) {
  515. $this->propertyPath->setValue($objectOrArray, $this->getData());
  516. }
  517. }
  518. /**
  519. * {@inheritDoc}
  520. */
  521. public function isEmpty()
  522. {
  523. return null === $this->data || '' === $this->data;
  524. }
  525. }