183 lines
		
	
	
		
			4.7 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			183 lines
		
	
	
		
			4.7 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
<?php
 | 
						|
 | 
						|
namespace Ulmus\Api\Stream;
 | 
						|
 | 
						|
use Psr\Http\Message\StreamInterface;
 | 
						|
 | 
						|
class Stream implements StreamInterface
 | 
						|
{
 | 
						|
    protected mixed $stream;
 | 
						|
 | 
						|
    public function __toString()
 | 
						|
    {
 | 
						|
        return $this->getContents();
 | 
						|
    }
 | 
						|
 | 
						|
    public static function fromTemp(string $content = "", string $mode = 'wb+') : StreamInterface
 | 
						|
    {
 | 
						|
        return static::fromMemory($content, $mode, "php://temp");
 | 
						|
    }
 | 
						|
 | 
						|
    public static function fromMemory(string $content = "", string $mode = 'wb+', string $destination = "php://memory") : StreamInterface
 | 
						|
    {
 | 
						|
        $obj = new static();
 | 
						|
        $obj->create($destination, $mode);
 | 
						|
        $obj->write($content);
 | 
						|
 | 
						|
        return $obj;
 | 
						|
    }
 | 
						|
 | 
						|
    public function create(mixed $stream, string $mode) : void
 | 
						|
    {
 | 
						|
        if (is_string($stream)) {
 | 
						|
            try {
 | 
						|
                $stream = fopen($stream, $mode);
 | 
						|
            } catch (\Throwable $e) {
 | 
						|
                throw new \RuntimeException(sprintf('Invalid stream reference provided: %s', $error->getMessage()), 0, $e);
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        if ( is_resource($stream) || $stream instanceof StreamInterface ) {
 | 
						|
            $this->attach($stream);
 | 
						|
        }
 | 
						|
        else {
 | 
						|
            throw new \RuntimeException("Given stream must be a resource, a StreamInterface or a string pointing to a stream destination for fopen.");
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    public function attach(mixed $stream) : void
 | 
						|
    {
 | 
						|
        $this->stream = $stream;
 | 
						|
    }
 | 
						|
 | 
						|
    public function detach() : mixed
 | 
						|
    {
 | 
						|
        $stream = $this->stream;
 | 
						|
        unset($this->stream);
 | 
						|
 | 
						|
        return $stream;
 | 
						|
    }
 | 
						|
 | 
						|
    public function getSize(): null|int
 | 
						|
    {
 | 
						|
        if (false !== $result = fstat($this->stream)){
 | 
						|
            return $result['size'];
 | 
						|
        }
 | 
						|
 | 
						|
        return null;
 | 
						|
    }
 | 
						|
 | 
						|
    public function tell() : int
 | 
						|
    {
 | 
						|
        if ( false === $result = ftell($this->stream) ) {
 | 
						|
            throw new \RuntimeException("An error occured while trying to fetch current position pointer in a stream.");
 | 
						|
        }
 | 
						|
 | 
						|
        return $result;
 | 
						|
    }
 | 
						|
 | 
						|
    public function eof() : bool
 | 
						|
    {
 | 
						|
        return ! $this->stream || feof($this->stream);
 | 
						|
    }
 | 
						|
 | 
						|
    public function isSeekable() : bool
 | 
						|
    {
 | 
						|
        return stream_get_meta_data($this->stream)['seekable'] ?? false;
 | 
						|
    }
 | 
						|
 | 
						|
    public function seek($offset, $whence = SEEK_SET) : void
 | 
						|
    {
 | 
						|
        if (! $this->isSeekable()) {
 | 
						|
            throw new \InvalidArgumentException("An error occured while trying to seek an unseekable stream.");
 | 
						|
        }
 | 
						|
 | 
						|
        if ( 0 !== fseek($this->stream, $offset, $whence) ) {
 | 
						|
            throw new \RuntimeException("An error occured while trying to seek a position in a stream.");
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    public function rewind() : void
 | 
						|
    {
 | 
						|
        $this->seek(0);
 | 
						|
    }
 | 
						|
 | 
						|
    public function isWritable() : bool
 | 
						|
    {
 | 
						|
        return $this->hasMode('acwx+');
 | 
						|
    }
 | 
						|
 | 
						|
    public function write($string) : false|int
 | 
						|
    {
 | 
						|
        if (! $this->isWritable()) {
 | 
						|
            throw new \UnexpectedValueException("An error occured while trying to seek an unseekable stream.");
 | 
						|
        }
 | 
						|
 | 
						|
        $result = fwrite($this->stream, $string);
 | 
						|
 | 
						|
        if ( $result === false ) {
 | 
						|
            throw new \UnexpectedValueException("fwrite() failed to write into stream.");
 | 
						|
        }
 | 
						|
 | 
						|
        return $result;
 | 
						|
    }
 | 
						|
 | 
						|
    public function close(): void
 | 
						|
    {
 | 
						|
        if (! $this->stream) {
 | 
						|
            return;
 | 
						|
        }
 | 
						|
 | 
						|
        $stream = $this->detach();
 | 
						|
        fclose($stream);
 | 
						|
    }
 | 
						|
 | 
						|
    public function isReadable(): bool
 | 
						|
    {
 | 
						|
        return $this->hasMode('r+');
 | 
						|
    }
 | 
						|
 | 
						|
    public function read($length): string
 | 
						|
    {
 | 
						|
        if (! $this->isReadable()) {
 | 
						|
            throw new \UnexpectedValueException("An error occured while trying to read a stream which was not readable.");
 | 
						|
        }
 | 
						|
        elseif ( false === $result = fread($this->stream, $length) )
 | 
						|
            throw new \RuntimeException("Unreadable stream given; please check your stream's mode");{
 | 
						|
        }
 | 
						|
 | 
						|
        return $result;
 | 
						|
    }
 | 
						|
 | 
						|
    public function getContents(): string
 | 
						|
    {
 | 
						|
        if (! $this->isReadable()) {
 | 
						|
            throw new \UnexpectedValueException("Unreadable stream given; please check your stream's mode");
 | 
						|
        }
 | 
						|
 | 
						|
        if ( $this->isSeekable() ) {
 | 
						|
            $this->rewind();
 | 
						|
        }
 | 
						|
 | 
						|
        if (false === $result = stream_get_contents($this->stream)) {
 | 
						|
            throw new \RuntimeException("Failure happened on stream_get_contents() from stream. ");
 | 
						|
        }
 | 
						|
 | 
						|
        return $result;
 | 
						|
    }
 | 
						|
 | 
						|
    public function getMetadata(string $key = null) : mixed
 | 
						|
    {
 | 
						|
        $metadata = stream_get_meta_data($this->stream);
 | 
						|
 | 
						|
        return is_null($key) ? $metadata : $metadata[$key] ?? null;
 | 
						|
    }
 | 
						|
 | 
						|
    protected function hasMode(string $search) : bool
 | 
						|
    {
 | 
						|
        $meta = stream_get_meta_data($this->stream);
 | 
						|
        $mode = $meta['mode'];
 | 
						|
 | 
						|
        return strpbrk($mode, $search) !== false;
 | 
						|
    }
 | 
						|
} |