Completed
Push — master ( bd5921...d9293e )
by Christian
17:54 queued 08:59
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
117
        $this->writeSection($output, 'Import MySQL Database');
118
        $dbHelper = $this->getHelper('database');
119
120
        $fileName = $this->checkFilename($input);
121
122
        $compressor = $this->getCompressor($input->getOption('compression'));
123
124
        if ($input->getOption('optimize')) {
125
            if ($input->getOption('only-command')) {
126
                throw new InvalidArgumentException('Options --only-command and --optimize are not compatible');
127
            }
128
            if ($input->getOption('compression')) {
129
                throw new InvalidArgumentException('Options --compression and --optimize are not compatible');
130
            }
131
            $output->writeln('<comment>Optimizing <info>' . $fileName . '</info> to temporary file');
132
            $fileName = $this->optimize($fileName);
133
        }
134
135
        // create import command
136
        $exec = $compressor->getDecompressingCommand(
137
            'mysql ' . $dbHelper->getMysqlClientToolConnectionString(),
138
            $fileName
139
        );
140
        if ($input->getOption('only-command')) {
141
            $output->writeln($exec);
142
            return;
143
        } else {
144
            if ($input->getOption('only-if-empty')
145
                && count($dbHelper->getTables()) > 0
146
            ) {
147
                $output->writeln('<comment>Skip import. Database is not empty</comment>');
148
149
                return;
150
            }
151
        }
152
153
        if ($input->getOption('drop')) {
154
            $dbHelper->dropDatabase($output);
155
            $dbHelper->createDatabase($output);
156
        }
157
        if ($input->getOption('drop-tables')) {
158
            $dbHelper->dropTables($output);
159
        }
160
161
        $this->doImport($output, $fileName, $exec);
162
163
        if ($input->getOption('optimize')) {
164
            unlink($fileName);
165
        }
166
    }
167
168
    public function asText() {
169
        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...
170
            $this->getCompressionHelp();
171
    }
172
173
    /**
174
     * @param InputInterface $input
175
     *
176
     * @return mixed
177
     * @throws InvalidArgumentException
178
     */
179
    protected function checkFilename(InputInterface $input)
180
    {
181
        $fileName = $input->getArgument('filename');
182
        if (!file_exists($fileName)) {
183
            throw new InvalidArgumentException('File does not exist');
184
        }
185
        return $fileName;
186
    }
187
188
    /**
189
     * @param OutputInterface $output
190
     * @param string          $fileName
191
     * @param string          $exec
192
     *
193
     * @return void
194
     */
195
    protected function doImport(OutputInterface $output, $fileName, $exec)
196
    {
197
        $returnValue = null;
198
        $commandOutput = null;
199
        $output->writeln(
200
            '<comment>Importing SQL dump <info>' . $fileName . '</info> to database <info>'
201
            . $this->dbSettings['dbname'] . '</info>'
202
        );
203
204
        Exec::run($exec, $commandOutput, $returnValue);
205
206
        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...
207
            $output->writeln('<error>' . $commandOutput . '</error>');
208
        }
209
        $output->writeln('<info>Finished</info>');
210
    }
211
}
212