Chris@0
|
1 <?php
|
Chris@0
|
2 namespace GuzzleHttp\Psr7;
|
Chris@0
|
3
|
Chris@0
|
4 use Psr\Http\Message\StreamInterface;
|
Chris@0
|
5
|
Chris@0
|
6 /**
|
Chris@0
|
7 * Stream decorator that can cache previously read bytes from a sequentially
|
Chris@0
|
8 * read stream.
|
Chris@0
|
9 */
|
Chris@0
|
10 class CachingStream implements StreamInterface
|
Chris@0
|
11 {
|
Chris@0
|
12 use StreamDecoratorTrait;
|
Chris@0
|
13
|
Chris@0
|
14 /** @var StreamInterface Stream being wrapped */
|
Chris@0
|
15 private $remoteStream;
|
Chris@0
|
16
|
Chris@0
|
17 /** @var int Number of bytes to skip reading due to a write on the buffer */
|
Chris@0
|
18 private $skipReadBytes = 0;
|
Chris@0
|
19
|
Chris@0
|
20 /**
|
Chris@0
|
21 * We will treat the buffer object as the body of the stream
|
Chris@0
|
22 *
|
Chris@0
|
23 * @param StreamInterface $stream Stream to cache
|
Chris@0
|
24 * @param StreamInterface $target Optionally specify where data is cached
|
Chris@0
|
25 */
|
Chris@0
|
26 public function __construct(
|
Chris@0
|
27 StreamInterface $stream,
|
Chris@0
|
28 StreamInterface $target = null
|
Chris@0
|
29 ) {
|
Chris@0
|
30 $this->remoteStream = $stream;
|
Chris@0
|
31 $this->stream = $target ?: new Stream(fopen('php://temp', 'r+'));
|
Chris@0
|
32 }
|
Chris@0
|
33
|
Chris@0
|
34 public function getSize()
|
Chris@0
|
35 {
|
Chris@0
|
36 return max($this->stream->getSize(), $this->remoteStream->getSize());
|
Chris@0
|
37 }
|
Chris@0
|
38
|
Chris@0
|
39 public function rewind()
|
Chris@0
|
40 {
|
Chris@0
|
41 $this->seek(0);
|
Chris@0
|
42 }
|
Chris@0
|
43
|
Chris@0
|
44 public function seek($offset, $whence = SEEK_SET)
|
Chris@0
|
45 {
|
Chris@0
|
46 if ($whence == SEEK_SET) {
|
Chris@0
|
47 $byte = $offset;
|
Chris@0
|
48 } elseif ($whence == SEEK_CUR) {
|
Chris@0
|
49 $byte = $offset + $this->tell();
|
Chris@0
|
50 } elseif ($whence == SEEK_END) {
|
Chris@0
|
51 $size = $this->remoteStream->getSize();
|
Chris@0
|
52 if ($size === null) {
|
Chris@0
|
53 $size = $this->cacheEntireStream();
|
Chris@0
|
54 }
|
Chris@0
|
55 $byte = $size + $offset;
|
Chris@0
|
56 } else {
|
Chris@0
|
57 throw new \InvalidArgumentException('Invalid whence');
|
Chris@0
|
58 }
|
Chris@0
|
59
|
Chris@0
|
60 $diff = $byte - $this->stream->getSize();
|
Chris@0
|
61
|
Chris@0
|
62 if ($diff > 0) {
|
Chris@0
|
63 // Read the remoteStream until we have read in at least the amount
|
Chris@0
|
64 // of bytes requested, or we reach the end of the file.
|
Chris@0
|
65 while ($diff > 0 && !$this->remoteStream->eof()) {
|
Chris@0
|
66 $this->read($diff);
|
Chris@0
|
67 $diff = $byte - $this->stream->getSize();
|
Chris@0
|
68 }
|
Chris@0
|
69 } else {
|
Chris@0
|
70 // We can just do a normal seek since we've already seen this byte.
|
Chris@0
|
71 $this->stream->seek($byte);
|
Chris@0
|
72 }
|
Chris@0
|
73 }
|
Chris@0
|
74
|
Chris@0
|
75 public function read($length)
|
Chris@0
|
76 {
|
Chris@0
|
77 // Perform a regular read on any previously read data from the buffer
|
Chris@0
|
78 $data = $this->stream->read($length);
|
Chris@0
|
79 $remaining = $length - strlen($data);
|
Chris@0
|
80
|
Chris@0
|
81 // More data was requested so read from the remote stream
|
Chris@0
|
82 if ($remaining) {
|
Chris@0
|
83 // If data was written to the buffer in a position that would have
|
Chris@0
|
84 // been filled from the remote stream, then we must skip bytes on
|
Chris@0
|
85 // the remote stream to emulate overwriting bytes from that
|
Chris@0
|
86 // position. This mimics the behavior of other PHP stream wrappers.
|
Chris@0
|
87 $remoteData = $this->remoteStream->read(
|
Chris@0
|
88 $remaining + $this->skipReadBytes
|
Chris@0
|
89 );
|
Chris@0
|
90
|
Chris@0
|
91 if ($this->skipReadBytes) {
|
Chris@0
|
92 $len = strlen($remoteData);
|
Chris@0
|
93 $remoteData = substr($remoteData, $this->skipReadBytes);
|
Chris@0
|
94 $this->skipReadBytes = max(0, $this->skipReadBytes - $len);
|
Chris@0
|
95 }
|
Chris@0
|
96
|
Chris@0
|
97 $data .= $remoteData;
|
Chris@0
|
98 $this->stream->write($remoteData);
|
Chris@0
|
99 }
|
Chris@0
|
100
|
Chris@0
|
101 return $data;
|
Chris@0
|
102 }
|
Chris@0
|
103
|
Chris@0
|
104 public function write($string)
|
Chris@0
|
105 {
|
Chris@0
|
106 // When appending to the end of the currently read stream, you'll want
|
Chris@0
|
107 // to skip bytes from being read from the remote stream to emulate
|
Chris@0
|
108 // other stream wrappers. Basically replacing bytes of data of a fixed
|
Chris@0
|
109 // length.
|
Chris@0
|
110 $overflow = (strlen($string) + $this->tell()) - $this->remoteStream->tell();
|
Chris@0
|
111 if ($overflow > 0) {
|
Chris@0
|
112 $this->skipReadBytes += $overflow;
|
Chris@0
|
113 }
|
Chris@0
|
114
|
Chris@0
|
115 return $this->stream->write($string);
|
Chris@0
|
116 }
|
Chris@0
|
117
|
Chris@0
|
118 public function eof()
|
Chris@0
|
119 {
|
Chris@0
|
120 return $this->stream->eof() && $this->remoteStream->eof();
|
Chris@0
|
121 }
|
Chris@0
|
122
|
Chris@0
|
123 /**
|
Chris@0
|
124 * Close both the remote stream and buffer stream
|
Chris@0
|
125 */
|
Chris@0
|
126 public function close()
|
Chris@0
|
127 {
|
Chris@0
|
128 $this->remoteStream->close() && $this->stream->close();
|
Chris@0
|
129 }
|
Chris@0
|
130
|
Chris@0
|
131 private function cacheEntireStream()
|
Chris@0
|
132 {
|
Chris@0
|
133 $target = new FnStream(['write' => 'strlen']);
|
Chris@0
|
134 copy_to_stream($this, $target);
|
Chris@0
|
135
|
Chris@0
|
136 return $this->tell();
|
Chris@0
|
137 }
|
Chris@0
|
138 }
|