Completed
Pull Request — develop (#333)
by Jens
14:58
created

Client::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 1

Importance

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