Completed
Push — master ( 122bdb...4f8f85 )
by Jens
10:49 queued 41s
created

Client::logException()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 19
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 3

Importance

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