Code Coverage  | 
     ||||||||||
Classes and Traits  | 
      Functions and Methods  | 
      Lines  | 
     ||||||||
| Total |         | 
      0.00%  | 
      0 / 1  | 
              | 
      68.18%  | 
      15 / 22  | 
      CRAP |         | 
      90.43%  | 
      104 / 115  | 
     
| Stream |         | 
      0.00%  | 
      0 / 1  | 
              | 
      68.18%  | 
      15 / 22  | 
      59.84 |         | 
      90.43%  | 
      104 / 115  | 
     
| __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%  | 
      10 / 10  | 
     |||
| write |         | 
      0.00%  | 
      0 / 1  | 
      4.03 |         | 
      87.50%  | 
      7 / 8  | 
     |||
| isReadable |         | 
      100.00%  | 
      1 / 1  | 
      3 |         | 
      100.00%  | 
      6 / 6  | 
     |||
| 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 |         | 
      100.00%  | 
      1 / 1  | 
      5 |         | 
      100.00%  | 
      3 / 3  | 
     |||
| anonymous function |         | 
      0.00%  | 
      0 / 1  | 
      1.12 |         | 
      50.00%  | 
      1 / 2  | 
     |||
| isAvailableStream |         | 
      100.00%  | 
      1 / 1  | 
      3 |         | 
      100.00%  | 
      3 / 3  | 
     |||
| closeTmpStream |         | 
      100.00%  | 
      1 / 1  | 
      2 |         | 
      100.00%  | 
      4 / 4  | 
     |||
| <?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); | |
| } | |
| } | |
| } |