Chris@0: [ Chris@0: 'r' => true, 'w+' => true, 'r+' => true, 'x+' => true, 'c+' => true, Chris@0: 'rb' => true, 'w+b' => true, 'r+b' => true, 'x+b' => true, Chris@0: 'c+b' => true, 'rt' => true, 'w+t' => true, 'r+t' => true, Chris@17: 'x+t' => true, 'c+t' => true, 'a+' => true, 'rb+' => true, Chris@0: ], Chris@0: 'write' => [ Chris@0: 'w' => true, 'w+' => true, 'rw' => true, 'r+' => true, 'x+' => true, Chris@17: 'c+' => true, 'wb' => true, 'w+b' => true, 'r+b' => true, 'rb+' => true, Chris@0: 'x+b' => true, 'c+b' => true, 'w+t' => true, 'r+t' => true, Chris@0: 'x+t' => true, 'c+t' => true, 'a' => true, 'a+' => true Chris@0: ] Chris@0: ]; Chris@0: Chris@0: /** Chris@0: * This constructor accepts an associative array of options. Chris@0: * Chris@0: * - size: (int) If a read stream would otherwise have an indeterminate Chris@0: * size, but the size is known due to foreknowledge, then you can Chris@0: * provide that size, in bytes. Chris@0: * - metadata: (array) Any additional metadata to return when the metadata Chris@0: * of the stream is accessed. Chris@0: * Chris@0: * @param resource $stream Stream resource to wrap. Chris@0: * @param array $options Associative array of options. Chris@0: * Chris@0: * @throws \InvalidArgumentException if the stream is not a stream resource Chris@0: */ Chris@0: public function __construct($stream, $options = []) Chris@0: { Chris@0: if (!is_resource($stream)) { Chris@0: throw new \InvalidArgumentException('Stream must be a resource'); Chris@0: } Chris@0: Chris@0: if (isset($options['size'])) { Chris@0: $this->size = $options['size']; Chris@0: } Chris@0: Chris@0: $this->customMetadata = isset($options['metadata']) Chris@0: ? $options['metadata'] Chris@0: : []; Chris@0: Chris@0: $this->stream = $stream; Chris@0: $meta = stream_get_meta_data($this->stream); Chris@0: $this->seekable = $meta['seekable']; Chris@0: $this->readable = isset(self::$readWriteHash['read'][$meta['mode']]); Chris@0: $this->writable = isset(self::$readWriteHash['write'][$meta['mode']]); Chris@0: $this->uri = $this->getMetadata('uri'); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Closes the stream when the destructed Chris@0: */ Chris@0: public function __destruct() Chris@0: { Chris@0: $this->close(); Chris@0: } Chris@0: Chris@0: public function __toString() Chris@0: { Chris@0: try { Chris@0: $this->seek(0); Chris@0: return (string) stream_get_contents($this->stream); Chris@0: } catch (\Exception $e) { Chris@0: return ''; Chris@0: } Chris@0: } Chris@0: Chris@0: public function getContents() Chris@0: { Chris@17: if (!isset($this->stream)) { Chris@17: throw new \RuntimeException('Stream is detached'); Chris@17: } Chris@17: Chris@0: $contents = stream_get_contents($this->stream); Chris@0: Chris@0: if ($contents === false) { Chris@0: throw new \RuntimeException('Unable to read stream contents'); Chris@0: } Chris@0: Chris@0: return $contents; Chris@0: } Chris@0: Chris@0: public function close() Chris@0: { Chris@0: if (isset($this->stream)) { Chris@0: if (is_resource($this->stream)) { Chris@0: fclose($this->stream); Chris@0: } Chris@0: $this->detach(); Chris@0: } Chris@0: } Chris@0: Chris@0: public function detach() Chris@0: { Chris@0: if (!isset($this->stream)) { Chris@0: return null; Chris@0: } Chris@0: Chris@0: $result = $this->stream; Chris@0: unset($this->stream); Chris@0: $this->size = $this->uri = null; Chris@0: $this->readable = $this->writable = $this->seekable = false; Chris@0: Chris@0: return $result; Chris@0: } Chris@0: Chris@0: public function getSize() Chris@0: { Chris@0: if ($this->size !== null) { Chris@0: return $this->size; Chris@0: } Chris@0: Chris@0: if (!isset($this->stream)) { Chris@0: return null; Chris@0: } Chris@0: Chris@0: // Clear the stat cache if the stream has a URI Chris@0: if ($this->uri) { Chris@0: clearstatcache(true, $this->uri); Chris@0: } Chris@0: Chris@0: $stats = fstat($this->stream); Chris@0: if (isset($stats['size'])) { Chris@0: $this->size = $stats['size']; Chris@0: return $this->size; Chris@0: } Chris@0: Chris@0: return null; Chris@0: } Chris@0: Chris@0: public function isReadable() Chris@0: { Chris@0: return $this->readable; Chris@0: } Chris@0: Chris@0: public function isWritable() Chris@0: { Chris@0: return $this->writable; Chris@0: } Chris@0: Chris@0: public function isSeekable() Chris@0: { Chris@0: return $this->seekable; Chris@0: } Chris@0: Chris@0: public function eof() Chris@0: { Chris@17: if (!isset($this->stream)) { Chris@17: throw new \RuntimeException('Stream is detached'); Chris@17: } Chris@17: Chris@17: return feof($this->stream); Chris@0: } Chris@0: Chris@0: public function tell() Chris@0: { Chris@17: if (!isset($this->stream)) { Chris@17: throw new \RuntimeException('Stream is detached'); Chris@17: } Chris@17: Chris@0: $result = ftell($this->stream); Chris@0: Chris@0: if ($result === false) { Chris@0: throw new \RuntimeException('Unable to determine stream position'); Chris@0: } Chris@0: Chris@0: return $result; Chris@0: } Chris@0: Chris@0: public function rewind() Chris@0: { Chris@0: $this->seek(0); Chris@0: } Chris@0: Chris@0: public function seek($offset, $whence = SEEK_SET) Chris@0: { Chris@17: if (!isset($this->stream)) { Chris@17: throw new \RuntimeException('Stream is detached'); Chris@17: } Chris@0: if (!$this->seekable) { Chris@0: throw new \RuntimeException('Stream is not seekable'); Chris@17: } Chris@17: if (fseek($this->stream, $offset, $whence) === -1) { Chris@0: throw new \RuntimeException('Unable to seek to stream position ' Chris@0: . $offset . ' with whence ' . var_export($whence, true)); Chris@0: } Chris@0: } Chris@0: Chris@0: public function read($length) Chris@0: { Chris@17: if (!isset($this->stream)) { Chris@17: throw new \RuntimeException('Stream is detached'); Chris@17: } Chris@0: if (!$this->readable) { Chris@0: throw new \RuntimeException('Cannot read from non-readable stream'); Chris@0: } Chris@0: if ($length < 0) { Chris@0: throw new \RuntimeException('Length parameter cannot be negative'); Chris@0: } Chris@0: Chris@0: if (0 === $length) { Chris@0: return ''; Chris@0: } Chris@0: Chris@0: $string = fread($this->stream, $length); Chris@0: if (false === $string) { Chris@0: throw new \RuntimeException('Unable to read from stream'); Chris@0: } Chris@0: Chris@0: return $string; Chris@0: } Chris@0: Chris@0: public function write($string) Chris@0: { Chris@17: if (!isset($this->stream)) { Chris@17: throw new \RuntimeException('Stream is detached'); Chris@17: } Chris@0: if (!$this->writable) { Chris@0: throw new \RuntimeException('Cannot write to a non-writable stream'); Chris@0: } Chris@0: Chris@0: // We can't know the size after writing anything Chris@0: $this->size = null; Chris@0: $result = fwrite($this->stream, $string); Chris@0: Chris@0: if ($result === false) { Chris@0: throw new \RuntimeException('Unable to write to stream'); Chris@0: } Chris@0: Chris@0: return $result; Chris@0: } Chris@0: Chris@0: public function getMetadata($key = null) Chris@0: { Chris@0: if (!isset($this->stream)) { Chris@0: return $key ? null : []; Chris@0: } elseif (!$key) { Chris@0: return $this->customMetadata + stream_get_meta_data($this->stream); Chris@0: } elseif (isset($this->customMetadata[$key])) { Chris@0: return $this->customMetadata[$key]; Chris@0: } Chris@0: Chris@0: $meta = stream_get_meta_data($this->stream); Chris@0: Chris@0: return isset($meta[$key]) ? $meta[$key] : null; Chris@0: } Chris@0: }