1<?php 2 3declare(strict_types=1); 4 5namespace GuzzleHttp\Psr7; 6 7use Psr\Http\Message\StreamInterface; 8 9/** 10 * Converts Guzzle streams into PHP stream resources. 11 * 12 * @see https://www.php.net/streamwrapper 13 */ 14final class StreamWrapper 15{ 16 /** @var resource */ 17 public $context; 18 19 /** @var StreamInterface */ 20 private $stream; 21 22 /** @var string r, r+, or w */ 23 private $mode; 24 25 /** 26 * Returns a resource representing the stream. 27 * 28 * @param StreamInterface $stream The stream to get a resource for 29 * 30 * @return resource 31 * 32 * @throws \InvalidArgumentException if stream is not readable or writable 33 */ 34 public static function getResource(StreamInterface $stream) 35 { 36 self::register(); 37 38 if ($stream->isReadable()) { 39 $mode = $stream->isWritable() ? 'r+' : 'r'; 40 } elseif ($stream->isWritable()) { 41 $mode = 'w'; 42 } else { 43 throw new \InvalidArgumentException('The stream must be readable, ' 44 .'writable, or both.'); 45 } 46 47 return fopen('guzzle://stream', $mode, false, self::createStreamContext($stream)); 48 } 49 50 /** 51 * Creates a stream context that can be used to open a stream as a php stream resource. 52 * 53 * @return resource 54 */ 55 public static function createStreamContext(StreamInterface $stream) 56 { 57 return stream_context_create([ 58 'guzzle' => ['stream' => $stream], 59 ]); 60 } 61 62 /** 63 * Registers the stream wrapper if needed 64 */ 65 public static function register(): void 66 { 67 if (!in_array('guzzle', stream_get_wrappers())) { 68 stream_wrapper_register('guzzle', __CLASS__); 69 } 70 } 71 72 public function stream_open(string $path, string $mode, int $options, string &$opened_path = null): bool 73 { 74 $options = stream_context_get_options($this->context); 75 76 if (!isset($options['guzzle']['stream'])) { 77 return false; 78 } 79 80 $this->mode = $mode; 81 $this->stream = $options['guzzle']['stream']; 82 83 return true; 84 } 85 86 public function stream_read(int $count): string 87 { 88 return $this->stream->read($count); 89 } 90 91 public function stream_write(string $data): int 92 { 93 return $this->stream->write($data); 94 } 95 96 public function stream_tell(): int 97 { 98 return $this->stream->tell(); 99 } 100 101 public function stream_eof(): bool 102 { 103 return $this->stream->eof(); 104 } 105 106 public function stream_seek(int $offset, int $whence): bool 107 { 108 $this->stream->seek($offset, $whence); 109 110 return true; 111 } 112 113 /** 114 * @return resource|false 115 */ 116 public function stream_cast(int $cast_as) 117 { 118 $stream = clone $this->stream; 119 $resource = $stream->detach(); 120 121 return $resource ?? false; 122 } 123 124 /** 125 * @return array{ 126 * dev: int, 127 * ino: int, 128 * mode: int, 129 * nlink: int, 130 * uid: int, 131 * gid: int, 132 * rdev: int, 133 * size: int, 134 * atime: int, 135 * mtime: int, 136 * ctime: int, 137 * blksize: int, 138 * blocks: int 139 * } 140 */ 141 public function stream_stat(): array 142 { 143 static $modeMap = [ 144 'r' => 33060, 145 'rb' => 33060, 146 'r+' => 33206, 147 'w' => 33188, 148 'wb' => 33188, 149 ]; 150 151 return [ 152 'dev' => 0, 153 'ino' => 0, 154 'mode' => $modeMap[$this->mode], 155 'nlink' => 0, 156 'uid' => 0, 157 'gid' => 0, 158 'rdev' => 0, 159 'size' => $this->stream->getSize() ?: 0, 160 'atime' => 0, 161 'mtime' => 0, 162 'ctime' => 0, 163 'blksize' => 0, 164 'blocks' => 0, 165 ]; 166 } 167 168 /** 169 * @return array{ 170 * dev: int, 171 * ino: int, 172 * mode: int, 173 * nlink: int, 174 * uid: int, 175 * gid: int, 176 * rdev: int, 177 * size: int, 178 * atime: int, 179 * mtime: int, 180 * ctime: int, 181 * blksize: int, 182 * blocks: int 183 * } 184 */ 185 public function url_stat(string $path, int $flags): array 186 { 187 return [ 188 'dev' => 0, 189 'ino' => 0, 190 'mode' => 0, 191 'nlink' => 0, 192 'uid' => 0, 193 'gid' => 0, 194 'rdev' => 0, 195 'size' => 0, 196 'atime' => 0, 197 'mtime' => 0, 198 'ctime' => 0, 199 'blksize' => 0, 200 'blocks' => 0, 201 ]; 202 } 203} 204