Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
88.98% |
105 / 118 |
|
85.71% |
18 / 21 |
CRAP | |
0.00% |
0 / 1 |
Stream | |
88.98% |
105 / 118 |
|
85.71% |
18 / 21 |
63.65 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
3 | |||
setMethod | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
2 | |||
stream | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
createContext | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
4 | |||
addContextOption | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
4 | |||
addContextParam | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
setContextOptions | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
setContextParams | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
setMode | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
getContext | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getContextOptions | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getContextOption | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
2 | |||
hasContextOption | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getContextParams | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getContextParam | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
2 | |||
hasContextParam | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getMode | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
open | |
93.75% |
45 / 48 |
|
0.00% |
0 / 1 |
21.11 | |||
send | |
85.00% |
17 / 20 |
|
0.00% |
0 / 1 |
5.08 | |||
reset | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
2 | |||
disconnect | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 |
1 | <?php |
2 | /** |
3 | * Pop PHP Framework (http://www.popphp.org/) |
4 | * |
5 | * @link https://github.com/popphp/popphp-framework |
6 | * @author Nick Sagona, III <dev@nolainteractive.com> |
7 | * @copyright Copyright (c) 2009-2023 NOLA Interactive, LLC. (http://www.nolainteractive.com) |
8 | * @license http://www.popphp.org/license New BSD License |
9 | */ |
10 | |
11 | /** |
12 | * @namespace |
13 | */ |
14 | namespace Pop\Http\Client; |
15 | |
16 | use Pop\Http\Parser; |
17 | use Pop\Mime\Message; |
18 | |
19 | /** |
20 | * HTTP stream client class |
21 | * |
22 | * @category Pop |
23 | * @package Pop\Http |
24 | * @author Nick Sagona, III <dev@nolainteractive.com> |
25 | * @copyright Copyright (c) 2009-2023 NOLA Interactive, LLC. (http://www.nolainteractive.com) |
26 | * @license http://www.popphp.org/license New BSD License |
27 | * @version 4.1.0 |
28 | */ |
29 | class Stream extends AbstractClient |
30 | { |
31 | |
32 | /** |
33 | * Stream context |
34 | * @var resource |
35 | */ |
36 | protected $context = null; |
37 | |
38 | /** |
39 | * Stream context options |
40 | * @var array |
41 | */ |
42 | protected $contextOptions = []; |
43 | |
44 | /** |
45 | * Stream context parameters |
46 | * @var array |
47 | */ |
48 | protected $contextParams = []; |
49 | |
50 | /** |
51 | * Stream mode |
52 | * @var string |
53 | */ |
54 | protected $mode = 'r'; |
55 | |
56 | /** |
57 | * HTTP response headers |
58 | * @var array |
59 | */ |
60 | protected $httpResponseHeaders = null; |
61 | |
62 | /** |
63 | * Constructor |
64 | * |
65 | * Instantiate the stream object |
66 | * |
67 | * @param string $url |
68 | * @param string $method |
69 | * @param string $mode |
70 | * @param array $options |
71 | * @param array $params |
72 | */ |
73 | public function __construct($url = null, $method = 'GET', $mode = 'r', array $options = [], array $params = []) |
74 | { |
75 | parent::__construct($url, $method); |
76 | |
77 | $this->setMode($mode); |
78 | |
79 | if (count($options) > 0) { |
80 | $this->setContextOptions($options); |
81 | } |
82 | if (count($params) > 0) { |
83 | $this->setContextParams($params); |
84 | } |
85 | } |
86 | |
87 | /** |
88 | * Set the method |
89 | * |
90 | * @param string $method |
91 | * @param boolean $strict |
92 | * @throws Exception |
93 | * @return Stream |
94 | */ |
95 | public function setMethod($method, $strict = true) |
96 | { |
97 | parent::setMethod($method, $strict); |
98 | |
99 | if (!isset($this->contextOptions['http'])) { |
100 | $this->contextOptions['http'] = []; |
101 | } |
102 | |
103 | $this->contextOptions['http']['method'] = $this->method; |
104 | |
105 | return $this; |
106 | } |
107 | |
108 | /** |
109 | * Return stream resource (alias to $this->getResource()) |
110 | * |
111 | * @return resource |
112 | */ |
113 | public function stream() |
114 | { |
115 | return $this->resource; |
116 | } |
117 | |
118 | /** |
119 | * Create stream context |
120 | * |
121 | * @return Stream |
122 | */ |
123 | public function createContext() |
124 | { |
125 | if ((count($this->contextOptions) > 0) && (count($this->contextParams) > 0)) { |
126 | $this->context = stream_context_create($this->contextOptions, $this->contextParams); |
127 | } else if (count($this->contextOptions) > 0) { |
128 | $this->context = stream_context_create($this->contextOptions); |
129 | } else { |
130 | $this->context = stream_context_create(); |
131 | } |
132 | |
133 | return $this; |
134 | } |
135 | |
136 | /** |
137 | * Add a context options |
138 | * |
139 | * @param string $name |
140 | * @param mixed $option |
141 | * @return Stream |
142 | */ |
143 | public function addContextOption($name, $option) |
144 | { |
145 | if (isset($this->contextOptions[$name]) && is_array($this->contextOptions[$name]) && is_array($option)) { |
146 | $this->contextOptions[$name] = array_merge($this->contextOptions[$name], $option); |
147 | } else { |
148 | $this->contextOptions[$name] = $option; |
149 | } |
150 | |
151 | return $this; |
152 | } |
153 | |
154 | /** |
155 | * Add a context parameter |
156 | * |
157 | * @param string $name |
158 | * @param mixed $param |
159 | * @return Stream |
160 | */ |
161 | public function addContextParam($name, $param) |
162 | { |
163 | $this->contextParams[$name] = $param; |
164 | return $this; |
165 | } |
166 | |
167 | /** |
168 | * Set the context options |
169 | * |
170 | * @param array $options |
171 | * @return Stream |
172 | */ |
173 | public function setContextOptions(array $options) |
174 | { |
175 | foreach ($options as $name => $option) { |
176 | $this->addContextOption($name, $option); |
177 | } |
178 | return $this; |
179 | } |
180 | |
181 | /** |
182 | * Set the context parameters |
183 | * |
184 | * @param array $params |
185 | * @return Stream |
186 | */ |
187 | public function setContextParams(array $params) |
188 | { |
189 | foreach ($params as $name => $param) { |
190 | $this->addContextParam($name, $param); |
191 | } |
192 | return $this; |
193 | } |
194 | |
195 | /** |
196 | * Set the mode |
197 | * |
198 | * @param string $mode |
199 | * @return Stream |
200 | */ |
201 | public function setMode($mode) |
202 | { |
203 | $this->mode = $mode; |
204 | return $this; |
205 | } |
206 | |
207 | /** |
208 | * Get the context resource |
209 | * |
210 | * @return resource |
211 | */ |
212 | public function getContext() |
213 | { |
214 | return $this->context; |
215 | } |
216 | |
217 | /** |
218 | * Get the context options |
219 | * |
220 | * @return array |
221 | */ |
222 | public function getContextOptions() |
223 | { |
224 | return $this->contextOptions; |
225 | } |
226 | |
227 | /** |
228 | * Get a context option |
229 | * |
230 | * @param string $name |
231 | * @return mixed |
232 | */ |
233 | public function getContextOption($name) |
234 | { |
235 | return (isset($this->contextOptions[$name])) ? $this->contextOptions[$name] : null; |
236 | } |
237 | |
238 | /** |
239 | * Determine if a context option has been set |
240 | * |
241 | * @param string $name |
242 | * @return boolean |
243 | */ |
244 | public function hasContextOption($name) |
245 | { |
246 | return (isset($this->contextOptions[$name])); |
247 | } |
248 | |
249 | /** |
250 | * Get the context parameters |
251 | * |
252 | * @return array |
253 | */ |
254 | public function getContextParams() |
255 | { |
256 | return $this->contextParams; |
257 | } |
258 | |
259 | /** |
260 | * Get a context parameter |
261 | * |
262 | * @param string $name |
263 | * @return mixed |
264 | */ |
265 | public function getContextParam($name) |
266 | { |
267 | return (isset($this->contextParams[$name])) ? $this->contextParams[$name] : null; |
268 | } |
269 | |
270 | /** |
271 | * Determine if a context parameter has been set |
272 | * |
273 | * @param string $name |
274 | * @return boolean |
275 | */ |
276 | public function hasContextParam($name) |
277 | { |
278 | return (isset($this->contextParams[$name])); |
279 | } |
280 | |
281 | /** |
282 | * Get the mode |
283 | * |
284 | * @return string |
285 | */ |
286 | public function getMode() |
287 | { |
288 | return $this->mode; |
289 | } |
290 | |
291 | /** |
292 | * Create and open stream resource |
293 | * |
294 | * @return Stream |
295 | */ |
296 | public function open() |
297 | { |
298 | $url = $this->url; |
299 | $http_response_header = null; |
300 | |
301 | if (isset($this->contextOptions['http']['header'])) { |
302 | $this->contextOptions['http']['header'] = null; |
303 | } |
304 | |
305 | if (null !== $this->request) { |
306 | // Set query data if there is any |
307 | if ($this->request->hasFields()) { |
308 | // Append GET query string to URL |
309 | if (($this->method == 'GET') && ((!$this->request->hasHeader('Content-Type')) || |
310 | ($this->request->getHeaderValue('Content-Type') == 'application/x-www-form-urlencoded'))) { |
311 | $url .= '?' . $this->request->getQuery(); |
312 | if (!$this->request->hasHeader('Content-Type')) { |
313 | $this->request->addHeader('Content-Type', 'application/x-www-form-urlencoded'); |
314 | } |
315 | // Else, prepare request data for transmission |
316 | } else { |
317 | // If request is JSON |
318 | if ($this->request->getHeaderValue('Content-Type') == 'application/json') { |
319 | $content = json_encode($this->request->getFields(), JSON_PRETTY_PRINT); |
320 | $this->request->addHeader('Content-Length', strlen($content)); |
321 | $this->contextOptions['http']['content'] = $content; |
322 | // If request is a URL-encoded form |
323 | } else if ($this->request->getHeaderValue('Content-Type') == 'application/x-www-form-urlencoded') { |
324 | $this->request->addHeader('Content-Length', $this->request->getQueryLength()); |
325 | $this->contextOptions['http']['content'] = $this->request->getQuery(); |
326 | // Else, if request is a multipart form |
327 | } else if ($this->request->isMultipartForm()) { |
328 | $formMessage = Message::createForm($this->request->getFields()); |
329 | $header = $formMessage->getHeader('Content-Type'); |
330 | $content = $formMessage->render(false); |
331 | $formMessage->removeHeader('Content-Type'); |
332 | $this->request->addHeader($header) |
333 | ->addHeader('Content-Length', strlen($content)); |
334 | $this->contextOptions['http']['content'] = $content; |
335 | // Else, basic request with data |
336 | } else { |
337 | $this->contextOptions['http']['content'] = $this->request->getQuery(); |
338 | if (!$this->request->hasHeader('Content-Type')) { |
339 | $this->request->addHeader('Content-Type', 'application/x-www-form-urlencoded'); |
340 | } |
341 | } |
342 | } |
343 | // Else, if there is raw body content |
344 | } else if ($this->request->hasBodyContent()) { |
345 | $this->request->addHeader('Content-Length', strlen($this->request->getBodyContent())); |
346 | $this->contextOptions['http']['content'] = $this->request->getBodyContent(); |
347 | } |
348 | |
349 | if ($this->request->hasHeaders()) { |
350 | $headers = []; |
351 | |
352 | foreach ($this->request->getHeaders() as $header => $value) { |
353 | if (is_array($value)) { |
354 | foreach ($value as $hdr => $val) { |
355 | $headers[] = (string)$val; |
356 | } |
357 | } else { |
358 | $headers[] = (string)$value; |
359 | } |
360 | } |
361 | |
362 | if (isset($this->contextOptions['http']['header'])) { |
363 | $this->contextOptions['http']['header'] .= "\r\n" . implode("\r\n", $headers) . "\r\n"; |
364 | } else { |
365 | $this->contextOptions['http']['header'] = implode("\r\n", $headers) . "\r\n"; |
366 | } |
367 | } |
368 | } |
369 | |
370 | if ((count($this->contextOptions) > 0) || (count($this->contextParams) > 0)) { |
371 | $this->createContext(); |
372 | } |
373 | |
374 | $this->resource = (null !== $this->context) ? |
375 | @fopen($url, $this->mode, false, $this->context) : @fopen($url, $this->mode); |
376 | |
377 | $this->httpResponseHeaders = $http_response_header; |
378 | |
379 | return $this; |
380 | } |
381 | |
382 | /** |
383 | * Method to send the request and get the response |
384 | * |
385 | * @return void |
386 | */ |
387 | public function send() |
388 | { |
389 | $rawHeader = null; |
390 | $headers = []; |
391 | $body = null; |
392 | |
393 | $this->open(); |
394 | |
395 | if (null === $this->response) { |
396 | $this->response = new Response(); |
397 | } |
398 | |
399 | if ($this->resource !== false) { |
400 | $meta = stream_get_meta_data($this->resource); |
401 | $headers = $meta['wrapper_data']; |
402 | $body = stream_get_contents($this->resource); |
403 | } else if (null !== $this->httpResponseHeaders) { |
404 | $headers = $this->httpResponseHeaders; |
405 | } |
406 | |
407 | // Parse response headers |
408 | $parsedHeaders = Parser::parseHeaders($headers); |
409 | $this->response->setVersion($parsedHeaders['version']); |
410 | $this->response->setCode($parsedHeaders['code']); |
411 | $this->response->setMessage($parsedHeaders['message']); |
412 | $this->response->addHeaders($parsedHeaders['headers']); |
413 | $this->response->setBody($body); |
414 | |
415 | if ($this->response->hasHeader('Content-Encoding')) { |
416 | $this->response->decodeBodyContent(); |
417 | } |
418 | } |
419 | |
420 | /** |
421 | * Method to reset the client object |
422 | * |
423 | * @return Stream |
424 | */ |
425 | public function reset() |
426 | { |
427 | $this->request = new Request(); |
428 | $this->response = new Response(); |
429 | $this->context = null; |
430 | $this->contextOptions = []; |
431 | $this->contextParams = []; |
432 | $this->httpResponseHeaders = null; |
433 | |
434 | return $this; |
435 | } |
436 | |
437 | /** |
438 | * Close the stream |
439 | * |
440 | * @return void |
441 | */ |
442 | public function disconnect() |
443 | { |
444 | if ($this->hasResource()) { |
445 | $this->resource = null; |
446 | $this->context = null; |
447 | } |
448 | } |
449 | |
450 | } |