UploadedFile.php 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316
  1. <?php
  2. namespace GuzzleHttp\Psr7;
  3. use InvalidArgumentException;
  4. use Psr\Http\Message\StreamInterface;
  5. use Psr\Http\Message\UploadedFileInterface;
  6. use RuntimeException;
  7. class UploadedFile implements UploadedFileInterface
  8. {
  9. /**
  10. * @var int[]
  11. */
  12. private static $errors = [
  13. UPLOAD_ERR_OK,
  14. UPLOAD_ERR_INI_SIZE,
  15. UPLOAD_ERR_FORM_SIZE,
  16. UPLOAD_ERR_PARTIAL,
  17. UPLOAD_ERR_NO_FILE,
  18. UPLOAD_ERR_NO_TMP_DIR,
  19. UPLOAD_ERR_CANT_WRITE,
  20. UPLOAD_ERR_EXTENSION,
  21. ];
  22. /**
  23. * @var string
  24. */
  25. private $clientFilename;
  26. /**
  27. * @var string
  28. */
  29. private $clientMediaType;
  30. /**
  31. * @var int
  32. */
  33. private $error;
  34. /**
  35. * @var null|string
  36. */
  37. private $file;
  38. /**
  39. * @var bool
  40. */
  41. private $moved = false;
  42. /**
  43. * @var int
  44. */
  45. private $size;
  46. /**
  47. * @var StreamInterface|null
  48. */
  49. private $stream;
  50. /**
  51. * @param StreamInterface|string|resource $streamOrFile
  52. * @param int $size
  53. * @param int $errorStatus
  54. * @param string|null $clientFilename
  55. * @param string|null $clientMediaType
  56. */
  57. public function __construct(
  58. $streamOrFile,
  59. $size,
  60. $errorStatus,
  61. $clientFilename = null,
  62. $clientMediaType = null
  63. ) {
  64. $this->setError($errorStatus);
  65. $this->setSize($size);
  66. $this->setClientFilename($clientFilename);
  67. $this->setClientMediaType($clientMediaType);
  68. if ($this->isOk()) {
  69. $this->setStreamOrFile($streamOrFile);
  70. }
  71. }
  72. /**
  73. * Depending on the value set file or stream variable
  74. *
  75. * @param mixed $streamOrFile
  76. * @throws InvalidArgumentException
  77. */
  78. private function setStreamOrFile($streamOrFile)
  79. {
  80. if (is_string($streamOrFile)) {
  81. $this->file = $streamOrFile;
  82. } elseif (is_resource($streamOrFile)) {
  83. $this->stream = new Stream($streamOrFile);
  84. } elseif ($streamOrFile instanceof StreamInterface) {
  85. $this->stream = $streamOrFile;
  86. } else {
  87. throw new InvalidArgumentException(
  88. 'Invalid stream or file provided for UploadedFile'
  89. );
  90. }
  91. }
  92. /**
  93. * @param int $error
  94. * @throws InvalidArgumentException
  95. */
  96. private function setError($error)
  97. {
  98. if (false === is_int($error)) {
  99. throw new InvalidArgumentException(
  100. 'Upload file error status must be an integer'
  101. );
  102. }
  103. if (false === in_array($error, UploadedFile::$errors)) {
  104. throw new InvalidArgumentException(
  105. 'Invalid error status for UploadedFile'
  106. );
  107. }
  108. $this->error = $error;
  109. }
  110. /**
  111. * @param int $size
  112. * @throws InvalidArgumentException
  113. */
  114. private function setSize($size)
  115. {
  116. if (false === is_int($size)) {
  117. throw new InvalidArgumentException(
  118. 'Upload file size must be an integer'
  119. );
  120. }
  121. $this->size = $size;
  122. }
  123. /**
  124. * @param mixed $param
  125. * @return boolean
  126. */
  127. private function isStringOrNull($param)
  128. {
  129. return in_array(gettype($param), ['string', 'NULL']);
  130. }
  131. /**
  132. * @param mixed $param
  133. * @return boolean
  134. */
  135. private function isStringNotEmpty($param)
  136. {
  137. return is_string($param) && false === empty($param);
  138. }
  139. /**
  140. * @param string|null $clientFilename
  141. * @throws InvalidArgumentException
  142. */
  143. private function setClientFilename($clientFilename)
  144. {
  145. if (false === $this->isStringOrNull($clientFilename)) {
  146. throw new InvalidArgumentException(
  147. 'Upload file client filename must be a string or null'
  148. );
  149. }
  150. $this->clientFilename = $clientFilename;
  151. }
  152. /**
  153. * @param string|null $clientMediaType
  154. * @throws InvalidArgumentException
  155. */
  156. private function setClientMediaType($clientMediaType)
  157. {
  158. if (false === $this->isStringOrNull($clientMediaType)) {
  159. throw new InvalidArgumentException(
  160. 'Upload file client media type must be a string or null'
  161. );
  162. }
  163. $this->clientMediaType = $clientMediaType;
  164. }
  165. /**
  166. * Return true if there is no upload error
  167. *
  168. * @return boolean
  169. */
  170. private function isOk()
  171. {
  172. return $this->error === UPLOAD_ERR_OK;
  173. }
  174. /**
  175. * @return boolean
  176. */
  177. public function isMoved()
  178. {
  179. return $this->moved;
  180. }
  181. /**
  182. * @throws RuntimeException if is moved or not ok
  183. */
  184. private function validateActive()
  185. {
  186. if (false === $this->isOk()) {
  187. throw new RuntimeException('Cannot retrieve stream due to upload error');
  188. }
  189. if ($this->isMoved()) {
  190. throw new RuntimeException('Cannot retrieve stream after it has already been moved');
  191. }
  192. }
  193. /**
  194. * {@inheritdoc}
  195. * @throws RuntimeException if the upload was not successful.
  196. */
  197. public function getStream()
  198. {
  199. $this->validateActive();
  200. if ($this->stream instanceof StreamInterface) {
  201. return $this->stream;
  202. }
  203. return new LazyOpenStream($this->file, 'r+');
  204. }
  205. /**
  206. * {@inheritdoc}
  207. *
  208. * @see http://php.net/is_uploaded_file
  209. * @see http://php.net/move_uploaded_file
  210. * @param string $targetPath Path to which to move the uploaded file.
  211. * @throws RuntimeException if the upload was not successful.
  212. * @throws InvalidArgumentException if the $path specified is invalid.
  213. * @throws RuntimeException on any error during the move operation, or on
  214. * the second or subsequent call to the method.
  215. */
  216. public function moveTo($targetPath)
  217. {
  218. $this->validateActive();
  219. if (false === $this->isStringNotEmpty($targetPath)) {
  220. throw new InvalidArgumentException(
  221. 'Invalid path provided for move operation; must be a non-empty string'
  222. );
  223. }
  224. if ($this->file) {
  225. $this->moved = php_sapi_name() == 'cli'
  226. ? rename($this->file, $targetPath)
  227. : move_uploaded_file($this->file, $targetPath);
  228. } else {
  229. copy_to_stream(
  230. $this->getStream(),
  231. new LazyOpenStream($targetPath, 'w')
  232. );
  233. $this->moved = true;
  234. }
  235. if (false === $this->moved) {
  236. throw new RuntimeException(
  237. sprintf('Uploaded file could not be moved to %s', $targetPath)
  238. );
  239. }
  240. }
  241. /**
  242. * {@inheritdoc}
  243. *
  244. * @return int|null The file size in bytes or null if unknown.
  245. */
  246. public function getSize()
  247. {
  248. return $this->size;
  249. }
  250. /**
  251. * {@inheritdoc}
  252. *
  253. * @see http://php.net/manual/en/features.file-upload.errors.php
  254. * @return int One of PHP's UPLOAD_ERR_XXX constants.
  255. */
  256. public function getError()
  257. {
  258. return $this->error;
  259. }
  260. /**
  261. * {@inheritdoc}
  262. *
  263. * @return string|null The filename sent by the client or null if none
  264. * was provided.
  265. */
  266. public function getClientFilename()
  267. {
  268. return $this->clientFilename;
  269. }
  270. /**
  271. * {@inheritdoc}
  272. */
  273. public function getClientMediaType()
  274. {
  275. return $this->clientMediaType;
  276. }
  277. }