1 <?php 2 3 declare(strict_types=1); 4 5 namespace GuzzleHttp\Psr7; 6 7 use Psr\Http\Message\StreamInterface; 8 9 /** 10 * PHP stream implementation. 11 */ 12 class Stream implements StreamInterface 13 { 14 /** 15 * @see http://php.net/manual/function.fopen.php 16 * @see http://php.net/manual/en/function.gzopen.php 17 */ 18 private const READABLE_MODES = '/r|a\+|ab\+|w\+|wb\+|x\+|xb\+|c\+|cb\+/'; 19 private const WRITABLE_MODES = '/a|w|r\+|rb\+|rw|x|c/'; 20 21 /** @var resource */ 22 private $stream; 23 /** @var int|null */ 24 private $size; 25 /** @var bool */ 26 private $seekable; 27 /** @var bool */ 28 private $readable; 29 /** @var bool */ 30 private $writable; 31 /** @var string|null */ 32 private $uri; 33 /** @var mixed[] */ 34 private $customMetadata; 35 36 /** 37 * This constructor accepts an associative array of options. 38 * 39 * - size: (int) If a read stream would otherwise have an indeterminate 40 * size, but the size is known due to foreknowledge, then you can 41 * provide that size, in bytes. 42 * - metadata: (array) Any additional metadata to return when the metadata 43 * of the stream is accessed. 44 * 45 * @param resource $stream Stream resource to wrap. 46 * @param array{size?: int, metadata?: array} $options Associative array of options. 47 * 48 * @throws \InvalidArgumentException if the stream is not a stream resource 49 */ 50 public function __construct($stream, array $options = []) 51 { 52 if (!is_resource($stream)) { 53 throw new \InvalidArgumentException('Stream must be a resource'); 54 } 55 56 if (isset($options['size'])) { 57 $this->size = $options['size']; 58 } 59 60 $this->customMetadata = $options['metadata'] ?? []; 61 $this->stream = $stream; 62 $meta = stream_get_meta_data($this->stream); 63 $this->seekable = $meta['seekable']; 64 $this->readable = (bool)preg_match(self::READABLE_MODES, $meta['mode']); 65 $this->writable = (bool)preg_match(self::WRITABLE_MODES, $meta['mode']); 66 $this->uri = $this->getMetadata('uri'); 67 } 68 69 /** 70 * Closes the stream when the destructed 71 */ 72 public function __destruct() 73 { 74 $this->close(); 75 } 76 77 public function __toString(): string 78 { 79 try { 80 if ($this->isSeekable()) { 81 $this->seek(0); 82 } 83 return $this->getContents(); 84 } catch (\Throwable $e) { 85 if (\PHP_VERSION_ID >= 70400) { 86 throw $e; 87 } 88 trigger_error(sprintf('%s::__toString exception: %s', self::class, (string) $e), E_USER_ERROR); 89 return ''; 90 } 91 } 92 93 public function getContents(): string 94 { 95 if (!isset($this->stream)) { 96 throw new \RuntimeException('Stream is detached'); 97 } 98 99 if (!$this->readable) { 100 throw new \RuntimeException('Cannot read from non-readable stream'); 101 } 102 103 return Utils::tryGetContents($this->stream); 104 } 105 106 public function close(): void 107 { 108 if (isset($this->stream)) { 109 if (is_resource($this->stream)) { 110 fclose($this->stream); 111 } 112 $this->detach(); 113 } 114 } 115 116 public function detach() 117 { 118 if (!isset($this->stream)) { 119 return null; 120 } 121 122 $result = $this->stream; 123 unset($this->stream); 124 $this->size = $this->uri = null; 125 $this->readable = $this->writable = $this->seekable = false; 126 127 return $result; 128 } 129 130 public function getSize(): ?int 131 { 132 if ($this->size !== null) { 133 return $this->size; 134 } 135 136 if (!isset($this->stream)) { 137 return null; 138 } 139 140 // Clear the stat cache if the stream has a URI 141 if ($this->uri) { 142 clearstatcache(true, $this->uri); 143 } 144 145 $stats = fstat($this->stream); 146 if (is_array($stats) && isset($stats['size'])) { 147 $this->size = $stats['size']; 148 return $this->size; 149 } 150 151 return null; 152 } 153 154 public function isReadable(): bool 155 { 156 return $this->readable; 157 } 158 159 public function isWritable(): bool 160 { 161 return $this->writable; 162 } 163 164 public function isSeekable(): bool 165 { 166 return $this->seekable; 167 } 168 169 public function eof(): bool 170 { 171 if (!isset($this->stream)) { 172 throw new \RuntimeException('Stream is detached'); 173 } 174 175 return feof($this->stream); 176 } 177 178 public function tell(): int 179 { 180 if (!isset($this->stream)) { 181 throw new \RuntimeException('Stream is detached'); 182 } 183 184 $result = ftell($this->stream); 185 186 if ($result === false) { 187 throw new \RuntimeException('Unable to determine stream position'); 188 } 189 190 return $result; 191 } 192 193 public function rewind(): void 194 { 195 $this->seek(0); 196 } 197 198 public function seek($offset, $whence = SEEK_SET): void 199 { 200 $whence = (int) $whence; 201 202 if (!isset($this->stream)) { 203 throw new \RuntimeException('Stream is detached'); 204 } 205 if (!$this->seekable) { 206 throw new \RuntimeException('Stream is not seekable'); 207 } 208 if (fseek($this->stream, $offset, $whence) === -1) { 209 throw new \RuntimeException('Unable to seek to stream position ' 210 . $offset . ' with whence ' . var_export($whence, true)); 211 } 212 } 213 214 public function read($length): string 215 { 216 if (!isset($this->stream)) { 217 throw new \RuntimeException('Stream is detached'); 218 } 219 if (!$this->readable) { 220 throw new \RuntimeException('Cannot read from non-readable stream'); 221 } 222 if ($length < 0) { 223 throw new \RuntimeException('Length parameter cannot be negative'); 224 } 225 226 if (0 === $length) { 227 return ''; 228 } 229 230 try { 231 $string = fread($this->stream, $length); 232 } catch (\Exception $e) { 233 throw new \RuntimeException('Unable to read from stream', 0, $e); 234 } 235 236 if (false === $string) { 237 throw new \RuntimeException('Unable to read from stream'); 238 } 239 240 return $string; 241 } 242 243 public function write($string): int 244 { 245 if (!isset($this->stream)) { 246 throw new \RuntimeException('Stream is detached'); 247 } 248 if (!$this->writable) { 249 throw new \RuntimeException('Cannot write to a non-writable stream'); 250 } 251 252 // We can't know the size after writing anything 253 $this->size = null; 254 $result = fwrite($this->stream, $string); 255 256 if ($result === false) { 257 throw new \RuntimeException('Unable to write to stream'); 258 } 259 260 return $result; 261 } 262 263 /** 264 * {@inheritdoc} 265 * 266 * @return mixed 267 */ 268 public function getMetadata($key = null) 269 { 270 if (!isset($this->stream)) { 271 return $key ? null : []; 272 } elseif (!$key) { 273 return $this->customMetadata + stream_get_meta_data($this->stream); 274 } elseif (isset($this->customMetadata[$key])) { 275 return $this->customMetadata[$key]; 276 } 277 278 $meta = stream_get_meta_data($this->stream); 279 280 return $meta[$key] ?? null; 281 } 282 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body