Passed
Branch refactor_config_class (788a46)
by Jens
08:38
created

Client::format()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 8
ccs 0
cts 5
cp 0
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 5
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\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
    protected $project;
155
156
    protected $context;
157
158
    /**
159
     * @param array|Config $config
160
     * @param $cache
161
     * @param LoggerInterface $logger
162
     */
163 63
    public function __construct($config, $cache = null, LoggerInterface $logger = null)
164
    {
165 63
        parent::__construct($config);
166
167 63
        $manager = new Manager($config, $cache);
168 63
        $this->setOauthManager($manager);
169 63
        $this->setLogger($logger);
170 63
    }
171
172
    /**
173
     * @param Config|array $config
174
     * @return $this
175
     */
176 63
    public function setConfig($config)
177
    {
178 63
        $config->getCredentials()->check();
179 63
        parent::setConfig($config);
180 63
        $this->project = $config->getCredentials()->getProject();
181 63
        $this->context = $config->getContext();
182
183 63
        return $this;
184
    }
185
186
    /**
187
     * @return Manager
188
     */
189 36
    public function getOauthManager()
190
    {
191 36
        return $this->oauthManager;
192
    }
193
194
    /**
195
     * @param Manager $oauthManager
196
     * @return $this
197
     */
198 63
    protected function setOauthManager(Manager $oauthManager)
199
    {
200 63
        $this->oauthManager = $oauthManager;
201 63
        return $this;
202
    }
203
204
    /**
205
     * @param LoggerInterface $logger
206
     * @return $this
207
     */
208 63
    public function setLogger(LoggerInterface $logger = null)
209
    {
210 63
        if ($logger instanceof LoggerInterface) {
211 41
            $this->logger = $logger;
212
        }
213 63
        return $this;
214
    }
215
216
    /**
217
     * @param array $options
218
     * @return AdapterInterface
219
     */
220 582
    public function getHttpClient($options = [])
221
    {
222 582
        if (is_null($this->httpClient)) {
223 61
            $clientOptions = $this->clientConfig->getClientOptions();
224 61
            if (count($clientOptions) > 0) {
225 60
                $options = array_merge($clientOptions, $options);
226
            }
227 61
            $client = parent::getHttpClient($options);
228 61
            if ($client instanceof TokenProviderAware) {
229 61
                $client->setOAuthTokenProvider($this->getOauthManager());
230
            }
231 61
            if ($this->logger instanceof LoggerInterface) {
0 ignored issues
show
introduced by
$this->logger is always a sub-type of Psr\Log\LoggerInterface.
Loading history...
232 41
                $client->setLogger(
233 41
                    $this->logger,
234 41
                    $this->clientConfig->getLogLevel(),
235 41
                    $this->clientConfig->getMessageFormatter()
236
                );
237
            }
238 61
            if ($this->clientConfig->getCorrelationIdProvider() instanceof CorrelationIdProvider
239 61
                && $client instanceof CorrelationIdAware
240
            ) {
241 33
                $client->setCorrelationIdProvider($this->clientConfig->getCorrelationIdProvider());
242
            }
243 61
            $this->httpClient = $client;
244
        }
245
246 582
        return $this->httpClient;
247
    }
248
249
250
    /**
251
     * @return string
252
     */
253 62
    protected function getBaseUrl()
254
    {
255 62
        return $this->clientConfig->getBaseUri() . '/' . $this->project . '/';
256
    }
257
258
    /**
259
     * Executes an API request synchronously
260
     *
261
     * @param ClientRequestInterface $request
262
     * @param array $headers
263
     * @param array $clientOptions
264
     * @return ApiResponseInterface
265
     * @throws ApiException
266
     * @throws InvalidTokenException
267
     */
268 573
    public function execute(ClientRequestInterface $request, array $headers = null, array $clientOptions = [])
