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