← All changes
|
integrations/mailchimp/vendor/guzzlehttp/psr7/src/CachingStream.php
+153
-0
5.7.2
→
6.0.4
View file →
| @@ -1,0 +1,153 @@ | ||
| 1 | +<?php | |
| 2 | + | |
| 3 | +declare(strict_types=1); | |
| 4 | + | |
| 5 | +namespace GuzzleHttp\Psr7; | |
| 6 | + | |
| 7 | +use Psr\Http\Message\StreamInterface; | |
| 8 | + | |
| 9 | +/** | |
| 10 | + * Stream decorator that can cache previously read bytes from a sequentially | |
| 11 | + * read stream. | |
| 12 | + */ | |
| 13 | +final class CachingStream implements StreamInterface | |
| 14 | +{ | |
| 15 | + use StreamDecoratorTrait; | |
| 16 | + | |
| 17 | + /** @var StreamInterface Stream being wrapped */ | |
| 18 | + private $remoteStream; | |
| 19 | + | |
| 20 | + /** @var int Number of bytes to skip reading due to a write on the buffer */ | |
| 21 | + private $skipReadBytes = 0; | |
| 22 | + | |
| 23 | + /** | |
| 24 | + * @var StreamInterface | |
| 25 | + */ | |
| 26 | + private $stream; | |
| 27 | + | |
| 28 | + /** | |
| 29 | + * We will treat the buffer object as the body of the stream | |
| 30 | + * | |
| 31 | + * @param StreamInterface $stream Stream to cache. The cursor is assumed to be at the beginning of the stream. | |
| 32 | + * @param StreamInterface $target Optionally specify where data is cached | |
| 33 | + */ | |
| 34 | + public function __construct( | |
| 35 | + StreamInterface $stream, | |
| 36 | + ?StreamInterface $target = null | |
| 37 | + ) { | |
| 38 | + $this->remoteStream = $stream; | |
| 39 | + $this->stream = $target ?: new Stream(Utils::tryFopen('php://temp', 'r+')); | |
| 40 | + } | |
| 41 | + | |
| 42 | + public function getSize(): ?int | |
| 43 | + { | |
| 44 | + $remoteSize = $this->remoteStream->getSize(); | |
| 45 | + | |
| 46 | + if (null === $remoteSize) { | |
| 47 | + return null; | |
| 48 | + } | |
| 49 | + | |
| 50 | + return max($this->stream->getSize(), $remoteSize); | |
| 51 | + } | |
| 52 | + | |
| 53 | + public function rewind(): void | |
| 54 | + { | |
| 55 | + $this->seek(0); | |
| 56 | + } | |
| 57 | + | |
| 58 | + public function seek($offset, $whence = SEEK_SET): void | |
| 59 | + { | |
| 60 | + if ($whence === SEEK_SET) { | |
| 61 | + $byte = $offset; | |
| 62 | + } elseif ($whence === SEEK_CUR) { | |
| 63 | + $byte = $offset + $this->tell(); | |
| 64 | + } elseif ($whence === SEEK_END) { | |
| 65 | + $size = $this->remoteStream->getSize(); | |
| 66 | + if ($size === null) { | |
| 67 | + $size = $this->cacheEntireStream(); | |
| 68 | + } | |
| 69 | + $byte = $size + $offset; | |
| 70 | + } else { | |
| 71 | + throw new \InvalidArgumentException('Invalid whence'); | |
| 72 | + } | |
| 73 | + | |
| 74 | + $diff = $byte - $this->stream->getSize(); | |
| 75 | + | |
| 76 | + if ($diff > 0) { | |
| 77 | + // Read the remoteStream until we have read in at least the amount | |
| 78 | + // of bytes requested, or we reach the end of the file. | |
| 79 | + while ($diff > 0 && !$this->remoteStream->eof()) { | |
| 80 | + $this->read($diff); | |
| 81 | + $diff = $byte - $this->stream->getSize(); | |
| 82 | + } | |
| 83 | + } else { | |
| 84 | + // We can just do a normal seek since we've already seen this byte. | |
| 85 | + $this->stream->seek($byte); | |
| 86 | + } | |
| 87 | + } | |
| 88 | + | |
| 89 | + public function read($length): string | |
| 90 | + { | |
| 91 | + // Perform a regular read on any previously read data from the buffer | |
| 92 | + $data = $this->stream->read($length); | |
| 93 | + $remaining = $length - strlen($data); | |
| 94 | + | |
| 95 | + // More data was requested so read from the remote stream | |
| 96 | + if ($remaining) { | |
| 97 | + // If data was written to the buffer in a position that would have | |
| 98 | + // been filled from the remote stream, then we must skip bytes on | |
| 99 | + // the remote stream to emulate overwriting bytes from that | |
| 100 | + // position. This mimics the behavior of other PHP stream wrappers. | |
| 101 | + $remoteData = $this->remoteStream->read( | |
| 102 | + $remaining + $this->skipReadBytes | |
| 103 | + ); | |
| 104 | + | |
| 105 | + if ($this->skipReadBytes) { | |
| 106 | + $len = strlen($remoteData); | |
| 107 | + $remoteData = substr($remoteData, $this->skipReadBytes); | |
| 108 | + $this->skipReadBytes = max(0, $this->skipReadBytes - $len); | |
| 109 | + } | |
| 110 | + | |
| 111 | + $data .= $remoteData; | |
| 112 | + $this->stream->write($remoteData); | |
| 113 | + } | |
| 114 | + | |
| 115 | + return $data; | |
| 116 | + } | |
| 117 | + | |
| 118 | + public function write($string): int | |
| 119 | + { | |
| 120 | + // When appending to the end of the currently read stream, you'll want | |
| 121 | + // to skip bytes from being read from the remote stream to emulate | |
| 122 | + // other stream wrappers. Basically replacing bytes of data of a fixed | |
| 123 | + // length. | |
| 124 | + $overflow = (strlen($string) + $this->tell()) - $this->remoteStream->tell(); | |
| 125 | + if ($overflow > 0) { | |
| 126 | + $this->skipReadBytes += $overflow; | |
| 127 | + } | |
| 128 | + | |
| 129 | + return $this->stream->write($string); | |
| 130 | + } | |
| 131 | + | |
| 132 | + public function eof(): bool | |
| 133 | + { | |
| 134 | + return $this->stream->eof() && $this->remoteStream->eof(); | |
| 135 | + } | |
| 136 | + | |
| 137 | + /** | |
| 138 | + * Close both the remote stream and buffer stream | |
| 139 | + */ | |
| 140 | + public function close(): void | |
| 141 | + { | |
| 142 | + $this->remoteStream->close(); | |
| 143 | + $this->stream->close(); | |
| 144 | + } | |
| 145 | + | |
| 146 | + private function cacheEntireStream(): int | |
| 147 | + { | |
| 148 | + $target = new FnStream(['write' => 'strlen']); | |
| 149 | + Utils::copyToStream($this, $target); | |
| 150 | + | |
| 151 | + return $this->tell(); | |
| 152 | + } | |
| 153 | +} | |