Completed
Pull Request — master (#221)
by Thomas
28:24 queued 26:59
created

BrowscapUpdater::checkUpdate()   B

Complexity

Conditions 9
Paths 7

Size

Total Lines 57
Code Lines 31

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 25
CRAP Score 9.2124

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 57
ccs 25
cts 29
cp 0.8621
rs 7.0745
cc 9
eloc 31
nc 7
nop 0
crap 9.2124

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * This file is part of the browscap-php package.
4
 *
5
 * Copyright (c) 1998-2017, Browser Capabilities Project
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
11
declare(strict_types = 1);
12
namespace BrowscapPHP;
13
14
use BrowscapPHP\Cache\BrowscapCache;
15
use BrowscapPHP\Exception\FetcherException;
16
use BrowscapPHP\Exception\NoCachedVersionException;
17
use BrowscapPHP\Helper\Converter;
18
use BrowscapPHP\Helper\ConverterInterface;
19
use BrowscapPHP\Helper\Filesystem;
20
use BrowscapPHP\Helper\IniLoader;
21
use BrowscapPHP\Helper\IniLoaderInterface;
22
use GuzzleHttp\Client;
23
use GuzzleHttp\ClientInterface;
24
use Psr\Log\LoggerInterface;
25
use Psr\SimpleCache\CacheInterface;
26
use Psr\SimpleCache\InvalidArgumentException;
27
28
/**
29
 * Browscap.ini parsing class with caching and update capabilities
30
 */
31
final class BrowscapUpdater implements BrowscapUpdaterInterface
32
{
33
    public const DEFAULT_TIMEOUT = 5;
34
35
    /**
36
     * The cache instance
37
     *
38
     * @var \BrowscapPHP\Cache\BrowscapCacheInterface
39
     */
40
    private $cache;
41
42
    /**
43
     * @var @var \Psr\Log\LoggerInterface|null
44
     */
45
    private $logger;
46
47
    /**
48
     * @var \GuzzleHttp\ClientInterface|null
49
     */
50
    private $client;
51
52
    /**
53
     * Curl connect timeout in seconds
54
     *
55
     * @var int
56
     */
57
    private $connectTimeout;
58
59
    /**
60
     * Browscap constructor.
61
     *
62
     * @param \Psr\SimpleCache\CacheInterface $cache
63
     * @param LoggerInterface                 $logger
64
     * @param ClientInterface|null            $client
65
     * @param int                             $connectTimeout
66
     */
67 13
    public function __construct(
68
        CacheInterface $cache,
69
        LoggerInterface $logger,
70
        ClientInterface $client = null,
71
        int $connectTimeout = self::DEFAULT_TIMEOUT
72
    ) {
73 13
        $this->cache = new BrowscapCache($cache, $logger);
74 13
        $this->logger = $logger;
75
76 13
        if (null === $client) {
77 13
            $client = new Client();
78
        }
79
80 13
        $this->client = $client;
81 13
        $this->connectTimeout = $connectTimeout;
82 13
    }
83
84
    /**
85
     * reads and parses an ini file and writes the results into the cache
86
     *
87
     * @param string $iniFile
88
     *
89
     * @throws \BrowscapPHP\Exception
90
     */
91 3
    public function convertFile(string $iniFile) : void
92
    {
93 3
        if (empty($iniFile)) {
94 1
            throw new Exception('the file name can not be empty');
95
        }
96
97 2
        if (! is_readable($iniFile)) {
98 1
            throw new Exception('it was not possible to read the local file ' . $iniFile);
99
        }
100
101
        try {
102 1
            $iniString = file_get_contents($iniFile);
103
        } catch (Helper\Exception $e) {
104
            throw new Exception('an error occured while converting the local file into the cache', 0, $e);
105
        }
106
107 1
        $this->convertString($iniString);
108 1
    }
109
110
    /**
111
     * reads and parses an ini string and writes the results into the cache
112
     *
113
     * @param string $iniString
114
     */
115 2
    public function convertString(string $iniString) : void
116
    {
117
        try {
118 2
            $cachedVersion = $this->cache->getItem('browscap.version', false, $success);
119
        } catch (InvalidArgumentException $e) {
120
            $this->logger->error(new \InvalidArgumentException('an error occured while reading the data version from the cache', 0, $e));
121
122
            return;
123
        }
124
125 2
        $converter = new Converter($this->logger, $this->cache);
126
127 2
        $this->storeContent($converter, $iniString, $cachedVersion);
128 2
    }
129
130
    /**
131
     * fetches a remote file and stores it into a local folder
132
     *
133
     * @param string $file       The name of the file where to store the remote content
134
     * @param string $remoteFile The code for the remote file to load
135
     *
136
     * @throws \BrowscapPHP\Exception\FetcherException
137
     * @throws \BrowscapPHP\Helper\Exception
138
     * @throws \GuzzleHttp\Exception\GuzzleException
139
     */
140 3
    public function fetch(string $file, string $remoteFile = IniLoaderInterface::PHP_INI) : void
141
    {
142
        try {
143 3
            if (null === ($cachedVersion = $this->checkUpdate())) {
144
                // no newer version available
145
                return;
146
            }
147 3
        } catch (NoCachedVersionException $e) {
148 2
            $cachedVersion = 0;
149
        }
150
151 2
        $this->logger->debug('started fetching remote file');
152
153 2
        $loader = new IniLoader();
154 2
        $loader->setRemoteFilename($remoteFile);
155
156 2
        $uri = $loader->getRemoteIniUrl();
157
158
        /** @var \Psr\Http\Message\ResponseInterface $response */
159 2
        $response = $this->client->request('get', $uri, ['connect_timeout' => $this->connectTimeout]);
160
161 2
        if ($response->getStatusCode() !== 200) {
162
            throw new FetcherException(
163
                'an error occured while fetching remote data from URI ' . $uri . ': StatusCode was '
164
                . $response->getStatusCode()
165
            );
166
        }
167
168
        try {
169 2
            $content = $response->getBody()->getContents();
170
        } catch (\Exception $e) {
171
            throw new FetcherException('an error occured while fetching remote data', 0, $e);
172
        }
173
174 2
        if (empty($content)) {
175
            $error = error_get_last();
176
            throw FetcherException::httpError($uri, $error['message']);
177
        }
178
179 2
        $this->logger->debug('finished fetching remote file');
180 2
        $this->logger->debug('started storing remote file into local file');
181
182 2
        $content = $this->sanitizeContent($content);
183
184 2
        $converter = new Converter($this->logger, $this->cache);
185 2
        $iniVersion = $converter->getIniVersion($content);
186
187 2
        if ($iniVersion > $cachedVersion) {
188 2
            $fs = new Filesystem();
189 2
            $fs->dumpFile($file, $content);
190
        }
191
192 2
        $this->logger->debug('finished storing remote file into local file');
193 2
    }
194
195
    /**
196
     * fetches a remote file, parses it and writes the result into the cache
197
     *
198
     * if the local stored information are in the same version as the remote data no actions are
199
     * taken
200
     *
201
     * @param string $remoteFile The code for the remote file to load
202
     *
203
     * @throws \BrowscapPHP\Exception\FileNotFoundException
204
     * @throws \BrowscapPHP\Helper\Exception
205
     * @throws \BrowscapPHP\Exception\FetcherException
206
     * @throws \GuzzleHttp\Exception\GuzzleException
207
     */
208 2
    public function update(string $remoteFile = IniLoaderInterface::PHP_INI) : void
209
    {
210 2
        $this->logger->debug('started fetching remote file');
211
212
        try {
213 2
            if (null === ($cachedVersion = $this->checkUpdate())) {
214
                // no newer version available
215
                return;
216
            }
217 2
        } catch (NoCachedVersionException $e) {
218 2
            $cachedVersion = 0;
219
        }
220
221 2
        $loader = new IniLoader();
222 2
        $loader->setRemoteFilename($remoteFile);
223
224 2
        $uri = $loader->getRemoteIniUrl();
225
226
        /** @var \Psr\Http\Message\ResponseInterface $response */
227 2
        $response = $this->client->request('get', $uri, ['connect_timeout' => $this->connectTimeout]);
228
229 2
        if ($response->getStatusCode() !== 200) {
230
            throw new FetcherException(
231
                'an error occured while fetching remote data from URI ' . $uri . ': StatusCode was '
232
                . $response->getStatusCode()
233
            );
234
        }
235
236
        try {
237 2
            $content = $response->getBody()->getContents();
238
        } catch (\Exception $e) {
239
            throw new FetcherException('an error occured while fetching remote data', 0, $e);
240
        }
241
242 2
        if (empty($content)) {
243 1
            $error = error_get_last();
244
245 1
            throw FetcherException::httpError($uri, $error['message'] ?? '');
246
        }
247
248 1
        $this->logger->debug('finished fetching remote file');
249
250 1
        $converter = new Converter($this->logger, $this->cache);
251
252 1
        $this->storeContent($converter, $content, $cachedVersion);
253 1
    }
254
255
    /**
256
     * checks if an update on a remote location for the local file or the cache
257
     *
258
     * @throws \BrowscapPHP\Helper\Exception
259
     * @throws \BrowscapPHP\Exception\FetcherException
260
     * @throws \GuzzleHttp\Exception\GuzzleException
261
     * @throws \BrowscapPHP\Exception\NoCachedVersionException
262
     *
263
     * @return int|null The actual cached version if a newer version is available, null otherwise
264
     */
265 9
    public function checkUpdate() : ?int
266
    {
267 9
        $success = null;
268
        try {
269 9
            $cachedVersion = $this->cache->getItem('browscap.version', false, $success);
270
        } catch (InvalidArgumentException $e) {
271
            throw new NoCachedVersionException('an error occured while reading the data version from the cache', 0, $e);
272
        }
273
274 9
        if (! $cachedVersion) {
275
            // could not load version from cache
276 5
            throw new NoCachedVersionException('there is no cached version available, please update from remote');
277
        }
278
279 4
        $uri = (new IniLoader())->getRemoteVersionUrl();
280
281
        /** @var \Psr\Http\Message\ResponseInterface $response */
282 4
        $response = $this->client->request('get', $uri, ['connect_timeout' => $this->connectTimeout]);
283
284 4
        if ($response->getStatusCode() !== 200) {
285 1
            throw new FetcherException(
286 1
                'an error occured while fetching version data from URI ' . $uri . ': StatusCode was '
287 1
                . $response->getStatusCode()
288
            );
289
        }
290
291
        try {
292 3
            $remoteVersion = $response->getBody()->getContents();
293 1
        } catch (\Exception $e) {
294 1
            throw new FetcherException(
295 1
                'an error occured while fetching version data from URI ' . $uri . ': StatusCode was '
296 1
                . $response->getStatusCode(),
297 1
                0,
298 1
                $e
299
            );
300
        }
301
302 2
        if (! $remoteVersion) {
303
            // could not load remote version
304
            throw new FetcherException(
305
                'could not load version from remote location'
306
            );
307
        }
308
309 2
        if ($cachedVersion && $remoteVersion && $remoteVersion <= $cachedVersion) {
310
            // no newer version available
311 1
            $this->logger->info('there is no newer version available');
312
313 1
            return null;
314
        }
315
316 1
        $this->logger->info(
317 1
            'a newer version is available, local version: ' . $cachedVersion . ', remote version: ' . $remoteVersion
318
        );
319
320 1
        return (int) $cachedVersion;
321
    }
322
323 5
    private function sanitizeContent(string $content) : string
324
    {
325
        // replace everything between opening and closing php and asp tags
326 5
        $content = preg_replace('/<[?%].*[?%]>/', '', $content);
327
328
        // replace opening and closing php and asp tags
329 5
        return str_replace(['<?', '<%', '?>', '%>'], '', $content);
330
    }
331
332
    /**
333
     * reads and parses an ini string and writes the results into the cache
334
     *
335
     * @param \BrowscapPHP\Helper\ConverterInterface $converter
336
     * @param string                                 $content
337
     * @param int|null                               $cachedVersion
338
     */
339 3
    private function storeContent(ConverterInterface $converter, string $content, ?int $cachedVersion)
340
    {
341 3
        $iniString = $this->sanitizeContent($content);
342 3
        $iniVersion = $converter->getIniVersion($iniString);
343
344 3
        if (! $cachedVersion || $iniVersion > $cachedVersion) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $cachedVersion of type integer|null is loosely compared to false; this is ambiguous if the integer can be zero. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
345 3
            $converter->storeVersion();
346 3
            $converter->convertString($iniString);
347
        }
348 3
    }
349
}
350