Completed
Push — develop ( fe4302...d9652d )
by Jens
14:25 queued 03:33
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\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 460
    public function getOauthManager()
168
    {
169 460
        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 486
    public function getHttpClient($options = [])
199
    {
200 486
        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 486
        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
     * @param array $headers
228
     * @return ApiResponseInterface
229
     * @throws ApiException
230
     * @throws InvalidTokenException
231
     */
232 478
    public function execute(ClientRequestInterface $request, array $headers = null)
233
    {
234 478
        if ($request instanceof ContextAwareInterface) {
235 478
            $request->setContextIfNull($this->getConfig()->getContext());
236
        }
237 478
        $httpRequest = $this->createHttpRequest($request, $headers);
238
239
        try {
240 478
            $httpResponse = $this->getHttpClient()->execute($httpRequest);
241 455
            $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 468
        $this->logDeprecatedRequest($httpResponse, $httpRequest);
256
257
258 468
        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, array $headers = null)
267
    {
268 2
        if ($request instanceof ContextAwareInterface) {
269 2
            $request->setContextIfNull($this->getConfig()->getContext());
270
        }
271 2
        $httpRequest = $this->createHttpRequest($request, $headers);
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
     * @param array $headers
287
     * @return RequestInterface
288
     */
289 485
    protected function createHttpRequest(ClientRequestInterface $request, array $headers = null)
290
    {
291 485
        $token = $this->getOauthManager()->getToken();
292 485
        $httpRequest = $request->httpRequest();
293
        $httpRequest = $httpRequest
294 485
            ->withHeader('Authorization', 'Bearer ' . $token->getToken())
295
        ;
296 485
        if (is_array($headers)) {
297 2
            foreach ($headers as $headerName => $headerValues) {
298
                $httpRequest = $httpRequest
299 2
                    ->withAddedHeader($headerName, $headerValues)
300
                ;
301
            }
302
        }
303
304 485
        return $httpRequest;
305
    }
306
307
    /**
308
     * Executes API requests in batch
309
     * @param array $headers
310
     * @return ApiResponseInterface[]
311
     * @throws ApiException
312
     */
313 394
    public function executeBatch(array $headers = null)
314
    {
315 394
        $requests = $this->getBatchHttpRequests($headers);
316 394
        $httpResponses = $this->getHttpClient()->executeBatch($requests);
317
318 394
        $responses = [];
319 394
        foreach ($httpResponses as $key => $httpResponse) {
320 394
            $request = $this->batchRequests[$key];
321 394
            $httpRequest = $requests[$key];
322 394
            if ($httpResponse instanceof ApiException) {
323 16
                $exception = $httpResponse;
324 16
                if ($this->getConfig()->getThrowExceptions() ||
325 16
                    !$httpResponse->getResponse() instanceof ResponseInterface
326
                ) {
327 1
                    throw $exception;
328
                }
329 15
                $this->logException($httpResponse);
330 15
                $httpResponse = $exception->getResponse();
331 15
                $responses[$request->getIdentifier()] = new ErrorResponse(
332
                    $exception,
333
                    $request,
334
                    $httpResponse
335
                );
336
            } else {
337 384
                $responses[$request->getIdentifier()] = $request->buildResponse($httpResponse);
338
            }
339 393
            $this->logDeprecatedRequest($httpResponse, $httpRequest);
340
        }
341 393
        unset($this->batchRequests);
342 393
        $this->batchRequests = [];
343
344 393
        return $responses;
345
    }
346
347
    /**
348
     * @param $exception
349
     * @return $this
350
     */
351 80
    protected function logException(ApiException $exception)
352
    {
353 80
        if (is_null($this->logger)) {
354 3
            return $this;
355
        }
356 77
        $response = $exception->getResponse();
357
358 77
        $context = [];
359 77
        if ($response instanceof ResponseInterface) {
360
            $context = [
361 77
                'responseStatusCode' => $response->getStatusCode(),
362 77
                'responseHeaders' => $response->getHeaders(),
363 77
                'responseBody' => (string)$response->getBody(),
364
            ];
365
        }
366 77
        $this->logger->error($exception->getMessage(), $context);
367
368 77
        return $this;
369
    }
370
371
    /**
372
     * @param ResponseInterface $response
373
     * @param RequestInterface $request
374
     * @return $this
375
     */
376 474
    protected function logDeprecatedRequest(ResponseInterface $response, RequestInterface $request)
377
    {
378 474
        if (is_null($this->logger)) {
379 7
            return $this;
380
        }
381
382 467
        if ($response->hasHeader(static::DEPRECATION_HEADER)) {
383 2
            $message = sprintf(
384 2
                Message::DEPRECATED_METHOD,
385 2
                $request->getUri(),
386 2
                $request->getMethod(),
387 2
                $response->getHeaderLine(static::DEPRECATION_HEADER)
388
            );
389 2
            $this->logger->warning($message);
390
        }
391 467
        return $this;
392
    }
393
394
    /**
395
     * @param RequestInterface $request
396
     * @param ResponseInterface $response
397
     * @return string
398
     */
399
    protected function format(RequestInterface $request, ResponseInterface $response)
400
    {
401
        $entries = [
402
            $request->getMethod(),
403
            (string)$request->getUri(),
404
            $response->getStatusCode()
405
        ];
406
        return implode(', ', $entries);
407
    }
408
409
    /**
410
     * @param array $headers
411
     * @return array
412
     */
413 394
    protected function getBatchHttpRequests(array $headers = null)
414
    {
415 394
        $requests = array_map(
416 394
            function ($request) use ($headers) {
417 394
                return $this->createHttpRequest($request, $headers);
418 394
            },
419 394
            $this->batchRequests
420
        );
421
422 394
        return $requests;
423
    }
424
425
    /**
426
     * Adds a request to the batch execution queue
427
     * @param ClientRequestInterface $request
428
     * @return $this
429
     */
430 394
    public function addBatchRequest(ClientRequestInterface $request)
431
    {
432 394
        if ($request instanceof ContextAwareInterface) {
433 394
            $request->setContextIfNull($this->getConfig()->getContext());
434
        }
435 394
        $this->batchRequests[] = $request;
436 394
        return $this;
437
    }
438
439
    /**
440
     * Instantiates a client with the given config
441
     * @param Config $config
442
     * @return static
443
     */
444
    public static function ofConfig(Config $config)
445
    {
446
        return new static($config);
447
    }
448
449
    /**
450
     * Instantiates a client with the given config and cache adapter
451
     * @param Config $config
452
     * @param $cache
453
     * @return static
454
     */
455
    public static function ofConfigAndCache(Config $config, $cache)
456
    {
457
        return new static($config, $cache);
458
    }
459
460
    /**
461
     * Instantiates a client with the given config and a PSR-3 compliant logger
462
     * @param Config $config
463
     * @param LoggerInterface $logger
464
     * @return static
465
     */
466 1
    public static function ofConfigAndLogger(Config $config, LoggerInterface $logger)
467
    {
468 1
        return new static($config, null, $logger);
469
    }
470
471
    /**
472
     * Instantiates a client with the given config, a cache adapter and a PSR-3 compliant logger
473
     * @param Config $config
474
     * @param $cache
475
     * @param LoggerInterface $logger
476
     * @return static
477
     */
478 30
    public static function ofConfigCacheAndLogger(Config $config, $cache, LoggerInterface $logger)
479
    {
480 30
        return new static($config, $cache, $logger);
481
    }
482
}
483