Completed
Push — master ( d96005...890d97 )
by
unknown
13s
created

AbstractCoreApiClient::isCachable()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 7
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 3
nc 3
nop 4
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
    /**
95
     * @return CacheItemPoolInterface
96
     */
97
    public function getCacheItemPool()
98
    {
99
        return $this->cacheItemPool;
100
    }
101
102
    /**
103
     * Perform the actual request in the implementation classes.
104
     *
105
     * @param string $method
106
     * @param string $uri
107
     * @param array  $options
108
     *
109
     * @return mixed
110
     */
111
    abstract protected function _doRequest($method, $uri, $options);
112
113
    /**
114
     * Make a request to the API and serialize the result according to our
115
     * serialization strategy.
116
     *
117
     * @param string $method
118
     * @param string $uri
119
     * @param array  $options
120
     *
121
     * @return object|ResponseInterface
122
     */
123
    protected function makeRequest($method, $uri, $options)
124
    {
125
        $logger = $this->getLogger();
126
127
        try {
128
            // Automagically pre-pend videoManagerId if the option is present in the
129
            // options for sending the request
130
            if (isset($options[self::OPT_VIDEO_MANAGER_ID])) {
131
                $uri = sprintf('%d/%s', $options[self::OPT_VIDEO_MANAGER_ID], $uri);
132
            }
133
134
            $cacheKey = $this->generateCacheKey($method, $uri, $options);
135
            $cacheItem = $this->cacheItemPool->getItem($cacheKey);
136
            if ($cacheItem->isHit()) {
137
                $logger->info(sprintf('Getting response from cache for %s request to %s', $method, $uri), [$uri]);
138
139
                return $this->unserializeResponse($cacheItem->get());
140
            }
141
142
            $logger->info(sprintf('Making API %s request to %s', $method, $uri), [$uri]);
143
144
            $stopwatchEvent = "$method-$uri";
145
            $this->stopwatch->start($stopwatchEvent);
146
            /** @var ResponseInterface $response */
147
            $response = $this->_doRequest($method, $uri, $options);
148
            $this->stopwatch->stop($stopwatchEvent);
149
150
            if ($this->isCacheable($method, $uri, $options, $response)) {
151
                $cacheItem->set($this->serializeResponse($response));
152
                $cacheItem->expiresAfter($this->cacheTtl);
153
                $this->cacheItemPool->save($cacheItem);
154
            }
155
156
            $logger->debug('Response from HTTP call was status code:', [$response->getStatusCode()]);
157
            $logger->debug('Response JSON was:', [$response->getBody()]);
158
159
            return $response;
160
        } catch (\Exception $e) {
161
            throw $e; // Just rethrow for now
162
        }
163
    }
164
165
    /**
166
     * Deserialize a response into an instance of it's associated class.
167
     *
168
     * @param string $data
169
     * @param string $serialisationClass
170
     *
171
     * @return object
172
     */
173
    protected function deserialize($data, $serialisationClass)
174
    {
175
        return $this->serializer->deserialize($data, $serialisationClass, 'json');
176
    }
177
178
    /**
179
     * Helper method to build the JSON data array for making a request
180
     * with ::makeRequest(). Optional parameters with empty or null value will be
181
     * omitted from the return value.
182
     *
183
     * Examples:
184
     *
185
     * $this->buildJsonParameters(['title' => 'test'], ['description' => '', 'bla' => 'test'])
186
     *
187
     * Would result in:
188
     *
189
     * [
190
     *     'title' => 'test',
191
     *     'bla' => 'test',
192
     * ]
193
     *
194
     * @param array $required
195
     * @param array $optional
196
     *
197
     * @return array
198
     */
199
    protected function buildJsonParameters(array $required, array $optional)
200
    {
201
        foreach ($required as $key => $value) {
202
            if (empty($value)) {
203
                throw new Exception(sprintf('Required parameter \'%s\' is missing..', $key));
204
            }
205
        }
206
207
        $json = $required;
208
209
        foreach ($optional as $key => $value) {
210
            if (!empty($value) || $value === false) {
211
                $json[$key] = $value;
212
            }
213
        }
214
215
        return $json;
216
    }
217
218
    /**
219
     * Generates the cache key based on the class name, request method, uri and options.
220
     *
221
     * @param string $method
222
     * @param string $uri
223
     * @param array  $options
224
     *
225
     * @return string
226
     */
227
    private function generateCacheKey($method, $uri, array $options = [])
228
    {
229
        return sha1(sprintf('%s.%s.%s.%s', get_class($this), $method, $uri, json_encode($options)));
230
    }
231
232
    /**
233
     * Checks if the request may be cached.
234
     *
235
     * @param string $method
236
     * @param string $uri
237
     * @param array  $options
238
     * @param mixed  $response
239
     *
240
     * @return bool
241
     */
242
    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...
243
    {
244
        /** @var ResponseInterface $statusCode */
245
        $statusCode = $response->getStatusCode();
246
247
        //cache only 2** responses
248
        if ($statusCode < 200 || $statusCode >= 300) {
249
            return false;
250
        }
251
252
        //GET is always safe to cache
253
        if ($method === 'GET') {
254
            return true;
255
        }
256
257
        //POST may be cached for certain endpoints only (forgive us Roy Fielding)
258
        if ($method === 'POST') {
259
            return in_array($uri, self::CACHEABLE_POST_ENDPOINTS);
260
        }
261
262
        //assume not cacheable in all other cases
263
        return false;
264
    }
265
266
    /**
267
     * Serializes the provided response to a string, suitable for caching.
268
     * The type of the $response argument varies depending on the guzzle version.
269
     *
270
     * @param mixed $response
271
     *
272
     * @return string
273
     */
274
    abstract protected function serializeResponse($response);
275
276
    /**
277
     * Unserializes the serialized response into a response object.
278
     * The return type varies depending on the guzzle version.
279
     *
280
     * @param string $serialized
281
     *
282
     * @return mixed
283
     *
284
     * @throws Exception
285
     */
286
    abstract protected function unserializeResponse($serialized);
287
}
288