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