1<?php
2
3declare(strict_types=1);
4
5namespace GuzzleHttp\Psr7;
6
7use Psr\Http\Message\StreamInterface;
8
9/**
10 * Provides a buffer stream that can be written to to fill a buffer, and read
11 * from to remove bytes from the buffer.
12 *
13 * This stream returns a "hwm" metadata value that tells upstream consumers
14 * what the configured high water mark of the stream is, or the maximum
15 * preferred size of the buffer.
16 */
17final class BufferStream implements StreamInterface
18{
19    /** @var int */
20    private $hwm;
21
22    /** @var string */
23    private $buffer = '';
24
25    /**
26     * @param int $hwm High water mark, representing the preferred maximum
27     *                 buffer size. If the size of the buffer exceeds the high
28     *                 water mark, then calls to write will continue to succeed
29     *                 but will return 0 to inform writers to slow down
30     *                 until the buffer has been drained by reading from it.
31     */
32    public function __construct(int $hwm = 16384)
33    {
34        $this->hwm = $hwm;
35    }
36
37    public function __toString(): string
38    {
39        return $this->getContents();
40    }
41
42    public function getContents(): string
43    {
44        $buffer = $this->buffer;
45        $this->buffer = '';
46
47        return $buffer;
48    }
49
50    public function close(): void
51    {
52        $this->buffer = '';
53    }
54
55    public function detach()
56    {
57        $this->close();
58
59        return null;
60    }
61
62    public function getSize(): ?int
63    {
64        return strlen($this->buffer);
65    }
66
67    public function isReadable(): bool
68    {
69        return true;
70    }
71
72    public function isWritable(): bool
73    {
74        return true;
75    }
76
77    public function isSeekable(): bool
78    {
79        return false;
80    }
81
82    public function rewind(): void
83    {
84        $this->seek(0);
85    }
86
87    public function seek($offset, $whence = SEEK_SET): void
88    {
89        throw new \RuntimeException('Cannot seek a BufferStream');
90    }
91
92    public function eof(): bool
93    {
94        return strlen($this->buffer) === 0;
95    }
96
97    public function tell(): int
98    {
99        throw new \RuntimeException('Cannot determine the position of a BufferStream');
100    }
101
102    /**
103     * Reads data from the buffer.
104     */
105    public function read($length): string
106    {
107        $currentLength = strlen($this->buffer);
108
109        if ($length >= $currentLength) {
110            // No need to slice the buffer because we don't have enough data.
111            $result = $this->buffer;
112            $this->buffer = '';
113        } else {
114            // Slice up the result to provide a subset of the buffer.
115            $result = substr($this->buffer, 0, $length);
116            $this->buffer = substr($this->buffer, $length);
117        }
118
119        return $result;
120    }
121
122    /**
123     * Writes data to the buffer.
124     */
125    public function write($string): int
126    {
127        $this->buffer .= $string;
128
129        if (strlen($this->buffer) >= $this->hwm) {
130            return 0;
131        }
132
133        return strlen($string);
134    }
135
136    public function getMetadata($key = null)
137    {
138        if ($key === 'hwm') {
139            return $this->hwm;
140        }
141
142        return $key ? null : [];
143    }
144}
145