Passed
Push — master ( da0795...78880b )
by Jens
14:45 queued 18s
created

Client::ofConfigAndCache()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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