1<?php
2namespace GuzzleHttp\Psr7;
3
4use Psr\Http\Message\StreamInterface;
5
6/**
7 * Provides a read only stream that pumps data from a PHP callable.
8 *
9 * When invoking the provided callable, the PumpStream will pass the amount of
10 * data requested to read to the callable. The callable can choose to ignore
11 * this value and return fewer or more bytes than requested. Any extra data
12 * returned by the provided callable is buffered internally until drained using
13 * the read() function of the PumpStream. The provided callable MUST return
14 * false when there is no more data to read.
15 */
16class PumpStream implements StreamInterface
17{
18    /** @var callable */
19    private $source;
20
21    /** @var int */
22    private $size;
23
24    /** @var int */
25    private $tellPos = 0;
26
27    /** @var array */
28    private $metadata;
29
30    /** @var BufferStream */
31    private $buffer;
32
33    /**
34     * @param callable $source Source of the stream data. The callable MAY
35     *                         accept an integer argument used to control the
36     *                         amount of data to return. The callable MUST
37     *                         return a string when called, or false on error
38     *                         or EOF.
39     * @param array $options   Stream options:
40     *                         - metadata: Hash of metadata to use with stream.
41     *                         - size: Size of the stream, if known.
42     */
43    public function __construct(callable $source, array $options = [])
44    {
45        $this->source = $source;
46        $this->size = isset($options['size']) ? $options['size'] : null;
47        $this->metadata = isset($options['metadata']) ? $options['metadata'] : [];
48        $this->buffer = new BufferStream();
49    }
50
51    public function __toString()
52    {
53        try {
54            return copy_to_string($this);
55        } catch (\Exception $e) {
56            return '';
57        }
58    }
59
60    public function close()
61    {
62        $this->detach();
63    }
64
65    public function detach()
66    {
67        $this->tellPos = false;
68        $this->source = null;
69    }
70
71    public function getSize()
72    {
73        return $this->size;
74    }
75
76    public function tell()
77    {
78        return $this->tellPos;
79    }
80
81    public function eof()
82    {
83        return !$this->source;
84    }
85
86    public function isSeekable()
87    {
88        return false;
89    }
90
91    public function rewind()
92    {
93        $this->seek(0);
94    }
95
96    public function seek($offset, $whence = SEEK_SET)
97    {
98        throw new \RuntimeException('Cannot seek a PumpStream');
99    }
100
101    public function isWritable()
102    {
103        return false;
104    }
105
106    public function write($string)
107    {
108        throw new \RuntimeException('Cannot write to a PumpStream');
109    }
110
111    public function isReadable()
112    {
113        return true;
114    }
115
116    public function read($length)
117    {
118        $data = $this->buffer->read($length);
119        $readLen = strlen($data);
120        $this->tellPos += $readLen;
121        $remaining = $length - $readLen;
122
123        if ($remaining) {
124            $this->pump($remaining);
125            $data .= $this->buffer->read($remaining);
126            $this->tellPos += strlen($data) - $readLen;
127        }
128
129        return $data;
130    }
131
132    public function getContents()
133    {
134        $result = '';
135        while (!$this->eof()) {
136            $result .= $this->read(1000000);
137        }
138
139        return $result;
140    }
141
142    public function getMetadata($key = null)
143    {
144        if (!$key) {
145            return $this->metadata;
146        }
147
148        return isset($this->metadata[$key]) ? $this->metadata[$key] : null;
149    }
150
151    private function pump($length)
152    {
153        if ($this->source) {
154            do {
155                $data = call_user_func($this->source, $length);
156                if ($data === false || $data === null) {
157                    $this->source = null;
158                    return;
159                }
160                $this->buffer->write($data);
161                $length -= strlen($data);
162            } while ($length > 0);
163        }
164    }
165}
166