2015-08-17 17:00:26 -07:00
< ? php
2015-08-27 12:03:05 -07:00
namespace GuzzleHttp\Psr7 ;
2015-08-17 17:00:26 -07:00
2015-08-27 12:03:05 -07:00
use Psr\Http\Message\StreamInterface ;
2015-08-17 17:00:26 -07:00
/**
* 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 ());
}
2015-08-27 12:03:05 -07:00
public function rewind ()
{
$this -> seek ( 0 );
}
2015-08-17 17:00:26 -07:00
public function seek ( $offset , $whence = SEEK_SET )
{
if ( $whence == SEEK_SET ) {
$byte = $offset ;
} elseif ( $whence == SEEK_CUR ) {
$byte = $offset + $this -> tell ();
2015-09-04 13:20:09 -07:00
} elseif ( $whence == SEEK_END ) {
$size = $this -> remoteStream -> getSize ();
if ( $size === null ) {
$size = $this -> cacheEntireStream ();
}
// Because 0 is the first byte, we seek to size - 1.
$byte = $size - 1 - $offset ;
2015-08-17 17:00:26 -07:00
} else {
2015-09-04 13:20:09 -07:00
throw new \InvalidArgumentException ( 'Invalid whence' );
2015-08-17 17:00:26 -07:00
}
2015-09-04 13:20:09 -07:00
$diff = $byte - $this -> stream -> getSize ();
2015-08-17 17:00:26 -07:00
2015-09-04 13:20:09 -07:00
if ( $diff > 0 ) {
// If the seek byte is greater the number of read bytes, then read
// the difference of bytes to cache the bytes and inherently seek.
$this -> read ( $diff );
} else {
// We can just do a normal seek since we've already seen this byte.
$this -> stream -> seek ( $byte );
}
2015-08-17 17:00:26 -07:00
}
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 stream
if ( $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 ();
}
2015-09-04 13:20:09 -07:00
private function cacheEntireStream ()
{
$target = new FnStream ([ 'write' => 'strlen' ]);
copy_to_stream ( $this , $target );
return $this -> tell ();
}
2015-08-17 17:00:26 -07:00
}