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