Client::createInfo()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 3
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
eloc 1
nc 1
nop 1
crap 1
1
<?php
2
/**
3
 * This file is part of the sauls/Xkcd-api-client package.
4
 *
5
 * @author    Saulius Vaičeliūnas <[email protected]>
6
 * @link      http://saulius.vaiceliunas.lt
7
 * @copyright 2018
8
 *
9
 * For the full copyright and license information, please view the LICENSE
10
 * file that was distributed with this source code.
11
 */
12
13
namespace Sauls\Component\Xkcd\Api\Client;
14
15
use function Sauls\Component\Helper\define_object;
16
use function Sauls\Component\Helper\string_camelize;
17
use GuzzleHttp\Client as GuzzleHttpClient;
18
use GuzzleHttp\ClientInterface as GuzzleHttpClientInterface;
19
use GuzzleHttp\Exception\ClientException;
20
use Psr\SimpleCache\CacheInterface;
21
use Sauls\Component\Collection\ImmutableArrayCollection;
22
use Sauls\Component\OptionsResolver\OptionsResolver;
23
use Sauls\Component\Xkcd\Api\Dto\Info;
24
use Sauls\Component\Xkcd\Api\Exception\ComicNotFoundException;
25
use Sauls\Component\Xkcd\Api\Exception\ServiceDownException;
26
use Sauls\Component\Xkcd\Api\Exception\XkcdClientException;
27
28
class Client implements ClientInterface
29
{
30
    private $guzzleHttpClient;
31
    private $cache = null;
32
    private $options;
33
34 8
    public function __construct(array $clientOptions = [], GuzzleHttpClientInterface $guzzleHttpClient = null)
35
    {
36 8
        $this->initializeClient($clientOptions);
37 8
        $this->guzzleHttpClient = $this->createGuzzleHttpClient($guzzleHttpClient);
38 8
    }
39
40 8
    private function initializeClient($clientOptions): void
41
    {
42 8
        $resolver = new OptionsResolver();
43 8
        $this->configureDefaults($resolver);
44 8
        $this->options = new ImmutableArrayCollection($resolver->resolve($clientOptions));
45 8
    }
46
47 8
    private function configureDefaults(OptionsResolver $resolver): void
48
    {
49
        $resolver
50 8
            ->setDefined([
51 8
                'client.url.latest',
52
                'client.url.comic',
53
                'client.cache.prefix',
54
                'client.cache.ttl',
55
                'http_client.base_uri',
56
            ])
57 8
            ->addAllowedTypes('client.url.latest', ['string'])
58 8
            ->addAllowedTypes('client.url.comic', ['string'])
59 8
            ->addAllowedTypes('client.cache.prefix', ['string'])
60 8
            ->addAllowedTypes('client.cache.ttl', ['int'])
61 8
            ->addAllowedTypes('http_client.base_uri', ['string'])
62 8
            ->setDefaults(
63
                [
64 8
                    'client' => [
65
                        'url' => [
66
                            'latest' => '/info.0.json',
67
                            'comic' => '/{num}/info.0.json',
68
                        ],
69
                        'cache' => [
70
                            'prefix' => '__xkcd__',
71
                            'ttl' => 720,
72
                        ],
73
                    ],
74
                    'http_client' => [
75
                        'base_uri' => 'http://xkcd.com',
76
                    ],
77
                ]
78
            );
79 8
    }
80
81 8
    private function createGuzzleHttpClient(
82
        GuzzleHttpClientInterface $guzzleHttpClient = null
83
    ): GuzzleHttpClientInterface {
84 8
        if (null === $guzzleHttpClient) {
85 1
            return new GuzzleHttpClient($this->options->get('http_client'));
86
        }
87
88 7
        return $guzzleHttpClient;
89
    }
90
91 1
    public function getRandom(): Info
92
    {
93 1
        $info = $this->getLatest();
94
95 1
        $randomComicNum = \random_int(1, $info->getNum());
96
97 1
        return $this->get($randomComicNum);
98
99
    }
100
101 6
    public function getLatest(): Info
102
    {
103 6
        $latestComicUrl = $this->options->get('client.url.latest');
104
105 6
        if (null !== $this->cache) {
106 1
            return $this->resolveCachedInfo('latest', $latestComicUrl);
107
        }
108
109 5
        return $this->request($latestComicUrl);
110
    }
111
112 2
    private function resolveCachedInfo(string $name, string $url): Info
113
    {
114 2
        $cacheKey = $this->createCacheKey($name);
115 2
        if (!$info = $this->cache->get($cacheKey)) {
116 2
            $info = $this->request($url);
117 2
            $this->cache->set($cacheKey, $info, $this->options->get('client.cache.ttl'));
118
        }
119
120 2
        return $info;
121
    }
122
123 2
    private function createCacheKey(string $name)
124
    {
125 2
        return sprintf('%s%s', $this->options->get('client.cache.prefix'), $name);
126
    }
127
128
    /**
129
     * @throws \Sauls\Component\Xkcd\Api\Exception\XkcdClientException
130
     * @throws \Sauls\Component\Xkcd\Api\Exception\ComicNotFoundException
131
     * @throws \Sauls\Component\Xkcd\Api\Exception\ServiceDownException
132
     */
133 8
    private function request(string $url): Info
134
    {
135
        try {
136 8
            $response = $this->guzzleHttpClient->get($url);
137
138 5
            return $this->createInfo(json_decode($response->getBody()->getContents(), true));
139 3
        } catch (ClientException $e) {
140 2
            if ($e->getResponse()->getStatusCode() === 404) {
141 1
                throw new ComicNotFoundException('Comic not found.', 404, $e);
142
            }
143 1
            throw new ServiceDownException('Xkcd service is down.', 500, $e);
144 1
        } catch (\Exception $e) {
145 1
            throw new XkcdClientException('Error occured.', 0, $e);
146
        }
147
    }
148
149 5
    private function createInfo(array $data): Info
150
    {
151 5
        return define_object(new Info, $this->formatData($data));
152
    }
153
154 5
    private function formatData($data): array
155
    {
156 5
        return \array_combine(
157 5
            \array_map(function ($value) {
158 5
                return \lcfirst(string_camelize($value));
159 5
            }, \array_keys($data)),
160 5
            $data
161
        );
162
    }
163
164 3
    public function get(int $num): Info
165
    {
166 3
        $concreteComicUrl = $this->createComicUrl($num);
167 3
        if (null !== $this->cache) {
168 1
            return $this->resolveCachedInfo($num, $concreteComicUrl);
169
        }
170
171 2
        return $this->request($concreteComicUrl);
172
    }
173
174 3
    private function createComicUrl(int $num): string
175
    {
176 3
        return strtr($this->options->get('client.url.comic'), [
177 3
            '{num}' => $num,
178
        ]);
179
    }
180
181 2
    public function setCache(CacheInterface $cache): void
182
    {
183 2
        $this->cache = $cache;
184 2
    }
185
}
186