Field.php 15 KB

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