269
    {
270 573
        if ($request instanceof ContextAwareInterface) {
271 573
            $request->setContextIfNull($this->context);
272
        }
273 573
        $httpRequest = $this->createHttpRequest($request, $headers);
274
275
        try {
276 573
            $client = $this->getHttpClient();
277 573
            if ($client instanceof AdapterOptionInterface) {
278 573
                $httpResponse = $client->execute($httpRequest, $clientOptions);
279
            } else {
280
                $httpResponse = $client->execute($httpRequest);
281
            }
282 549
            $response = $request->buildResponse($httpResponse);
283 81
        } catch (ApiException $exception) {
284 81
            if ($exception instanceof InvalidTokenException && !$this->tokenRefreshed) {
285 3
                $this->tokenRefreshed = true;
286 3
                $this->getOauthManager()->refreshToken();
287 2
                return $this->execute($request);
288
            }
289 79
            if ($this->clientConfig->isThrowExceptions() || !$exception->getResponse() instanceof ResponseInterface) {
290 10
                throw $exception;
291
            }
292 69
            $httpResponse = $exception->getResponse();
293 69
            $this->logException($exception);
294 69
            $response = new ErrorResponse($exception, $request, $httpResponse);
295
        }
296 563
        $this->logDeprecatedRequest($httpResponse, $httpRequest);
297
298
299 563
        return $response;
300
    }
301
302
    /**
303
     * Executes an API request asynchronously
304
     * @param ClientRequestInterface $request
305
     * @param array $clientOptions
306
     * @return ApiResponseInterface
307
     */
308 2
    public function executeAsync(ClientRequestInterface $request, array $headers = null, array $clientOptions = [])
309
    {
310 2
        if ($request instanceof ContextAwareInterface) {
311 2
            $request->setContextIfNull($this->context);
312
        }
313 2
        $httpRequest = $this->createHttpRequest($request, $headers);
314 2
        $client = $this->getHttpClient();
315 2
        if ($client instanceof AdapterOptionInterface) {
316 2
            $response = $request->buildResponse($client->executeAsync($httpRequest, $clientOptions));
317
        } else {
318
            $response = $request->buildResponse($client->executeAsync($httpRequest));
319
        }
320
321 2
        $response = $response->then(
322 2
            function ($httpResponse) use ($httpRequest) {
323 2
                $this->logDeprecatedRequest($httpResponse, $httpRequest);
324 2
                return $httpResponse;
325 2
            }
326
        );
327
328 2
        return $response;
329
    }
330
331
    /**
332
     * @param ClientRequestInterface $request
333
     * @param array $headers
334
     * @return RequestInterface
335
     */
336 580
    protected function createHttpRequest(ClientRequestInterface $request, array $headers = null)
337
    {
338 580
        $httpRequest = $request->httpRequest();
339 580
        if (!$this->getHttpClient() instanceof TokenProviderAware) {
340
            $token = $this->getOauthManager()->getToken();
341
            $httpRequest = $httpRequest
342
                ->withHeader('Authorization', 'Bearer ' . $token->getToken())
343
            ;
344
        }
345 580
        if (is_array($headers)) {
346 3
            foreach ($headers as $headerName => $headerValues) {
347
                $httpRequest = $httpRequest
348 3
                    ->withAddedHeader($headerName, $headerValues)
349
                ;
350
            }
351
        }
352
353 580
        return $httpRequest;
354
    }
355
356
    /**
357
     * Executes API requests in batch
358
     * @param array $headers
359
     * @param array $clientOptions
360
     * @return ApiResponseInterface[]
361
     * @throws ApiException
362
     */
363 482
    public function executeBatch(array $headers = null, array $clientOptions = [])
364
    {
365 482
        $requests = $this->getBatchHttpRequests($headers);
366 482
        $client = $this->getHttpClient();
367 482
        if ($client instanceof AdapterOptionInterface) {
368 482
            $httpResponses = $client->executeBatch($requests, $clientOptions);
369
        } else {
370
            $httpResponses = $client->executeBatch($requests);
371
        }
372
373 482
        $responses = [];
374 482
        foreach ($httpResponses as $key => $httpResponse) {
375 482
            $request = $this->batchRequests[$key];
376 482
            $httpRequest = $requests[$key];
377 482
            if ($httpResponse instanceof ApiException) {
378 11
                $exception = $httpResponse;
379 11
                if ($this->clientConfig->isThrowExceptions() ||
380 11
                    !$httpResponse->getResponse() instanceof ResponseInterface
381
                ) {
382 1
                    throw $exception;
383
                }
384 10
                $this->logException($httpResponse);
385 10
                $httpResponse = $exception->getResponse();
386 10
                $responses[$request->getIdentifier()] = new ErrorResponse(
387 10
                    $exception,
388 10
                    $request,
389 10
                    $httpResponse
390
                );
391
            } else {
392 475
                $responses[$request->getIdentifier()] = $request->buildResponse($httpResponse);
393
            }
394 481
            $this->logDeprecatedRequest($httpResponse, $httpRequest);
395
        }
396 481
        unset($this->batchRequests);
397 481
        $this->batchRequests = [];
398
399 481
        return $responses;
400
    }
