PluginProbe
Depicter — Popup & Slider Builder / trunk
Depicter — Popup & Slider Builder vtrunk
4.8.1 trunk 1.0.0 1.1.0 1.1.2 1.1.4 1.1.6 1.1.7 1.1.8 1.1.9 1.2.0 1.3.0 1.3.1 1.3.2 1.3.3 1.3.5 1.3.8 1.5.0 1.5.1 1.5.2 1.5.5 1.6.0 1.6.1 1.6.2 1.7.0 All 76 releases
depicter / modules / GuzzleHttp / Client.php

Client.php in Depicter — Popup & Slider Builder trunk, at modules/GuzzleHttp/Client.php

484 lines 18.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Depicter\GuzzleHttp;
4
5 use Depicter\GuzzleHttp\Cookie\CookieJar;
6 use Depicter\GuzzleHttp\Exception\GuzzleException;
7 use Depicter\GuzzleHttp\Exception\InvalidArgumentException;
8 use Depicter\GuzzleHttp\Promise as P;
9 use Depicter\GuzzleHttp\Promise\PromiseInterface;
10 use Depicter\Psr\Http\Message\RequestInterface;
11 use Depicter\Psr\Http\Message\ResponseInterface;
12 use Depicter\Psr\Http\Message\UriInterface;
13
14 /**
15 * @final
16 */
17 class Client implements ClientInterface, \Depicter\Psr\Http\Client\ClientInterface
18 {
19 use ClientTrait;
20
21 /**
22 * @var array Default request options
23 */
24 private $config;
25
26 /**
27 * Clients accept an array of constructor parameters.
28 *
29 * Here's an example of creating a client using a base_uri and an array of
30 * default request options to apply to each request:
31 *
32 * $client = new Client([
33 * 'base_uri' => 'http://www.foo.com/1.0/',
34 * 'timeout' => 0,
35 * 'allow_redirects' => false,
36 * 'proxy' => '192.168.16.1:10'
37 * ]);
38 *
39 * Client configuration settings include the following options:
40 *
41 * - handler: (callable) Function that transfers HTTP requests over the
42 * wire. The function is called with a Psr7\Http\Message\RequestInterface
43 * and array of transfer options, and must return a
44 * Depicter\GuzzleHttp\Promise\PromiseInterface that is fulfilled with a
45 * Psr7\Http\Message\ResponseInterface on success.
46 * If no handler is provided, a default handler will be created
47 * that enables all of the request options below by attaching all of the
48 * default middleware to the handler.
49 * - base_uri: (string|UriInterface) Base URI of the client that is merged
50 * into relative URIs. Can be a string or instance of UriInterface.
51 * - **: any request option
52 *
53 * @param array $config Client configuration settings.
54 *
55 * @see \Depicter\GuzzleHttp\RequestOptions for a list of available request options.
56 */
57 public function __construct(array $config = [])
58 {
59 if (!isset($config['handler'])) {
60 $config['handler'] = HandlerStack::create();
61 } elseif (!\is_callable($config['handler'])) {
62 throw new InvalidArgumentException('handler must be a callable');
63 }
64
65 // Convert the base_uri to a UriInterface
66 if (isset($config['base_uri'])) {
67 $config['base_uri'] = Psr7\Utils::uriFor($config['base_uri']);
68 }
69
70 $this->configureDefaults($config);
71 }
72
73 /**
74 * @param string $method
75 * @param array $args
76 *
77 * @return PromiseInterface|ResponseInterface
78 *
79 * @deprecated Client::__call will be removed in guzzlehttp/guzzle:8.0.
80 */
81 public function __call($method, $args)
82 {
83 if (\count($args) < 1) {
84 throw new InvalidArgumentException('Magic request methods require a URI and optional options array');
85 }
86
87 $uri = $args[0];
88 $opts = $args[1] ?? [];
89
90 return \substr($method, -5) === 'Async'
91 ? $this->requestAsync(\substr($method, 0, -5), $uri, $opts)
92 : $this->request($method, $uri, $opts);
93 }
94
95 /**
96 * Asynchronously send an HTTP request.
97 *
98 * @param array $options Request options to apply to the given
99 * request and to the transfer. See \Depicter\GuzzleHttp\RequestOptions.
100 */
101 public function sendAsync(RequestInterface $request, array $options = []): PromiseInterface
102 {
103 // Merge the base URI into the request URI if needed.
104 $options = $this->prepareDefaults($options);
105
106 return $this->transfer(
107 $request->withUri($this->buildUri($request->getUri(), $options), $request->hasHeader('Host')),
108 $options
109 );
110 }
111
112 /**
113 * Send an HTTP request.
114 *
115 * @param array $options Request options to apply to the given
116 * request and to the transfer. See \Depicter\GuzzleHttp\RequestOptions.
117 *
118 * @throws GuzzleException
119 */
120 public function send(RequestInterface $request, array $options = []): ResponseInterface
121 {
122 $options[RequestOptions::SYNCHRONOUS] = true;
123
124 return $this->sendAsync($request, $options)->wait();
125 }
126
127 /**
128 * The HttpClient PSR (PSR-18) specify this method.
129 *
130 * {@inheritDoc}
131 */
132 public function sendRequest(RequestInterface $request): ResponseInterface
133 {
134 $options[RequestOptions::SYNCHRONOUS] = true;
135 $options[RequestOptions::ALLOW_REDIRECTS] = false;
136 $options[RequestOptions::HTTP_ERRORS] = false;
137
138 return $this->sendAsync($request, $options)->wait();
139 }
140
141 /**
142 * Create and send an asynchronous HTTP request.
143 *
144 * Use an absolute path to override the base path of the client, or a
145 * relative path to append to the base path of the client. The URL can
146 * contain the query string as well. Use an array to provide a URL
147 * template and additional variables to use in the URL template expansion.
148 *
149 * @param string $method HTTP method
150 * @param string|UriInterface $uri URI object or string.
151 * @param array $options Request options to apply. See \Depicter\GuzzleHttp\RequestOptions.
152 */
153 public function requestAsync(string $method, $uri = '', array $options = []): PromiseInterface
154 {
155 $options = $this->prepareDefaults($options);
156 // Remove request modifying parameter because it can be done up-front.
157 $headers = $options['headers'] ?? [];
158 $body = $options['body'] ?? null;
159 $version = $options['version'] ?? '1.1';
160 // Merge the URI into the base URI.
161 $uri = $this->buildUri(Psr7\Utils::uriFor($uri), $options);
162 if (\is_array($body)) {
163 throw $this->invalidBody();
164 }
165 $request = new Psr7\Request($method, $uri, $headers, $body, $version);
166 // Remove the option so that they are not doubly-applied.
167 unset($options['headers'], $options['body'], $options['version']);
168
169 return $this->transfer($request, $options);
170 }
171
172 /**
173 * Create and send an HTTP request.
174 *
175 * Use an absolute path to override the base path of the client, or a
176 * relative path to append to the base path of the client. The URL can
177 * contain the query string as well.
178 *
179 * @param string $method HTTP method.
180 * @param string|UriInterface $uri URI object or string.
181 * @param array $options Request options to apply. See \Depicter\GuzzleHttp\RequestOptions.
182 *
183 * @throws GuzzleException
184 */
185 public function request(string $method, $uri = '', array $options = []): ResponseInterface
186 {
187 $options[RequestOptions::SYNCHRONOUS] = true;
188
189 return $this->requestAsync($method, $uri, $options)->wait();
190 }
191
192 /**
193 * Get a client configuration option.
194 *
195 * These options include default request options of the client, a "handler"
196 * (if utilized by the concrete client), and a "base_uri" if utilized by
197 * the concrete client.
198 *
199 * @param string|null $option The config option to retrieve.
200 *
201 * @return mixed
202 *
203 * @deprecated Client::getConfig will be removed in guzzlehttp/guzzle:8.0.
204 */
205 public function getConfig(string $option = null)
206 {
207 return $option === null
208 ? $this->config
209 : ($this->config[$option] ?? null);
210 }
211
212 private function buildUri(UriInterface $uri, array $config): UriInterface
213 {
214 if (isset($config['base_uri'])) {
215 $uri = Psr7\UriResolver::resolve(Psr7\Utils::uriFor($config['base_uri']), $uri);
216 }
217
218 if (isset($config['idn_conversion']) && ($config['idn_conversion'] !== false)) {
219 $idnOptions = ($config['idn_conversion'] === true) ? \IDNA_DEFAULT : $config['idn_conversion'];
220 $uri = Utils::idnUriConvert($uri, $idnOptions);
221 }
222
223 return $uri->getScheme() === '' && $uri->getHost() !== '' ? $uri->withScheme('http') : $uri;
224 }
225
226 /**
227 * Configures the default options for a client.
228 */
229 private function configureDefaults(array $config): void
230 {
231 $defaults = [
232 'allow_redirects' => RedirectMiddleware::$defaultSettings,
233 'http_errors' => true,
234 'decode_content' => true,
235 'verify' => true,
236 'cookies' => false,
237 'idn_conversion' => false,
238 ];
239
240 // Use the standard Linux HTTP_PROXY and HTTPS_PROXY if set.
241
242 // We can only trust the HTTP_PROXY environment variable in a CLI
243 // process due to the fact that PHP has no reliable mechanism to
244 // get environment variables that start with "HTTP_".
245 if (\PHP_SAPI === 'cli' && ($proxy = Utils::getenv('HTTP_PROXY'))) {
246 $defaults['proxy']['http'] = $proxy;
247 }
248
249 if ($proxy = Utils::getenv('HTTPS_PROXY')) {
250 $defaults['proxy']['https'] = $proxy;
251 }
252
253 if ($noProxy = Utils::getenv('NO_PROXY')) {
254 $cleanedNoProxy = \str_replace(' ', '', $noProxy);
255 $defaults['proxy']['no'] = \explode(',', $cleanedNoProxy);
256 }
257
258 $this->config = $config + $defaults;
259
260 if (!empty($config['cookies']) && $config['cookies'] === true) {
261 $this->config['cookies'] = new CookieJar();
262 }
263
264 // Add the default user-agent header.
265 if (!isset($this->config['headers'])) {
266 $this->config['headers'] = ['User-Agent' => Utils::defaultUserAgent()];
267 } else {
268 // Add the User-Agent header if one was not already set.
269 foreach (\array_keys($this->config['headers']) as $name) {
270 if (\strtolower($name) === 'user-agent') {
271 return;
272 }
273 }
274 $this->config['headers']['User-Agent'] = Utils::defaultUserAgent();
275 }
276 }
277
278 /**
279 * Merges default options into the array.
280 *
281 * @param array $options Options to modify by reference
282 */
283 private function prepareDefaults(array $options): array
284 {
285 $defaults = $this->config;
286
287 if (!empty($defaults['headers'])) {
288 // Default headers are only added if they are not present.
289 $defaults['_conditional'] = $defaults['headers'];
290 unset($defaults['headers']);
291 }
292
293 // Special handling for headers is required as they are added as
294 // conditional headers and as headers passed to a request ctor.
295 if (\array_key_exists('headers', $options)) {
296 // Allows default headers to be unset.
297 if ($options['headers'] === null) {
298 $defaults['_conditional'] = [];
299 unset($options['headers']);
300 } elseif (!\is_array($options['headers'])) {
301 throw new InvalidArgumentException('headers must be an array');
302 }
303 }
304
305 // Shallow merge defaults underneath options.
306 $result = $options + $defaults;
307
308 // Remove null values.
309 foreach ($result as $k => $v) {
310 if ($v === null) {
311 unset($result[$k]);
312 }
313 }
314
315 return $result;
316 }
317
318 /**
319 * Transfers the given request and applies request options.
320 *
321 * The URI of the request is not modified and the request options are used
322 * as-is without merging in default options.
323 *
324 * @param array $options See \Depicter\GuzzleHttp\RequestOptions.
325 */
326 private function transfer(RequestInterface $request, array $options): PromiseInterface
327 {
328 $request = $this->applyOptions($request, $options);
329 /** @var HandlerStack $handler */
330 $handler = $options['handler'];
331
332 try {
333 return P\Create::promiseFor($handler($request, $options));
334 } catch (\Exception $e) {
335 return P\Create::rejectionFor($e);
336 }
337 }
338
339 /**
340 * Applies the array of request options to a request.
341 */
342 private function applyOptions(RequestInterface $request, array &$options): RequestInterface
343 {
344 $modify = [
345 'set_headers' => [],
346 ];
347
348 if (isset($options['headers'])) {
349 if (array_keys($options['headers']) === range(0, count($options['headers']) - 1)) {
350 throw new InvalidArgumentException('The headers array must have header name as keys.');
351 }
352 $modify['set_headers'] = $options['headers'];
353 unset($options['headers']);
354 }
355
356 if (isset($options['form_params'])) {
357 if (isset($options['multipart'])) {
358 throw new InvalidArgumentException('You cannot use '
359 .'form_params and multipart at the same time. Use the '
360 .'form_params option if you want to send application/'
361 .'x-www-form-urlencoded requests, and the multipart '
362 .'option to send multipart/form-data requests.');
363 }
364 $options['body'] = \http_build_query($options['form_params'], '', '&');
365 unset($options['form_params']);
366 // Ensure that we don't have the header in different case and set the new value.
367 $options['_conditional'] = Psr7\Utils::caselessRemove(['Content-Type'], $options['_conditional']);
368 $options['_conditional']['Content-Type'] = 'application/x-www-form-urlencoded';
369 }
370
371 if (isset($options['multipart'])) {
372 $options['body'] = new Psr7\MultipartStream($options['multipart']);
373 unset($options['multipart']);
374 }
375
376 if (isset($options['json'])) {
377 $options['body'] = Utils::jsonEncode($options['json']);
378 unset($options['json']);
379 // Ensure that we don't have the header in different case and set the new value.
380 $options['_conditional'] = Psr7\Utils::caselessRemove(['Content-Type'], $options['_conditional']);
381 $options['_conditional']['Content-Type'] = 'application/json';
382 }
383
384 if (!empty($options['decode_content'])
385 && $options['decode_content'] !== true
386 ) {
387 // Ensure that we don't have the header in different case and set the new value.
388 $options['_conditional'] = Psr7\Utils::caselessRemove(['Accept-Encoding'], $options['_conditional']);
389 $modify['set_headers']['Accept-Encoding'] = $options['decode_content'];
390 }
391
392 if (isset($options['body'])) {
393 if (\is_array($options['body'])) {
394 throw $this->invalidBody();
395 }
396 $modify['body'] = Psr7\Utils::streamFor($options['body']);
397 unset($options['body']);
398 }
399
400 if (!empty($options['auth']) && \is_array($options['auth'])) {
401 $value = $options['auth'];
402 $type = isset($value[2]) ? \strtolower($value[2]) : 'basic';
403 switch ($type) {
404 case 'basic':
405 // Ensure that we don't have the header in different case and set the new value.
406 $modify['set_headers'] = Psr7\Utils::caselessRemove(['Authorization'], $modify['set_headers']);
407 $modify['set_headers']['Authorization'] = 'Basic '
408 .\base64_encode("$value[0]:$value[1]");
409 break;
410 case 'digest':
411 // @todo: Do not rely on curl
412 $options['curl'][\CURLOPT_HTTPAUTH] = \CURLAUTH_DIGEST;
413 $options['curl'][\CURLOPT_USERPWD] = "$value[0]:$value[1]";
414 break;
415 case 'ntlm':
416 $options['curl'][\CURLOPT_HTTPAUTH] = \CURLAUTH_NTLM;
417 $options['curl'][\CURLOPT_USERPWD] = "$value[0]:$value[1]";
418 break;
419 }
420 }
421
422 if (isset($options['query'])) {
423 $value = $options['query'];
424 if (\is_array($value)) {
425 $value = \http_build_query($value, '', '&', \PHP_QUERY_RFC3986);
426 }
427 if (!\is_string($value)) {
428 throw new InvalidArgumentException('query must be a string or array');
429 }
430 $modify['query'] = $value;
431 unset($options['query']);
432 }
433
434 // Ensure that sink is not an invalid value.
435 if (isset($options['sink'])) {
436 // TODO: Add more sink validation?
437 if (\is_bool($options['sink'])) {
438 throw new InvalidArgumentException('sink must not be a boolean');
439 }
440 }
441
442 if (isset($options['version'])) {
443 $modify['version'] = $options['version'];
444 }
445
446 $request = Psr7\Utils::modifyRequest($request, $modify);
447 if ($request->getBody() instanceof Psr7\MultipartStream) {
448 // Use a multipart/form-data POST if a Content-Type is not set.
449 // Ensure that we don't have the header in different case and set the new value.
450 $options['_conditional'] = Psr7\Utils::caselessRemove(['Content-Type'], $options['_conditional']);
451 $options['_conditional']['Content-Type'] = 'multipart/form-data; boundary='
452 .$request->getBody()->getBoundary();
453 }
454
455 // Merge in conditional headers if they are not present.
456 if (isset($options['_conditional'])) {
457 // Build up the changes so it's in a single clone of the message.
458 $modify = [];
459 foreach ($options['_conditional'] as $k => $v) {
460 if (!$request->hasHeader($k)) {
461 $modify['set_headers'][$k] = $v;
462 }
463 }
464 $request = Psr7\Utils::modifyRequest($request, $modify);
465 // Don't pass this internal value along to middleware/handlers.
466 unset($options['_conditional']);
467 }
468
469 return $request;
470 }
471
472 /**
473 * Return an InvalidArgumentException with pre-set message.
474 */
475 private function invalidBody(): InvalidArgumentException
476 {
477 return new InvalidArgumentException('Passing in the "body" request '
478 .'option as an array to send a request is not supported. '
479 .'Please use the "form_params" request option to send a '
480 .'application/x-www-form-urlencoded request, or the "multipart" '
481 .'request option to send a multipart/form-data request.');
482 }
483 }
484