File indexing completed on 2025-02-23 05:32:05
0001 <?php 0002 namespace GuzzleHttp\Psr7; 0003 0004 use Psr\Http\Message\StreamInterface; 0005 0006 /** 0007 * Reads from multiple streams, one after the other. 0008 * 0009 * This is a read-only stream decorator. 0010 */ 0011 class AppendStream implements StreamInterface 0012 { 0013 /** @var StreamInterface[] Streams being decorated */ 0014 private $streams = []; 0015 0016 private $seekable = true; 0017 private $current = 0; 0018 private $pos = 0; 0019 private $detached = false; 0020 0021 /** 0022 * @param StreamInterface[] $streams Streams to decorate. Each stream must 0023 * be readable. 0024 */ 0025 public function __construct(array $streams = []) 0026 { 0027 foreach ($streams as $stream) { 0028 $this->addStream($stream); 0029 } 0030 } 0031 0032 public function __toString() 0033 { 0034 try { 0035 $this->rewind(); 0036 return $this->getContents(); 0037 } catch (\Exception $e) { 0038 return ''; 0039 } 0040 } 0041 0042 /** 0043 * Add a stream to the AppendStream 0044 * 0045 * @param StreamInterface $stream Stream to append. Must be readable. 0046 * 0047 * @throws \InvalidArgumentException if the stream is not readable 0048 */ 0049 public function addStream(StreamInterface $stream) 0050 { 0051 if (!$stream->isReadable()) { 0052 throw new \InvalidArgumentException('Each stream must be readable'); 0053 } 0054 0055 // The stream is only seekable if all streams are seekable 0056 if (!$stream->isSeekable()) { 0057 $this->seekable = false; 0058 } 0059 0060 $this->streams[] = $stream; 0061 } 0062 0063 public function getContents() 0064 { 0065 return copy_to_string($this); 0066 } 0067 0068 /** 0069 * Closes each attached stream. 0070 * 0071 * {@inheritdoc} 0072 */ 0073 public function close() 0074 { 0075 $this->pos = $this->current = 0; 0076 0077 foreach ($this->streams as $stream) { 0078 $stream->close(); 0079 } 0080 0081 $this->streams = []; 0082 } 0083 0084 /** 0085 * Detaches each attached stream 0086 * 0087 * {@inheritdoc} 0088 */ 0089 public function detach() 0090 { 0091 $this->close(); 0092 $this->detached = true; 0093 } 0094 0095 public function tell() 0096 { 0097 return $this->pos; 0098 } 0099 0100 /** 0101 * Tries to calculate the size by adding the size of each stream. 0102 * 0103 * If any of the streams do not return a valid number, then the size of the 0104 * append stream cannot be determined and null is returned. 0105 * 0106 * {@inheritdoc} 0107 */ 0108 public function getSize() 0109 { 0110 $size = 0; 0111 0112 foreach ($this->streams as $stream) { 0113 $s = $stream->getSize(); 0114 if ($s === null) { 0115 return null; 0116 } 0117 $size += $s; 0118 } 0119 0120 return $size; 0121 } 0122 0123 public function eof() 0124 { 0125 return !$this->streams || 0126 ($this->current >= count($this->streams) - 1 && 0127 $this->streams[$this->current]->eof()); 0128 } 0129 0130 public function rewind() 0131 { 0132 $this->seek(0); 0133 } 0134 0135 /** 0136 * Attempts to seek to the given position. Only supports SEEK_SET. 0137 * 0138 * {@inheritdoc} 0139 */ 0140 public function seek($offset, $whence = SEEK_SET) 0141 { 0142 if (!$this->seekable) { 0143 throw new \RuntimeException('This AppendStream is not seekable'); 0144 } elseif ($whence !== SEEK_SET) { 0145 throw new \RuntimeException('The AppendStream can only seek with SEEK_SET'); 0146 } 0147 0148 $this->pos = $this->current = 0; 0149 0150 // Rewind each stream 0151 foreach ($this->streams as $i => $stream) { 0152 try { 0153 $stream->rewind(); 0154 } catch (\Exception $e) { 0155 throw new \RuntimeException('Unable to seek stream ' 0156 . $i . ' of the AppendStream', 0, $e); 0157 } 0158 } 0159 0160 // Seek to the actual position by reading from each stream 0161 while ($this->pos < $offset && !$this->eof()) { 0162 $result = $this->read(min(8096, $offset - $this->pos)); 0163 if ($result === '') { 0164 break; 0165 } 0166 } 0167 } 0168 0169 /** 0170 * Reads from all of the appended streams until the length is met or EOF. 0171 * 0172 * {@inheritdoc} 0173 */ 0174 public function read($length) 0175 { 0176 $buffer = ''; 0177 $total = count($this->streams) - 1; 0178 $remaining = $length; 0179 $progressToNext = false; 0180 0181 while ($remaining > 0) { 0182 0183 // Progress to the next stream if needed. 0184 if ($progressToNext || $this->streams[$this->current]->eof()) { 0185 $progressToNext = false; 0186 if ($this->current === $total) { 0187 break; 0188 } 0189 $this->current++; 0190 } 0191 0192 $result = $this->streams[$this->current]->read($remaining); 0193 0194 // Using a loose comparison here to match on '', false, and null 0195 if ($result == null) { 0196 $progressToNext = true; 0197 continue; 0198 } 0199 0200 $buffer .= $result; 0201 $remaining = $length - strlen($buffer); 0202 } 0203 0204 $this->pos += strlen($buffer); 0205 0206 return $buffer; 0207 } 0208 0209 public function isReadable() 0210 { 0211 return true; 0212 } 0213 0214 public function isWritable() 0215 { 0216 return false; 0217 } 0218 0219 public function isSeekable() 0220 { 0221 return $this->seekable; 0222 } 0223 0224 public function write($string) 0225 { 0226 throw new \RuntimeException('Cannot write to an AppendStream'); 0227 } 0228 0229 public function getMetadata($key = null) 0230 { 0231 return $key ? null : []; 0232 } 0233 }