1<?php 2namespace GuzzleHttp\Stream; 3 4/** 5 * PHP stream implementation 6 */ 7class Stream implements StreamInterface 8{ 9 private $stream; 10 private $size; 11 private $seekable; 12 private $readable; 13 private $writable; 14 private $uri; 15 private $customMetadata; 16 17 /** @var array Hash of readable and writable stream types */ 18 private static $readWriteHash = [ 19 'read' => [ 20 'r' => true, 'w+' => true, 'r+' => true, 'x+' => true, 'c+' => true, 21 'rb' => true, 'w+b' => true, 'r+b' => true, 'x+b' => true, 22 'c+b' => true, 'rt' => true, 'w+t' => true, 'r+t' => true, 23 'x+t' => true, 'c+t' => true, 'a+' => true, 24 ], 25 'write' => [ 26 'w' => true, 'w+' => true, 'rw' => true, 'r+' => true, 'x+' => true, 27 'c+' => true, 'wb' => true, 'w+b' => true, 'r+b' => true, 28 'x+b' => true, 'c+b' => true, 'w+t' => true, 'r+t' => true, 29 'x+t' => true, 'c+t' => true, 'a' => true, 'a+' => true, 30 ], 31 ]; 32 33 /** 34 * Create a new stream based on the input type. 35 * 36 * This factory accepts the same associative array of options as described 37 * in the constructor. 38 * 39 * @param resource|string|StreamInterface $resource Entity body data 40 * @param array $options Additional options 41 * 42 * @return Stream 43 * @throws \InvalidArgumentException if the $resource arg is not valid. 44 */ 45 public static function factory($resource = '', array $options = []) 46 { 47 $type = gettype($resource); 48 49 if ($type == 'string') { 50 $stream = fopen('php://temp', 'r+'); 51 if ($resource !== '') { 52 fwrite($stream, $resource); 53 fseek($stream, 0); 54 } 55 return new self($stream, $options); 56 } 57 58 if ($type == 'resource') { 59 return new self($resource, $options); 60 } 61 62 if ($resource instanceof StreamInterface) { 63 return $resource; 64 } 65 66 if ($type == 'object' && method_exists($resource, '__toString')) { 67 return self::factory((string) $resource, $options); 68 } 69 70 if (is_callable($resource)) { 71 return new PumpStream($resource, $options); 72 } 73 74 if ($resource instanceof \Iterator) { 75 return new PumpStream(function () use ($resource) { 76 if (!$resource->valid()) { 77 return false; 78 } 79 $result = $resource->current(); 80 $resource->next(); 81 return $result; 82 }, $options); 83 } 84 85 throw new \InvalidArgumentException('Invalid resource type: ' . $type); 86 } 87 88 /** 89 * This constructor accepts an associative array of options. 90 * 91 * - size: (int) If a read stream would otherwise have an indeterminate 92 * size, but the size is known due to foreknownledge, then you can 93 * provide that size, in bytes. 94 * - metadata: (array) Any additional metadata to return when the metadata 95 * of the stream is accessed. 96 * 97 * @param resource $stream Stream resource to wrap. 98 * @param array $options Associative array of options. 99 * 100 * @throws \InvalidArgumentException if the stream is not a stream resource 101 */ 102 public function __construct($stream, $options = []) 103 { 104 if (!is_resource($stream)) { 105 throw new \InvalidArgumentException('Stream must be a resource'); 106 } 107 108 if (isset($options['size'])) { 109 $this->size = $options['size']; 110 } 111 112 $this->customMetadata = isset($options['metadata']) 113 ? $options['metadata'] 114 : []; 115 116 $this->attach($stream); 117 } 118 119 /** 120 * Closes the stream when the destructed 121 */ 122 public function __destruct() 123 { 124 $this->close(); 125 } 126 127 public function __toString() 128 { 129 if (!$this->stream) { 130 return ''; 131 } 132 133 $this->seek(0); 134 135 return (string) stream_get_contents($this->stream); 136 } 137 138 public function getContents() 139 { 140 return $this->stream ? stream_get_contents($this->stream) : ''; 141 } 142 143 public function close() 144 { 145 if (is_resource($this->stream)) { 146 fclose($this->stream); 147 } 148 149 $this->detach(); 150 } 151 152 public function detach() 153 { 154 $result = $this->stream; 155 $this->stream = $this->size = $this->uri = null; 156 $this->readable = $this->writable = $this->seekable = false; 157 158 return $result; 159 } 160 161 public function attach($stream) 162 { 163 $this->stream = $stream; 164 $meta = stream_get_meta_data($this->stream); 165 $this->seekable = $meta['seekable']; 166 $this->readable = isset(self::$readWriteHash['read'][$meta['mode']]); 167 $this->writable = isset(self::$readWriteHash['write'][$meta['mode']]); 168 $this->uri = $this->getMetadata('uri'); 169 } 170 171 public function getSize() 172 { 173 if ($this->size !== null) { 174 return $this->size; 175 } 176 177 if (!$this->stream) { 178 return null; 179 } 180 181 // Clear the stat cache if the stream has a URI 182 if ($this->uri) { 183 clearstatcache(true, $this->uri); 184 } 185 186 $stats = fstat($this->stream); 187 if (isset($stats['size'])) { 188 $this->size = $stats['size']; 189 return $this->size; 190 } 191 192 return null; 193 } 194 195 public function isReadable() 196 { 197 return $this->readable; 198 } 199 200 public function isWritable() 201 { 202 return $this->writable; 203 } 204 205 public function isSeekable() 206 { 207 return $this->seekable; 208 } 209 210 public function eof() 211 { 212 return !$this->stream || feof($this->stream); 213 } 214 215 public function tell() 216 { 217 return $this->stream ? ftell($this->stream) : false; 218 } 219 220 public function setSize($size) 221 { 222 $this->size = $size; 223 224 return $this; 225 } 226 227 public function seek($offset, $whence = SEEK_SET) 228 { 229 return $this->seekable 230 ? fseek($this->stream, $offset, $whence) === 0 231 : false; 232 } 233 234 public function read($length) 235 { 236 return $this->readable ? fread($this->stream, $length) : false; 237 } 238 239 public function write($string) 240 { 241 // We can't know the size after writing anything 242 $this->size = null; 243 244 return $this->writable ? fwrite($this->stream, $string) : false; 245 } 246 247 public function getMetadata($key = null) 248 { 249 if (!$this->stream) { 250 return $key ? null : []; 251 } elseif (!$key) { 252 return $this->customMetadata + stream_get_meta_data($this->stream); 253 } elseif (isset($this->customMetadata[$key])) { 254 return $this->customMetadata[$key]; 255 } 256 257 $meta = stream_get_meta_data($this->stream); 258 259 return isset($meta[$key]) ? $meta[$key] : null; 260 } 261} 262