Stream.php 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270
  1. <?php
  2. namespace Mpdf\PsrHttpMessageShim;
  3. /**
  4. * @link nyholm/psr7
  5. */
  6. class Stream implements \Psr\Http\Message\StreamInterface
  7. {
  8. /**
  9. * A resource reference.
  10. *
  11. * @var resource
  12. */
  13. private $stream;
  14. /**
  15. * @var bool
  16. */
  17. private $seekable;
  18. /**
  19. * @var bool
  20. */
  21. private $readable;
  22. /**
  23. * @var bool
  24. */
  25. private $writable;
  26. /**
  27. * @var array|mixed|null|void
  28. */
  29. private $uri;
  30. /**
  31. * @var int
  32. */
  33. private $size;
  34. /** @var array Hash of readable and writable stream types */
  35. private static $readWriteHash = [
  36. 'read' => [
  37. 'r' => true, 'w+' => true, 'r+' => true, 'x+' => true, 'c+' => true,
  38. 'rb' => true, 'w+b' => true, 'r+b' => true, 'x+b' => true,
  39. 'c+b' => true, 'rt' => true, 'w+t' => true, 'r+t' => true,
  40. 'x+t' => true, 'c+t' => true, 'a+' => true,
  41. ],
  42. 'write' => [
  43. 'w' => true, 'w+' => true, 'rw' => true, 'r+' => true, 'x+' => true,
  44. 'c+' => true, 'wb' => true, 'w+b' => true, 'r+b' => true,
  45. 'x+b' => true, 'c+b' => true, 'w+t' => true, 'r+t' => true,
  46. 'x+t' => true, 'c+t' => true, 'a' => true, 'a+' => true,
  47. ],
  48. ];
  49. private function __construct()
  50. {
  51. }
  52. /**
  53. * @param resource $resource
  54. *
  55. * @return Stream
  56. */
  57. public static function createFromResource($resource)
  58. {
  59. if (!is_resource($resource)) {
  60. throw new \InvalidArgumentException('Stream must be a resource');
  61. }
  62. $obj = new self();
  63. $obj->stream = $resource;
  64. $meta = stream_get_meta_data($obj->stream);
  65. $obj->seekable = $meta['seekable'];
  66. $obj->readable = isset(self::$readWriteHash['read'][$meta['mode']]);
  67. $obj->writable = isset(self::$readWriteHash['write'][$meta['mode']]);
  68. $obj->uri = $obj->getMetadata('uri');
  69. return $obj;
  70. }
  71. /**
  72. * @param string $content
  73. *
  74. * @return Stream
  75. */
  76. public static function create($content)
  77. {
  78. $resource = fopen('php://temp', 'rwb+');
  79. $stream = self::createFromResource($resource);
  80. $stream->write($content);
  81. $stream->seek(0);
  82. return $stream;
  83. }
  84. /**
  85. * Closes the stream when the destructed.
  86. */
  87. public function __destruct()
  88. {
  89. $this->close();
  90. }
  91. public function __toString()
  92. {
  93. try {
  94. if ($this->isSeekable()) {
  95. $this->seek(0);
  96. }
  97. return $this->getContents();
  98. } catch (\Exception $e) {
  99. return '';
  100. }
  101. }
  102. public function close()
  103. {
  104. if (isset($this->stream)) {
  105. if (is_resource($this->stream)) {
  106. fclose($this->stream);
  107. }
  108. $this->detach();
  109. }
  110. }
  111. public function detach()
  112. {
  113. if (!isset($this->stream)) {
  114. return;
  115. }
  116. $result = $this->stream;
  117. unset($this->stream);
  118. $this->size = $this->uri = null;
  119. $this->readable = $this->writable = $this->seekable = false;
  120. return $result;
  121. }
  122. public function getSize()
  123. {
  124. if ($this->size !== null) {
  125. return $this->size;
  126. }
  127. if (!isset($this->stream)) {
  128. return;
  129. }
  130. // Clear the stat cache if the stream has a URI
  131. if ($this->uri) {
  132. clearstatcache(true, $this->uri);
  133. }
  134. $stats = fstat($this->stream);
  135. if (isset($stats['size'])) {
  136. $this->size = $stats['size'];
  137. return $this->size;
  138. }
  139. }
  140. public function tell()
  141. {
  142. $result = ftell($this->stream);
  143. if ($result === false) {
  144. throw new \RuntimeException('Unable to determine stream position');
  145. }
  146. return $result;
  147. }
  148. public function eof()
  149. {
  150. return !$this->stream || feof($this->stream);
  151. }
  152. public function isSeekable()
  153. {
  154. return $this->seekable;
  155. }
  156. public function seek($offset, $whence = SEEK_SET)
  157. {
  158. if (!$this->seekable) {
  159. throw new \RuntimeException('Stream is not seekable');
  160. }
  161. if (fseek($this->stream, $offset, $whence) === -1) {
  162. throw new \RuntimeException('Unable to seek to stream position '.$offset.' with whence '.var_export($whence, true));
  163. }
  164. }
  165. public function rewind()
  166. {
  167. $this->seek(0);
  168. }
  169. public function isWritable()
  170. {
  171. return $this->writable;
  172. }
  173. public function write($string)
  174. {
  175. if (!$this->writable) {
  176. throw new \RuntimeException('Cannot write to a non-writable stream');
  177. }
  178. // We can't know the size after writing anything
  179. $this->size = null;
  180. $result = fwrite($this->stream, $string);
  181. if ($result === false) {
  182. throw new \RuntimeException('Unable to write to stream');
  183. }
  184. return $result;
  185. }
  186. public function isReadable()
  187. {
  188. return $this->readable;
  189. }
  190. public function read($length)
  191. {
  192. if (!$this->readable) {
  193. throw new \RuntimeException('Cannot read from non-readable stream');
  194. }
  195. return fread($this->stream, $length);
  196. }
  197. public function getContents()
  198. {
  199. if (!isset($this->stream)) {
  200. throw new \RuntimeException('Unable to read stream contents');
  201. }
  202. $contents = stream_get_contents($this->stream);
  203. if ($contents === false) {
  204. throw new \RuntimeException('Unable to read stream contents');
  205. }
  206. return $contents;
  207. }
  208. public function getMetadata($key = null)
  209. {
  210. if (!isset($this->stream)) {
  211. return $key ? null : [];
  212. }
  213. if ($key === null) {
  214. return stream_get_meta_data($this->stream);
  215. }
  216. $meta = stream_get_meta_data($this->stream);
  217. return isset($meta[$key]) ? $meta[$key] : null;
  218. }
  219. }