GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Push — master ( c88b34...7dd43d )
by Cees-Jan
11s
created

Client::applyApiSettingsToRequest()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 15
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 15
ccs 6
cts 6
cp 1
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 10
nc 2
nop 1
crap 2
1
<?php declare(strict_types=1);
2
3
namespace ApiClients\Foundation\Transport;
4
5
use ApiClients\Foundation\Hydrator\Factory as HydratorFactory;
6
use ApiClients\Foundation\Hydrator\Hydrator;
7
use ApiClients\Foundation\Hydrator\Options as HydratorOptions;
8
use ApiClients\Foundation\Transport\CommandBus;
9
use GuzzleHttp\Client as GuzzleClient;
10
use GuzzleHttp\Psr7\Request as Psr7Request;
11
use GuzzleHttp\Psr7\Response as Psr7Response;
12
use GuzzleHttp\Psr7\Uri;
13
use GuzzleHttp\RequestOptions;
14
use Psr\Http\Message\RequestInterface;
15
use Psr\Http\Message\ResponseInterface;
16
use Psr\Http\Message\UriInterface;
17
use React\Cache\CacheInterface;
18
use React\EventLoop\LoopInterface;
19
use React\EventLoop\Timer\TimerInterface;
20
use React\Promise\PromiseInterface;
21
use function React\Promise\reject;
22
use React\Promise\RejectedPromise;
23
use function React\Promise\resolve;
24
use function WyriHaximus\React\futureFunctionPromise;
25
26
class Client
27
{
28
    const DEFAULT_OPTIONS = [
29
        Options::SCHEMA => 'https',
30
        Options::PATH => '/',
31
        Options::USER_AGENT => 'WyriHaximus/php-api-client',
32
        Options::HEADERS => [],
33
    ];
34
35
    /**
36
     * @var GuzzleClient
37
     */
38
    protected $handler;
39
40
    /**
41
     * @var LoopInterface
42
     */
43
    protected $loop;
44
45
    /**
46
     * @var array
47
     */
48
    protected $options = [];
49
50
    /**
51
     * @var Hydrator
52
     */
53
    protected $hydrator;
54
55
    /**
56
     * @var CacheInterface
57
     */
58
    protected $cache;
59
60
    /**
61
     * @param LoopInterface $loop
62
     * @param GuzzleClient $handler
63 12
     * @param array $options
64
     */
65 12
    public function __construct(LoopInterface $loop, GuzzleClient $handler, array $options = [])
66 12
    {
67 12
        $this->loop = $loop;
68 12
        $this->handler = $handler;
69 3
        $this->options = $options + self::DEFAULT_OPTIONS;
70
71 12 View Code Duplication
        if (isset($this->options[Options::CACHE]) && $this->options[Options::CACHE] instanceof CacheInterface) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
72 12
            $this->cache = $this->options[Options::CACHE];
73
        }
74
75
        $this->hydrator = $this->determineHydrator();
76
    }
77
78 12
    /**
79
     * @return Hydrator
80 12
     * @throws \Exception
81
     */
82
    protected function determineHydrator(): Hydrator
83
    {
84 12 View Code Duplication
        if (isset($this->options[Options::HYDRATOR]) && $this->options[Options::HYDRATOR] instanceof Hydrator) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
85
            return $this->options[Options::HYDRATOR];
86
        }
87
88 12
        if (!isset($this->options[Options::HYDRATOR_OPTIONS])) {
89 12
            throw new \Exception('Missing Hydrator options');
90
        }
91
92 12
        $this->ensureCommandHandlers();
93
94 12
        return HydratorFactory::create($this->options[Options::HYDRATOR_OPTIONS]);
95
    }
96
97
    protected function ensureCommandHandlers()
98
    {
99
        $mapping = [];
100
        if (isset($this->options[Options::HYDRATOR_OPTIONS][HydratorOptions::COMMAND_BUS])) {
101
            $mapping = $this->options[Options::HYDRATOR_OPTIONS][HydratorOptions::COMMAND_BUS];
102 5
        }
103
104
        $requestHandler = new CommandBus\Handler\RequestHandler($this);
105 3
        $mapping[CommandBus\Command\RequestCommand::class] = $requestHandler;
106 5
        $mapping[CommandBus\Command\SimpleRequestCommand::class] = $requestHandler;
107
        $mapping[CommandBus\Command\JsonEncodeCommand::class] = new CommandBus\Handler\JsonEncodeHandler($this->loop);
108
        $mapping[CommandBus\Command\JsonDecodeCommand::class] = new CommandBus\Handler\JsonDecodeHandler($this->loop);
109
110
        $this->options[Options::HYDRATOR_OPTIONS][HydratorOptions::COMMAND_BUS] = $mapping;
111
    }
112
113
    /**
114 5
     * @param UriInterface $uri
115
     * @return PromiseInterface
116 5
     */
117 5
    protected function checkCache(UriInterface $uri): PromiseInterface
118
    {
119
        if (!($this->cache instanceof CacheInterface)) {
120 3
            return reject();
121 5
        }
122
123
        $key = $this->determineCacheKey($uri);
124
        return $this->cache->get($key)->then(function ($document) {
125
            $document = json_decode($document, true);
126
            $response = new Psr7Response(
127
                $document['status_code'],
128 3
                $document['headers'],
129
                $document['body'],
130 3
                $document['protocol_version'],
131 1
                $document['reason_phrase']
132
            );
133
134 2
            return resolve($response);
135
        });
136 1
    }
