Completed
Push — develop ( 6f71dd...40edc3 )
by Tom
11s
created

ImportCommand::optimize()   B

Complexity

Conditions 9
Paths 8

Size

Total Lines 56
Code Lines 39

Duplication

Lines 15
Ratio 26.79 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
c 1
b 1
f 0
dl 15
loc 56
rs 7.1584
cc 9
eloc 39
nc 8
nop 1

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
namespace N98\Magento\Command\Database;
4
5
use InvalidArgumentException;
6
use Symfony\Component\Console\Input\InputArgument;
7
use Symfony\Component\Console\Input\InputInterface;
8
use Symfony\Component\Console\Input\InputOption;
9
use Symfony\Component\Console\Output\OutputInterface;
10
use N98\Util\Exec;
11
12
class ImportCommand extends AbstractDatabaseCommand
13
{
14
    protected function configure()
15
    {
16
        $this
17
            ->setName('db:import')
18
            ->addArgument('filename', InputArgument::OPTIONAL, 'Dump filename')
19
            ->addOption('compression', 'c', InputOption::VALUE_REQUIRED, 'The compression of the specified file')
20
            ->addOption('only-command', null, InputOption::VALUE_NONE, 'Print only mysql command. Do not execute')
21
            ->addOption('only-if-empty', null, InputOption::VALUE_NONE, 'Imports only if database is empty')
22
            ->addOption(
23
                'optimize',
24
                null,
25
                InputOption::VALUE_NONE,
26
                'Convert verbose INSERTs to short ones before import (not working with compression)'
27
            )
28
            ->addOption('drop', null, InputOption::VALUE_NONE, 'Drop and recreate database before import')
29
            ->addOption('drop-tables', null, InputOption::VALUE_NONE, 'Drop tables before import')
30
            ->setDescription('Imports database with mysql cli client according to database defined in local.xml');
31
32
        $help = <<<HELP
33
Imports an SQL file with mysql cli client into current configured database.
34
35
You need to have MySQL client tools installed on your system.
36
HELP;
37
        $this->setHelp($help);
38
    }
39
40
    /**
41
     * @return bool
42
     */
43
    public function isEnabled()
44
    {
45
        return Exec::allowed();
46
    }
47
48
    /**
49
     * Optimize a dump by converting single INSERTs per line to INSERTs with multiple lines
50
     * @param $fileName
51
     * @return string temporary filename
52
     */
53
    protected function optimize($fileName)
54
    {
55
        $in = fopen($fileName, 'r');
56
        $result = tempnam(sys_get_temp_dir(), 'dump') . '.sql';
57
        $out = fopen($result, 'w');
58
59
        fwrite($out, 'SET autocommit=0;' . "\n");
60
        $currentTable = '';
61
        $maxlen = 8 * 1024 * 1024; // 8 MB
62
        $len = 0;
63
        while ($line = fgets($in)) {
64
            if (strtolower(substr($line, 0, 11)) == 'insert into') {
65
                preg_match('/^insert into `(.*)` \([^)]*\) values (.*);/i', $line, $m);
66
67 View Code Duplication
                if (count($m) < 3) { // fallback for very long lines or other cases where the preg_match fails
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
68
                    if ($currentTable != '') {
69
                        fwrite($out, ";\n");
70
                    }
71
                    fwrite($out, $line);
72
                    $currentTable = '';
73
                    continue;
74
                }
75
76
                $table = $m[1];
77
                $values = $m[2];
78
79
                if ($table != $currentTable || ($len > $maxlen - 1000)) {
80
                    if ($currentTable != '') {
81
                        fwrite($out, ";\n");
82
                    }
83
                    $currentTable = $table;
84
                    $insert = 'INSERT INTO `' . $table . '` VALUES ' . $values;
85
                    fwrite($out, $insert);
86
                    $len = strlen($insert);
87
                } else {
88
                    fwrite($out, ',' . $values);
89
                    $len += strlen($values) + 1;
90
                }
91 View Code Duplication
            } else {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
92
                if ($currentTable != '') {
93
                    fwrite($out, ";\n");
94
                    $currentTable = '';
95
                }
96
                fwrite($out, $line);
97
            }
98
        }
99
100
        fwrite($out, ";\n");
101
102
        fwrite($out, 'COMMIT;' . "\n");
103
104
        fclose($in);
105
        fclose($out);
106
107
        return $result;
108
    }
109
    /**
110
     * @param InputInterface  $input
111
     * @param OutputInterface $output
112
     *
113
     * @return int|void
114
     */
115
    protected function execute(InputInterface $input, OutputInterface $output)
116
    {
117
        $this->detectDbSettings($output);
118
119
        $this->writeSection($output, 'Import MySQL Database');
120
        $dbHelper = $this->getHelper('database');
121
122
        $fileName = $this->checkFilename($input);
123
124
        $compressor = $this->getCompressor($input->getOption('compression'));
125
126
        if ($input->getOption('optimize')) {
127
            if ($input->getOption('only-command')) {
128
                throw new InvalidArgumentException('Options --only-command and --optimize are not compatible');
129
            }
130
            if ($input->getOption('compression')) {
131
                throw new InvalidArgumentException('Options --compression and --optimize are not compatible');
132
            }
133
            $output->writeln('<comment>Optimizing <info>' . $fileName . '</info> to temporary file');
134
            $fileName = $this->optimize($fileName);
135
        }
136
137
        // create import command
138
        $exec = $compressor->getDecompressingCommand(
139
            'mysql ' . $dbHelper->getMysqlClientToolConnectionString(),
140
            $fileName
141
        );
142
        if ($input->getOption('only-command')) {
143
            $output->writeln($exec);
144
            return;
145
        } else {
146
            if ($input->getOption('only-if-empty')
147
                && count($dbHelper->getTables()) > 0
148
            ) {
149
                $output->writeln('<comment>Skip import. Database is not empty</comment>');
150
151
                return;
152
            }
153
        }
154
155
        if ($input->getOption('drop')) {
156
            $dbHelper->dropDatabase($output);
157
            $dbHelper->createDatabase($output);
158
        }
159
        if ($input->getOption('drop-tables')) {
160
            $dbHelper->dropTables($output);
161
        }
162
163
        $this->doImport($output, $fileName, $exec);
164
165
        if ($input->getOption('optimize')) {
166
            unlink($fileName);
167
        }
168
    }
169
170
    public function asText()
171
    {
172
        return parent::asText() . "\n" .
0 ignored issues
show
Deprecated Code introduced by
The method Symfony\Component\Consol...mmand\Command::asText() has been deprecated with message: since version 2.3, to be removed in 3.0.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
173
            $this->getCompressionHelp();
174
    }
175
176
    /**
177
     * @param InputInterface $input
178
     *
179
     * @return mixed
180
     * @throws InvalidArgumentException
181
     */
182
    protected function checkFilename(InputInterface $input)
183
    {
184
        $fileName = $input->getArgument('filename');
185
        if (!file_exists($fileName)) {
186
            throw new InvalidArgumentException('File does not exist');
187
        }
188
        return $fileName;
189
    }
190
191
    /**
192
     * @param OutputInterface $output
193
     * @param string          $fileName
194
     * @param string          $exec
195
     *
196
     * @return void
197
     */
198
    protected function doImport(OutputInterface $output, $fileName, $exec)
199
    {
200
        $returnValue = null;
201
        $commandOutput = null;
202
        $output->writeln(
203
            '<comment>Importing SQL dump <info>' . $fileName . '</info> to database <info>'
204
            . $this->dbSettings['dbname'] . '</info>'
205
        );
206
207
        Exec::run($exec, $commandOutput, $returnValue);
208
209
        if ($returnValue <> 0) {
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $returnValue of type integer|null to 0; this is ambiguous as not only 0 == 0 is true, but null == 0 is true, too. Consider using a strict comparison ===.
Loading history...
210
            $output->writeln('<error>' . $commandOutput . '</error>');
211
        }
212
        $output->writeln('<info>Finished</info>');
213
    }
214
}
215