Query   B
last analyzed

Complexity

Total Complexity 43

Size/Duplication

Total Lines 404
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 10

Importance

Changes 3
Bugs 0 Features 0
Metric Value
wmc 43
c 3
b 0
f 0
lcom 1
cbo 10
dl 0
loc 404
rs 8.3157

27 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 11 1
A setTimeout() 0 5 1
A getTimeout() 0 4 1
A getUrl() 0 12 4
A getRegion() 0 4 1
A setRegion() 0 6 1
A getClient() 0 4 1
A setClient() 0 6 1
A getCacheTtl() 0 4 1
A setCacheTtl() 0 6 1
A getLogger() 0 4 1
A setLogger() 0 6 1
A getCache() 0 4 1
A setCache() 0 6 1
B run() 0 45 5
A getEndpoint() 0 4 1
A setEndpoint() 0 6 1
A renderRequest() 0 4 1
A renderRequestJson() 0 4 1
A log() 0 4 1
A getFromCache() 0 8 3
A getParams() 0 4 1
A setParams() 0 6 1
A handleResponse() 0 18 2
A saveToCache() 0 8 3
A setRequest() 0 6 1
B getPem() 0 14 5

How to fix   Complexity   

Complex Class

Complex classes like Query often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Query, and based on these observations, apply Extract Interface, too.

1
<?php namespace BinPacking3d;
2
3
use BinPacking3d\Entity\Packed;
4
use BinPacking3d\Entity\Request;
5
use BinPacking3d\Entity\Response;
6
use BinPacking3d\Exception\CriticalException;
7
use Doctrine\Common\Cache\Cache;
8
use GuzzleHttp\Client;
9
use GuzzleHttp\Exception\RequestException;
10
use Psr\Log\LoggerInterface;
11
use Psr\Log\NullLogger;
12
13
/**
14
 * Class Query
15
 * @package BinPacking3d
16
 */
