UploadedFile.php 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325
  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. *
  77. * @throws InvalidArgumentException
  78. */
  79. private function setStreamOrFile($streamOrFile)
  80. {
  81. if (is_string($streamOrFile)) {
  82. $this->file = $streamOrFile;
  83. } elseif (is_resource($streamOrFile)) {
  84. $this->stream = new Stream($streamOrFile);
  85. } elseif ($streamOrFile instanceof StreamInterface) {
  86. $this->stream = $streamOrFile;
  87. } else {
  88. throw new InvalidArgumentException(
  89. 'Invalid stream or file provided for UploadedFile'
  90. );
  91. }
  92. }
  93. /**
  94. * @param int $error
  95. *
  96. * @throws InvalidArgumentException
  97. */
  98. private function setError($error)
  99. {
  100. if (false === is_int($error)) {
  101. throw new InvalidArgumentException(
  102. 'Upload file error status must be an integer'
  103. );
  104. }
  105. if (false === in_array($error, UploadedFile::$errors)) {
  106. throw new InvalidArgumentException(
  107. 'Invalid error status for UploadedFile'
  108. );
  109. }
  110. $this->error = $error;
  111. }
  112. /**
  113. * @param int $size
  114. *
  115. * @throws InvalidArgumentException
  116. */
  117. private function setSize($size)
  118. {
  119. if (false === is_int($size)) {
  120. throw new InvalidArgumentException(
  121. 'Upload file size must be an integer'
  122. );
  123. }
  124. $this->size = $size;
  125. }
  126. /**
  127. * @param mixed $param
  128. * @return boolean
  129. */
  130. private function isStringOrNull($param)
  131. {
  132. return in_array(gettype($param), ['string', 'NULL']);
  133. }
  134. /**
  135. * @param mixed $param
  136. * @return boolean
  137. */
  138. private function isStringNotEmpty($param)
  139. {
  140. return is_string($param) && false === empty($param);
  141. }
  142. /**
  143. * @param string|null $clientFilename
  144. *
  145. * @throws InvalidArgumentException
  146. */
  147. private function setClientFilename($clientFilename)
  148. {
  149. if (false === $this->isStringOrNull($clientFilename)) {
  150. throw new InvalidArgumentException(
  151. 'Upload file client filename must be a string or null'
  152. );
  153. }
  154. $this->clientFilename = $clientFilename;
  155. }
  156. /**
  157. * @param string|null $clientMediaType
  158. *
  159. * @throws InvalidArgumentException
  160. */
  161. private function setClientMediaType($clientMediaType)
  162. {
  163. if (false === $this->isStringOrNull($clientMediaType)) {
  164. throw new InvalidArgumentException(
  165. 'Upload file client media type must be a string or null'
  166. );
  167. }
  168. $this->clientMediaType = $clientMediaType;
  169. }
  170. /**
  171. * Return true if there is no upload error
  172. *
  173. * @return boolean
  174. */
  175. private function isOk()
  176. {
  177. return $this->error === UPLOAD_ERR_OK;
  178. }
  179. /**
  180. * @return boolean
  181. */
  182. public function isMoved()
  183. {
  184. return $this->moved;
  185. }
  186. /**
  187. * @throws RuntimeException if is moved or not ok
  188. */
  189. private function validateActive()
  190. {
  191. if (false === $this->isOk()) {
  192. throw new RuntimeException('Cannot retrieve stream due to upload error');
  193. }
  194. if ($this->isMoved()) {
  195. throw new RuntimeException('Cannot retrieve stream after it has already been moved');
  196. }
  197. }
  198. /**
  199. * {@inheritdoc}
  200. *
  201. * @throws RuntimeException if the upload was not successful.
  202. */
  203. public function getStream()
  204. {
  205. $this->validateActive();
  206. if ($this->stream instanceof StreamInterface) {
  207. return $this->stream;
  208. }
  209. return new LazyOpenStream($this->file, 'r+');
  210. }
  211. /**
  212. * {@inheritdoc}
  213. *
  214. * @see http://php.net/is_uploaded_file
  215. * @see http://php.net/move_uploaded_file
  216. *
  217. * @param string $targetPath Path to which to move the uploaded file.
  218. *
  219. * @throws RuntimeException if the upload was not successful.
  220. * @throws InvalidArgumentException if the $path specified is invalid.
  221. * @throws RuntimeException on any error during the move operation, or on
  222. * the second or subsequent call to the method.
  223. */
  224. public function moveTo($targetPath)
  225. {
  226. $this->validateActive();
  227. if (false === $this->isStringNotEmpty($targetPath)) {
  228. throw new InvalidArgumentException(
  229. 'Invalid path provided for move operation; must be a non-empty string'
  230. );
  231. }
  232. if ($this->file) {
  233. $this->moved = php_sapi_name() == 'cli'
  234. ? rename($this->file, $targetPath)
  235. : move_uploaded_file($this->file, $targetPath);
  236. } else {
  237. Utils::copyToStream(
  238. $this->getStream(),
  239. new LazyOpenStream($targetPath, 'w')
  240. );
  241. $this->moved = true;
  242. }
  243. if (false === $this->moved) {
  244. throw new RuntimeException(
  245. sprintf('Uploaded file could not be moved to %s', $targetPath)
  246. );
  247. }
  248. }
  249. /**
  250. * {@inheritdoc}
  251. *
  252. * @return int|null The file size in bytes or null if unknown.
  253. */
  254. public function getSize()
  255. {
  256. return $this->size;
  257. }
  258. /**
  259. * {@inheritdoc}
  260. *
  261. * @see http://php.net/manual/en/features.file-upload.errors.php
  262. * @return int One of PHP's UPLOAD_ERR_XXX constants.
  263. */
  264. public function getError()
  265. {
  266. return $this->error;
  267. }
  268. /**
  269. * {@inheritdoc}
  270. *
  271. * @return string|null The filename sent by the client or null if none
  272. * was provided.
  273. */
  274. public function getClientFilename()
  275. {
  276. return $this->clientFilename;
  277. }
  278. /**
  279. * {@inheritdoc}
  280. */
  281. public function getClientMediaType()
  282. {
  283. return $this->clientMediaType;
  284. }
  285. }