Completed
Pull Request — master (#47)
by
unknown
03:35
created

AbstractCoreApiClient   A

Complexity

Total Complexity 24

Size/Duplication

Total Lines 272
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 10

Importance

Changes 0
Metric Value
wmc 24
lcom 2
cbo 10
dl 0
loc 272
rs 10
c 0
b 0
f 0

12 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 13 3
A setCacheItemPool() 0 4 1
A disableCaching() 0 4 1
A getCacheItemPool() 0 4 1
_doRequest() 0 1 ?
B makeRequest() 0 41 5
A deserialize() 0 4 1
B buildJsonParameters() 0 18 6
A generateCacheKey() 0 4 1
B isCacheable() 0 23 5
serializeResponse() 0 1 ?
unserializeResponse() 0 1 ?
1
<?php
2
3
namespace MovingImage\Client\VMPro\ApiClient;
4
5
use Cache\Adapter\Void\VoidCachePool;
6
use GuzzleHttp\ClientInterface;
7
use JMS\Serializer\Serializer;
8
use MovingImage\Client\VMPro\Exception;
9
use MovingImage\Client\VMPro\Interfaces\StopwatchInterface;
10
use MovingImage\Client\VMPro\Stopwatch\NullStopwatch;
11
use MovingImage\Client\VMPro\Util\Logging\Traits\LoggerAwareTrait;
12
use Psr\Cache\CacheItemPoolInterface;
13
use Psr\Http\Message\ResponseInterface;
14
use Psr\Log\LoggerAwareInterface;
15
16
/**
17
 * Class AbstractCoreApiClient.
18
 *
19
 * @author Ruben Knol <[email protected]>
20
 */
21
abstract class AbstractCoreApiClient implements LoggerAwareInterface
22
{
23
    use LoggerAwareTrait;
24
25
    /**
26
     * @const string
27
     */
28
    const OPT_VIDEO_MANAGER_ID = 'videoManagerId';
29
30
    /**
31
     * List of endpoints that may be cached, even though they use POST.
32
     */
33
    const CACHEABLE_POST_ENDPOINTS = ['search'];
34
35
    /**
36
     * @var ClientInterface The Guzzle HTTP client
37
     */
38
    protected $httpClient;
39
40
    /**
41
     * @var Serializer The JMS Serializer instance
42
     */
43
    protected $serializer;
44
45
    /**
46
     * @var CacheItemPoolInterface PSR6 cache pool implementation
47
     */
48
    protected $cacheItemPool;
49
50
    /**
51
     * @var mixed time-to-live for cached responses
52
     *            The type of this property might be integer, \DateInterval or null
53
     *
54
     * @see CacheItemInterface::expiresAfter()
55
     */
56
    protected $cacheTtl;
57
58
    /**
59
     * @var StopwatchInterface
60
     */
61
    protected $stopwatch;
62
63
    /**
64
     * ApiClient constructor.
65
     *
66
     * @param ClientInterface        $httpClient
67
     * @param Serializer             $serializer
68
     * @param CacheItemPoolInterface $cacheItemPool
69
     * @param int                    $cacheTtl
70
     * @param StopwatchInterface     $stopwatch
71
     */
72
    public function __construct(
73
        ClientInterface $httpClient,
74
        Serializer $serializer,
75
        CacheItemPoolInterface $cacheItemPool = null,
76
        $cacheTtl = null,
77
        StopwatchInterface $stopwatch = null
78
    ) {
79
        $this->httpClient = $httpClient;
80
        $this->serializer = $serializer;
81
        $this->cacheItemPool = $cacheItemPool ?: new VoidCachePool();
82
        $this->cacheTtl = $cacheTtl;
83
        $this->stopwatch = $stopwatch ?: new NullStopwatch();
84
    }
85
86
    /**
87
     * @param CacheItemPoolInterface $cacheItemPool
88
     */
89
    public function setCacheItemPool(CacheItemPoolInterface $cacheItemPool)
90
    {
91
        $this->cacheItemPool = $cacheItemPool;
92
    }
93
94
    public function disableCaching()
95
    {
96
        $this->setCacheItemPool(new VoidCachePool());
97
    }
98
99
    /**
100
     * @return CacheItemPoolInterface
101
     */
102
    public function getCacheItemPool()
103
    {
104
        return $this->cacheItemPool;
105
    }
106
107
    /**
108
     * Perform the actual request in the implementation classes.
109
     *
110
     * @param string $method
111
     * @param string $uri
112
     * @param array  $options
113
     *
114
     * @return mixed
115
     */
116
    abstract protected function _doRequest($method, $uri, $options);
117
118
    /**
119
     * Make a request to the API and serialize the result according to our
120
     * serialization strategy.
121
     *
122
     * @param string $method
123
     * @param string $uri
124
     * @param array  $options
125
     *
126
     * @return object|ResponseInterface
127
     */
128
    protected function makeRequest($method, $uri, $options)
129
    {
130
        $logger = $this->getLogger();
131
132
        try {
133
            // Automagically pre-pend videoManagerId if the option is present in the
134
            // options for sending the request
135
            if (isset($options[self::OPT_VIDEO_MANAGER_ID])) {
136
                $uri = sprintf('%d/%s', $options[self::OPT_VIDEO_MANAGER_ID], $uri);
137
            }
138
139
            $cacheKey = $this->generateCacheKey($method, $uri, $options);
140
            $cacheItem = $this->cacheItemPool->getItem($cacheKey);
141
            if ($cacheItem->isHit()) {
142
                $logger->info(sprintf('Getting response from cache for %s request to %s', $method, $uri), [$uri]);
143
144
                return $this->unserializeResponse($cacheItem->get());
145
            }
146
147
            $logger->info(sprintf('Making API %s request to %s', $method, $uri), [$uri]);
148
149
            $stopwatchEvent = "$method-$uri";
150
            $this->stopwatch->start($stopwatchEvent);
151
            /** @var ResponseInterface $response */
152
            $response = $this->_doRequest($method, $uri, $options);
153
            $this->stopwatch->stop($stopwatchEvent);
154
155
            if ($this->isCacheable($method, $uri, $options, $response)) {
156
                $cacheItem->set($this->serializeResponse($response));
157
                $cacheItem->expiresAfter($this->cacheTtl);
158
                $this->cacheItemPool->save($cacheItem);
159
            }
160
161
            $logger->debug('Response from HTTP call was status code:', [$response->getStatusCode()]);
162
            $logger->debug('Response JSON was:', [$response->getBody()]);
163
164
            return $response;
165
        } catch (\Exception $e) {
166
            throw $e; // Just rethrow for now
167
        }
168
    }
169
170
    /**
171
     * Deserialize a response into an instance of it's associated class.
172
     *
173
     * @param string $data
174
     * @param string $serialisationClass
175
     *
176
     * @return object
177
     */
178
    protected function deserialize($data, $serialisationClass)
179
    {
180
        return $this->serializer->deserialize($data, $serialisationClass, 'json');
181
    }
182
183
    /**
184
     * Helper method to build the JSON data array for making a request
185
     * with ::makeRequest(). Optional parameters with empty or null value will be
186
     * omitted from the return value.
187
     *
188
     * Examples:
189
     *
190
     * $this->buildJsonParameters(['title' => 'test'], ['description' => '', 'bla' => 'test'])
191
     *
192
     * Would result in:
193
     *
194
     * [
195
     *     'title' => 'test',
196
     *     'bla' => 'test',
197
     * ]
198
     *
199
     * @param array $required
200
     * @param array $optional
201
     *
202
     * @return array
203
     */
204
    protected function buildJsonParameters(array $required, array $optional)
205
    {
206
        foreach ($required as $key => $value) {
207
            if (empty($value)) {
208
                throw new Exception(sprintf('Required parameter \'%s\' is missing..', $key));
209
            }
210
        }
211
212
        $json = $required;
213
214
        foreach ($optional as $key => $value) {
215
            if (!empty($value) || false === $value) {
216
                $json[$key] = $value;
217
            }
218
        }
219
220
        return $json;
221
    }
222
223
    /**
224
     * Generates the cache key based on the class name, request method, uri and options.
225
     *
226
     * @param string $method
227
     * @param string $uri
228
     * @param array  $options
229
     *
230
     * @return string
231
     */
232
    private function generateCacheKey($method, $uri, array $options = [])
233
    {
234
        return sha1(sprintf('%s.%s.%s.%s', get_class($this), $method, $uri, json_encode($options)));
235
    }
236
237
    /**
238
     * Checks if the request may be cached.
239
     *
240
     * @param string $method
241
     * @param string $uri
242
     * @param array  $options
243
     * @param mixed  $response
244
     *
245
     * @return bool
246
     */
247
    private function isCacheable($method, $uri, array $options, $response)
0 ignored issues
show
Unused Code introduced by
The parameter $options is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
248
    {
249
        /** @var ResponseInterface $statusCode */
250
        $statusCode = $response->getStatusCode();
251
252
        //cache only 2** responses
253
        if ($statusCode < 200 || $statusCode >= 300) {
254
            return false;
255
        }
256
257
        //GET is always safe to cache
258
        if ('GET' === $method) {
259
            return true;
260
        }
261
262
        //POST may be cached for certain endpoints only (forgive us Roy Fielding)
263
        if ('POST' === $method) {
264
            return in_array($uri, self::CACHEABLE_POST_ENDPOINTS);
265
        }
266
267
        //assume not cacheable in all other cases
268
        return false;
269
    }
270
271
    /**
272
     * Serializes the provided response to a string, suitable for caching.
273
     * The type of the $response argument varies depending on the guzzle version.
274
     *
275
     * @param mixed $response
276
     *
277
     * @return string
278
     */
279
    abstract protected function serializeResponse($response);
280
281
    /**
282
     * Unserializes the serialized response into a response object.
283
     * The return type varies depending on the guzzle version.
284
     *
285
     * @param string $serialized
286
     *
287
     * @return mixed
288
     *
289
     * @throws Exception
290
     */
291
    abstract protected function unserializeResponse($serialized);
292
}
293