Completed
Push — master ( f1b31f...5a2174 )
by Peter
04:21 queued 12s
created

UpdateDatabaseCommand::execute()   B

Complexity

Conditions 8
Paths 10

Size

Total Lines 85

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 72

Importance

Changes 0
Metric Value
dl 0
loc 85
ccs 0
cts 46
cp 0
rs 7.0828
c 0
b 0
f 0
cc 8
nc 10
nop 2
crap 72

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
 * GpsLab component.
4
 *
5
 * @author    Peter Gribanov <[email protected]>
6
 * @copyright Copyright (c) 2017, Peter Gribanov
7
 * @license   http://opensource.org/licenses/MIT
8
 */
9
10
namespace GpsLab\Bundle\GeoIP2Bundle\Command;
11
12
use GpsLab\Component\Compressor\CompressorInterface;
13
use Symfony\Component\Console\Command\Command;
14
use Symfony\Component\Console\Input\InputArgument;
15
use Symfony\Component\Console\Input\InputInterface;
16
use Symfony\Component\Console\Output\OutputInterface;
17
use Symfony\Component\Console\Style\SymfonyStyle;
18
use Symfony\Component\Filesystem\Filesystem;
19
use Symfony\Component\Stopwatch\Stopwatch;
20
use Symfony\Component\Stopwatch\StopwatchEvent;
21
22
class UpdateDatabaseCommand extends Command
23
{
24
    /**
25
     * @var Stopwatch
26
     */
27
    private $stopwatch;
28
29
    /**
30
     * @var Filesystem
31
     */
32
    private $fs;
33
34
    /**
35
     * @var string
36
     */
37
    private $url;
38
39
    /**
40
     * @var string
41
     */
42
    private $cache;
43
44
    /**
45
     * @param Filesystem $fs
46
     * @param Stopwatch $stopwatch
47
     * @param CompressorInterface $compressor
48
     * @param string $url
49
     * @param string $cache
50
     */
51
    public function __construct(Filesystem $fs, Stopwatch $stopwatch, CompressorInterface $compressor, $url, $cache)
0 ignored issues
show
Unused Code introduced by
The parameter $compressor is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
52
    {
53
        $this->fs = $fs;
54
        $this->url = $url;
55
        $this->cache = $cache;
56
        $this->stopwatch = $stopwatch;
57
        parent::__construct();
58
    }
59
60
    protected function configure()
61
    {
62
        $this
63
            ->setName('geoip2:update')
64
            ->setDescription('Downloads and update the GeoIP2 database')
65
            ->addArgument(
66
                'url',
67
                InputArgument::OPTIONAL,
68
                'URL to downloaded GeoIP2 database',
69
                $this->url
70
            )
71
            ->addArgument(
72
                'target',
73
                InputArgument::OPTIONAL,
74
                'Target download path',
75
                $this->cache
76
            )
77
        ;
78
    }
79
80
    /**
81
     * @param InputInterface $input
82
     * @param OutputInterface $output
83
     *
84
     * @return int
85
     */
86
    protected function execute(InputInterface $input, OutputInterface $output)
87
    {
88
        $io = new SymfonyStyle($input, $output);
89
        $url = $input->getArgument('url');
90
        $target = $input->getArgument('target');
91
92
        $io->title('Update the GeoIP2 database');
93
        $this->stopwatch->start('update');
94
95
        $tmp_zip = sys_get_temp_dir().'/GeoLite2.tar.gz';
96
        $tmp_unzip = sys_get_temp_dir().'/GeoLite2.tar';
97
        $tmp_untar = sys_get_temp_dir().'/GeoLite2';
98
99
        // remove old files and folders for correct overwrite it
100
        $this->fs->remove([$tmp_zip, $tmp_unzip, $tmp_untar]);
0 ignored issues
show
Documentation introduced by
array($tmp_zip, $tmp_unzip, $tmp_untar) is of type array<integer,string,{"0..."string","2":"string"}>, but the function expects a string|object<Symfony\Co...nt\Filesystem\iterable>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
101
102
        $io->comment(sprintf('Beginning download of file <info>%s</info>', $url));
103
104
        file_put_contents($tmp_zip, fopen($url, 'rb'));
105
106
        $io->comment(sprintf('Download complete to <info>%s</info>', $tmp_zip));
107
        $io->comment(sprintf('De-compressing file to <info>%s</info>', $tmp_unzip));
108
109
        $this->fs->mkdir(dirname($target), 0777);
110
111
        // decompress gz file
112
        $phar = new \PharData($tmp_zip);
113
        $phar->decompress();
114
115
        $io->comment('Decompression complete');
116
        $io->comment(sprintf('Extract tar file to <info>%s</info>', $tmp_untar));
117
118
        // extract tar archive
119
        $phar = new \PharData($tmp_unzip);
120
        $phar->extractTo($tmp_untar);
121
122
        $io->comment('Tar archive extracted');
123
124
        // find database in archive
125
        $database = '';
126
        foreach (scandir($tmp_untar) as $folder) {
127
            $path = $tmp_untar.'/'.$folder;
128
129
            // find folder with database
130
            // expected something like that "GeoLite2-City_20200114"
131
            if (
132
                preg_match('/^(?<database>.+)_(?<year>\d{4})(?<month>\d{2})(?<day>\d{2})$/', $folder, $match) &&
133
                is_dir($path)
134
            ) {
135
                // find database in folder
136
                // expected something like that "GeoLite2-City.mmdb"
137
                foreach (scandir($path) as $filename) {
138
                    $file = $path.'/'.$filename;
139
140
                    if (strpos($filename, $match['database']) === 0 && is_file($file)) {
141
                        $io->comment(sprintf(
142
                            'Found <info>%s</info> database updated at <info>%s-%s-%s</info>',
143
                            $match['database'],
144
                            $match['year'],
145
                            $match['month'],
146
                            $match['day']
147
                        ));
148
149
                        $database = $file;
150
                    }
151
                }
152
            }
153
        }
154
155
        if (!$database) {
156
            throw new \RuntimeException('Not found GeoLite2 database in archive.');
157
        }
158
159
        $this->fs->copy($database, $target, true);
0 ignored issues
show
Bug introduced by
It seems like $target defined by $input->getArgument('target') on line 90 can also be of type array<integer,string> or null; however, Symfony\Component\Filesystem\Filesystem::copy() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
160
        $this->fs->chmod($target, 0777);
0 ignored issues
show
Bug introduced by
It seems like $target defined by $input->getArgument('target') on line 90 can also be of type array<integer,string> or null; however, Symfony\Component\Filesystem\Filesystem::chmod() does only seem to accept string|object<Symfony\Co...nt\Filesystem\iterable>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
161
        $this->fs->remove([$tmp_zip, $tmp_unzip, $tmp_untar]);
0 ignored issues
show
Documentation introduced by
array($tmp_zip, $tmp_unzip, $tmp_untar) is of type array<integer,string,{"0..."string","2":"string"}>, but the function expects a string|object<Symfony\Co...nt\Filesystem\iterable>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
162
163
        $io->comment(sprintf('Database moved to <info>%s</info>', $target));
164
165
        $io->success('Finished downloading');
166
167
        $this->stopwatch($io, $this->stopwatch->stop('update'));
168
169
        return 0;
170
    }
171
172
    /**
173
     * @param SymfonyStyle $io
174
     * @param StopwatchEvent $event
175
     */
176
    private function stopwatch(SymfonyStyle $io, StopwatchEvent $event)
177
    {
178
        $io->writeln([
0 ignored issues
show
Documentation introduced by
array(sprintf('Time: <in...emory() / 1024 / 1024)) is of type array<integer,string,{"0":"string","1":"string"}>, but the function expects a string|object<Symfony\Co...onsole\Output\iterable>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
179
            sprintf('Time: <info>%.2F</info> s.', $event->getDuration() / 1000),
180
            sprintf('Memory: <info>%.2F</info> MiB.', $event->getMemory() / 1024 / 1024),
181
        ]);
182
    }
183
}
184