137 1
138 1
    /**
139 1
     * @param RequestInterface $request
140 1
     * @param ResponseInterface $response
141 1
     */
142 1
    protected function storeCache(RequestInterface $request, ResponseInterface $response)
143
    {
144
        if (!($this->cache instanceof CacheInterface)) {
145 1
            return;
146 2
        }
147
148
        $document = [
149
            'body' => $response->getBody()->getContents(),
150
            'headers' => $response->getHeaders(),
151
            'protocol_version' => $response->getProtocolVersion(),
152
            'reason_phrase' => $response->getReasonPhrase(),
153 2
            'status_code' => $response->getStatusCode(),
154
        ];
155 2
156
        $key = $this->determineCacheKey($request->getUri());
157
158
        $this->cache->set($key, json_encode($document));
159
    }
160 2
161 2
    /**
162 2
     * @param UriInterface $uri
163 2
     * @return string
164 2
     */
165
    protected function determineCacheKey(UriInterface $uri): string
166
    {
167 2
        return $this->stripExtraSlashes(
168
            implode(
169 2
                '/',
170 2
                [
171
                    $uri->getScheme(),
172
                    $uri->getHost(),
173
                    $uri->getPort(),
174
                    $uri->getPath(),
175
                    md5($uri->getQuery()),
176 3
                ]
177
            )
178 3
        );
179
    }
180 3
181
    /**
182 3
     * @param string $string
183 3
     * @return string
184 3
     */
185 3
    protected function stripExtraSlashes(string $string): string
186 3
    {
187
        return preg_replace('#/+#', '/', $string);
188
    }
189
190
    /**
191
     * @param RequestInterface $request
192
     * @param bool $refresh
193
     * @param array $options
194
     * @return PromiseInterface
195
     */
196 3
    public function request(RequestInterface $request, array $options = [], bool $refresh = false): PromiseInterface
197
    {
198 3
        $promise = new RejectedPromise();
199
200
        $request = $this->applyApiSettingsToRequest($request);
201
202
        if ((!isset($options[RequestOptions::STREAM]) || !$options[RequestOptions::STREAM]) && !$refresh) {
203
            $promise = $this->checkCache($request->getUri());
204
        }
205
206 5
        return $promise->otherwise(function () use ($request, $options) {
207
            return resolve($this->handler->sendAsync(
208 5
                $request,
209
                $options
210 5
            ));
211 3
        })->then(function (ResponseInterface $response) use ($request, $options, $refresh) {
212
            if (isset($options[RequestOptions::STREAM]) && $options[RequestOptions::STREAM] === true) {
213
                $responseWrapper = new Response('', $response);
214
                $this->streamBody($responseWrapper);
215 4
                return resolve($responseWrapper);
216
            }
217 4
218
            $contents = $response->getBody()->getContents();
219
220 2
            $this->storeCache(
221 2
                $request,
222
                new Psr7Response(
223 2
                    $response->getStatusCode(),
224 2
                    $response->getHeaders(),
225 2
                    $contents,
226
                    $response->getProtocolVersion(),
227 2
                    $response->getReasonPhrase()
228 2
                )
229
            );
230
231 2
            return resolve(
232 2
                new Response(
233 2
                    $contents,
234 2
                    new Psr7Response(
235
                        $response->getStatusCode(),
236 2
                        $response->getHeaders(),
237 2
                        $contents,
238
                        $response->getProtocolVersion(),
239
                        $response->getReasonPhrase()
240
                    )
241
                )
242 4
            );
243
        });
244 4
    }
245 5
246
    protected function streamBody(Response $response)
247
    {
248
        $stream = $response->getResponse()->getBody();
249
        $this->loop->addPeriodicTimer(0.001, function (TimerInterface $timer) use ($stream, $response) {
250
            if ($stream->eof()) {
251
                $timer->cancel();
252 5
                $response->emit('end');
253
                return;
254 5
            }
255 5
256 5
            $size = $stream->getSize();
257
            if ($size === 0) {
258
                return;
259
            }
260
261
            $response->emit('data', [$stream->read($size)]);
262 5
        });
263
    }
264
265 5
    protected function applyApiSettingsToRequest(RequestInterface $request): RequestInterface
266
    {
267 5
        $uri = $request->getUri();
268 5
        if (substr((string)$uri, 0, 4) !== 'http') {
269
            $uri = Uri::resolve(new Uri($this->getBaseURL()), $request->getUri());
270
        }
271
272
        return new Psr7Request(
273
            $request->getMethod(),
274
            $uri,
275
            $this->getHeaders() + $request->getHeaders(),
276
            $request->getBody(),
277 3
            $request->getProtocolVersion()
278 3
        );
279 3
    }
280
281
    /**
282
     * @return array
283
     */
284
    public function getHeaders(): array
285 1
    {
286
        $headers = [
287 1
            'User-Agent' => $this->options[Options::USER_AGENT],
288
        ];
289
        $headers += $this->options[Options::HEADERS];
290
        return $headers;
291
    }
292
293 3
    /**
294
     * @return Hydrator
295 3
     */
296
    public function getHydrator(): Hydrator
297
    {
298
        return $this->hydrator;
299
    }
300
301 8
    /**
302
     * @return LoopInterface
303 8
     */
304
    public function getLoop(): LoopInterface
305
    {
306
        return $this->loop;
307
    }
308
309
    /**
310
     * @return string
311
     */
312
    public function getBaseURL(): string
313
    {
314
        return $this->options[Options::SCHEMA] . '://' . $this->options[Options::HOST] . $this->options[Options::PATH];
315
    }
316
}
317