Completed
Pull Request — master (#127)
by Thomas
10:50
created

Browscap   C

Complexity

Total Complexity 46

Size/Duplication

Total Lines 491
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 18

Test Coverage

Coverage 80.23%

Importance

Changes 20
Bugs 3 Features 5
Metric Value
wmc 46
c 20
b 3
f 5
lcom 1
cbo 18
dl 0
loc 491
ccs 138
cts 172
cp 0.8023
rs 5.6571

18 Methods

Rating   Name   Duplication   Size   Complexity  
A setFormatter() 0 6 1
A getFormatter() 0 8 2
A getCache() 0 14 2
A setCache() 0 16 3
A setParser() 0 6 1
A getParser() 0 15 2
A setLogger() 0 6 1
A getLogger() 0 8 2
A setOptions() 0 6 1
A getLoader() 0 8 2
A setLoader() 0 4 1
A getBrowser() 0 19 3
A convertFile() 0 18 3
A convertString() 0 5 1
B fetch() 0 41 5
C update() 0 80 8
C checkUpdate() 0 44 7
A sanitizeContent() 0 8 1

How to fix   Complexity   

Complex Class

Complex classes like Browscap often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Browscap, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * Copyright (c) 1998-2015 Browser Capabilities Project
4
 *
5
 * Permission is hereby granted, free of charge, to any person obtaining a
6
 * copy of this software and associated documentation files (the "Software"),
7
 * to deal in the Software without restriction, including without limitation
8
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
9
 * and/or sell copies of the Software, and to permit persons to whom the
10
 * Software is furnished to do so, subject to the following conditions:
11
 *
12
 * The above copyright notice and this permission notice shall be included
13
 * in all copies or substantial portions of the Software.
14
 *
15
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
16
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
 * THE SOFTWARE.
22
 *
23
 * @category   Browscap-PHP
24
 * @package    Browscap
25
 * @copyright  1998-2015 Browser Capabilities Project
26
 * @license    http://www.opensource.org/licenses/MIT MIT License
27
 * @link       https://github.com/browscap/browscap-php/
28
 */
29
30
namespace BrowscapPHP;
31
32
use Browscap\Generator\BuildGenerator;
33
use Browscap\Helper\CollectionCreator;
34
use Browscap\Writer\Factory\PhpWriterFactory;
35
use BrowscapPHP\Cache\BrowscapCache;
36
use BrowscapPHP\Cache\BrowscapCacheInterface;
37
use BrowscapPHP\Exception\FetcherException;
38
use BrowscapPHP\Helper\Converter;
39
use BrowscapPHP\Helper\Filesystem;
40
use BrowscapPHP\Helper\IniLoader;
41
use BrowscapPHP\Helper\Quoter;
42
use BrowscapPHP\Parser\ParserInterface;
43
use Psr\Log\LoggerInterface;
44
use Psr\Log\NullLogger;
45
use WurflCache\Adapter\AdapterInterface;
46
use WurflCache\Adapter\File;
47
48
/**
49
 * Browscap.ini parsing class with caching and update capabilities
50
 *
51
 * @category   Browscap-PHP
52
 * @package    Browscap
53
 * @author     Jonathan Stoppani <[email protected]>
54
 * @author     Vítor Brandão <[email protected]>
55
 * @author     Mikołaj Misiurewicz <[email protected]>
56
 * @author     Christoph Ziegenberg <[email protected]>
57
 * @author     Thomas Müller <[email protected]>
58
 * @copyright  Copyright (c) 1998-2015 Browser Capabilities Project
59
 * @version    3.0
60
 * @license    http://www.opensource.org/licenses/MIT MIT License
61
 * @link       https://github.com/browscap/browscap-php/
62
 */
63
class Browscap
64
{
65
    /**
66
     * Parser to use
67
     *
68
     * @var \BrowscapPHP\Parser\ParserInterface
69
     */
70
    private $parser = null;
71
72
    /**
73
     * Formatter to use
74
     *
75
     * @var \BrowscapPHP\Formatter\FormatterInterface
76
     */
77
    private $formatter = null;
78
79
    /**
80
     * The cache instance
81
     *
82
     * @var \BrowscapPHP\Cache\BrowscapCacheInterface
83
     */
84
    private $cache = null;
85
86
    /**
87
     * @var @var \Psr\Log\LoggerInterface
88
     */
89
    private $logger = null;
90
91
    /**
92
     * Options for the updater. The array should be overwritten,
93
     * containing all options as keys, set to the default value.
94
     *
95
     * @var array
96
     */
97
    private $options = array();
98
99
    /**
100
     * @var \BrowscapPHP\Helper\IniLoader
101
     */
102
    private $loader = null;
103
104
    /**
105
     * Set theformatter instance to use for the getBrowser() result
106
     *
107
     * @param \BrowscapPHP\Formatter\FormatterInterface $formatter
108
     *
109
     * @return \BrowscapPHP\Browscap
110
     */
111 6
    public function setFormatter(Formatter\FormatterInterface $formatter)
112
    {
113 6
        $this->formatter = $formatter;
114
115 6
        return $this;
116
    }
117
118
    /**
119
     * @return \BrowscapPHP\Formatter\FormatterInterface
120
     */
121 4
    public function getFormatter()
122
    {
123 4
        if (null === $this->formatter) {
124 2
            $this->setFormatter(new Formatter\PhpGetBrowser());
125
        }
126
127 4
        return $this->formatter;
128
    }
129
130
    /**
131
     * Gets a cache instance
132
     *
133
     * @return \BrowscapPHP\Cache\BrowscapCacheInterface
134
     */
135 16
    public function getCache()
136
    {
137 16
        if (null === $this->cache) {
138 3
            $resourceDirectory = __DIR__.'/../resources/';
139
140 3
            $cacheAdapter = new File(
141 3
                array(File::DIR => $resourceDirectory)
142 3
            );
143
144 3
            $this->cache = new BrowscapCache($cacheAdapter);
145
        }
146
147 16
        return $this->cache;
148
    }
149
150
    /**
151
     * Sets a cache instance
152
     *
153
     * @param \BrowscapPHP\Cache\BrowscapCacheInterface|\WurflCache\Adapter\AdapterInterface $cache
154
     *
155
     * @throws \BrowscapPHP\Exception
156
     * @return \BrowscapPHP\Browscap
157
     */
158 14
    public function setCache($cache)
159
    {
160 14
        if ($cache instanceof BrowscapCacheInterface) {
161 10
            $this->cache = $cache;
162 14
        } elseif ($cache instanceof AdapterInterface) {
163 3
            $this->cache = new BrowscapCache($cache);
164 3
        } else {
165 1
            throw new Exception(
166
                'the cache has to be an instance of \BrowscapPHP\Cache\BrowscapCacheInterface or '
167 1
                .'an instanceof of \WurflCache\Adapter\AdapterInterface',
168
                Exception::CACHE_INCOMPATIBLE
169 1
            );
170
        }
171
172 14
        return $this;
173
    }
174
175
    /**
176
     * Sets the parser instance to use
177
     *
178
     * @param \BrowscapPHP\Parser\ParserInterface $parser
179
     *
180
     * @return \BrowscapPHP\Browscap
181
     */
182 4
    public function setParser(ParserInterface $parser)
183
    {
184 4
        $this->parser = $parser;
185
186 4
        return $this;
187
    }
188
189
    /**
190
     * returns an instance of the used parser class
191
     *
192
     * @return \BrowscapPHP\Parser\ParserInterface
193
     */
194 6
    public function getParser()
195
    {
196 6
        if (null === $this->parser) {
197 2
            $cache  = $this->getCache();
198 2
            $logger = $this->getLogger();
199 2
            $quoter = new Quoter();
200
201 2
            $patternHelper = new Parser\Helper\GetPattern($cache, $logger);
202 2
            $dataHelper    = new Parser\Helper\GetData($cache, $logger, $quoter);
203
204 2
            $this->parser = new Parser\Ini($patternHelper, $dataHelper, $this->getFormatter());
205
        }
206
207 6
        return $this->parser;
208
    }
209
210
    /**
211
     * Sets a logger instance
212
     *
213
     * @param \Psr\Log\LoggerInterface $logger
214
     *
215
     * @return \BrowscapPHP\Browscap
216
     */
217 10
    public function setLogger(LoggerInterface $logger)
218
    {
219 10
        $this->logger = $logger;
220
221 10
        return $this;
222
    }
223
224
    /**
225
     * returns a logger instance
226
     *
227
     * @return \Psr\Log\LoggerInterface
228
     */
229 15
    public function getLogger()
230
    {
231 15
        if (null === $this->logger) {
232 5
            $this->logger = new NullLogger();
233 1
        }
234
235 15
        return $this->logger;
236
    }
237
238
    /**
239
     * Sets multiple loader options at once
240
     *
241
     * @param array $options
242
     *
243
     * @return \BrowscapPHP\Browscap
244
     */
245 1
    public function setOptions(array $options)
246
    {
247 1
        $this->options = $options;
248
249 1
        return $this;
250
    }
251
252
    /**
253
     * @return \BrowscapPHP\Helper\IniLoader
254
     */
255 6
    public function getLoader()
256
    {
257 6
        if (null === $this->loader) {
258
            $this->loader = new IniLoader();
259
        }
260
261 6
        return $this->loader;
262
    }
263
264
    /**
265
     * @param \BrowscapPHP\Helper\IniLoader $loader
266
     */
267 7
    public function setLoader(IniLoader $loader)
268
    {
269 7
        $this->loader = $loader;
270 7
    }
271
272
    /**
273
     * parses the given user agent to get the information about the browser
274
     *
275
     * if no user agent is given, it uses {@see \BrowscapPHP\Helper\Support} to get it
276
     *
277
     * @param string $userAgent the user agent string
278
     *
279
     * @throws \BrowscapPHP\Exception
280
     * @return \stdClass              the object containing the browsers details. Array if
281
     *                                $return_array is set to true.
282
     */
283 4
    public function getBrowser($userAgent = null)
284
    {
285
        // Automatically detect the useragent
286 4
        if (!isset($userAgent)) {
287 2
            $support   = new Helper\Support($_SERVER);
288 2
            $userAgent = $support->getUserAgent();
289
        }
290
291
        // try to get browser data
292 4
        $formatter = $this->getParser()->getBrowser($userAgent);
293
294
        // if return is still NULL, updates are disabled... in this
295
        // case we return an empty formatter instance
296 4
        if ($formatter === null) {
297 2
            return $this->getFormatter()->getData();
298
        }
299
300 2
        return $formatter->getData();
301
    }
302
303
    /**
304
     * reads and parses an ini file and writes the results into the cache
305
     *
306
     * @param  string $iniFile
307
     *
308
     * @throws \BrowscapPHP\Exception
309
     */
310 4
    public function convertFile($iniFile)
311
    {
312 4
        $loader = new IniLoader();
313
314
        try {
315 4
            $loader->setLocalFile($iniFile);
316 4
        } catch (Helper\Exception $e) {
317 1
            throw new Exception('an error occured while setting the local file', 0, $e);
318
        }
319
320 3
        $converter = new Converter($this->getLogger(), $this->getCache());
321
322
        try {
323 3
            $converter->convertString($loader->load());
324 3
        } catch (Helper\Exception $e) {
325 1
            throw new Exception('an error occured while converting the local file into the cache', 0, $e);
326
        }
327 2
    }
328
329
    /**
330
     * reads and parses an ini string and writes the results into the cache
331
     *
332
     * @param string $iniString
333
     */
334 1
    public function convertString($iniString)
335
    {
336 1
        $converter = new Converter($this->getLogger(), $this->getCache());
337 1
        $converter->convertString($iniString);
338 1
    }
339
340
    /**
341
     * fetches a remote file and stores it into a local folder
342
     *
343
     * @param string $file       The name of the file where to store the remote content
344
     * @param string $remoteFile The code for the remote file to load
345
     *
346
     * @throws \BrowscapPHP\Exception\FetcherException
347
     * @throws \BrowscapPHP\Helper\Exception
348
     */
349 3
    public function fetch($file, $remoteFile = IniLoader::PHP_INI)
350
    {
351 3
        if (null === ($cachedVersion = $this->checkUpdate($remoteFile))) {
352
            // no newer version available
353
            return;
354
        }
355
356 3
        $this->getLoader()
357 3
            ->setRemoteFilename($remoteFile)
358 3
            ->setOptions($this->options)
359 3
            ->setLogger($this->getLogger())
360
        ;
361
362 3
        $this->getLogger()->debug('started fetching remote file');
363
364
        try {
365 3
            $content = $this->getLoader()->load();
366 3
        } catch (Helper\Exception $e) {
367
            throw new FetcherException('an error occured while fetching remote data', 0, $e);
368
        }
369
370 3
        if (false === $content) {
371 1
            $error = error_get_last();
372 1
            throw FetcherException::httpError($this->getLoader()->getRemoteIniUrl(), $error['message']);
373
        }
374
375 2
        $this->getLogger()->debug('finished fetching remote file');
376 2
        $this->getLogger()->debug('started storing remote file into local file');
377
378 2
        $content = $this->sanitizeContent($content);
379
380 2
        $converter  = new Converter($this->getLogger(), $this->getCache());
381 2
        $iniVersion = $converter->getIniVersion($content);
382
383 2
        if ($iniVersion > $cachedVersion) {
384 2
            $fs = new Filesystem();
385 2
            $fs->dumpFile($file, $content);
386
        }
387
388 2
        $this->getLogger()->debug('finished storing remote file into local file');
389 2
    }
390
391
    /**
392
     * fetches a remote file, parses it and writes the result into the cache
393
     *
394
     * if the local stored information are in the same version as the remote data no actions are
395
     * taken
396
     *
397
     * @param string      $remoteFile The code for the remote file to load
398
     * @param string|null $buildFolder
399
     * @param int|null    $buildNumber
400
     *
401
     * @throws \BrowscapPHP\Exception\FileNotFoundException
402
     * @throws \BrowscapPHP\Helper\Exception
403
     * @throws \BrowscapPHP\Exception\FetcherException
404
     */
405 1
    public function update($remoteFile = IniLoader::PHP_INI, $buildFolder = null, $buildNumber = null)
406
    {
407 1
        $this->getLogger()->debug('started fetching remote file');
408
409 1
        $converter = new Converter($this->getLogger(), $this->getCache());
410
411 1
        if (class_exists('\Browscap\Browscap')) {
412
            $resourceFolder = 'vendor/browscap/browscap/resources/';
413
414
            if (null === $buildNumber) {
415
                $buildNumber = (int)file_get_contents('vendor/browscap/browscap/BUILD_NUMBER');
416
            }
417
418
            if (null === $buildFolder) {
419
                $buildFolder = 'resources';
420
            }
421
422
            $buildFolder .= '/browscap-ua-test-'.$buildNumber;
423
            $iniFile     = $buildFolder.'/full_php_browscap.ini';
424
425
            mkdir($buildFolder, 0777, true);
426
427
            $writerCollectionFactory = new PhpWriterFactory();
428
            $writerCollection        = $writerCollectionFactory->createCollection($this->getLogger(), $buildFolder);
429
430
            $buildGenerator = new BuildGenerator($resourceFolder, $buildFolder);
431
            $buildGenerator
432
                ->setLogger($this->getLogger())
433
                ->setCollectionCreator(new CollectionCreator())
434
                ->setWriterCollection($writerCollection)
435
                ->run($buildNumber, false)
436
            ;
437
438
            $converter
439
                ->setVersion($buildNumber)
440
                ->storeVersion()
441
                ->convertFile($iniFile)
442
            ;
443
444
            $filesystem = new Filesystem();
445
            $filesystem->remove($buildFolder);
446
        } else {
447 1
            if (null === ($cachedVersion = $this->checkUpdate($remoteFile))) {
448
                // no newer version available
449
                return;
450
            }
451
452 1
            $this->getLoader()
453 1
                ->setRemoteFilename($remoteFile)
454 1
                ->setOptions($this->options)
455 1
                ->setLogger($this->getLogger())
456
            ;
457
458
            try {
459 1
                $content = $this->getLoader()->load();
460 1
            } catch (Helper\Exception $e) {
461
                throw new FetcherException('an error occured while loading remote data', 0, $e);
462
            }
463
464 1
            $internalLoader = $this->getLoader()->getLoader();
465
466 1
            if (false === $content) {
467 1
                $error = error_get_last();
468 1
                throw FetcherException::httpError($internalLoader->getUri(), $error['message']);
469
            }
470
471
            $this->getLogger()->debug('finished fetching remote file');
472
473
            $content = $this->sanitizeContent($content);
474
475
            $iniVersion = $converter->getIniVersion($content);
476
477
            if ($iniVersion > $cachedVersion) {
478
                $converter
479
                    ->storeVersion()
480
                    ->convertString($content)
481
                ;
482
            }
483
        }
484
    }
485
486
    /**
487
     * checks if an update on a remote location for the local file or the cache
488
     *
489
     * @param string $remoteFile
490
     *
491
     * @return int|null The actual cached version if a newer version is available, null otherwise
492
     * @throws \BrowscapPHP\Helper\Exception
493
     * @throws \BrowscapPHP\Exception\FetcherException
494
     */
495 7
    public function checkUpdate($remoteFile = IniLoader::PHP_INI)
496
    {
497 7
        $success       = null;
498 7
        $cachedVersion = $this->getCache()->getItem('browscap.version', false, $success);
499
500 7
        if (!$cachedVersion) {
501
            // could not load version from cache
502 4
            $this->getLogger()->info('there is no cached version available, please update from remote');
503
504 4
            return 0;
505
        }
506
507 3
        $this->getLoader()
508 3
            ->setRemoteFilename($remoteFile)
509 3
            ->setOptions($this->options)
510 3
            ->setLogger($this->getLogger())
511
        ;
512
513
        try {
514 3
            $remoteVersion = $this->getLoader()->getRemoteVersion();
515 3
        } catch (Helper\Exception $e) {
516
            throw new FetcherException('an error occured while checking remote version', 0, $e);
517
        }
518
519 3
        if (!$remoteVersion) {
520
            // could not load remote version
521 1
            $this->getLogger()->info('could not load version from remote location');
522
523 1
            return 0;
524
        }
525
526 2
        if ($cachedVersion && $remoteVersion && $remoteVersion <= $cachedVersion) {
527
            // no newer version available
528 1
            $this->getLogger()->info('there is no newer version available');
529
530 1
            return null;
531
        }
532
533 1
        $this->getLogger()->info(
534 1
            'a newer version is available, local version: ' . $cachedVersion . ', remote version: ' . $remoteVersion
535 1
        );
536
537 1
        return (int) $cachedVersion;
538
    }
539
540
    /**
541
     * @param string $content
542
     *
543
     * @return mixed
544
     */
545 2
    private function sanitizeContent($content)
546
    {
547
        // replace everything between opening and closing php and asp tags
548 2
        $content = preg_replace('/<[?%].*[?%]>/', '', $content);
549
550
        // replace opening and closing php and asp tags
551 2
        return str_replace(array('<?', '<%', '?>', '%>'), '', $content);
552
    }
553
}
554