File indexing completed on 2025-01-26 05:29:13

0001 <?php
0002 namespace GuzzleHttp\Psr7;
0003 
0004 use Psr\Http\Message\StreamInterface;
0005 
0006 /**
0007  * PHP stream implementation.
0008  *
0009  * @var $stream
0010  */
0011 class Stream implements StreamInterface
0012 {
0013     private $stream;
0014     private $size;
0015     private $seekable;
0016     private $readable;
0017     private $writable;
0018     private $uri;
0019     private $customMetadata;
0020 
0021     /** @var array Hash of readable and writable stream types */
0022     private static $readWriteHash = [
0023         'read' => [
0024             'r' => true, 'w+' => true, 'r+' => true, 'x+' => true, 'c+' => true,
0025             'rb' => true, 'w+b' => true, 'r+b' => true, 'x+b' => true,
0026             'c+b' => true, 'rt' => true, 'w+t' => true, 'r+t' => true,
0027             'x+t' => true, 'c+t' => true, 'a+' => true
0028         ],
0029         'write' => [
0030             'w' => true, 'w+' => true, 'rw' => true, 'r+' => true, 'x+' => true,
0031             'c+' => true, 'wb' => true, 'w+b' => true, 'r+b' => true,
0032             'x+b' => true, 'c+b' => true, 'w+t' => true, 'r+t' => true,
0033             'x+t' => true, 'c+t' => true, 'a' => true, 'a+' => true
0034         ]
0035     ];
0036 
0037     /**
0038      * This constructor accepts an associative array of options.
0039      *
0040      * - size: (int) If a read stream would otherwise have an indeterminate
0041      *   size, but the size is known due to foreknowledge, then you can
0042      *   provide that size, in bytes.
0043      * - metadata: (array) Any additional metadata to return when the metadata
0044      *   of the stream is accessed.
0045      *
0046      * @param resource $stream  Stream resource to wrap.
0047      * @param array    $options Associative array of options.
0048      *
0049      * @throws \InvalidArgumentException if the stream is not a stream resource
0050      */
0051     public function __construct($stream, $options = [])
0052     {
0053         if (!is_resource($stream)) {
0054             throw new \InvalidArgumentException('Stream must be a resource');
0055         }
0056 
0057         if (isset($options['size'])) {
0058             $this->size = $options['size'];
0059         }
0060 
0061         $this->customMetadata = isset($options['metadata'])
0062             ? $options['metadata']
0063             : [];
0064 
0065         $this->stream = $stream;
0066         $meta = stream_get_meta_data($this->stream);
0067         $this->seekable = $meta['seekable'];
0068         $this->readable = isset(self::$readWriteHash['read'][$meta['mode']]);
0069         $this->writable = isset(self::$readWriteHash['write'][$meta['mode']]);
0070         $this->uri = $this->getMetadata('uri');
0071     }
0072 
0073     public function __get($name)
0074     {
0075         if ($name == 'stream') {
0076             throw new \RuntimeException('The stream is detached');
0077         }
0078 
0079         throw new \BadMethodCallException('No value for ' . $name);
0080     }
0081 
0082     /**
0083      * Closes the stream when the destructed
0084      */
0085     public function __destruct()
0086     {
0087         $this->close();
0088     }
0089 
0090     public function __toString()
0091     {
0092         try {
0093             $this->seek(0);
0094             return (string) stream_get_contents($this->stream);
0095         } catch (\Exception $e) {
0096             return '';
0097         }
0098     }
0099 
0100     public function getContents()
0101     {
0102         $contents = stream_get_contents($this->stream);
0103 
0104         if ($contents === false) {
0105             throw new \RuntimeException('Unable to read stream contents');
0106         }
0107 
0108         return $contents;
0109     }
0110 
0111     public function close()
0112     {
0113         if (isset($this->stream)) {
0114             if (is_resource($this->stream)) {
0115                 fclose($this->stream);
0116             }
0117             $this->detach();
0118         }
0119     }
0120 
0121     public function detach()
0122     {
0123         if (!isset($this->stream)) {
0124             return null;
0125         }
0126 
0127         $result = $this->stream;
0128         unset($this->stream);
0129         $this->size = $this->uri = null;
0130         $this->readable = $this->writable = $this->seekable = false;
0131 
0132         return $result;
0133     }
0134 
0135     public function getSize()
0136     {
0137         if ($this->size !== null) {
0138             return $this->size;
0139         }
0140 
0141         if (!isset($this->stream)) {
0142             return null;
0143         }
0144 
0145         // Clear the stat cache if the stream has a URI
0146         if ($this->uri) {
0147             clearstatcache(true, $this->uri);
0148         }
0149 
0150         $stats = fstat($this->stream);
0151         if (isset($stats['size'])) {
0152             $this->size = $stats['size'];
0153             return $this->size;
0154         }
0155 
0156         return null;
0157     }
0158 
0159     public function isReadable()
0160     {
0161         return $this->readable;
0162     }
0163 
0164     public function isWritable()
0165     {
0166         return $this->writable;
0167     }
0168 
0169     public function isSeekable()
0170     {
0171         return $this->seekable;
0172     }
0173 
0174     public function eof()
0175     {
0176         return !$this->stream || feof($this->stream);
0177     }
0178 
0179     public function tell()
0180     {
0181         $result = ftell($this->stream);
0182 
0183         if ($result === false) {
0184             throw new \RuntimeException('Unable to determine stream position');
0185         }
0186 
0187         return $result;
0188     }
0189 
0190     public function rewind()
0191     {
0192         $this->seek(0);
0193     }
0194 
0195     public function seek($offset, $whence = SEEK_SET)
0196     {
0197         if (!$this->seekable) {
0198             throw new \RuntimeException('Stream is not seekable');
0199         } elseif (fseek($this->stream, $offset, $whence) === -1) {
0200             throw new \RuntimeException('Unable to seek to stream position '
0201                 . $offset . ' with whence ' . var_export($whence, true));
0202         }
0203     }
0204 
0205     public function read($length)
0206     {
0207         if (!$this->readable) {
0208             throw new \RuntimeException('Cannot read from non-readable stream');
0209         }
0210         if ($length < 0) {
0211             throw new \RuntimeException('Length parameter cannot be negative');
0212         }
0213 
0214         if (0 === $length) {
0215             return '';
0216         }
0217 
0218         $string = fread($this->stream, $length);
0219         if (false === $string) {
0220             throw new \RuntimeException('Unable to read from stream');
0221         }
0222 
0223         return $string;
0224     }
0225 
0226     public function write($string)
0227     {
0228         if (!$this->writable) {
0229             throw new \RuntimeException('Cannot write to a non-writable stream');
0230         }
0231 
0232         // We can't know the size after writing anything
0233         $this->size = null;
0234         $result = fwrite($this->stream, $string);
0235 
0236         if ($result === false) {
0237             throw new \RuntimeException('Unable to write to stream');
0238         }
0239 
0240         return $result;
0241     }
0242 
0243     public function getMetadata($key = null)
0244     {
0245         if (!isset($this->stream)) {
0246             return $key ? null : [];
0247         } elseif (!$key) {
0248             return $this->customMetadata + stream_get_meta_data($this->stream);
0249         } elseif (isset($this->customMetadata[$key])) {
0250             return $this->customMetadata[$key];
0251         }
0252 
0253         $meta = stream_get_meta_data($this->stream);
0254 
0255         return isset($meta[$key]) ? $meta[$key] : null;
0256     }
0257 }