Completed
Push — develop ( 3efd9f...1b0b93 )
by Jens
10:45
created

Client::ofConfig()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

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