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