1<?php
2namespace GuzzleHttp\Stream;
3
4use GuzzleHttp\Stream\Exception\CannotAttachException;
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        return Utils::copyToString($this);
54    }
55
56    public function close()
57    {
58        $this->detach();
59    }
60
61    public function detach()
62    {
63        $this->tellPos = false;
64        $this->source = null;
65    }
66
67    public function attach($stream)
68    {
69        throw new CannotAttachException();
70    }
71
72    public function getSize()
73    {
74        return $this->size;
75    }
76
77    public function tell()
78    {
79        return $this->tellPos;
80    }
81
82    public function eof()
83    {
84        return !$this->source;
85    }
86
87    public function isSeekable()
88    {
89        return false;
90    }
91
92    public function seek($offset, $whence = SEEK_SET)
93    {
94        return false;
95    }
96
97    public function isWritable()
98    {
99        return false;
100    }
101
102    public function write($string)
103    {
104        return false;
105    }
106
107    public function isReadable()
108    {
109        return true;
110    }
111
112    public function read($length)
113    {
114        $data = $this->buffer->read($length);
115        $readLen = strlen($data);
116        $this->tellPos += $readLen;
117        $remaining = $length - $readLen;
118
119        if ($remaining) {
120            $this->pump($remaining);
121            $data .= $this->buffer->read($remaining);
122            $this->tellPos += strlen($data) - $readLen;
123        }
124
125        return $data;
126    }
127
128    public function getContents()
129    {
130        $result = '';
131        while (!$this->eof()) {
132            $result .= $this->read(1000000);
133        }
134
135        return $result;
136    }
137
138    public function getMetadata($key = null)
139    {
140        if (!$key) {
141            return $this->metadata;
142        }
143
144        return isset($this->metadata[$key]) ? $this->metadata[$key] : null;
145    }
146
147    private function pump($length)
148    {
149        if ($this->source) {
150            do {
151                $data = call_user_func($this->source, $length);
152                if ($data === false || $data === null) {
153                    $this->source = null;
154                    return;
155                }
156                $this->buffer->write($data);
157                $length -= strlen($data);
158            } while ($length > 0);
159        }
160    }
161}
162