Completed
Push — develop ( 445b2f...03e8c5 )
by Jens
14:38
created

Client   B

Complexity

Total Complexity 38

Size/Duplication

Total Lines 345
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 12

Test Coverage

Coverage 92.86%

Importance

Changes 0
Metric Value
wmc 38
lcom 2
cbo 12
dl 0
loc 345
ccs 117
cts 126
cp 0.9286
rs 8.3999
c 0
b 0
f 0

19 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 8 1
A getOauthManager() 0 4 1
A setOauthManager() 0 5 1
A setLogger() 0 7 2
A getHttpClient() 0 15 3
A getBaseUrl() 0 4 1
C execute() 0 28 7
A executeAsync() 0 17 2
A createHttpRequest() 0 10 1
B executeBatch() 0 33 5
A logException() 0 19 3
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\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->mapFromResponse($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 59
    public function __construct($config, $cache = null, LoggerInterface $logger = null)
156
    {
157 59
        parent::__construct($config);
158
159 59
        $manager = new Manager($config, $cache);
160 59
        $this->setOauthManager($manager);
161 59
        $this->setLogger($logger);
162 59
    }
163
164
    /**
165
     * @return Manager
166
     */
167 459
    public function getOauthManager()
168
    {
169 459
        return $this->oauthManager;
170
    }
171
172
    /**
173
     * @param Manager $oauthManager
174
     * @return $this
175
     */
176 59
    protected function setOauthManager(Manager $oauthManager)
177
    {
178 59
        $this->oauthManager = $oauthManager;
179 59
        return $this;
180
    }
181
182
    /**
183
     * @param LoggerInterface $logger
184
     * @return $this
185
     */
186 59
    public function setLogger(LoggerInterface $logger = null)
187
    {
188 59
        if ($logger instanceof LoggerInterface) {
189 39
            $this->logger = $logger;
190
        }
191 59
        return $this;
192
    }
193
194
    /**
195
     * @param array $options
196
     * @return AdapterInterface
197
     */
198 485
    public function getHttpClient($options = [])
199
    {
200 485
        if (is_null($this->httpClient)) {
201 57
            $client = parent::getHttpClient($options);
202 57
            if ($this->logger instanceof LoggerInterface) {
203 39
                $client->setLogger(
204 39
                    $this->logger,
205 39
                    $this->getConfig()->getLogLevel(),
206 39
                    $this->getConfig()->getMessageFormatter()
207
                );
208
            }
209
        }
210
211 485
        return $this->httpClient;
212
    }
213
214
215
    /**
216
     * @return string
217
     */
218 58
    protected function getBaseUrl()
219
    {
220 58
        return $this->getConfig()->getApiUrl() . '/' . $this->getConfig()->getProject() . '/';
221
    }
222
223
    /**
224
     * Executes an API request synchronously
225
     *
226
     * @param ClientRequestInterface $request
227
     * @return ApiResponseInterface
228
     * @throws InvalidTokenException
229
     * @throws ApiException
230
     * @throws \Exception
231
     */
232 477
    public function execute(ClientRequestInterface $request)
233
    {
234 477
        if ($request instanceof ContextAwareInterface) {
235 477
            $request->setContextIfNull($this->getConfig()->getContext());
236
        }
237 477
        $httpRequest = $this->createHttpRequest($request);
238
239
        try {
240 477
            $httpResponse = $this->getHttpClient()->execute($httpRequest);
241 454
            $response = $request->buildResponse($httpResponse);
242 77
        } catch (ApiException $exception) {
243 77
            if ($exception instanceof InvalidTokenException && !$this->tokenRefreshed) {
244 3
                $this->tokenRefreshed = true;
245 3
                $this->getOauthManager()->refreshToken();
246 2
                return $this->execute($request);
247
            }
248 75
            if ($this->getConfig()->getThrowExceptions() || !$exception->getResponse() instanceof ResponseInterface) {
249 10
                throw $exception;
250
            }
251 65
            $httpResponse = $exception->getResponse();
252 65
            $this->logException($exception);
253 65
            $response = new ErrorResponse($exception, $request, $httpResponse);
254
        }
255 467
        $this->logDeprecatedRequest($httpResponse, $httpRequest);
256
257
258 467
        return $response;
259
    }
260
261
    /**
262
     * Executes an API request asynchronously
263
     * @param ClientRequestInterface $request
264
     * @return ApiResponseInterface
265
     */
266 2
    public function executeAsync(ClientRequestInterface $request)
267
    {
268 2
        if ($request instanceof ContextAwareInterface) {
269 2
            $request->setContextIfNull($this->getConfig()->getContext());
270
        }
271 2
        $httpRequest = $this->createHttpRequest($request);
272 2
        $response = $request->buildResponse($this->getHttpClient()->executeAsync($httpRequest));
273
274 2
        $response = $response->then(
275
            function ($httpResponse) use ($httpRequest) {
276 2
                $this->logDeprecatedRequest($httpResponse, $httpRequest);
277 2
                return $httpResponse;
278 2
            }
279
        );
280
281 2
        return $response;
282
    }
283
284
    /**
285
     * @param ClientRequestInterface $request
286
     * @return RequestInterface
287
     */
288 484
    protected function createHttpRequest(ClientRequestInterface $request)
289
    {
290 484
        $token = $this->getOauthManager()->getToken();
291
292 484
        $httpRequest = $request->httpRequest();
293
        $httpRequest = $httpRequest
294 484
            ->withHeader('Authorization', 'Bearer ' . $token->getToken())
295
        ;
296 484
        return $httpRequest;
297
    }
298
299
    /**
300
     * Executes API requests in batch
301
     * @return Response\ApiResponseInterface[]
302
     * @throws ApiException
303
     */
304 393
    public function executeBatch()
305
    {
306 393
        $requests = $this->getBatchHttpRequests();
307 393
        $httpResponses = $this->getHttpClient()->executeBatch($requests);
308
309 393
        $responses = [];
310 393
        foreach ($httpResponses as $key => $httpResponse) {
311 393
            $request = $this->batchRequests[$key];
312 393
            $httpRequest = $requests[$key];
313 393
            if ($httpResponse instanceof ApiException) {
314 17
                $exception = $httpResponse;
315 17
                if ($this->getConfig()->getThrowExceptions() ||
316 17
                    !$httpResponse->getResponse() instanceof ResponseInterface
317
                ) {
318 1
                    throw $exception;
319
                }
320 16
                $this->logException($httpResponse);
321 16
                $httpResponse = $exception->getResponse();
322 16
                $responses[$request->getIdentifier()] = new ErrorResponse(
323
                    $exception,
324
                    $request,
325
                    $httpResponse
326
                );
327
            } else {
328 383
                $responses[$request->getIdentifier()] = $request->buildResponse($httpResponse);
329
            }
330 392
            $this->logDeprecatedRequest($httpResponse, $httpRequest);
331
        }
332 392
        unset($this->batchRequests);
333 392
        $this->batchRequests = [];
334
335 392
        return $responses;
336
    }
337
338
    /**
339
     * @param $exception
340
     * @return $this
341
     */
342 81
    protected function logException(ApiException $exception)
343
    {
344 81
        if (is_null($this->logger)) {
345 3
            return $this;
346
        }
347 78
        $response = $exception->getResponse();
348
349 78
        $context = [];
350 78
        if ($response instanceof ResponseInterface) {
351
            $context = [
352 78
                'responseStatusCode' => $response->getStatusCode(),
353 78
                'responseHeaders' => $response->getHeaders(),
354 78
                'responseBody' => (string)$response->getBody(),
355
            ];
356
        }
357 78
        $this->logger->error($exception->getMessage(), $context);
358
359 78
        return $this;
360
    }
361
362
    /**
363
     * @param ResponseInterface $response
364
     * @param RequestInterface $request
365
     * @return $this
366
     */
367 473
    protected function logDeprecatedRequest(ResponseInterface $response, RequestInterface $request)
368
    {
369 473
        if (is_null($this->logger)) {
370 7
            return $this;
371
        }
372
373 466
        if ($response->hasHeader(static::DEPRECATION_HEADER)) {
374 2
            $message = sprintf(
375 2
                Message::DEPRECATED_METHOD,
376 2
                $request->getUri(),
377 2
                $request->getMethod(),
378 2
                $response->getHeaderLine(static::DEPRECATION_HEADER)
379
            );
380 2
            $this->logger->warning($message);
381
        }
382 466
        return $this;
383
    }
384
385
    /**
386
     * @param RequestInterface $request
387
     * @param ResponseInterface $response
388
     * @return string
389
     */
390
    protected function format(RequestInterface $request, ResponseInterface $response)
391
    {
392
        $entries = [
393
            $request->getMethod(),
394
            (string)$request->getUri(),
395
            $response->getStatusCode()
396
        ];
397
        return implode(', ', $entries);
398
    }
399
400
    /**
401
     * @return array
402
     */
403 393
    protected function getBatchHttpRequests()
404
    {
405 393
        $requests = array_map(
406 393
            function ($request) {
407 393
                return $this->createHttpRequest($request);
408 393
            },
409 393
            $this->batchRequests
410
        );
411
412 393
        return $requests;
413
    }
414
415
    /**
416
     * Adds a request to the batch execution queue
417
     * @param ClientRequestInterface $request
418
     * @return $this
419
     */
420 393
    public function addBatchRequest(ClientRequestInterface $request)
421
    {
422 393
        if ($request instanceof ContextAwareInterface) {
423 393
            $request->setContextIfNull($this->getConfig()->getContext());
424
        }
425 393
        $this->batchRequests[] = $request;
426 393
        return $this;
427
    }
428
429
    /**
430
     * Instantiates a client with the given config
431
     * @param Config $config
432
     * @return static
433
     */
434
    public static function ofConfig(Config $config)
435
    {
436
        return new static($config);
437
    }
438
439
    /**
440
     * Instantiates a client with the given config and cache adapter
441
     * @param Config $config
442
     * @param $cache
443
     * @return static
444
     */
445
    public static function ofConfigAndCache(Config $config, $cache)
446
    {
447
        return new static($config, $cache);
448
    }
449
450
    /**
451
     * Instantiates a client with the given config and a PSR-3 compliant logger
452
     * @param Config $config
453
     * @param LoggerInterface $logger
454
     * @return static
455
     */
456 1
    public static function ofConfigAndLogger(Config $config, LoggerInterface $logger)
457
    {
458 1
        return new static($config, null, $logger);
459
    }
460
461
    /**
462
     * Instantiates a client with the given config, a cache adapter and a PSR-3 compliant logger
463
     * @param Config $config
464
     * @param $cache
465
     * @param LoggerInterface $logger
466
     * @return static
467
     */
468 30
    public static function ofConfigCacheAndLogger(Config $config, $cache, LoggerInterface $logger)
469
    {
470 30
        return new static($config, $cache, $logger);
471
    }
472
}
473