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 }