Code Coverage |
||||||||||
Classes and Traits |
Functions and Methods |
Lines |
||||||||
Total | |
0.00% |
0 / 1 |
|
66.67% |
14 / 21 |
CRAP | |
90.91% |
100 / 110 |
Stream | |
0.00% |
0 / 1 |
|
66.67% |
14 / 21 |
58.36 | |
90.91% |
100 / 110 |
__construct | |
100.00% |
1 / 1 |
1 | |
100.00% |
2 / 2 |
|||
__destruct | |
100.00% |
1 / 1 |
1 | |
100.00% |
2 / 2 |
|||
__toString | |
0.00% |
0 / 1 |
3.33 | |
66.67% |
4 / 6 |
|||
close | |
100.00% |
1 / 1 |
2 | |
100.00% |
5 / 5 |
|||
detach | |
100.00% |
1 / 1 |
1 | |
100.00% |
3 / 3 |
|||
attach | |
100.00% |
1 / 1 |
1 | |
100.00% |
3 / 3 |
|||
getSize | |
100.00% |
1 / 1 |
2 | |
100.00% |
4 / 4 |
|||
tell | |
0.00% |
0 / 1 |
3.04 | |
83.33% |
5 / 6 |
|||
eof | |
100.00% |
1 / 1 |
2 | |
100.00% |
3 / 3 |
|||
isSeekable | |
100.00% |
1 / 1 |
2 | |
100.00% |
4 / 4 |
|||
seek | |
0.00% |
0 / 1 |
4.25 | |
75.00% |
6 / 8 |
|||
rewind | |
100.00% |
1 / 1 |
1 | |
100.00% |
1 / 1 |
|||
isWritable | |
100.00% |
1 / 1 |
6 | |
100.00% |
9 / 9 |
|||
write | |
0.00% |
0 / 1 |
4.03 | |
87.50% |
7 / 8 |
|||
isReadable | |
100.00% |
1 / 1 |
3 | |
100.00% |
5 / 5 |
|||
read | |
0.00% |
0 / 1 |
4.25 | |
75.00% |
6 / 8 |
|||
getContents | |
0.00% |
0 / 1 |
3.04 | |
83.33% |
5 / 6 |
|||
getMetaData | |
100.00% |
1 / 1 |
3 | |
100.00% |
6 / 6 |
|||
setStream | |
0.00% |
0 / 1 |
5.01 | |
93.33% |
14 / 15 |
|||
isAvailableStream | |
100.00% |
1 / 1 |
3 | |
100.00% |
3 / 3 |
|||
closeTmpStream | |
100.00% |
1 / 1 |
2 | |
100.00% |
3 / 3 |
<?php | |
namespace Puyo\Psr7; | |
/** | |
* ストリームを抽象化したもの | |
* | |
* PSR-7 の StreamInterface の実装。 | |
* HTTPに限らず利用できるが、現状はバイナリにしか対応していない。 | |
*/ | |
class Stream implements \Psr\Http\Message\StreamInterface | |
{ | |
/** @var resource */ | |
protected $fp; | |
/** | |
* ファイルのパスから生成した場合に内部で開くためのStream | |
* @var resource | |
*/ | |
protected $tmpFp; | |
/** | |
* ファイルのフルパスもしくはresourceから生成する | |
* @param resource|string $fileOrStream | |
* @param string $mode | |
*/ | |
public function __construct($fileOrStream, $mode='rb') { | |
$this->setStream($fileOrStream, $mode); | |
} | |
/** | |
* ファイルパスから生成した場合の一時Streamを解放する | |
*/ | |
public function __destruct() { | |
$this->closeTmpStream(); | |
} | |
/** | |
* すべてのデータを文字列で取得する | |
* @return string content | |
*/ | |
public function __toString() { | |
if(!$this->isReadable()) { | |
return ''; | |
} | |
try { | |
$this->rewind(); | |
return $this->getContents(); | |
} catch(\RuntimeException $e) { | |
return ''; | |
} | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function close() { | |
if(!$this->fp) { | |
return; | |
} | |
$fp = $this->detach(); | |
fclose($fp); | |
} | |
/** | |
* @return resource | |
*/ | |
public function detach() { | |
$fp = $this->fp; | |
$this->fp = null; | |
return $fp; | |
} | |
/** | |
* streamもしくはファイルのフルパスからstreamを割り当てる | |
* @param string|resource $fileOrStream | |
* @param string $mode | |
*/ | |
public function attach($fileOrStream, $mode='rb') { | |
$this->closeTmpStream(); | |
$this->setStream($fileOrStream, $mode); | |
} | |
/** | |
* データサイズを返す | |
* @return int|null 閉じられている場合はnull | |
*/ | |
public function getSize() { | |
if(null === $this->fp) { | |
return null; | |
} | |
$stats = fstat($this->fp); | |
return $stats['size']; | |
} | |
/** | |
* 現在の書き込みのポインタ位置を返す | |
* {@inheritdoc} | |
* @return int | |
*/ | |
public function tell() { | |
if(null === $this->fp) { | |
throw new \RuntimeException('No resource available. Cannot tell position'); | |
} | |
$result = ftell($this->fp); | |
if(!is_int($result)) { | |
throw new \RuntimeException('Cannot tell position'); | |
} | |
return $result; | |
} | |
/** | |
* {@inheritdoc} | |
* @return bool 終端、もしくは未オープンの場合 | |
*/ | |
public function eof() { | |
if(null === $this->fp) { | |
return true; | |
} | |
return feof($this->fp); | |
} | |
/** | |
* {@inheritdoc} | |
* @return bool | |
*/ | |
public function isSeekable() { | |
if(!$this->isAvailableStream($this->fp)) { | |
return false; | |
} | |
$meta = stream_get_meta_data($this->fp); | |
return $meta['seekable']; | |
} | |
/** | |
* {@inheritdoc} | |
* @param int $offset | |
* @param int $whence SEEK_* constant | |
* @return void | |
* @throws \RuntimeException シーク失敗時 | |
*/ | |
public function seek($offset, $whence=SEEK_SET) { | |
if (null === $this->fp) { | |
throw new \RuntimeException('No resource available. Cannot seek position'); | |
} | |
if (!$this->isSeekable()) { | |
throw new \RuntimeException('Stream is not seekable'); | |
} | |
$result = fseek($this->fp, $offset, $whence); | |
if (0 !== $result) { | |
throw new \RuntimeException('Error seeking within stream'); | |
} | |
} | |
/** | |
* ポインタを先頭に戻す | |
* @return void | |
*/ | |
public function rewind() { | |
return $this->seek(0); | |
} | |
/** | |
* {@inheritdoc} | |
* @return bool | |
*/ | |
public function isWritable() { | |
if(null === $this->fp) { | |
return false; | |
} | |
$meta = stream_get_meta_data($this->fp); | |
$mode = $meta['mode']; | |
return ( | |
strstr($mode, 'x') | |
|| strstr($mode, 'w') | |
|| strstr($mode, 'c') | |
|| strstr($mode, 'a') | |
|| strstr($mode, '+') | |
); | |
} | |
/** | |
* 書き込む | |
* @param string $string | |
* @return int 書き込みバイト数 | |
*/ | |
public function write($string) { | |
if(null === $this->fp) { | |
throw new \RuntimeException('No resource available. Cannot write'); | |
} | |
if (!$this->isWritable()) { | |
throw new \RuntimeException('Stream is not writable'); | |
} | |
$result = fwrite($this->fp, $string); | |
if(false === $result) { | |
throw new \RuntimeException('Error writing to stream'); | |
} | |
return $result; | |
} | |
/** | |
* {@inheritdoc} | |
* @return bool | |
*/ | |
public function isReadable() { | |
if(null === $this->fp) { | |
return false; | |
} | |
$meta = stream_get_meta_data($this->fp); | |
$mode = $meta['mode']; | |
return ( | |
strstr($mode, 'r') || strstr($mode, '+') | |
); | |
} | |
/** | |
* 読み込み | |
* @param int $length bytes | |
* @return string 読み込んだデータ | |
*/ | |
public function read($length) { | |
if (null === $this->fp) { | |
throw new \RuntimeException('No resource available. Cannot read'); | |
} | |
if (!$this->isReadable()) { | |
throw new \RuntimeException('Stream is not readable'); | |
} | |
$result = fread($this->fp, $length); | |
if (false === $result) { | |
throw new \RuntimeException('Error reading from stream'); | |
} | |
return $result; | |
} | |
/** | |
* 残りのストリームすべてを返す | |
* @return string | |
*/ | |
public function getContents() { | |
if (!$this->isReadable()) { | |
throw new \RuntimeException('Stream is not readable'); | |
} | |
$result = stream_get_contents($this->fp); | |
if (false === $result) { | |
throw new \RuntimeException('Error reading from stream'); | |
} | |
return $result; | |
} | |
/** | |
* {@inheritdoc} | |
* @param string $key (optional) | |
* @return array|null | |
*/ | |
public function getMetaData($key=null) { | |
$metadata = stream_get_meta_data($this->fp); | |
if($key === null) { | |
return $metadata; | |
} | |
if (!array_key_exists($key, $metadata)) { | |
return null; | |
} | |
return $metadata[$key]; | |
} | |
private function setStream($fileOrStream, $mode='rb') { | |
$error = null; | |
$fp = $tmpFp = null; | |
if(is_string($fileOrStream)) { | |
set_error_handler(function ($e) use (&$error) { | |
$error = $e; | |
}, E_WARNING); | |
$tmpFp = fopen($fileOrStream, $mode); | |
restore_error_handler(); | |
if($error !== null) { | |
throw new \InvalidArgumentException('Invalid stream reference provided'); | |
} | |
} elseif($this->isAvailableStream($fileOrStream)) { | |
$fp = $fileOrStream; | |
} | |
$this->fp = $fp; | |
if($tmpFp !== null) { | |
$this->fp = $this->tmpFp = $tmpFp; | |
} | |
} | |
/** | |
* @param resource $fp | |
* @return bool | |
*/ | |
private function isAvailableStream($fp) { | |
if(is_resource($fp) && 'stream' === get_resource_type($fp)) { | |
return true; | |
} | |
return false; | |
} | |
private function closeTmpStream() { | |
if($this->isAvailableStream($this->tmpFp)) { | |
fclose($this->tmpFp); | |
} | |
} | |
} |