Blame | Last modification | View Log | RSS feed
<?phpnamespace GuzzleHttp\Psr7;use Psr\Http\Message\StreamInterface;/*** Stream decorator that can cache previously read bytes from a sequentially* read stream.*/class CachingStream implements StreamInterface{use StreamDecoratorTrait;/** @var StreamInterface Stream being wrapped */private $remoteStream;/** @var int Number of bytes to skip reading due to a write on the buffer */private $skipReadBytes = 0;/*** We will treat the buffer object as the body of the stream** @param StreamInterface $stream Stream to cache* @param StreamInterface $target Optionally specify where data is cached*/public function __construct(StreamInterface $stream,StreamInterface $target = null) {$this->remoteStream = $stream;$this->stream = $target ?: new Stream(fopen('php://temp', 'r+'));}public function getSize(){return max($this->stream->getSize(), $this->remoteStream->getSize());}public function rewind(){$this->seek(0);}public function seek($offset, $whence = SEEK_SET){if ($whence == SEEK_SET) {$byte = $offset;} elseif ($whence == SEEK_CUR) {$byte = $offset + $this->tell();} elseif ($whence == SEEK_END) {$size = $this->remoteStream->getSize();if ($size === null) {$size = $this->cacheEntireStream();}$byte = $size + $offset;} else {throw new \InvalidArgumentException('Invalid whence');}$diff = $byte - $this->stream->getSize();if ($diff > 0) {// Read the remoteStream until we have read in at least the amount// of bytes requested, or we reach the end of the file.while ($diff > 0 && !$this->remoteStream->eof()) {$this->read($diff);$diff = $byte - $this->stream->getSize();}} else {// We can just do a normal seek since we've already seen this byte.$this->stream->seek($byte);}}public function read($length){// Perform a regular read on any previously read data from the buffer$data = $this->stream->read($length);$remaining = $length - strlen($data);// More data was requested so read from the remote streamif ($remaining) {// If data was written to the buffer in a position that would have// been filled from the remote stream, then we must skip bytes on// the remote stream to emulate overwriting bytes from that// position. This mimics the behavior of other PHP stream wrappers.$remoteData = $this->remoteStream->read($remaining + $this->skipReadBytes);if ($this->skipReadBytes) {$len = strlen($remoteData);$remoteData = substr($remoteData, $this->skipReadBytes);$this->skipReadBytes = max(0, $this->skipReadBytes - $len);}$data .= $remoteData;$this->stream->write($remoteData);}return $data;}public function write($string){// When appending to the end of the currently read stream, you'll want// to skip bytes from being read from the remote stream to emulate// other stream wrappers. Basically replacing bytes of data of a fixed// length.$overflow = (strlen($string) + $this->tell()) - $this->remoteStream->tell();if ($overflow > 0) {$this->skipReadBytes += $overflow;}return $this->stream->write($string);}public function eof(){return $this->stream->eof() && $this->remoteStream->eof();}/*** Close both the remote stream and buffer stream*/public function close(){$this->remoteStream->close() && $this->stream->close();}private function cacheEntireStream(){$target = new FnStream(['write' => 'strlen']);copy_to_stream($this, $target);return $this->tell();}}