Passed
Push — develop ( 166b0f...9fc299 )
by Jens
04:57
created

Client::logDeprecatedRequest()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 17
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 3

Importance

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