Form.php 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278
  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\Form\Exception\FormException;
  15. use Symfony\Component\Form\CsrfProvider\CsrfProviderInterface;
  16. /**
  17. * Form represents a form.
  18. *
  19. * A form is composed of a validator schema and a widget form schema.
  20. *
  21. * Form also takes care of CSRF protection by default.
  22. *
  23. * A CSRF secret can be any random string. If set to false, it disables the
  24. * CSRF protection, and if set to null, it forces the form to use the global
  25. * CSRF secret. If the global CSRF secret is also null, then a random one
  26. * is generated on the fly.
  27. *
  28. * @author Fabien Potencier <fabien.potencier@symfony-project.com>
  29. * @author Bernhard Schussek <bernhard.schussek@symfony-project.com>
  30. */
  31. class Form extends FieldGroup
  32. {
  33. /**
  34. * The validator to validate form values
  35. * @var ValidatorInterface
  36. */
  37. protected $validator = null;
  38. /**
  39. * Constructor.
  40. *
  41. * @param string $name
  42. * @param array|object $data
  43. * @param ValidatorInterface $validator
  44. * @param array $options
  45. */
  46. public function __construct($name = null, $data = null, ValidatorInterface $validator = null, array $options = array())
  47. {
  48. $this->validator = $validator;
  49. // Prefill the form with the given data
  50. if (null !== $data) {
  51. $this->setData($data);
  52. }
  53. $this->addOption('csrf_field_name', '_token');
  54. $this->addOption('csrf_provider');
  55. $this->addOption('field_factory');
  56. $this->addOption('validation_groups');
  57. if (isset($options['validation_groups'])) {
  58. $options['validation_groups'] = (array)$options['validation_groups'];
  59. }
  60. parent::__construct($name, $options);
  61. // If data is passed to this constructor, objects from parent forms
  62. // should be ignored
  63. if (null !== $data) {
  64. $this->setPropertyPath(null);
  65. }
  66. // Enable CSRF protection, if necessary
  67. if ($this->getOption('csrf_provider')) {
  68. if (!$this->getOption('csrf_provider') instanceof CsrfProviderInterface) {
  69. throw new FormException('The object passed to the "csrf_provider" option must implement CsrfProviderInterface');
  70. }
  71. $token = $this->getOption('csrf_provider')->generateCsrfToken(get_class($this));
  72. $field = new HiddenField($this->getOption('csrf_field_name'), array(
  73. 'property_path' => null,
  74. ));
  75. $field->setData($token);
  76. $this->add($field);
  77. }
  78. }
  79. /**
  80. * Returns a factory for automatically creating fields based on metadata
  81. * available for a form's object
  82. *
  83. * @return FieldFactoryInterface The factory
  84. */
  85. public function getFieldFactory()
  86. {
  87. return $this->getOption('field_factory');
  88. }
  89. /**
  90. * Returns the validator used by the form
  91. *
  92. * @return ValidatorInterface The validator instance
  93. */
  94. public function getValidator()
  95. {
  96. return $this->validator;
  97. }
  98. /**
  99. * Returns the validation groups validated by the form
  100. *
  101. * @return array A list of validation groups or null
  102. */
  103. public function getValidationGroups()
  104. {
  105. return $this->getOption('validation_groups');
  106. }
  107. /**
  108. * Returns the name used for the CSRF protection field
  109. *
  110. * @return string The field name
  111. */
  112. public function getCsrfFieldName()
  113. {
  114. return $this->getOption('csrf_field_name');
  115. }
  116. /**
  117. * Returns the provider used for generating and validating CSRF tokens
  118. *
  119. * @return CsrfProviderInterface The provider instance
  120. */
  121. public function getCsrfProvider()
  122. {
  123. return $this->getOption('csrf_provider');
  124. }
  125. /**
  126. * Binds the form with submitted data from a Request object
  127. *
  128. * @param Request $request The request object
  129. * @see bind()
  130. */
  131. public function bindRequest(Request $request)
  132. {
  133. $values = $request->request->get($this->getName());
  134. $files = $request->files->get($this->getName());
  135. $this->bind(self::deepArrayUnion($values, $files));
  136. }
  137. /**
  138. * Binds the form with submitted data from the PHP globals $_POST and
  139. * $_FILES
  140. *
  141. * @see bind()
  142. */
  143. public function bindGlobals()
  144. {
  145. $values = $_POST[$this->getName()];
  146. // fix files array and convert to UploadedFile instances
  147. $fileBag = new FileBag($_FILES);
  148. $files = $fileBag->get($this->getName());
  149. $this->bind(self::deepArrayUnion($values, $files));
  150. }
  151. /**
  152. * {@inheritDoc}
  153. */
  154. public function bind($values)
  155. {
  156. parent::bind($values);
  157. if ($this->getParent() === null) {
  158. if ($this->validator === null) {
  159. throw new FormException('A validator is required for binding. Forgot to pass it to the constructor of the form?');
  160. }
  161. if ($violations = $this->validator->validate($this, $this->getOption('validation_groups'))) {
  162. // TODO: test me
  163. foreach ($violations as $violation) {
  164. $propertyPath = new PropertyPath($violation->getPropertyPath());
  165. $iterator = $propertyPath->getIterator();
  166. if ($iterator->current() == 'data') {
  167. $type = self::DATA_ERROR;
  168. $iterator->next(); // point at the first data element
  169. } else {
  170. $type = self::FIELD_ERROR;
  171. }
  172. $this->addError(new FieldError($violation->getMessageTemplate(), $violation->getMessageParameters()), $iterator, $type);
  173. }
  174. }
  175. }
  176. }
  177. /**
  178. * @return true if this form is CSRF protected
  179. */
  180. public function isCsrfProtected()
  181. {
  182. return $this->has($this->getOption('csrf_field_name'));
  183. }
  184. /**
  185. * Returns whether the CSRF token is valid
  186. *
  187. * @return Boolean
  188. */
  189. public function isCsrfTokenValid()
  190. {
  191. if (!$this->isCsrfProtected()) {
  192. return true;
  193. } else {
  194. $token = $this->get($this->getOption('csrf_field_name'))->getDisplayedData();
  195. return $this->getOption('csrf_provider')->isCsrfTokenValid(get_class($this), $token);
  196. }
  197. }
  198. /**
  199. * Returns whether the maximum POST size was reached in this request.
  200. *
  201. * @return Boolean
  202. */
  203. public function isPostMaxSizeReached()
  204. {
  205. if (isset($_SERVER['CONTENT_LENGTH'])) {
  206. $length = (int) $_SERVER['CONTENT_LENGTH'];
  207. $max = trim(ini_get('post_max_size'));
  208. switch (strtolower(substr($max, -1))) {
  209. // The 'G' modifier is available since PHP 5.1.0
  210. case 'g':
  211. $max *= 1024;
  212. case 'm':
  213. $max *= 1024;
  214. case 'k':
  215. $max *= 1024;
  216. }
  217. return $length > $max;
  218. } else {
  219. return false;
  220. }
  221. }
  222. /**
  223. * Merges two arrays without reindexing numeric keys.
  224. *
  225. * @param array $array1 An array to merge
  226. * @param array $array2 An array to merge
  227. *
  228. * @return array The merged array
  229. */
  230. static protected function deepArrayUnion($array1, $array2)
  231. {
  232. foreach ($array2 as $key => $value) {
  233. if (is_array($value) && isset($array1[$key]) && is_array($array1[$key])) {
  234. $array1[$key] = self::deepArrayUnion($array1[$key], $value);
  235. } else {
  236. $array1[$key] = $value;
  237. }
  238. }
  239. return $array1;
  240. }
  241. }