1<?php 2namespace GuzzleHttp\Stream; 3 4use GuzzleHttp\Stream\Exception\CannotAttachException; 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 return Utils::copyToString($this); 54 } 55 56 public function close() 57 { 58 $this->detach(); 59 } 60 61 public function detach() 62 { 63 $this->tellPos = false; 64 $this->source = null; 65 } 66 67 public function attach($stream) 68 { 69 throw new CannotAttachException(); 70 } 71 72 public function getSize() 73 { 74 return $this->size; 75 } 76 77 public function tell() 78 { 79 return $this->tellPos; 80 } 81 82 public function eof() 83 { 84 return !$this->source; 85 } 86 87 public function isSeekable() 88 { 89 return false; 90 } 91 92 public function seek($offset, $whence = SEEK_SET) 93 { 94 return false; 95 } 96 97 public function isWritable() 98 { 99 return false; 100 } 101 102 public function write($string) 103 { 104 return false; 105 } 106 107 public function isReadable() 108 { 109 return true; 110 } 111 112 public function read($length) 113 { 114 $data = $this->buffer->read($length); 115 $readLen = strlen($data); 116 $this->tellPos += $readLen; 117 $remaining = $length - $readLen; 118 119 if ($remaining) { 120 $this->pump($remaining); 121 $data .= $this->buffer->read($remaining); 122 $this->tellPos += strlen($data) - $readLen; 123 } 124 125 return $data; 126 } 127 128 public function getContents() 129 { 130 $result = ''; 131 while (!$this->eof()) { 132 $result .= $this->read(1000000); 133 } 134 135 return $result; 136 } 137 138 public function getMetadata($key = null) 139 { 140 if (!$key) { 141 return $this->metadata; 142 } 143 144 return isset($this->metadata[$key]) ? $this->metadata[$key] : null; 145 } 146 147 private function pump($length) 148 { 149 if ($this->source) { 150 do { 151 $data = call_user_func($this->source, $length); 152 if ($data === false || $data === null) { 153 $this->source = null; 154 return; 155 } 156 $this->buffer->write($data); 157 $length -= strlen($data); 158 } while ($length > 0); 159 } 160 } 161} 162