Test Setup Failed
Push — master ( 36b79e...9d8f9a )
by
unknown
24:25
created

AbstractCoreApiClient::makeRequest()   A

Complexity

Conditions 5
Paths 37

Size

Total Lines 38

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 38
rs 9.0008
c 0
b 0
f 0
cc 5
nc 37
nop 3
1
<?php
2
3
declare(strict_types=1);
4
5
namespace MovingImage\Client\VMPro\ApiClient;
6
7
use Cache\Adapter\Void\VoidCachePool;
8
use GuzzleHttp\ClientInterface;
9
use JMS\Serializer\Serializer;
10
use MovingImage\Client\VMPro\Exception;
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
abstract class AbstractCoreApiClient implements LoggerAwareInterface
17
{
18
    use LoggerAwareTrait;
19
20
    protected const OPT_VIDEO_MANAGER_ID = 'videoManagerId';
21
22
    /**
23
     * List of endpoints that may be cached, even though they use POST.
24
     */
25
    protected const CACHEABLE_POST_ENDPOINTS = ['search'];
26
27
    /**
28
     * @var ClientInterface The Guzzle HTTP client
29
     */
30
    protected $httpClient;
31
32
    /**
33
     * @var Serializer The JMS Serializer instance
34
     */
35
    protected $serializer;
36
37
    /**
38
     * @var CacheItemPoolInterface PSR6 cache pool implementation
39
     */
40
    protected $cacheItemPool;
41
42
    /**
43
     * @var mixed time-to-live for cached responses
44
     *            The type of this property might be integer, \DateInterval or null
45
     *
46
     * @see CacheItemInterface::expiresAfter()
47
     */
48
    protected $cacheTtl;
49
50
    public function __construct(
51
        ClientInterface $httpClient,
52
        Serializer $serializer,
53
        ?CacheItemPoolInterface $cacheItemPool = null,
54
        ?int $cacheTtl = null
55
    ) {
56
        $this->httpClient = $httpClient;
57
        $this->serializer = $serializer;
58
        $this->cacheItemPool = $cacheItemPool ?: new VoidCachePool();
59
        $this->cacheTtl = $cacheTtl;
60
    }
61
62
    public function setCacheItemPool(CacheItemPoolInterface $cacheItemPool): void
63
    {
64
        $this->cacheItemPool = $cacheItemPool;
65
    }
66
67
    public function getCacheItemPool(): CacheItemPoolInterface
68
    {
69
        return $this->cacheItemPool;
70
    }
71
72
    /**
73
     * Perform the actual request in the implementation classes.
74
     *
75
     * @return mixed
76
     */
77
    abstract protected function _doRequest(string $method, string $uri, array $options);
78
79
    /**
80
     * Make a request to the API and serialize the result according to our
81
     * serialization strategy.
82
     *
83
     * @return object|ResponseInterface
84
     * @throws \Exception
85
     */
86
    protected function makeRequest(string $method, string $uri, array $options)
87
    {
88
        $logger = $this->getLogger();
89
90
        try {
91
            // Automagically pre-pend videoManagerId if the option is present in the
92
            // options for sending the request
93
            if (isset($options[self::OPT_VIDEO_MANAGER_ID])) {
94
                $uri = sprintf('%d/%s', $options[self::OPT_VIDEO_MANAGER_ID], $uri);
95
            }
96
97
            $cacheKey = $this->generateCacheKey($method, $uri, $options);
98
            $cacheItem = $this->cacheItemPool->getItem($cacheKey);
99
            if ($cacheItem->isHit()) {
100
                $logger->info(sprintf('Getting response from cache for %s request to %s', $method, $uri), [$uri]);
101
102
                return $this->unserializeResponse($cacheItem->get());
103
            }
104
105
            $logger->info(sprintf('Making API %s request to %s', $method, $uri), [$uri]);
106
107
            /** @var ResponseInterface $response */
108
            $response = $this->_doRequest($method, $uri, $options);
109
110
            if ($this->isCacheable($method, $uri, $options, $response)) {
111
                $cacheItem->set($this->serializeResponse($response));
112
                $cacheItem->expiresAfter($this->cacheTtl);
113
                $this->cacheItemPool->save($cacheItem);
114
            }
115
116
            $logger->debug('Response from HTTP call was status code:', [$response->getStatusCode()]);
117
            $logger->debug('Response JSON was:', [$response->getBody()]);
118
119
            return $response;
120
        } catch (\Exception $e) {
121
            throw $e; // Just rethrow for now
122
        }
123
    }
124
125
    /**
126
     * Deserialize a response into an instance of it's associated class.
127
     *
128
     * @return object
129
     */
130
    protected function deserialize(string $data, string $serialisationClass)
131
    {
132
        return $this->serializer->deserialize($data, $serialisationClass, 'json');
133
    }
134
135
    /**
136
     * Helper method to build the JSON data array for making a request
137
     * with ::makeRequest(). Optional parameters with empty or null value will be
138
     * omitted from the return value.
139
     *
140
     * Examples:
141
     *
142
     * $this->buildJsonParameters(['title' => 'test'], ['description' => '', 'bla' => 'test'])
143
     *
144
     * Would result in:
145
     *
146
     * [
147
     *     'title' => 'test',
148
     *     'bla' => 'test',
149
     * ]
150
     *
151
     * @throws Exception
152
     */
153
    protected function buildJsonParameters(array $required, array $optional): array
154
    {
155
        foreach ($required as $key => $value) {
156
            if (empty($value)) {
157
                throw new Exception(sprintf('Required parameter \'%s\' is missing..', $key));
158
            }
159
        }
160
161
        $json = $required;
162
163
        foreach ($optional as $key => $value) {
164
            if (!empty($value) || false === $value) {
165
                $json[$key] = $value;
166
            }
167
        }
168
169
        return $json;
170
    }
171
172
    /**
173
     * Generates the cache key based on the class name, request method, uri and options.
174
     */
175
    private function generateCacheKey(string $method, string $uri, array $options): string
176
    {
177
        return sha1(sprintf('%s.%s.%s.%s', get_class($this), $method, $uri, json_encode($options)));
178
    }
179
180
    /**
181
     * Checks if the request may be cached.
182
     */
183
    private function isCacheable(string $method, string $uri, array $options, $response): bool
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...
184
    {
185
        /** @var ResponseInterface $statusCode */
186
        $statusCode = $response->getStatusCode();
187
188
        //cache only 2** responses
189
        if ($statusCode < 200 || $statusCode >= 300) {
190
            return false;
191
        }
192
193
        //GET is always safe to cache
194
        if ('GET' === $method) {
195
            return true;
196
        }
197
198
        //POST may be cached for certain endpoints only (forgive us Roy Fielding)
199
        if ('POST' === $method) {
200
            return in_array($uri, self::CACHEABLE_POST_ENDPOINTS);
201
        }
202
203
        //assume not cacheable in all other cases
204
        return false;
205
    }
206
207
    /**
208
     * Serializes the provided response to a string, suitable for caching.
209
     * The type of the $response argument varies depending on the guzzle version.
210
     *
211
     * @param mixed $response
212
     *
213
     * @return string
214
     */
215
    abstract protected function serializeResponse($response);
216
217
    /**
218
     * Unserializes the serialized response into a response object.
219
     * The return type varies depending on the guzzle version.
220
     *
221
     * @param string $serialized
222
     *
223
     * @return mixed
224
     *
225
     * @throws Exception
226
     */
227
    abstract protected function unserializeResponse($serialized);
228
}
229