17
abstract class Query
18
{
19
20
    /**
21
     * Regions
22
     */
23
    const REGION_US = 'US'; // US AWS
24
    const REGION_EU = 'EU'; // EU AWS
25
    const REGION_GLOBAL = 'GLOBAL'; // Global API used Latency based routing to find US or EU API
26
27
    /**
28
     * @var
29
     */
30
    protected $endpoint;
31
32
    /**
33
     * @var Client
34
     */
35
    private $client;
36
37
    /**
38
     * @var Request
39
     */
40
    private $request;
41
42
    /**
43
     * @var LoggerInterface
44
     */
45
    private $logger;
46
47
    /**
48
     * @var
49
     */
50
    private $params;
51
52
    /**
53
     * @var
54
     */
55
    private $cache;
56
57
    /**
58
     * @var string
59
     */
60
    private $region;
61
62
    /**
63
     * @var int
64
     */
65
    private $cacheTtl = 3600;
66
67
    /**
68
     * @var
69
     */
70
    private $timeout;
71
72
    /**
73
     * @param string $region
74
     */
75
    public function __construct($region = self::REGION_GLOBAL)
76
    {
77
        $this->region = $region;
78
79
        $this->client = ClientFactory::getInstance(
80
            $this->getUrl($region),
81
            $this->getPem($region)
82
        );
83
84
        $this->setLogger(new NullLogger);
85
    }
86
87
    /**
88
     * @param mixed $timeout
89
     * @return Query
90
     */
91
    public function setTimeout($timeout)
92
    {
93
        $this->timeout = $timeout;
94
        return $this;
95
    }
96
97
    /**
98
     * @return mixed
99
     */
100
    public function getTimeout()
101
    {
102
        return $this->timeout;
103
    }
104
105
    /**
106
     * Supplying a region will use the closest by URL
107
     * Supplying false will disable certificate verification and will start using the global URL always
108
     *
109
     * @param string|bool $region
110
     * @return string
111
     */
112
    public function getUrl($region)
113
    {
114
        switch ($region) {
115
            case self::REGION_US:
116
                return 'https://us-east.api.3dbinpacking.com/packer/';
117
            case self::REGION_EU:
118
                return 'https://eu.api.3dbinpacking.com/packer/';
119
            case self::REGION_GLOBAL:
120
            default:
121
                return 'https://global-api.3dbinpacking.com/packer/';
122
        }
123
    }
124
125
    /**
126
     * Supplying false disables CA verification, which is not advised.
127
     * Supplying a region will check against the proper certificate.
128
     *
129
     * @param string|bool $region
130
     * @return string
131
     */
132
    public function getPem($region)
133
    {
134
        switch ($region) {
135
            case self::REGION_US:
136
                return __DIR__ . '/../cert/us-east.api.3dbinpacking.com.pem';
137
            case self::REGION_EU:
138
                return __DIR__ . '/../cert/eu.api.3dbinpacking.com.pem';
139
            case false:
140
                return false;
141
            case self::REGION_GLOBAL:
142
            default:
143
                return __DIR__ . '/../cert/global-api.3dbinpacking.com.pem';
144
        }
145
    }
146
147
    /**
148
     * @return string
149
     */
150
    public function getRegion()
151
    {
152
        return $this->region;
153
    }
154
155
    /**
156
     * @param string $region
157
     * @return Query
158
     */
159
    public function setRegion($region)
160
    {
161
        $this->region = $region;
162
163
        return $this;
164
    }
165
166
    /**
167
     * @return Client
168
     */
169
    public function getClient()
170
    {
171
        return $this->client;
172
    }
173
174
    /**
175
     * @param Client $client
176
     * @return Query
177
     */
178
    public function setClient($client)
179
    {
180
        $this->client = $client;
181
182
        return $this;
183
    }
184
185
    /**
186
     * @return int
187
     */
188
    public function getCacheTtl()
189
    {
190
        return $this->cacheTtl;
191
    }
192
193
    /**
194
     * @param int $cacheTtl
195
     * @return Query
196
     */
197
    public function setCacheTtl($cacheTtl)
198
    {
199
        $this->cacheTtl = $cacheTtl;
200
201
        return $this;
202
    }
203
204
    /**
205
     * @return LoggerInterface
206
     */
207
    public function getLogger()
208
    {
209
        return $this->logger;
210
    }
211
212
    /**
213
     * @param LoggerInterface $logger
214
     * @return Query
215
     */
216
    public function setLogger(LoggerInterface $logger)
217
    {
218
        $this->logger = $logger;
219
220
        return $this;
221
    }
222
223
    /**
224
     * @return Cache
225
     */
226
    public function getCache()
227
    {
228
        return $this->cache;
229
    }
230
231
    /**
232
     * @param Cache $cache
233
     * @return $this
234
     */
235
    public function setCache(Cache $cache)
236
    {
237
        $this->cache = $cache;
238
239
        return $this;
240
    }
241
242
    /**
243
     * @return Packed
244
     * @throws \Exception
245
     */
246
    public function run()
247
    {
248
        // Get url
249
        $url = $this->getEndpoint();
250
251
        // Run request
252
        try {
253
            // Build request
254
            $request = $this->renderRequest();
255
            $requestJson = $this->renderRequestJson();
256
257
            // Log request
258
            $this->log('info', ($this->cache ? 'Request to cache' : 'Request to 3dbinpacking'));
259
            $this->log('debug', $requestJson);
260
261
            // Build cache key
262
            $cacheKey = md5($requestJson);
263
264
            // If we have cache, check if we can get some result
265
            $contents = $this->getFromCache($cacheKey);
266
            if ($contents) {
267
                $this->log('info', 'Response from cache');
268
                $this->log('debug', json_encode($contents));
269
270
                return new Packed(new Response($contents), $this->request);
271
            }
272
273
            // Prepare request parameters
274
            $params = [
275
                'json' => array_merge($request, ['params' => $this->getParams()]),
276
            ];
277
278
            // Add timeout
279
            if (!is_null($this->timeout)) {
280
                $params['timeout'] = $this->timeout;
281
            }
282
283
            // No cache, or not connected, then we perform the real request
284
            $response = $this->client->get($url, $params);
285
286
            return $this->handleResponse($response, $cacheKey);
287
        } catch (RequestException $e) {
288
            throw new CriticalException($e->getMessage(), 0, $e);
289
        }
290
    }
291
292
    /**
293
     * @return string
294
     */
295
    public function getEndpoint()
296
    {
297
        return $this->endpoint;
298
    }
299
300
    /**
301
     * @param string $endpoint
302
     * @return Query
303
     */
304
    protected function setEndpoint($endpoint)
305
    {
306
        $this->endpoint = $endpoint;
307
308
        return $this;
309
    }
310
311
    /**
312
     * @return array
313
     */
314
    public function renderRequest()
315
    {
316
        return $this->request->render();
317
    }
318
319
    /**
320
     * @return string
321
     */
322
    public function renderRequestJson()
323
    {
324
        return json_encode($this->renderRequest());
325
    }
326
327
    /**
328
     * @param string $level
329
     * @param $message
330
     * @return bool
331
     */
332
    private function log($level, $message)
333
    {
334
        return $this->logger->{$level}($message);
335
    }
336
337
    /**
338
     * @param string $cacheKey
339
     * @return mixed
340
     */
341
    private function getFromCache($cacheKey)
342
    {
343
        if ($this->cache && $this->cache->contains($cacheKey)) {
344
            return json_decode($this->cache->fetch($cacheKey));
345
        }
346
347
        return false;
348
    }
349
350
    /**
351
     * @return mixed
352
     */
353
    public function getParams()
354
    {
355
        return $this->params;
356
    }
357
358
    /**
359
     * @param mixed $params
360
     * @return Query
361
     */
362
    public function setParams($params)
363
    {
364
        $this->params = $params;
365
366
        return $this;
367
    }
368
369
    /**
370
     * Handle response and return
371
     *
372
     * @param \Psr\Http\Message\ResponseInterface $response
373
     * @param string $cacheKey
374
     * @return Packed
375
     * @throws CriticalException
376
     */
377
    public function handleResponse(\Psr\Http\Message\ResponseInterface $response, $cacheKey = null)
378
    {
379
        $contents = $response->getBody()->getContents();
380
381
        // Log response
382
        $this->log('info', 'Response from 3dbinpacking');
383
        $this->log('debug', $contents);
384
385
        if ($response->getStatusCode() === 200) {
386
            // If cache and we get here, save it
387
            $this->saveToCache($cacheKey, $contents);
388
389
            // Return Packed object with data processed
390
            return new Packed(new Response(json_decode($contents)), $this->request);
391
        }
392
393
        throw new CriticalException('Non 200 response');
394
    }
395
396
    /**
397
     * @param $cacheKey
398
     * @param string $contents
399
     */
400
    private function saveToCache($cacheKey, $contents)
401
    {
402
        if ($this->cache && !is_null($cacheKey)) {
403
            return $this->cache->save($cacheKey, $contents, $this->cacheTtl);
404
        }
405
406
        return false;
407
    }
408
409
    /**
410
     * @param Request $request
411
     * @return $this
412
     */
413
    protected function setRequest(Request $request)
414
    {
415
        $this->request = $request;
416
417
        return $this;
418
    }
419
420
}
421