Field.php 16 KB

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