401
402
    /**
403
     * @param $exception
404
     * @return $this
405
     */
406 79
    protected function logException(ApiException $exception)
407
    {
408 79
        if (is_null($this->logger)) {
409 3
            return $this;
410
        }
411 76
        $response = $exception->getResponse();
412
413 76
        $context = [];
414 76
        if ($response instanceof ResponseInterface) {
0 ignored issues
show
introduced by
$response is always a sub-type of Psr\Http\Message\ResponseInterface.
Loading history...
415
            $context = [
416 76
                'responseStatusCode' => $response->getStatusCode(),
417 76
                'responseHeaders' => $response->getHeaders(),
418 76
                'responseBody' => (string)$response->getBody(),
419
            ];
420
        }
421 76
        $this->logger->error($exception->getMessage(), $context);
422
423 76
        return $this;
424
    }
425
426
    /**
427
     * @param ResponseInterface $response
428
     * @param RequestInterface $request
429
     * @return $this
430
     */
431 569
    protected function logDeprecatedRequest(ResponseInterface $response, RequestInterface $request)
432
    {
433 569
        if (is_null($this->logger)) {
434 7
            return $this;
435
        }
436
437 562
        if ($response->hasHeader(static::DEPRECATION_HEADER)) {
438 3
            $message = sprintf(
439 3
                Message::DEPRECATED_METHOD,
440 3
                $request->getUri(),
441 3
                $request->getMethod(),
442 3
                $response->getHeaderLine(static::DEPRECATION_HEADER)
443
            );
444 3
            $this->logger->warning($message);
445
        }
446 562
        return $this;
447
    }
448
449
    /**
450
     * @param RequestInterface $request
451
     * @param ResponseInterface $response
452
     * @return string
453
     */
454
    protected function format(RequestInterface $request, ResponseInterface $response)
455
    {
456
        $entries = [
457
            $request->getMethod(),
458
            (string)$request->getUri(),
459
            $response->getStatusCode()
460
        ];
461
        return implode(', ', $entries);
462
    }
463
464
    /**
465
     * @param array $headers
466
     * @return array
467
     */
468 482
    protected function getBatchHttpRequests(array $headers = null)
469
    {
470 482
        $requests = array_map(
471 482
            function ($request) use ($headers) {
472 482
                return $this->createHttpRequest($request, $headers);
473 482
            },
474 482
            $this->batchRequests
475
        );
476
477 482
        return $requests;
478
    }
479
480
    /**
481
     * Adds a request to the batch execution queue
482
     * @param ClientRequestInterface $request
483
     * @return $this
484
     */
485 482
    public function addBatchRequest(ClientRequestInterface $request)
486
    {
487 482
        if ($request instanceof ContextAwareInterface) {
488 482
            $request->setContextIfNull($this->context);
489
        }
490 482
        $this->batchRequests[] = $request;
491 482
        return $this;
492
    }
493
494
    /**
495
     * Instantiates a client with the given config
496
     * @param Config $config
497
     * @return static
498
     */
499 2
    public static function ofConfig(Config $config)
500
    {
501 2
        return new static($config);
502
    }
503
504
    /**
505
     * Instantiates a client with the given config and cache adapter
506
     * @param Config $config
507
     * @param $cache
508
     * @return static
509
     */
510
    public static function ofConfigAndCache(Config $config, $cache)
511
    {
512
        return new static($config, $cache);
513
    }
514
515
    /**
516
     * Instantiates a client with the given config and a PSR-3 compliant logger
517
     * @param Config $config
518
     * @param LoggerInterface $logger
519
     * @return static
520
     */
521 1
    public static function ofConfigAndLogger(Config $config, LoggerInterface $logger)
522
    {
523 1
        return new static($config, null, $logger);
524
    }
525
526
    /**
527
     * Instantiates a client with the given config, a cache adapter and a PSR-3 compliant logger
528
     * @param Config $config
529
     * @param $cache
530
     * @param LoggerInterface $logger
531
     * @return static
532
     */
533 32
    public static function ofConfigCacheAndLogger(Config $config, $cache, LoggerInterface $logger)
534
    {
535 32
        return new static($config, $cache, $logger);
536
    }
537
}
538