Completed
Pull Request — develop (#743)
by Tom
06:01
created

ImportCommand::isEnabled()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
c 1
b 1
f 0
dl 0
loc 4
rs 10
cc 1
eloc 2
nc 1
nop 0
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('optimize', null, InputOption::VALUE_NONE, 'Convert verbose INSERTs to short ones before import (not working with compression)')
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 152 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
23
            ->addOption('drop', null, InputOption::VALUE_NONE, 'Drop and recreate database before import')
24
            ->addOption('drop-tables', null, InputOption::VALUE_NONE, 'Drop tables before import')
25
            ->setDescription('Imports database with mysql cli client according to database defined in local.xml');
26
27
        $help = <<<HELP
28
Imports an SQL file with mysql cli client into current configured database.
29
30
You need to have MySQL client tools installed on your system.
31
HELP;
32
        $this->setHelp($help);
33
34
    }
35
36
    /**
37
     * @return bool
38
     */
39
    public function isEnabled()
40
    {
41
        return Exec::allowed();
42
    }
43
44
    /**
45
     * Optimize a dump by converting single INSERTs per line to INSERTs with multiple lines
46
     * @param $fileName
47
     * @return string temporary filename
48
     */
49
    protected function optimize($fileName)
50
    {
51
        $in = fopen($fileName, 'r');
52
        $result = tempnam(sys_get_temp_dir(), 'dump') . '.sql';
53
        $out = fopen($result, 'w');
54
55
        fwrite($out, 'SET autocommit=0;' . "\n");
56
        $currentTable = '';
57
        $maxlen = 8 * 1024 * 1024; // 8 MB
58
        $len = 0;
59
        while ($line = fgets($in)) {
60
            if (strtolower(substr($line, 0, 11)) == 'insert into') {
61
                preg_match('/^insert into `(.*)` \([^)]*\) values (.*);/i', $line, $m);
62
63 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...
64
                    if ($currentTable != '') {
65
                        fwrite($out, ";\n");
66
                    }
67
                    fwrite($out, $line);
68
                    $currentTable = '';
69
                    continue;
70
                }
71
72
                $table = $m[1];
73
                $values = $m[2];
74
75
                if ($table != $currentTable || ($len > $maxlen - 1000)) {
76
                    if ($currentTable != '') {
77
                        fwrite($out, ";\n");
78
                    }
79
                    $currentTable = $table;
80
                    $insert = 'INSERT INTO `' . $table . '` VALUES ' . $values;
81
                    fwrite($out, $insert);
82
                    $len = strlen($insert);
83
                } else {
84
                    fwrite($out, ',' . $values);
85
                    $len += strlen($values) + 1;
86
                }
87 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...
88
                if ($currentTable != '') {
89
                    fwrite($out, ";\n");
90
                    $currentTable = '';
91
                }
92
                fwrite($out, $line);
93
            }
94
95
        }
96
97
        fwrite($out, ";\n");
98
99
        fwrite($out, 'COMMIT;' . "\n");
100
101
        fclose($in);
102
        fclose($out);
103
104
        return $result;
105
106
    }
107
    /**
108
     * @param InputInterface  $input
109
     * @param OutputInterface $output
110
     *
111
     * @return int|void
112
     */
113
    protected function execute(InputInterface $input, OutputInterface $output)
114
    {
115
        $this->detectDbSettings($output);
116
        $this->writeSection($output, 'Import MySQL Database');
117
        $dbHelper = $this->getHelper('database');
118
119
        $fileName = $this->checkFilename($input);
120
121
        $compressor = $this->getCompressor($input->getOption('compression'));
122
123
        if ($input->getOption('optimize')) {
124
            if ($input->getOption('only-command')) {
125
                throw new InvalidArgumentException('Options --only-command and --optimize are not compatible');
126
            }
127
            if ($input->getOption('compression')) {
128
                throw new InvalidArgumentException('Options --compression and --optimize are not compatible');
129
            }
130
            $output->writeln('<comment>Optimizing <info>' . $fileName . '</info> to temporary file');
131
            $fileName = $this->optimize($fileName);
132
        }
133
134
        // create import command
135
        $exec = $compressor->getDecompressingCommand(
136
            'mysql ' . $dbHelper->getMysqlClientToolConnectionString(),
137
            $fileName
138
        );
139
        if ($input->getOption('only-command')) {
140
            $output->writeln($exec);
141
            return;
142
        } else {
143
            if ($input->getOption('only-if-empty')
144
                && count($dbHelper->getTables()) > 0
145
            ) {
146
                $output->writeln('<comment>Skip import. Database is not empty</comment>');
147
148
                return;
149
            }
150
        }
151
152
        if ($input->getOption('drop')) {
153
            $dbHelper->dropDatabase($output);
154
            $dbHelper->createDatabase($output);
155
        }
156
        if ($input->getOption('drop-tables')) {
157
            $dbHelper->dropTables($output);
158
        }
159
160
        $this->doImport($output, $fileName, $exec);
161
162
        if ($input->getOption('optimize')) {
163
            unlink($fileName);
164
        }
165
    }
166
167
    public function asText() {
168
        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...
169
            $this->getCompressionHelp();
170
    }
171
172
    /**
173
     * @param InputInterface $input
174
     *
175
     * @return mixed
176
     * @throws InvalidArgumentException
177
     */
178
    protected function checkFilename(InputInterface $input)
179
    {
180
        $fileName = $input->getArgument('filename');
181
        if (!file_exists($fileName)) {
182
            throw new InvalidArgumentException('File does not exist');
183
        }
184
        return $fileName;
185
    }
186
187
    /**
188
     * @param OutputInterface $output
189
     * @param string          $fileName
190
     * @param string          $exec
191
     *
192
     * @return void
193
     */
194
    protected function doImport(OutputInterface $output, $fileName, $exec)
195
    {
196
        $returnValue = null;
197
        $commandOutput = null;
198
        $output->writeln(
199
            '<comment>Importing SQL dump <info>' . $fileName . '</info> to database <info>'
200
            . $this->dbSettings['dbname'] . '</info>'
201
        );
202
203
        Exec::run($exec, $commandOutput, $returnValue);
204
205
        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...
206
            $output->writeln('<error>' . $commandOutput . '</error>');
207
        }
208
        $output->writeln('<info>Finished</info>');
209
    }
210
}
211