Completed
Pull Request — master (#186)
by Jay
05:38
created

LogfileCommand::setCache()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 6
ccs 3
cts 3
cp 1
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 3
nc 1
nop 1
crap 1
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
 * @copyright  1998-2015 Browser Capabilities Project
25
 * @license    http://www.opensource.org/licenses/MIT MIT License
26
 * @link       https://github.com/browscap/browscap-php/
27
 * @since      added with version 3.0
28
 */
29
30
namespace BrowscapPHP\Command;
31
32
use BrowscapPHP\Browscap;
33
use BrowscapPHP\Cache\BrowscapCache;
34
use BrowscapPHP\Cache\BrowscapCacheInterface;
35
use BrowscapPHP\Exception\InvalidArgumentException;
36
use BrowscapPHP\Exception\ReaderException;
37
use BrowscapPHP\Exception\UnknownBrowserException;
38
use BrowscapPHP\Exception\UnknownBrowserTypeException;
39
use BrowscapPHP\Exception\UnknownDeviceException;
40
use BrowscapPHP\Exception\UnknownEngineException;
41
use BrowscapPHP\Exception\UnknownPlatformException;
42
use BrowscapPHP\Helper\Filesystem;
43
use BrowscapPHP\Helper\LoggerHelper;
44
use BrowscapPHP\Util\Logfile\ReaderCollection;
45
use BrowscapPHP\Util\Logfile\ReaderFactory;
46
use Symfony\Component\Console\Command\Command;
47
use Symfony\Component\Console\Input\InputArgument;
48
use Symfony\Component\Console\Input\InputInterface;
49
use Symfony\Component\Console\Input\InputOption;
50
use Symfony\Component\Console\Output\OutputInterface;
51
use Symfony\Component\Filesystem\Exception\IOException;
52
use Symfony\Component\Finder\Finder;
53
use Symfony\Component\Finder\SplFileInfo;
54
use WurflCache\Adapter\File;
55
56
/**
57
 * commands to parse a log file and parse the useragents in it
58
 *
59
 * @category   Browscap-PHP
60
 * @author     Dave Olsen, http://dmolsen.com
61
 * @author     Thomas Müller <[email protected]>
62
 * @copyright  Copyright (c) 1998-2015 Browser Capabilities Project
63
 * @version    3.0
64
 * @license    http://www.opensource.org/licenses/MIT MIT License
65
 * @link       https://github.com/browscap/browscap-php/
66
 */
67
class LogfileCommand extends Command
68
{
69
    /**
70
     * @var array
71
     */
72
    private $undefinedClients = [];
73
74
    private $uas         = [];
75
    private $uasWithType = [];
76
77
    private $countOk    = 0;
78
    private $countNok   = 0;
79
    private $totalCount = 0;
80
81
    /**
82
     * @var BrowscapCacheInterface
83
     */
84
    private $cache = null;
85
86
    /**
87
     * @var string
88
     */
89
    private $defaultCacheFolder;
90
91
    /**
92
     * @param string $defaultCacheFolder
93
     */
94 1
    public function __construct($defaultCacheFolder)
95
    {
96 1
        $this->defaultCacheFolder = $defaultCacheFolder;
97
98 1
        parent::__construct();
99 1
    }
100
101
    /**
102
     * @param \BrowscapPHP\Cache\BrowscapCacheInterface $cache
103
     *
104
     * @return $this
105
     */
106 1
    public function setCache(BrowscapCacheInterface $cache)
107
    {
108 1
        $this->cache = $cache;
109
110 1
        return $this;
111
    }
112
113
    /**
114
     * Configures the current command.
115
     */
116 1
    protected function configure()
117
    {
118
        $this
119 1
            ->setName('browscap:log')
120 1
            ->setDescription('Parses the supplied webserver log file.')
121 1
            ->addArgument(
122 1
                'output',
123 1
                InputArgument::REQUIRED,
124 1
                'Path to output log file',
125 1
                null
126
            )
127 1
            ->addOption(
128 1
                'log-file',
129 1
                'f',
130 1
                InputOption::VALUE_REQUIRED,
131 1
                'Path to a webserver log file'
132
            )
133 1
            ->addOption(
134 1
                'log-dir',
135 1
                'd',
136 1
                InputOption::VALUE_REQUIRED,
137 1
                'Path to webserver log directory'
138
            )
139 1
            ->addOption(
140 1
                'include',
141 1
                'i',
142 1
                InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED,
143 1
                'Include glob expressions for log files in the log directory',
144 1
                ['*.log', '*.log*.gz', '*.log*.bz2']
145
            )
146 1
            ->addOption(
147 1
                'exclude',
148 1
                'e',
149 1
                InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED,
150 1
                'Exclude glob expressions for log files in the log directory',
151 1
                ['*error*']
152
            )
153 1
            ->addOption(
154 1
                'debug',
155 1
                null,
156 1
                InputOption::VALUE_NONE,
157 1
                'Should the debug mode entered?'
158
            )
159 1
            ->addOption(
160 1
                'cache',
161 1
                'c',
162 1
                InputOption::VALUE_OPTIONAL,
163 1
                'Where the cache files are located',
164 1
                $this->defaultCacheFolder
165
            );
166 1
    }
167
168
    /**
169
     * @param InputInterface  $input
170
     * @param OutputInterface $output
171
     *
172
     * @throws \UnexpectedValueException
173
     * @throws \BrowscapPHP\Exception\InvalidArgumentException
174
     * @return int|null|void
175
     */
176
    protected function execute(InputInterface $input, OutputInterface $output)
177
    {
178
        if (!$input->getOption('log-file') && !$input->getOption('log-dir')) {
179
            throw InvalidArgumentException::oneOfCommandArguments('log-file', 'log-dir');
180
        }
181
182
        $loggerHelper = new LoggerHelper();
183
        $logger       = $loggerHelper->create($input->getOption('debug'));
184
185
        $browscap   = new Browscap();
186
        $collection = ReaderFactory::factory();
187
        $fs         = new Filesystem();
188
189
        $browscap
190
            ->setLogger($logger)
191
            ->setCache($this->getCache($input));
192
193
        /** @var $file \Symfony\Component\Finder\SplFileInfo */
194
        foreach ($this->getFiles($input) as $file) {
195
            $this->uas = [];
196
            $path      = $this->getPath($file);
197
198
            $this->countOk  = 0;
199
            $this->countNok = 0;
200
201
            $logger->info('Analyzing file "' . $file->getPathname() . '"');
202
203
            $lines = file($path);
204
205
            if (empty($lines)) {
206
                $logger->info('Skipping empty file "' . $file->getPathname() . '"');
207
                continue;
208
            }
209
210
            $this->totalCount = count($lines);
211
212
            foreach ($lines as $line) {
213
                $this->handleLine(
214
                    $output,
215
                    $collection,
216
                    $browscap,
217
                    $line
218
                );
219
            }
220
221
            $this->outputProgress($output, '', true);
222
223
            arsort($this->uas, SORT_NUMERIC);
224
225
            try {
226
                $fs->dumpFile(
227
                    $input->getArgument('output') . '/output.txt',
228
                    implode(PHP_EOL, array_unique($this->undefinedClients))
229
                );
230
            } catch (IOException $e) {
231
                // do nothing
232
            }
233
234
            try {
235
                $fs->dumpFile(
236
                    $input->getArgument('output') . '/output-with-amount.txt',
237
                    $this->createAmountContent()
238
                );
239
            } catch (IOException $e) {
240
                // do nothing
241
            }
242
243
            try {
244
                $fs->dumpFile(
245
                    $input->getArgument('output') . '/output-with-amount-and-type.txt',
246
                    $this->createAmountTypeContent()
247
                );
248
            } catch (IOException $e) {
249
                // do nothing
250
            }
251
        }
252
253
        $outputFile = $input->getArgument('output') . '/output.txt';
254
255
        try {
256
            $fs->dumpFile(
257
                $outputFile,
258
                implode(PHP_EOL, array_unique($this->undefinedClients))
259
            );
260
        } catch (IOException $e) {
261
            throw new \UnexpectedValueException('writing to file "' . $outputFile . '" failed', 0, $e);
262
        }
263
264
        try {
265
            $fs->dumpFile(
266
                $input->getArgument('output') . '/output-with-amount.txt',
267
                $this->createAmountContent()
268
            );
269
        } catch (IOException $e) {
270
            // do nothing
271
        }
272
273
        try {
274
            $fs->dumpFile(
275
                $input->getArgument('output') . '/output-with-amount-and-type.txt',
276
                $this->createAmountTypeContent()
277
            );
278
        } catch (IOException $e) {
279
            // do nothing
280
        }
281
    }
282
283
    private function createAmountContent()
284
    {
285
        $counts = [];
286
287
        foreach ($this->uasWithType as $uas) {
288
            foreach ($uas as $userAgentString => $count) {
289
                if (isset($counts[$userAgentString])) {
290
                    $counts[$userAgentString] += $count;
291
                } else {
292
                    $counts[$userAgentString] = $count;
293
                }
294
            }
295
        }
296
297
        $content = '';
298
299
        arsort($counts, SORT_NUMERIC);
300
301
        foreach ($counts as $agentOfLine => $count) {
302
            $content .= "$count\t$agentOfLine\n";
303
        }
304
305
        return $content;
306
    }
307
308
    private function createAmountTypeContent()
309
    {
310
        $content = '';
311
        $types   = ['B', 'T', 'P', 'D', 'N', 'U'];
312
313
        foreach ($types as $type) {
314
            if (!isset($this->uasWithType[$type])) {
315
                continue;
316
            }
317
318
            arsort($this->uasWithType[$type], SORT_NUMERIC);
319
320
            foreach ($this->uasWithType[$type] as $agentOfLine => $count) {
321
                $content .= "$type\t$count\t$agentOfLine\n";
322
            }
323
        }
324
325
        return $content;
326
    }
327
328
    /**
329
     * @param \Symfony\Component\Console\Output\OutputInterface $output
330
     * @param \BrowscapPHP\Util\Logfile\ReaderCollection        $collection
331
     * @param \BrowscapPHP\Browscap                             $browscap
332
     * @param int                                               $line
333
     *
334
     * @throws UnknownBrowserException
335
     * @throws UnknownBrowserTypeException
336
     * @throws UnknownDeviceException
337
     * @throws UnknownEngineException
338
     * @throws UnknownPlatformException
339
     * @throws \Exception
340
     */
341
    private function handleLine(OutputInterface $output, ReaderCollection $collection, Browscap $browscap, $line)
342
    {
343
        $userAgentString = '';
344
345
        try {
346
            $userAgentString = $collection->read($line);
347
348
            try {
349
                $this->getResult($browscap->getBrowser($userAgentString));
350
            } catch (\Exception $e) {
351
                $this->undefinedClients[] = $userAgentString;
352
353
                throw $e;
354
            }
355
356
            $type = '.';
357
            ++$this->countOk;
358
        } catch (ReaderException $e) {
359
            $type = 'E';
360
            ++$this->countNok;
361
        } catch (UnknownBrowserTypeException $e) {
362
            $type = 'T';
363
            ++$this->countNok;
364
        } catch (UnknownBrowserException $e) {
365
            $type = 'B';
366
            ++$this->countNok;
367
        } catch (UnknownPlatformException $e) {
368
            $type = 'P';
369
            ++$this->countNok;
370
        } catch (UnknownDeviceException $e) {
371
            $type = 'D';
372
            ++$this->countNok;
373
        } catch (UnknownEngineException $e) {
374
            $type = 'N';
375
            ++$this->countNok;
376
        } catch (\Exception $e) {
377
            $type = 'U';
378
            ++$this->countNok;
379
        }
380
381
        $this->outputProgress($output, $type);
382
383
        // count all useragents
384
        if (isset($this->uas[$userAgentString])) {
385
            ++$this->uas[$userAgentString];
386
        } else {
387
            $this->uas[$userAgentString] = 1;
388
        }
389
390
        if ('.' !== $type && 'E' !== $type) {
391
            // count all undetected useragents grouped by detection error
392
            if (!isset($this->uasWithType[$type])) {
393
                $this->uasWithType[$type] = [];
394
            }
395
396
            if (isset($this->uasWithType[$type][$userAgentString])) {
397
                ++$this->uasWithType[$type][$userAgentString];
398
            } else {
399
                $this->uasWithType[$type][$userAgentString] = 1;
400
            }
401
        }
402
    }
403
404
    /**
405
     * @param \Symfony\Component\Console\Output\OutputInterface $output
406
     * @param string                                            $result
407
     * @param bool                                              $end
408
     *
409
     * @return int
410
     */
411
    private function outputProgress(OutputInterface $output, $result, $end = false)
412
    {
413
        if (($this->totalCount % 70) === 0 || $end) {
414
            $formatString = '  %' . strlen($this->countOk) . 'd OK, %' . strlen($this->countNok) . 'd NOK, Summary %'
415
                . strlen($this->totalCount) . 'd';
416
417
            if ($end) {
418
                $result = str_pad($result, 70 - ($this->totalCount % 70), ' ', STR_PAD_RIGHT);
419
            }
420
421
            $endString = sprintf($formatString, $this->countOk, $this->countNok, $this->totalCount);
422
423
            $output->writeln($result . $endString);
424
425
            return;
426
        }
427
428
        $output->write($result);
429
    }
430
431
    /**
432
     * @param \stdClass $result
433
     *
434
     * @return string
435
     */
436
    private function getResult(\stdClass $result)
437
    {
438
        if ('Default Browser' === $result->browser) {
439
            throw new UnknownBrowserException('unknwon browser found');
440
        }
441
442
        if ('unknown' === $result->browser_type) {
443
            throw new UnknownBrowserTypeException('unknwon browser type found');
444
        }
445
446
        if (in_array($result->browser_type, ['Bot/Crawler', 'Library'])) {
447
            return '.';
448
        }
449
450
        if ('unknown' === $result->platform) {
451
            throw new UnknownPlatformException('unknown platform found');
452
        }
453
454
        if ('unknown' === $result->device_type) {
455
            throw new UnknownDeviceException('unknwon device type found');
456
        }
457
458
        if ('unknown' === $result->renderingengine_name) {
459
            throw new UnknownEngineException('unknown rendering engine found');
460
        }
461
462
        return '.';
463
    }
464
465
    /**
466
     * @param \Symfony\Component\Console\Input\InputInterface $input
467
     *
468
     * @return \Symfony\Component\Finder\Finder
469
     */
470
    private function getFiles(InputInterface $input)
471
    {
472
        $finder = Finder::create();
473
474
        if ($input->getOption('log-file')) {
475
            $file = $input->getOption('log-file');
476
            $finder->append(Finder::create()->in(dirname($file))->name(basename($file)));
477
        }
478
479
        if ($input->getOption('log-dir')) {
480
            $dirFinder = Finder::create()
481
                ->in($input->getOption('log-dir'));
482
            array_map([$dirFinder, 'name'], $input->getOption('include'));
483
            array_map([$dirFinder, 'notName'], $input->getOption('exclude'));
484
485
            $finder->append($dirFinder);
486
        }
487
488
        return $finder;
489
    }
490
491
    /**
492
     * @param \Symfony\Component\Finder\SplFileInfo $file
493
     *
494
     * @return string
495
     */
496
    private function getPath(SplFileInfo $file)
497
    {
498
        switch ($file->getExtension()) {
499
            case 'gz':
500
                $path = 'compress.zlib://' . $file->getPathname();
501
                break;
502
            case 'bz2':
503
                $path = 'compress.bzip2://' . $file->getPathname();
504
                break;
505
            default:
506
                $path = $file->getPathname();
507
                break;
508
        }
509
510
        return $path;
511
    }
512
513
    /**
514
     * @param InputInterface $input
515
     *
516
     * @return BrowscapCacheInterface
517
     */
518 View Code Duplication
    private function getCache(InputInterface $input)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
519
    {
520
        if (null === $this->cache) {
521
            $cacheAdapter = new File([File::DIR => $input->getOption('cache')]);
522
            $this->cache  = new BrowscapCache($cacheAdapter);
523
        }
524
525
        return $this->cache;
526
    }
527
}
528