/src/http/Stream.php

https://github.com/bixuehujin/blink · PHP · 320 lines · 187 code · 56 blank · 77 comment · 31 complexity · 03eb75bd1aa180b860f7825c3fb1f52d MD5 · raw file

  1. <?php
  2. /**
  3. * The file is modified from zend-diactoros project.
  4. *
  5. * Zend Framework (http://framework.zend.com/)
  6. *
  7. * @see http://github.com/zendframework/zend-diactoros for the canonical source repository
  8. * @copyright Copyright (c) 2015-2016 Zend Technologies USA Inc. (http://www.zend.com)
  9. * @license https://github.com/zendframework/zend-diactoros/blob/master/LICENSE.md New BSD License
  10. */
  11. namespace blink\http;
  12. use RuntimeException;
  13. use InvalidArgumentException;
  14. use Psr\Http\Message\StreamInterface;
  15. /**
  16. * Class Stream
  17. *
  18. * @package blink\http
  19. */
  20. class Stream implements StreamInterface
  21. {
  22. /**
  23. * @var resource
  24. */
  25. protected $resource;
  26. /**
  27. * @var string|resource
  28. */
  29. protected $stream;
  30. /**
  31. * @param string|resource $stream
  32. * @param string $mode Mode with which to open stream
  33. * @throws InvalidArgumentException
  34. */
  35. public function __construct($stream, $mode = 'r')
  36. {
  37. $this->setStream($stream, $mode);
  38. }
  39. /**
  40. * {@inheritdoc}
  41. */
  42. public function __toString()
  43. {
  44. if (! $this->isReadable()) {
  45. return '';
  46. }
  47. try {
  48. $this->rewind();
  49. return $this->getContents();
  50. } catch (RuntimeException $e) {
  51. var_dump($e);
  52. return '';
  53. }
  54. }
  55. /**
  56. * @inheritDoc
  57. */
  58. public function close()
  59. {
  60. if (!$this->resource) {
  61. return;
  62. }
  63. $resource = $this->detach();
  64. fclose($resource);
  65. }
  66. /**
  67. * @inheritDoc
  68. */
  69. public function detach()
  70. {
  71. $resource = $this->resource;
  72. $this->resource = null;
  73. return $resource;
  74. }
  75. /**
  76. * @inheritDoc
  77. */
  78. public function getSize()
  79. {
  80. if (null === $this->resource) {
  81. return null;
  82. }
  83. $stats = fstat($this->resource);
  84. return $stats['size'];
  85. }
  86. /**
  87. * @inheritDoc
  88. */
  89. public function tell()
  90. {
  91. if (!$this->resource) {
  92. throw new RuntimeException('No resource available; cannot tell position');
  93. }
  94. $result = ftell($this->resource);
  95. if (!is_int($result)) {
  96. throw new RuntimeException('Error occurred during tell operation');
  97. }
  98. return $result;
  99. }
  100. /**
  101. * @inheritDoc
  102. */
  103. public function eof()
  104. {
  105. if (!$this->resource) {
  106. return true;
  107. }
  108. return feof($this->resource);
  109. }
  110. /**
  111. * @inheritDoc
  112. */
  113. public function isSeekable()
  114. {
  115. if (!$this->resource) {
  116. return false;
  117. }
  118. $meta = stream_get_meta_data($this->resource);
  119. return $meta['seekable'];
  120. }
  121. /**
  122. * @inheritDoc
  123. */
  124. public function seek($offset, $whence = SEEK_SET)
  125. {
  126. if (!$this->resource) {
  127. throw new RuntimeException('No resource available; cannot seek position');
  128. }
  129. if (!$this->isSeekable()) {
  130. throw new RuntimeException('Stream is not seekable');
  131. }
  132. $result = fseek($this->resource, $offset, $whence);
  133. if (0 !== $result) {
  134. throw new RuntimeException('Error seeking within stream');
  135. }
  136. return true;
  137. }
  138. /**
  139. * @inheritDoc
  140. */
  141. public function rewind()
  142. {
  143. return $this->seek(0);
  144. }
  145. /**
  146. * @inheritDoc
  147. */
  148. public function isWritable()
  149. {
  150. if (! $this->resource) {
  151. return false;
  152. }
  153. $meta = stream_get_meta_data($this->resource);
  154. $mode = $meta['mode'];
  155. return (
  156. strstr($mode, 'x')
  157. || strstr($mode, 'w')
  158. || strstr($mode, 'c')
  159. || strstr($mode, 'a')
  160. || strstr($mode, '+')
  161. );
  162. }
  163. /**
  164. * @inheritDoc
  165. */
  166. public function write($string)
  167. {
  168. if (!$this->resource) {
  169. throw new RuntimeException('No resource available; cannot write');
  170. }
  171. if (!$this->isWritable()) {
  172. throw new RuntimeException('Stream is not writable');
  173. }
  174. $result = fwrite($this->resource, $string);
  175. if (false === $result) {
  176. throw new RuntimeException('Error writing to stream');
  177. }
  178. return $result;
  179. }
  180. /**
  181. * @inheritDoc
  182. */
  183. public function isReadable()
  184. {
  185. if (!$this->resource) {
  186. return false;
  187. }
  188. $meta = stream_get_meta_data($this->resource);
  189. return strstr($meta['mode'], 'r') || strstr($meta['mode'], '+');
  190. }
  191. /**
  192. * @inheritDoc
  193. */
  194. public function read($length)
  195. {
  196. if (!$this->resource) {
  197. throw new RuntimeException('No resource available; cannot read');
  198. }
  199. if (!$this->isReadable()) {
  200. throw new RuntimeException('Stream is not readable');
  201. }
  202. $result = fread($this->resource, $length);
  203. if (false === $result) {
  204. throw new RuntimeException('Error reading stream');
  205. }
  206. return $result;
  207. }
  208. /**
  209. * @inheritDoc
  210. */
  211. public function getContents()
  212. {
  213. if (!$this->isReadable()) {
  214. throw new RuntimeException('Stream is not readable');
  215. }
  216. $result = stream_get_contents($this->resource);
  217. if (false === $result) {
  218. throw new RuntimeException('Error reading from stream');
  219. }
  220. return $result;
  221. }
  222. /**
  223. * @inheritDoc
  224. */
  225. public function getMetadata($key = null)
  226. {
  227. $meta = stream_get_meta_data($this->resource);
  228. if ($key === null) {
  229. return $meta;
  230. }
  231. return array_key_exists($key, $meta) ? $meta[$key] : null;
  232. }
  233. /**
  234. * Set the internal stream resource.
  235. *
  236. * @param string|resource $stream String stream target or stream resource.
  237. * @param string $mode Resource mode for stream target.
  238. * @throws InvalidArgumentException for invalid streams or resources.
  239. */
  240. private function setStream($stream, $mode = 'r')
  241. {
  242. $error = null;
  243. $resource = $stream;
  244. if (is_string($stream)) {
  245. set_error_handler(function ($e) use (&$error) {
  246. $error = $e;
  247. }, E_WARNING);
  248. $resource = fopen($stream, $mode);
  249. restore_error_handler();
  250. }
  251. if ($error) {
  252. throw new InvalidArgumentException('Invalid stream reference provided');
  253. }
  254. if (! is_resource($resource) || 'stream' !== get_resource_type($resource)) {
  255. throw new InvalidArgumentException(
  256. 'Invalid stream provided; must be a string stream identifier or stream resource'
  257. );
  258. }
  259. if ($stream !== $resource) {
  260. $this->stream = $stream;
  261. }
  262. $this->resource = $resource;
  263. }
  264. }