5
use GuzzleHttp\Adapter\AdapterInterface;
6
use GuzzleHttp\Adapter\Curl\CurlAdapter;
7
use GuzzleHttp\Adapter\Curl\MultiAdapter;
8
use GuzzleHttp\Adapter\FakeParallelAdapter;
9
use GuzzleHttp\Adapter\ParallelAdapterInterface;
10
use GuzzleHttp\Adapter\StreamAdapter;
11
use GuzzleHttp\Adapter\StreamingProxyAdapter;
12
use GuzzleHttp\Adapter\Transaction;
13
use GuzzleHttp\Adapter\TransactionIterator;
14
use GuzzleHttp\Event\HasEmitterTrait;
15
use GuzzleHttp\Exception\RequestException;
16
use GuzzleHttp\Message\MessageFactory;
17
use GuzzleHttp\Message\MessageFactoryInterface;
18
use GuzzleHttp\Message\RequestInterface;
23
class Client implements ClientInterface
27
const DEFAULT_CONCURRENCY = 25;
29
/** @var MessageFactoryInterface Request factory used by the client */
30
private $messageFactory;
32
/** @var AdapterInterface */
35
/** @var ParallelAdapterInterface */
36
private $parallelAdapter;
38
/** @var Url Base URL of the client */
41
/** @var array Default request options */
45
* Clients accept an array of constructor parameters.
47
* Here's an example of creating a client using an URI template for the
48
* client's base_url and an array of default request options to apply
51
* $client = new Client([
53
* 'http://www.foo.com/{version}/',
54
* ['version' => '123']
58
* 'allow_redirects' => false,
59
* 'proxy' => '192.168.16.1:10'
63
* @param array $config Client configuration settings
64
* - base_url: Base URL of the client that is merged into relative URLs.
65
* Can be a string or an array that contains a URI template followed
66
* by an associative array of expansion variables to inject into the
68
* - adapter: Adapter used to transfer requests
69
* - parallel_adapter: Adapter used to transfer requests in parallel
70
* - message_factory: Factory used to create request and response object
71
* - defaults: Default request options to apply to each request
72
* - emitter: Event emitter used for request events
74
public function __construct(array $config = [])
76
$this->configureBaseUrl($config);
77
$this->configureDefaults($config);
78
$this->configureAdapter($config);
79
if (isset($config['emitter'])) {
80
$this->emitter = $config['emitter'];
85
* Get the default User-Agent string to use with Guzzle
89
public static function getDefaultUserAgent()
91
static $defaultAgent = '';
93
$defaultAgent = 'Guzzle/' . self::VERSION;
94
if (extension_loaded('curl')) {
95
$defaultAgent .= ' curl/' . curl_version()['version'];
97
$defaultAgent .= ' PHP/' . PHP_VERSION;
100
return $defaultAgent;
103
public function __call($name, $arguments)
105
return \GuzzleHttp\deprecation_proxy(
109
['getEventDispatcher' => 'getEmitter']
113
public function getDefaultOption($keyOrPath = null)
115
return $keyOrPath === null
117
: \GuzzleHttp\get_path($this->defaults, $keyOrPath);
120
public function setDefaultOption($keyOrPath, $value)
122
\GuzzleHttp\set_path($this->defaults, $keyOrPath, $value);
125
public function getBaseUrl()
127
return (string) $this->baseUrl;
130
public function createRequest($method, $url = null, array $options = [])
132
$headers = $this->mergeDefaults($options);
133
// Use a clone of the client's emitter
134
$options['config']['emitter'] = clone $this->getEmitter();
136
$request = $this->messageFactory->createRequest(
138
$url ? (string) $this->buildUrl($url) : (string) $this->baseUrl,
142
// Merge in default headers
144
foreach ($headers as $key => $value) {
145
if (!$request->hasHeader($key)) {
146
$request->setHeader($key, $value);
154
public function get($url = null, $options = [])
156
return $this->send($this->createRequest('GET', $url, $options));
159
public function head($url = null, array $options = [])
161
return $this->send($this->createRequest('HEAD', $url, $options));
164
public function delete($url = null, array $options = [])
166
return $this->send($this->createRequest('DELETE', $url, $options));
169
public function put($url = null, array $options = [])
171
return $this->send($this->createRequest('PUT', $url, $options));
174
public function patch($url = null, array $options = [])
176
return $this->send($this->createRequest('PATCH', $url, $options));
179
public function post($url = null, array $options = [])
181
return $this->send($this->createRequest('POST', $url, $options));
184
public function options($url = null, array $options = [])
186
return $this->send($this->createRequest('OPTIONS', $url, $options));
189
public function send(RequestInterface $request)
191
$transaction = new Transaction($this, $request);
193
if ($response = $this->adapter->send($transaction)) {
196
throw new \LogicException('No response was associated with the transaction');
197
} catch (RequestException $e) {
199
} catch (\Exception $e) {
200
// Wrap exceptions in a RequestException to adhere to the interface
201
throw new RequestException($e->getMessage(), $request, null, $e);
205
public function sendAll($requests, array $options = [])
207
if (!($requests instanceof TransactionIterator)) {
208
$requests = new TransactionIterator($requests, $this, $options);
211
$this->parallelAdapter->sendAll(
213
isset($options['parallel'])
214
? $options['parallel']
215
: self::DEFAULT_CONCURRENCY
220
* Get an array of default options to apply to the client
224
protected function getDefaultOptions()
227
'allow_redirects' => true,
228
'exceptions' => true,
229
'decode_content' => true,
230
'verify' => __DIR__ . '/cacert.pem'
233
// Use the bundled cacert if it is a regular file, or set to true if
234
// using a phar file (because curL and the stream wrapper can't read
235
// cacerts from the phar stream wrapper). Favor the ini setting over
236
// the system's cacert.
237
if (substr(__FILE__, 0, 7) == 'phar://') {
238
$settings['verify'] = ini_get('openssl.cafile') ?: true;
241
// Use the standard Linux HTTP_PROXY and HTTPS_PROXY if set
242
if ($proxy = getenv('HTTP_PROXY')) {
243
$settings['proxy']['http'] = $proxy;
246
if ($proxy = getenv('HTTPS_PROXY')) {
247
$settings['proxy']['https'] = $proxy;
254
* Expand a URI template and inherit from the base URL if it's relative
256
* @param string|array $url URL or URI template to expand
260
private function buildUrl($url)
262
if (!is_array($url)) {
263
if (strpos($url, '://')) {
264
return (string) $url;
266
return (string) $this->baseUrl->combine($url);
267
} elseif (strpos($url[0], '://')) {
268
return \GuzzleHttp\uri_template($url[0], $url[1]);
271
return (string) $this->baseUrl->combine(
272
\GuzzleHttp\uri_template($url[0], $url[1])
277
* Get a default parallel adapter to use based on the environment
279
* @return ParallelAdapterInterface
281
private function getDefaultParallelAdapter()
283
return extension_loaded('curl')
284
? new MultiAdapter($this->messageFactory)
285
: new FakeParallelAdapter($this->adapter);
289
* Create a default adapter to use based on the environment
290
* @throws \RuntimeException
292
private function getDefaultAdapter()
294
if (extension_loaded('curl')) {
295
$this->parallelAdapter = new MultiAdapter($this->messageFactory);
296
$this->adapter = function_exists('curl_reset')
297
? new CurlAdapter($this->messageFactory)
298
: $this->parallelAdapter;
299
if (ini_get('allow_url_fopen')) {
300
$this->adapter = new StreamingProxyAdapter(
302
new StreamAdapter($this->messageFactory)
305
} elseif (ini_get('allow_url_fopen')) {
306
$this->adapter = new StreamAdapter($this->messageFactory);
308
throw new \RuntimeException('Guzzle requires cURL, the '
309
. 'allow_url_fopen ini setting, or a custom HTTP adapter.');
313
private function configureBaseUrl(&$config)
315
if (!isset($config['base_url'])) {
316
$this->baseUrl = new Url('', '');
317
} elseif (is_array($config['base_url'])) {
318
$this->baseUrl = Url::fromString(
319
\GuzzleHttp\uri_template(
320
$config['base_url'][0],
321
$config['base_url'][1]
324
$config['base_url'] = (string) $this->baseUrl;
326
$this->baseUrl = Url::fromString($config['base_url']);
330
private function configureDefaults($config)
332
if (!isset($config['defaults'])) {
333
$this->defaults = $this->getDefaultOptions();
335
$this->defaults = array_replace(
336
$this->getDefaultOptions(),
341
// Add the default user-agent header
342
if (!isset($this->defaults['headers'])) {
343
$this->defaults['headers'] = [
344
'User-Agent' => static::getDefaultUserAgent()
346
} elseif (!isset(array_change_key_case($this->defaults['headers'])['user-agent'])) {
347
// Add the User-Agent header if one was not already set
348
$this->defaults['headers']['User-Agent'] = static::getDefaultUserAgent();
352
private function configureAdapter(&$config)
354
if (isset($config['message_factory'])) {
355
$this->messageFactory = $config['message_factory'];
357
$this->messageFactory = new MessageFactory();
359
if (isset($config['adapter'])) {
360
$this->adapter = $config['adapter'];
362
$this->getDefaultAdapter();
364
// If no parallel adapter was explicitly provided and one was not
365
// defaulted when creating the default adapter, then create one now.
366
if (isset($config['parallel_adapter'])) {
367
$this->parallelAdapter = $config['parallel_adapter'];
368
} elseif (!$this->parallelAdapter) {
369
$this->parallelAdapter = $this->getDefaultParallelAdapter();
374
* Merges default options into the array passed by reference and returns
375
* an array of headers that need to be merged in after the request is
378
* @param array $options Options to modify by reference
382
private function mergeDefaults(&$options)
384
// Merging optimization for when no headers are present
385
if (!isset($options['headers'])
386
|| !isset($this->defaults['headers'])) {
387
$options = array_replace_recursive($this->defaults, $options);
391
$defaults = $this->defaults;
392
unset($defaults['headers']);
393
$options = array_replace_recursive($defaults, $options);
395
return $this->defaults['headers'];