xref: /plugin/dw2pdf/vendor/mpdf/psr-http-message-shim/src/Stream.php (revision 92200750cd9cfc4a9489fe7c04c6e41b5a3de6ee)
1<?php
2
3namespace Mpdf\PsrHttpMessageShim;
4
5/**
6 * @link nyholm/psr7
7 */
8class Stream implements \Psr\Http\Message\StreamInterface
9{
10	/**
11	 * A resource reference.
12	 *
13	 * @var resource
14	 */
15	private $stream;
16
17	/**
18	 * @var bool
19	 */
20	private $seekable;
21
22	/**
23	 * @var bool
24	 */
25	private $readable;
26
27	/**
28	 * @var bool
29	 */
30	private $writable;
31
32	/**
33	 * @var array|mixed|null|void
34	 */
35	private $uri;
36
37	/**
38	 * @var int
39	 */
40	private $size;
41
42	/** @var array Hash of readable and writable stream types */
43	private static $readWriteHash = [
44		'read' => [
45			'r' => true, 'w+' => true, 'r+' => true, 'x+' => true, 'c+' => true,
46			'rb' => true, 'w+b' => true, 'r+b' => true, 'x+b' => true,
47			'c+b' => true, 'rt' => true, 'w+t' => true, 'r+t' => true,
48			'x+t' => true, 'c+t' => true, 'a+' => true,
49		],
50		'write' => [
51			'w' => true, 'w+' => true, 'rw' => true, 'r+' => true, 'x+' => true,
52			'c+' => true, 'wb' => true, 'w+b' => true, 'r+b' => true,
53			'x+b' => true, 'c+b' => true, 'w+t' => true, 'r+t' => true,
54			'x+t' => true, 'c+t' => true, 'a' => true, 'a+' => true,
55		],
56	];
57
58	private function __construct()
59	{
60	}
61
62	/**
63	 * @param resource $resource
64	 *
65	 * @return Stream
66	 */
67	public static function createFromResource($resource)
68	{
69		if (!is_resource($resource)) {
70			throw new \InvalidArgumentException('Stream must be a resource');
71		}
72
73		$obj = new self();
74		$obj->stream = $resource;
75		$meta = stream_get_meta_data($obj->stream);
76		$obj->seekable = $meta['seekable'];
77		$obj->readable = isset(self::$readWriteHash['read'][$meta['mode']]);
78		$obj->writable = isset(self::$readWriteHash['write'][$meta['mode']]);
79		$obj->uri = $obj->getMetadata('uri');
80
81		return $obj;
82	}
83
84	/**
85	 * @param string $content
86	 *
87	 * @return Stream
88	 */
89	public static function create($content)
90	{
91		$resource = fopen('php://temp', 'rwb+');
92		$stream = self::createFromResource($resource);
93		$stream->write($content);
94		$stream->seek(0);
95
96		return $stream;
97	}
98
99	/**
100	 * Closes the stream when the destructed.
101	 */
102	public function __destruct()
103	{
104		$this->close();
105	}
106
107	public function __toString(): string
108	{
109		try {
110			if ($this->isSeekable()) {
111				$this->seek(0);
112			}
113
114			return $this->getContents();
115		} catch (\Exception $e) {
116			return '';
117		}
118	}
119
120	public function close(): void
121	{
122		if (isset($this->stream)) {
123			if (is_resource($this->stream)) {
124				fclose($this->stream);
125			}
126			$this->detach();
127		}
128	}
129
130	public function detach()
131	{
132		if (!isset($this->stream)) {
133			return;
134		}
135
136		$result = $this->stream;
137		unset($this->stream);
138		$this->size = $this->uri = null;
139		$this->readable = $this->writable = $this->seekable = false;
140
141		return $result;
142	}
143
144	public function getSize(): ?int
145	{
146		if ($this->size !== null) {
147			return $this->size;
148		}
149
150		if (!isset($this->stream)) {
151			return null;
152		}
153
154		// Clear the stat cache if the stream has a URI
155		if ($this->uri) {
156			clearstatcache(true, $this->uri);
157		}
158
159		$stats = fstat($this->stream);
160		if (isset($stats['size'])) {
161			$this->size = $stats['size'];
162
163			return $this->size;
164		}
165		return null;
166	}
167
168	public function tell(): int
169	{
170		$result = ftell($this->stream);
171
172		if ($result === false) {
173			throw new \RuntimeException('Unable to determine stream position');
174		}
175
176		return $result;
177	}
178
179	public function eof(): bool
180	{
181		return !$this->stream || feof($this->stream);
182	}
183
184	public function isSeekable(): bool
185	{
186		return $this->seekable;
187	}
188
189	public function seek(int $offset, int $whence = SEEK_SET): void
190	{
191		if (!$this->seekable) {
192			throw new \RuntimeException('Stream is not seekable');
193		}
194
195		if (fseek($this->stream, $offset, $whence) === -1) {
196			throw new \RuntimeException('Unable to seek to stream position '.$offset.' with whence '.var_export($whence, true));
197		}
198	}
199
200	public function rewind(): void
201	{
202		$this->seek(0);
203	}
204
205	public function isWritable(): bool
206	{
207		return $this->writable;
208	}
209
210	public function write(string $string): int
211	{
212		if (!$this->writable) {
213			throw new \RuntimeException('Cannot write to a non-writable stream');
214		}
215
216		// We can't know the size after writing anything
217		$this->size = null;
218		$result = fwrite($this->stream, $string);
219
220		if ($result === false) {
221			throw new \RuntimeException('Unable to write to stream');
222		}
223
224		return $result;
225	}
226
227	public function isReadable(): bool
228	{
229		return $this->readable;
230	}
231
232	public function read(int $length): string
233	{
234		if (!$this->readable) {
235			throw new \RuntimeException('Cannot read from non-readable stream');
236		}
237
238		return fread($this->stream, $length);
239	}
240
241	public function getContents(): string
242	{
243		if (!isset($this->stream)) {
244			throw new \RuntimeException('Unable to read stream contents');
245		}
246
247		$contents = stream_get_contents($this->stream);
248
249		if ($contents === false) {
250			throw new \RuntimeException('Unable to read stream contents');
251		}
252
253		return $contents;
254	}
255
256	public function getMetadata(?string $key = null): bool
257	{
258		if (!isset($this->stream)) {
259			return $key ? null : [];
260		}
261
262		if ($key === null) {
263			return stream_get_meta_data($this->stream);
264		}
265
266		$meta = stream_get_meta_data($this->stream);
267
268		return isset($meta[$key]) ? $meta[$key] : null;
269	}
270
271}
272