File indexing completed on 2024-12-29 05:27:23
0001 <?php 0002 namespace GuzzleHttp\Psr7; 0003 0004 use Psr\Http\Message\StreamInterface; 0005 0006 /** 0007 * Stream decorator that can cache previously read bytes from a sequentially 0008 * read stream. 0009 */ 0010 class CachingStream implements StreamInterface 0011 { 0012 use StreamDecoratorTrait; 0013 0014 /** @var StreamInterface Stream being wrapped */ 0015 private $remoteStream; 0016 0017 /** @var int Number of bytes to skip reading due to a write on the buffer */ 0018 private $skipReadBytes = 0; 0019 0020 /** 0021 * We will treat the buffer object as the body of the stream 0022 * 0023 * @param StreamInterface $stream Stream to cache 0024 * @param StreamInterface $target Optionally specify where data is cached 0025 */ 0026 public function __construct( 0027 StreamInterface $stream, 0028 StreamInterface $target = null 0029 ) { 0030 $this->remoteStream = $stream; 0031 $this->stream = $target ?: new Stream(fopen('php://temp', 'r+')); 0032 } 0033 0034 public function getSize() 0035 { 0036 return max($this->stream->getSize(), $this->remoteStream->getSize()); 0037 } 0038 0039 public function rewind() 0040 { 0041 $this->seek(0); 0042 } 0043 0044 public function seek($offset, $whence = SEEK_SET) 0045 { 0046 if ($whence == SEEK_SET) { 0047 $byte = $offset; 0048 } elseif ($whence == SEEK_CUR) { 0049 $byte = $offset + $this->tell(); 0050 } elseif ($whence == SEEK_END) { 0051 $size = $this->remoteStream->getSize(); 0052 if ($size === null) { 0053 $size = $this->cacheEntireStream(); 0054 } 0055 $byte = $size + $offset; 0056 } else { 0057 throw new \InvalidArgumentException('Invalid whence'); 0058 } 0059 0060 $diff = $byte - $this->stream->getSize(); 0061 0062 if ($diff > 0) { 0063 // Read the remoteStream until we have read in at least the amount 0064 // of bytes requested, or we reach the end of the file. 0065 while ($diff > 0 && !$this->remoteStream->eof()) { 0066 $this->read($diff); 0067 $diff = $byte - $this->stream->getSize(); 0068 } 0069 } else { 0070 // We can just do a normal seek since we've already seen this byte. 0071 $this->stream->seek($byte); 0072 } 0073 } 0074 0075 public function read($length) 0076 { 0077 // Perform a regular read on any previously read data from the buffer 0078 $data = $this->stream->read($length); 0079 $remaining = $length - strlen($data); 0080 0081 // More data was requested so read from the remote stream 0082 if ($remaining) { 0083 // If data was written to the buffer in a position that would have 0084 // been filled from the remote stream, then we must skip bytes on 0085 // the remote stream to emulate overwriting bytes from that 0086 // position. This mimics the behavior of other PHP stream wrappers. 0087 $remoteData = $this->remoteStream->read( 0088 $remaining + $this->skipReadBytes 0089 ); 0090 0091 if ($this->skipReadBytes) { 0092 $len = strlen($remoteData); 0093 $remoteData = substr($remoteData, $this->skipReadBytes); 0094 $this->skipReadBytes = max(0, $this->skipReadBytes - $len); 0095 } 0096 0097 $data .= $remoteData; 0098 $this->stream->write($remoteData); 0099 } 0100 0101 return $data; 0102 } 0103 0104 public function write($string) 0105 { 0106 // When appending to the end of the currently read stream, you'll want 0107 // to skip bytes from being read from the remote stream to emulate 0108 // other stream wrappers. Basically replacing bytes of data of a fixed 0109 // length. 0110 $overflow = (strlen($string) + $this->tell()) - $this->remoteStream->tell(); 0111 if ($overflow > 0) { 0112 $this->skipReadBytes += $overflow; 0113 } 0114 0115 return $this->stream->write($string); 0116 } 0117 0118 public function eof() 0119 { 0120 return $this->stream->eof() && $this->remoteStream->eof(); 0121 } 0122 0123 /** 0124 * Close both the remote stream and buffer stream 0125 */ 0126 public function close() 0127 { 0128 $this->remoteStream->close() && $this->stream->close(); 0129 } 0130 0131 private function cacheEntireStream() 0132 { 0133 $target = new FnStream(['write' => 'strlen']); 0134 copy_to_stream($this, $target); 0135 0136 return $this->tell(); 0137 } 0138 }