ResourceStream.php 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  1. <?php
  2. declare(strict_types=1);
  3. namespace ZipStream\Test;
  4. use Psr\Http\Message\StreamInterface;
  5. use RuntimeException;
  6. /**
  7. * @internal
  8. */
  9. class ResourceStream implements StreamInterface
  10. {
  11. public function __construct(
  12. /**
  13. * @var resource
  14. */
  15. private $stream
  16. ) {}
  17. public function __toString(): string
  18. {
  19. if ($this->isSeekable()) {
  20. $this->seek(0);
  21. }
  22. return (string) stream_get_contents($this->stream);
  23. }
  24. public function close(): void
  25. {
  26. $stream = $this->detach();
  27. if ($stream) {
  28. fclose($stream);
  29. }
  30. }
  31. public function detach()
  32. {
  33. $result = $this->stream;
  34. // According to the interface, the stream is left in an unusable state;
  35. /** @psalm-suppress PossiblyNullPropertyAssignmentValue */
  36. $this->stream = null;
  37. return $result;
  38. }
  39. public function seek(int $offset, int $whence = SEEK_SET): void
  40. {
  41. if (!$this->isSeekable()) {
  42. throw new RuntimeException();
  43. }
  44. if (fseek($this->stream, $offset, $whence) !== 0) {
  45. // @codeCoverageIgnoreStart
  46. throw new RuntimeException();
  47. // @codeCoverageIgnoreEnd
  48. }
  49. }
  50. public function isSeekable(): bool
  51. {
  52. return (bool) $this->getMetadata('seekable');
  53. }
  54. public function getMetadata(?string $key = null)
  55. {
  56. $metadata = stream_get_meta_data($this->stream);
  57. return $key !== null ? @$metadata[$key] : $metadata;
  58. }
  59. public function getSize(): ?int
  60. {
  61. $stats = fstat($this->stream);
  62. return $stats['size'];
  63. }
  64. public function tell(): int
  65. {
  66. $position = ftell($this->stream);
  67. if ($position === false) {
  68. // @codeCoverageIgnoreStart
  69. throw new RuntimeException();
  70. // @codeCoverageIgnoreEnd
  71. }
  72. return $position;
  73. }
  74. public function eof(): bool
  75. {
  76. return feof($this->stream);
  77. }
  78. public function rewind(): void
  79. {
  80. $this->seek(0);
  81. }
  82. public function write(string $string): int
  83. {
  84. if (!$this->isWritable()) {
  85. throw new RuntimeException();
  86. }
  87. if (fwrite($this->stream, $string) === false) {
  88. // @codeCoverageIgnoreStart
  89. throw new RuntimeException();
  90. // @codeCoverageIgnoreEnd
  91. }
  92. return strlen($string);
  93. }
  94. public function isWritable(): bool
  95. {
  96. $mode = $this->getMetadata('mode');
  97. if (!is_string($mode)) {
  98. // @codeCoverageIgnoreStart
  99. throw new RuntimeException('Could not get stream mode from metadata!');
  100. // @codeCoverageIgnoreEnd
  101. }
  102. return preg_match('/[waxc+]/', $mode) === 1;
  103. }
  104. public function read(int $length): string
  105. {
  106. if (!$this->isReadable()) {
  107. throw new RuntimeException();
  108. }
  109. $result = fread($this->stream, $length);
  110. if ($result === false) {
  111. // @codeCoverageIgnoreStart
  112. throw new RuntimeException();
  113. // @codeCoverageIgnoreEnd
  114. }
  115. return $result;
  116. }
  117. public function isReadable(): bool
  118. {
  119. $mode = $this->getMetadata('mode');
  120. if (!is_string($mode)) {
  121. // @codeCoverageIgnoreStart
  122. throw new RuntimeException('Could not get stream mode from metadata!');
  123. // @codeCoverageIgnoreEnd
  124. }
  125. return preg_match('/[r+]/', $mode) === 1;
  126. }
  127. public function getContents(): string
  128. {
  129. if (!$this->isReadable()) {
  130. throw new RuntimeException();
  131. }
  132. $result = stream_get_contents($this->stream);
  133. if ($result === false) {
  134. // @codeCoverageIgnoreStart
  135. throw new RuntimeException();
  136. // @codeCoverageIgnoreEnd
  137. }
  138. return $result;
  139. }
  140. }