PumpStream.php 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. <?php
  2. declare(strict_types=1);
  3. namespace GuzzleHttp\Psr7;
  4. use Psr\Http\Message\StreamInterface;
  5. /**
  6. * Provides a read only stream that pumps data from a PHP callable.
  7. *
  8. * When invoking the provided callable, the PumpStream will pass the amount of
  9. * data requested to read to the callable. The callable can choose to ignore
  10. * this value and return fewer or more bytes than requested. Any extra data
  11. * returned by the provided callable is buffered internally until drained using
  12. * the read() function of the PumpStream. The provided callable MUST return
  13. * false when there is no more data to read.
  14. */
  15. final class PumpStream implements StreamInterface
  16. {
  17. /** @var callable|null */
  18. private $source;
  19. /** @var int|null */
  20. private $size;
  21. /** @var int */
  22. private $tellPos = 0;
  23. /** @var array */
  24. private $metadata;
  25. /** @var BufferStream */
  26. private $buffer;
  27. /**
  28. * @param callable(int): (string|null|false) $source Source of the stream data. The callable MAY
  29. * accept an integer argument used to control the
  30. * amount of data to return. The callable MUST
  31. * return a string when called, or false|null on error
  32. * or EOF.
  33. * @param array{size?: int, metadata?: array} $options Stream options:
  34. * - metadata: Hash of metadata to use with stream.
  35. * - size: Size of the stream, if known.
  36. */
  37. public function __construct(callable $source, array $options = [])
  38. {
  39. $this->source = $source;
  40. $this->size = $options['size'] ?? null;
  41. $this->metadata = $options['metadata'] ?? [];
  42. $this->buffer = new BufferStream();
  43. }
  44. public function __toString(): string
  45. {
  46. try {
  47. return Utils::copyToString($this);
  48. } catch (\Throwable $e) {
  49. if (\PHP_VERSION_ID >= 70400) {
  50. throw $e;
  51. }
  52. trigger_error(sprintf('%s::__toString exception: %s', self::class, (string) $e), E_USER_ERROR);
  53. return '';
  54. }
  55. }
  56. public function close(): void
  57. {
  58. $this->detach();
  59. }
  60. public function detach()
  61. {
  62. $this->tellPos = 0;
  63. $this->source = null;
  64. return null;
  65. }
  66. public function getSize(): ?int
  67. {
  68. return $this->size;
  69. }
  70. public function tell(): int
  71. {
  72. return $this->tellPos;
  73. }
  74. public function eof(): bool
  75. {
  76. return $this->source === null;
  77. }
  78. public function isSeekable(): bool
  79. {
  80. return false;
  81. }
  82. public function rewind(): void
  83. {
  84. $this->seek(0);
  85. }
  86. public function seek($offset, $whence = SEEK_SET): void
  87. {
  88. throw new \RuntimeException('Cannot seek a PumpStream');
  89. }
  90. public function isWritable(): bool
  91. {
  92. return false;
  93. }
  94. public function write($string): int
  95. {
  96. throw new \RuntimeException('Cannot write to a PumpStream');
  97. }
  98. public function isReadable(): bool
  99. {
  100. return true;
  101. }
  102. public function read($length): string
  103. {
  104. $data = $this->buffer->read($length);
  105. $readLen = strlen($data);
  106. $this->tellPos += $readLen;
  107. $remaining = $length - $readLen;
  108. if ($remaining) {
  109. $this->pump($remaining);
  110. $data .= $this->buffer->read($remaining);
  111. $this->tellPos += strlen($data) - $readLen;
  112. }
  113. return $data;
  114. }
  115. public function getContents(): string
  116. {
  117. $result = '';
  118. while (!$this->eof()) {
  119. $result .= $this->read(1000000);
  120. }
  121. return $result;
  122. }
  123. /**
  124. * {@inheritdoc}
  125. *
  126. * @return mixed
  127. */
  128. public function getMetadata($key = null)
  129. {
  130. if (!$key) {
  131. return $this->metadata;
  132. }
  133. return $this->metadata[$key] ?? null;
  134. }
  135. private function pump(int $length): void
  136. {
  137. if ($this->source) {
  138. do {
  139. $data = call_user_func($this->source, $length);
  140. if ($data === false || $data === null) {
  141. $this->source = null;
  142. return;
  143. }
  144. $this->buffer->write($data);
  145. $length -= strlen($data);
  146. } while ($length > 0);
  147. }
  148. }
  149. }