Completed
Push — master ( 44676f...b7bc18 )
by Jens
13:18
created

Client::getHttpClient()   C

Complexity

Conditions 7
Paths 17

Size

Total Lines 28
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 17
CRAP Score 7.0084

Importance

Changes 0
Metric Value
dl 0
loc 28
ccs 17
cts 18
cp 0.9444
rs 6.7272
c 0
b 0
f 0
cc 7
eloc 18
nc 17
nop 1
crap 7.0084
1
<?php
2
/**
3
 * @author @jayS-de <[email protected]>
4
 * @created 19.01.15, 14:29
5
 */
6
7
namespace Commercetools\Core;
8
9
use Commercetools\Core\Client\Adapter\CorrelationIdAware;
10
use Commercetools\Core\Client\Adapter\TokenProviderAware;
11
use Commercetools\Core\Helper\CorrelationIdProvider;
12
use Commercetools\Core\Client\Adapter\AdapterOptionInterface;
13
use Commercetools\Core\Response\ErrorResponse;
14
use Psr\Http\Message\RequestInterface;
15
use Psr\Http\Message\ResponseInterface;
16
use Psr\Log\LoggerAwareInterface;
17
use Psr\Log\LoggerInterface;
18
use Commercetools\Core\Client\Adapter\AdapterInterface;
19
use Commercetools\Core\Error\InvalidTokenException;
20
use Commercetools\Core\Error\Message;
21
use Commercetools\Core\Error\ApiException;
22
use Commercetools\Core\Model\Common\ContextAwareInterface;
23
use Commercetools\Core\Response\ApiResponseInterface;
24
use Commercetools\Core\Request\ClientRequestInterface;
25
use Commercetools\Core\Client\OAuth\Manager;
26
27
/**
28
 * The client for communicating with the commercetools platform
29
 *
30
 * @description
31
 * ## Instantiation
32
 *
33
 * ```php
34
 * $config = Config::fromArray(
35
 *  ['client_id' => '<client_id>', 'client_secret' => '<client_secret>', 'project' => '<project>']
36
 * );
37
 * $client = Client::ofConfig($config);
38
 * ```
39
 *
40
 * ## Execution
41
 *
42
 * ### Synchronous
43
 * There are two main approaches for retrieving result objects
44
 *
45
 * Client centric:
46
 *
47
 * ```php
48
 * $response = $client->execute(ProductProjectionSearchRequest::of());
49
 * $products = $response->toObject();
50
 * ```
51
 *
52
 * Request centric:
53
 *
54
 * ```php
55
 * $request = ProductProjectionSearchRequest::of();
56
 * $response = $request->executeWithClient($client);
57
 * $products = $request->mapFromResponse($response);
58
 * ```
59
 *
60
 * By using the request centric approach the IDE is capable of resolving the correct classes and give
61
 * a maximum of support with available methods.
62
 *
63
 * ### Asynchronous
64
 * The asynchronous execution will return a promise to fulfill the request.
65
 *
66
 * ```php
67
 * $response = $client->executeAsync(ProductProjectionSearchRequest::of());
68
 * ```
69
 *
70
 * ### Batch
71
 * By filling the batch queue and starting the execution all requests will be executed in parallel.
72
 *
73
 * ```php
74
 * $client->addBatchRequest(ProductProjectionSearchRequest::of())
75
 *     ->addBatchRequest(CartByIdGetRequest::ofId($cartId));
76
 * $responses = $client->executeBatch();
77
 * ```
78
 *
79
 * ## Instantiation options
80
 *
81
 * ### Using a logger
82
 *
83
 * The client uses the PSR-3 logger interface for logging requests and deprecation notices. To enable
84
 * logging provide a PSR-3 compliant logger (e.g. Monolog).
85
 *
86
 * ```php
87
 * $logger = new \Monolog\Logger('name');
88
 * $logger->pushHandler(new StreamHandler('./requests.log'));
89
 * $client = Client::ofConfigAndLogger($config, $logger);
90
 * ```
91
 *
92
 * ### Using a cache adapter ###
93
 *
94
 * The client will automatically request an OAuth token and store the token in the provided cache.
95
 *
96
 * It's also possible to use a different cache adapter. The SDK provides a Doctrine, a Redis and an APCu cache adapter.
97
 * By default the SDK tries to instantiate the APCu or a PSR-6 filesystem cache adapter if there is no cache given.
98
 * E.g. Redis:
99
 *
100
 * ```php
101
 * $redis = new \Redis();
102
 * $redis->connect('localhost');
103
 * $client = Client::ofConfigAndCache($config, $redis);
104
 * ```
105
 *
106
 * #### Using cache and logger ####
107
 *
108
 * ```php
109
 * $client = Client::ofConfigCacheAndLogger($config, $cache, $logger);
110
 * ```
111
 *
112
 * #### Using a custom cache adapter ####
113
 *
114
 * ```php
115
 * class <CacheClass>Adapter implements \Commercetools\Core\Cache\CacheAdapterInterface {
116
 *     protected $cache;
117
 *     public function __construct(<CacheClass> $cache) {
118
 *         $this->cache = $cache;
119
 *     }
120
 * }
121
 *
122
 * $client->getAdapterFactory()->registerCallback(function ($cache) {
123
 *     if ($cache instanceof <CacheClass>) {
124
 *         return new <CacheClass>Adapter($cache);
125
 *     }
126
 *     return null;
127
 * });
128
 * ```
129
 *
130
 * @package Commercetools\Core
131
 */
