NativeRequestHandler.php 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. <?php
  2. /*
  3. * This file is part of the Symfony package.
  4. *
  5. * (c) Fabien Potencier <fabien@symfony.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\Form\Exception\UnexpectedTypeException;
  12. use Symfony\Component\Form\FormInterface;
  13. use Symfony\Component\Form\RequestHandlerInterface;
  14. /**
  15. * A request handler using PHP's super globals $_GET, $_POST and $_SERVER.
  16. *
  17. * @author Bernhard Schussek <bschussek@gmail.com>
  18. */
  19. class NativeRequestHandler implements RequestHandlerInterface
  20. {
  21. /**
  22. * The allowed keys of the $_FILES array.
  23. *
  24. * @var array
  25. */
  26. private static $fileKeys = array(
  27. 'error',
  28. 'name',
  29. 'size',
  30. 'tmp_name',
  31. 'type',
  32. );
  33. /**
  34. * {@inheritdoc}
  35. */
  36. public function handleRequest(FormInterface $form, $request = null)
  37. {
  38. if (null !== $request) {
  39. throw new UnexpectedTypeException($request, 'null');
  40. }
  41. $name = $form->getName();
  42. $method = $form->getConfig()->getMethod();
  43. if ($method !== self::getRequestMethod()) {
  44. return;
  45. }
  46. if ('GET' === $method) {
  47. if ('' === $name) {
  48. $data = $_GET;
  49. } else {
  50. // Don't submit GET requests if the form's name does not exist
  51. // in the request
  52. if (!isset($_GET[$name])) {
  53. return;
  54. }
  55. $data = $_GET[$name];
  56. }
  57. } else {
  58. $fixedFiles = array();
  59. foreach ($_FILES as $name => $file) {
  60. $fixedFiles[$name] = self::stripEmptyFiles(self::fixPhpFilesArray($file));
  61. }
  62. if ('' === $name) {
  63. $params = $_POST;
  64. $files = $fixedFiles;
  65. } elseif (array_key_exists($name, $_POST) || array_key_exists($name, $fixedFiles)) {
  66. $default = $form->getConfig()->getCompound() ? array() : null;
  67. $params = array_key_exists($name, $_POST) ? $_POST[$name] : $default;
  68. $files = array_key_exists($name, $fixedFiles) ? $fixedFiles[$name] : $default;
  69. } else {
  70. // Don't submit the form if it is not present in the request
  71. return;
  72. }
  73. if (is_array($params) && is_array($files)) {
  74. $data = array_replace_recursive($params, $files);
  75. } else {
  76. $data = $params ?: $files;
  77. }
  78. }
  79. // Don't auto-submit the form unless at least one field is present.
  80. if ('' === $name && count(array_intersect_key($data, $form->all())) <= 0) {
  81. return;
  82. }
  83. $form->submit($data, 'PATCH' !== $method);
  84. }
  85. /**
  86. * Returns the method used to submit the request to the server.
  87. *
  88. * @return string The request method.
  89. */
  90. private static function getRequestMethod()
  91. {
  92. $method = isset($_SERVER['REQUEST_METHOD'])
  93. ? strtoupper($_SERVER['REQUEST_METHOD'])
  94. : 'GET';
  95. if ('POST' === $method && isset($_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'])) {
  96. $method = strtoupper($_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE']);
  97. }
  98. return $method;
  99. }
  100. /**
  101. * Fixes a malformed PHP $_FILES array.
  102. *
  103. * PHP has a bug that the format of the $_FILES array differs, depending on
  104. * whether the uploaded file fields had normal field names or array-like
  105. * field names ("normal" vs. "parent[child]").
  106. *
  107. * This method fixes the array to look like the "normal" $_FILES array.
  108. *
  109. * It's safe to pass an already converted array, in which case this method
  110. * just returns the original array unmodified.
  111. *
  112. * This method is identical to {@link Symfony\Component\HttpFoundation\FileBag::fixPhpFilesArray}
  113. * and should be kept as such in order to port fixes quickly and easily.
  114. *
  115. * @param array $data
  116. *
  117. * @return array
  118. */
  119. private static function fixPhpFilesArray($data)
  120. {
  121. if (!is_array($data)) {
  122. return $data;
  123. }
  124. $keys = array_keys($data);
  125. sort($keys);
  126. if (self::$fileKeys !== $keys || !isset($data['name']) || !is_array($data['name'])) {
  127. return $data;
  128. }
  129. $files = $data;
  130. foreach (self::$fileKeys as $k) {
  131. unset($files[$k]);
  132. }
  133. foreach (array_keys($data['name']) as $key) {
  134. $files[$key] = self::fixPhpFilesArray(array(
  135. 'error' => $data['error'][$key],
  136. 'name' => $data['name'][$key],
  137. 'type' => $data['type'][$key],
  138. 'tmp_name' => $data['tmp_name'][$key],
  139. 'size' => $data['size'][$key]
  140. ));
  141. }
  142. return $files;
  143. }
  144. /**
  145. * Sets empty uploaded files to NULL in the given uploaded files array.
  146. *
  147. * @param mixed $data The file upload data.
  148. *
  149. * @return array|null Returns the stripped upload data.
  150. */
  151. private static function stripEmptyFiles($data)
  152. {
  153. if (!is_array($data)) {
  154. return $data;
  155. }
  156. $keys = array_keys($data);
  157. sort($keys);
  158. if (self::$fileKeys === $keys) {
  159. if (UPLOAD_ERR_NO_FILE === $data['error']) {
  160. return null;
  161. }
  162. return $data;
  163. }
  164. foreach ($data as $key => $value) {
  165. $data[$key] = self::stripEmptyFiles($value);
  166. }
  167. return $data;
  168. }
  169. }