3 * @see https://github.com/zendframework/zend-diactoros for the canonical source repository
4 * @copyright Copyright (c) 2015-2017 Zend Technologies USA Inc. (http://www.zend.com)
5 * @license https://github.com/zendframework/zend-diactoros/blob/master/LICENSE.md New BSD License
8 namespace Zend\Diactoros;
10 use InvalidArgumentException;
12 use Psr\Http\Message\StreamInterface;
15 * Implementation of PSR HTTP streams
17 class Stream implements StreamInterface
25 * @var string|resource
30 * @param string|resource $stream
31 * @param string $mode Mode with which to open stream
32 * @throws InvalidArgumentException
34 public function __construct($stream, $mode = 'r')
36 $this->setStream($stream, $mode);
42 public function __toString()
44 if (! $this->isReadable()) {
49 if ($this->isSeekable()) {
53 return $this->getContents();
54 } catch (RuntimeException $e) {
62 public function close()
64 if (! $this->resource) {
68 $resource = $this->detach();
75 public function detach()
77 $resource = $this->resource;
78 $this->resource = null;
83 * Attach a new stream/resource to the instance.
85 * @param string|resource $resource
87 * @throws InvalidArgumentException for stream identifier that cannot be
89 * @throws InvalidArgumentException for non-resource stream
91 public function attach($resource, $mode = 'r')
93 $this->setStream($resource, $mode);
99 public function getSize()
101 if (null === $this->resource) {
105 $stats = fstat($this->resource);
106 if ($stats !== false) {
107 return $stats['size'];
116 public function tell()
118 if (! $this->resource) {
119 throw new RuntimeException('No resource available; cannot tell position');
122 $result = ftell($this->resource);
123 if (! is_int($result)) {
124 throw new RuntimeException('Error occurred during tell operation');
133 public function eof()
135 if (! $this->resource) {
139 return feof($this->resource);
145 public function isSeekable()
147 if (! $this->resource) {
151 $meta = stream_get_meta_data($this->resource);
152 return $meta['seekable'];
158 public function seek($offset, $whence = SEEK_SET)
160 if (! $this->resource) {
161 throw new RuntimeException('No resource available; cannot seek position');
164 if (! $this->isSeekable()) {
165 throw new RuntimeException('Stream is not seekable');
168 $result = fseek($this->resource, $offset, $whence);
171 throw new RuntimeException('Error seeking within stream');
180 public function rewind()
182 return $this->seek(0);
188 public function isWritable()
190 if (! $this->resource) {
194 $meta = stream_get_meta_data($this->resource);
195 $mode = $meta['mode'];
199 || strstr($mode, 'w')
200 || strstr($mode, 'c')
201 || strstr($mode, 'a')
202 || strstr($mode, '+')
209 public function write($string)
211 if (! $this->resource) {
212 throw new RuntimeException('No resource available; cannot write');
215 if (! $this->isWritable()) {
216 throw new RuntimeException('Stream is not writable');
219 $result = fwrite($this->resource, $string);
221 if (false === $result) {
222 throw new RuntimeException('Error writing to stream');
230 public function isReadable()
232 if (! $this->resource) {
236 $meta = stream_get_meta_data($this->resource);
237 $mode = $meta['mode'];
239 return (strstr($mode, 'r') || strstr($mode, '+'));
245 public function read($length)
247 if (! $this->resource) {
248 throw new RuntimeException('No resource available; cannot read');
251 if (! $this->isReadable()) {
252 throw new RuntimeException('Stream is not readable');
255 $result = fread($this->resource, $length);
257 if (false === $result) {
258 throw new RuntimeException('Error reading stream');
267 public function getContents()
269 if (! $this->isReadable()) {
270 throw new RuntimeException('Stream is not readable');
273 $result = stream_get_contents($this->resource);
274 if (false === $result) {
275 throw new RuntimeException('Error reading from stream');
283 public function getMetadata($key = null)
286 return stream_get_meta_data($this->resource);
289 $metadata = stream_get_meta_data($this->resource);
290 if (! array_key_exists($key, $metadata)) {
294 return $metadata[$key];
298 * Set the internal stream resource.
300 * @param string|resource $stream String stream target or stream resource.
301 * @param string $mode Resource mode for stream target.
302 * @throws InvalidArgumentException for invalid streams or resources.
304 private function setStream($stream, $mode = 'r')
309 if (is_string($stream)) {
310 set_error_handler(function ($e) use (&$error) {
313 $resource = fopen($stream, $mode);
314 restore_error_handler();
318 throw new InvalidArgumentException('Invalid stream reference provided');
321 if (! is_resource($resource) || 'stream' !== get_resource_type($resource)) {
322 throw new InvalidArgumentException(
323 'Invalid stream provided; must be a string stream identifier or stream resource'
327 if ($stream !== $resource) {
328 $this->stream = $stream;
331 $this->resource = $resource;