Completed
Push — master ( 1d2d63...5f432b )
by Jens
09:03
created

Client   B

Complexity

Total Complexity 38

Size/Duplication

Total Lines 341
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 12

Test Coverage

Coverage 100%

Importance

Changes 3
Bugs 1 Features 0
Metric Value
wmc 38
c 3
b 1
f 0
lcom 2
cbo 12
dl 0
loc 341
ccs 114
cts 114
cp 1
rs 8.3999

19 Methods

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