132
class Client extends AbstractHttpClient implements LoggerAwareInterface
133
{
134
    const DEPRECATION_HEADER = 'X-DEPRECATION-NOTICE';
135
136
    /**
137
     * @sideeffect Test123
138
     * @var LoggerInterface
139
     */
140
    protected $logger;
141
142
    /**
143
     * @var Manager
144
     */
145
    protected $oauthManager;
146
147
    /**
148
     * @var ClientRequestInterface[]
149
     */
150
    protected $batchRequests = [];
151
152
    protected $tokenRefreshed = false;
153
154
    /**
155
     * @param array|Config $config
156
     * @param $cache
157
     * @param LoggerInterface $logger
158
     */
159 61
    public function __construct($config, $cache = null, LoggerInterface $logger = null)
160
    {
161 61
        parent::__construct($config);
162
163 61
        $manager = new Manager($config, $cache);
164 61
        $this->setOauthManager($manager);
165 61
        $this->setLogger($logger);
166 61
    }
167
168
    /**
169
     * @return Manager
170
     */
171 34
    public function getOauthManager()
172
    {
173 34
        return $this->oauthManager;
174
    }
175
176
    /**
177
     * @param Manager $oauthManager
178
     * @return $this
179
     */
180 61
    protected function setOauthManager(Manager $oauthManager)
181
    {
182 61
        $this->oauthManager = $oauthManager;
183 61
        return $this;
184
    }
185
186
    /**
187
     * @param LoggerInterface $logger
188
     * @return $this
189
     */
190 61
    public function setLogger(LoggerInterface $logger = null)
191
    {
192 61
        if ($logger instanceof LoggerInterface) {
193 40
            $this->logger = $logger;
194
        }
195 61
        return $this;
196
    }
197
198
    /**
199
     * @param array $options
200
     * @return AdapterInterface
201
     */
202 520
    public function getHttpClient($options = [])
203
    {
204 520
        if (is_null($this->httpClient)) {
205 59
            $clientOptions = $this->config->getClientOptions();
206 59
            if (count($clientOptions)) {
207
                $options = array_merge($clientOptions, $options);
208
            }
209 59
            $client = parent::getHttpClient($options);
210 59
            if ($client instanceof TokenProviderAware) {
211 59
                $client->setOAuthTokenProvider($this->getOauthManager());
212
            }
213 59
            if ($this->logger instanceof LoggerInterface) {
214 40
                $client->setLogger(
215 40
                    $this->logger,
216 40
                    $this->getConfig()->getLogLevel(),
217 40
                    $this->getConfig()->getMessageFormatter()
218
                );
219
            }
220 59
            if ($this->getConfig()->getCorrelationIdProvider() instanceof CorrelationIdProvider
221 59
                && $client instanceof CorrelationIdAware
222
            ) {
223 58
                $client->setCorrelationIdProvider($this->getConfig()->getCorrelationIdProvider());
224
            }
225 59
            $this->httpClient = $client;
226
        }
227
228 520
        return $this->httpClient;
229
    }
230
231
232
    /**
233
     * @return string
234
     */
235 60
    protected function getBaseUrl()
236
    {
237 60
        return $this->getConfig()->getApiUrl() . '/' . $this->getConfig()->getProject() . '/';
238
    }
239
240
    /**
241
     * Executes an API request synchronously
242
     *
243
     * @param ClientRequestInterface $request
244
     * @param array $headers
245
     * @param array $clientOptions
246
     * @return ApiResponseInterface
247
     * @throws ApiException
248
     * @throws InvalidTokenException
249
     */
250 512
    public function execute(ClientRequestInterface $request, array $headers = null, array $clientOptions = [])
251
    {
252 512
        if ($request instanceof ContextAwareInterface) {
253 512
            $request->setContextIfNull($this->getConfig()->getContext());
254
        }
255 512
        $httpRequest = $this->createHttpRequest($request, $headers);
256
257
        try {
258 512
            $client = $this->getHttpClient();
259 512
            if ($client instanceof AdapterOptionInterface) {
260 512
                $httpResponse = $client->execute($httpRequest, $clientOptions);
261
            } else {
262
                $httpResponse = $client->execute($httpRequest);
263
            }
264 489
            $response = $request->buildResponse($httpResponse);
265 107
        } catch (ApiException $exception) {
266 107
            if ($exception instanceof InvalidTokenException && !$this->tokenRefreshed) {
267 3
                $this->tokenRefreshed = true;
268 3
                $this->getOauthManager()->refreshToken();
269 2
                return $this->execute($request);
270
            }
271 105
            if ($this->getConfig()->getThrowExceptions() || !$exception->getResponse() instanceof ResponseInterface) {
272 10
                throw $exception;
273
            }
274 95
            $httpResponse = $exception->getResponse();
275 95
            $this->logException($exception);
276 95
            $response = new ErrorResponse($exception, $request, $httpResponse);
277
        }
278 502
        $this->logDeprecatedRequest($httpResponse, $httpRequest);
279
280
281 502
        return $response;
282
    }
283
284
    /**
285
     * Executes an API request asynchronously
286
     * @param ClientRequestInterface $request
287
     * @param array $clientOptions
288
     * @return ApiResponseInterface
289
     */
290 2
    public function executeAsync(ClientRequestInterface $request, array $headers = null, array $clientOptions = [])
291
    {
292 2
        if ($request instanceof ContextAwareInterface) {
293 2
            $request->setContextIfNull($this->getConfig()->getContext());
294
        }
295 2
        $httpRequest = $this->createHttpRequest($request, $headers);
296 2
        $client = $this->getHttpClient();
297 2
        if ($client instanceof AdapterOptionInterface) {
298 2
            $response = $request->buildResponse($client->executeAsync($httpRequest, $clientOptions));
299
        } else {
300
            $response = $request->buildResponse($client->executeAsync($httpRequest));
301
        }
302
303 2
        $response = $response->then(
304 2
            function ($httpResponse) use ($httpRequest) {
305 2
                $this->logDeprecatedRequest($httpResponse, $httpRequest);
306 2
                return $httpResponse;
307 2
            }
308
        );
309
310 2
        return $response;
311
    }
312
313
    /**
314
     * @param ClientRequestInterface $request
315
     * @param array $headers
316
     * @return RequestInterface
317
     */
318 519
    protected function createHttpRequest(ClientRequestInterface $request, array $headers = null)
319
    {
320 519
        $httpRequest = $request->httpRequest();
321 519
        if (!$this->getHttpClient() instanceof TokenProviderAware) {
322
            $token = $this->getOauthManager()->getToken();
323
            $httpRequest = $httpRequest
324
                ->withHeader('Authorization', 'Bearer ' . $token->getToken())
325
            ;
326
        }
327 519
        if (is_array($headers)) {
328 2
            foreach ($headers as $headerName => $headerValues) {
329
                $httpRequest = $httpRequest
330 2
                    ->withAddedHeader($headerName, $headerValues)
331
                ;
332
            }
333
        }
334
335 519
        return $httpRequest;
336
    }
337
338
    /**
339
     * Executes API requests in batch
340
     * @param array $headers
341
     * @param array $clientOptions
342
     * @return ApiResponseInterface[]
343
     * @throws ApiException
344
     */
345 421
    public function executeBatch(array $headers = null, array $clientOptions = [])
346
    {
347 421
        $requests = $this->getBatchHttpRequests($headers);
348 421
        $client = $this->getHttpClient();
349 421
        if ($client instanceof AdapterOptionInterface) {
350 421
            $httpResponses = $client->executeBatch($requests, $clientOptions);
351
        } else {
352
            $httpResponses = $client->executeBatch($requests);
353
        }
354
355 421
        $responses = [];
356 421
        foreach ($httpResponses as $key => $httpResponse) {
357 421
            $request = $this->batchRequests[$key];
358 421
            $httpRequest = $requests[$key];
359 421
            if ($httpResponse instanceof ApiException) {
360 17
                $exception = $httpResponse;
361 17
                if ($this->getConfig()->getThrowExceptions() ||
362 17
                    !$httpResponse->getResponse() instanceof ResponseInterface
363
                ) {
364 1
                    throw $exception;
365
                }
366 16
                $this->logException($httpResponse);
367 16
                $httpResponse = $exception->getResponse();
368 16
                $responses[$request->getIdentifier()] = new ErrorResponse(
369 16
                    $exception,
370 16
                    $request,
371 16
                    $httpResponse
372
                );
373
            } else {
374 409
                $responses[$request->getIdentifier()] = $request->buildResponse($httpResponse);
375
            }
376 420
            $this->logDeprecatedRequest($httpResponse, $httpRequest);
377
        }
378 420
        unset($this->batchRequests);
379 420
        $this->batchRequests = [];
380
381 420
        return $responses;
382
    }
383
384
    /**
385
     * @param $exception
386
     * @return $this
387
     */
388 111
    protected function logException(ApiException $exception)
389
    {
390 111
        if (is_null($this->logger)) {
391 3
            return $this;
392
        }
393 108
        $response = $exception->getResponse();
394
395 108
        $context = [];
396 108
        if ($response instanceof ResponseInterface) {
397
            $context = [
398 108
                'responseStatusCode' => $response->getStatusCode(),
399 108
                'responseHeaders' => $response->getHeaders(),
400 108
                'responseBody' => (string)$response->getBody(),
401
            ];
402
        }
403 108
        $this->logger->error($exception->getMessage(), $context);
404
405 108
        return $this;
406
    }
407
408
    /**
409
     * @param ResponseInterface $response
410
     * @param RequestInterface $request
411
     * @return $this
412
     */
413 508
    protected function logDeprecatedRequest(ResponseInterface $response, RequestInterface $request)
414
    {
415 508
        if (is_null($this->logger)) {
416 7
            return $this;
417
        }
418
419 501
        if ($response->hasHeader(static::DEPRECATION_HEADER)) {
420 3
            $message = sprintf(
421 3
                Message::DEPRECATED_METHOD,
422 3
                $request->getUri(),
423 3
                $request->getMethod(),
424 3
                $response->getHeaderLine(static::DEPRECATION_HEADER)
425
            );
426 3
            $this->logger->warning($message);
427
        }
428 501
        return $this;
429
    }
430
431
    /**
432
     * @param RequestInterface $request
433
     * @param ResponseInterface $response
434
     * @return string
435
     */
436
    protected function format(RequestInterface $request, ResponseInterface $response)
437
    {
438
        $entries = [
439
            $request->getMethod(),
440
            (string)$request->getUri(),
441
            $response->getStatusCode()
442
        ];
443
        return implode(', ', $entries);
444
    }
445
446
    /**
447
     * @param array $headers
448
     * @return array
449
     */
450 421
    protected function getBatchHttpRequests(array $headers = null)
451
    {
452 421
        $requests = array_map(
453 421
            function ($request) use ($headers) {
454 421
                return $this->createHttpRequest($request, $headers);
455 421
            },
456 421
            $this->batchRequests
457
        );
458
459 421
        return $requests;
460
    }
461
462
    /**
463
     * Adds a request to the batch execution queue
464
     * @param ClientRequestInterface $request
465
     * @return $this
466
     */
467 421
    public function addBatchRequest(ClientRequestInterface $request)
468
    {
469 421
        if ($request instanceof ContextAwareInterface) {
470 421
            $request->setContextIfNull($this->getConfig()->getContext());
471
        }
472 421
        $this->batchRequests[] = $request;
473 421
        return $this;
474
    }
475
476
    /**
477
     * Instantiates a client with the given config
478
     * @param Config $config
479
     * @return static
480
     */
481 1
    public static function ofConfig(Config $config)
482
    {
483 1
        return new static($config);
484
    }
485
486
    /**
487
     * Instantiates a client with the given config and cache adapter
488
     * @param Config $config
489
     * @param $cache
490
     * @return static
491
     */
492
    public static function ofConfigAndCache(Config $config, $cache)
493
    {
494
        return new static($config, $cache);
495
    }
496
497
    /**
498
     * Instantiates a client with the given config and a PSR-3 compliant logger
499
     * @param Config $config
500
     * @param LoggerInterface $logger
501
     * @return static
502
     */
503 1
    public static function ofConfigAndLogger(Config $config, LoggerInterface $logger)
504
    {
505 1
        return new static($config, null, $logger);
506
    }
507
508
    /**
509
     * Instantiates a client with the given config, a cache adapter and a PSR-3 compliant logger
510
     * @param Config $config
511
     * @param $cache
512
     * @param LoggerInterface $logger
513
     * @return static
514
     */
515 31
    public static function ofConfigCacheAndLogger(Config $config, $cache, LoggerInterface $logger)
516
    {
517 31
        return new static($config, $cache, $logger);
518
    }
519
}
520