Passed
Pull Request — master (#13)
by Jeroen
03:13
created

RunCommand::execute()   B

Complexity

Conditions 10
Paths 100

Size

Total Lines 64
Code Lines 38

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 38
CRAP Score 10.0016

Importance

Changes 0
Metric Value
eloc 38
dl 0
loc 64
ccs 38
cts 39
cp 0.9744
rs 7.6666
c 0
b 0
f 0
cc 10
nc 100
nop 2
crap 10.0016

How to fix   Long Method    Complexity   

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
 * neuralyzer : Data Anonymization Library and CLI Tool
4
 *
5
 * PHP Version 7.1
6
 *
7
 * @author Emmanuel Dyan
8
 * @author Rémi Sauvat
9
 * @copyright 2018 Emmanuel Dyan
10
 *
11
 * @package edyan/neuralyzer
12
 *
13
 * @license GNU General Public License v2.0
14
 *
15
 * @link https://github.com/edyan/neuralyzer
16
 */
17
18
namespace Edyan\Neuralyzer\Console\Commands;
19
20
use Edyan\Neuralyzer\Configuration\Reader;
21
use Edyan\Neuralyzer\Utils\DBUtils;
22
use Edyan\Neuralyzer\Utils\FileLoader;
23
use Symfony\Component\Console\Command\Command;
24
use Symfony\Component\Console\Helper\ProgressBar;
25
use Symfony\Component\Console\Input\InputInterface;
26
use Symfony\Component\Console\Input\InputOption;
27
use Symfony\Component\Console\Output\OutputInterface;
28
use Symfony\Component\Console\Question\Question;
29
use Symfony\Component\Stopwatch\Stopwatch;
30
31
/**
32
 * Command to launch an anonymization based on a config file
33
 */
34
class RunCommand extends Command
35
{
36
    /**
37
     * Store the DB Object
38
     *
39
     * @var \Edyan\Neuralyzer\Anonymizer\DB
40
     */
41
    private $db;
42
43
    /**
44
     * Set the command shortcut to be used in configuration
45
     *
46
     * @var string
47
     */
48
    private $command = 'run';
49
50
    /**
51
     * Symfony's Input Class for parameters and options
52
     *
53
     * @var InputInterface
54
     */
55
    private $input;
56
57
    /**
58
     * Symfony's Output Class to display info
59
     *
60
     * @var OutputInterface
61
     */
62
    private $output;
63
64
    /**
65
     * Neuralyzer reader
66
     *
67
     * @var Reader
68
     */
69
    private $reader;
70
71
72
    /**
73
     * Configure the command
74
     *
75
     * @return void
76
     */
77 28
    protected function configure(): void
78
    {
79
        // First command : Test the DB Connexion
80 28
        $this->setName($this->command)
81 28
            ->setDescription('Run Anonymizer')
82 28
            ->setHelp(
83 28
                'This command will connect to a DB and run the anonymizer from a yaml config' . PHP_EOL .
84 28
                "Usage: <info>./bin/neuralyzer {$this->command} -u app -p app -f neuralyzer.yml</info>"
85 28
            )->addOption(
86 28
                'driver',
87 28
                'D',
88 28
                InputOption::VALUE_REQUIRED,
89 28
                'Driver (check Doctrine documentation to have the list)',
90 28
                'pdo_mysql'
91 28
            )->addOption(
92 28
                'host',
93 28
                'H',
94 28
                InputOption::VALUE_REQUIRED,
95 28
                'Host',
96 28
                '127.0.0.1'
97 28
            )->addOption(
98 28
                'db',
99 28
                'd',
100 28
                InputOption::VALUE_REQUIRED,
101 28
                'Database Name'
102 28
            )->addOption(
103 28
                'user',
104 28
                'u',
105 28
                InputOption::VALUE_REQUIRED,
106 28
                'User Name',
107 28
                get_current_user()
108 28
            )->addOption(
109 28
                'password',
110 28
                'p',
111 28
                InputOption::VALUE_REQUIRED,
112 28
                'Password (or prompted)'
113 28
            )->addOption(
114 28
                'config',
115 28
                'c',
116 28
                InputOption::VALUE_REQUIRED,
117 28
                'Configuration File',
118 28
                'neuralyzer.yml'
119 28
            )->addOption(
120 28
                'table',
121 28
                't',
122 28
                InputOption::VALUE_REQUIRED,
123 28
                'Do a single table'
124 28
            )->addOption(
125 28
                'pretend',
126 28
                null,
127 28
                InputOption::VALUE_NONE,
128 28
                "Don't run the queries"
129 28
            )->addOption(
130 28
                'sql',
131 28
                's',
132 28
                InputOption::VALUE_NONE,
133 28
                'Display the SQL'
134 28
            )->addOption(
135 28
                'limit',
136 28
                'l',
137 28
                InputOption::VALUE_REQUIRED,
138 28
                'Limit the number of written records (update or insert). 100 by default for insert'
139 28
            )->addOption(
140 28
                'mode',
141 28
                'm',
142 28
                InputOption::VALUE_REQUIRED,
143 28
                'Set the mode : batch or queries',
144 28
                'batch'
145 28
            )->addOption(
146 28
                'bootstrap',
147 28
                'b',
148 28
                InputOption::VALUE_REQUIRED,
149 28
                'Provide a bootstrap file to load a custom setup before executing the command. Format /path/to/bootstrap.php'
150
            )
151
        ;
152 28
    }
153
154
    /**
155
     * Execute the command
156
     *
157
     * @param InputInterface  $input   Symfony's Input Class for parameters and options
158
     * @param OutputInterface $output  Symfony's Output Class to display infos
159
     *
160
     * @return null|int null or 0 if everything went fine, or an error code
161
     */
162 18
    protected function execute(InputInterface $input, OutputInterface $output): ?int
163
    {
164 18
        if (!empty($input->getOption('bootstrap'))) {
165
            FileLoader::checkAndLoad($input->getOption('bootstrap'));
166
        }
167
168
        // Throw an exception immediately if we dont have the required DB parameter
169 18
        if (empty($input->getOption('db'))) {
170 1
            throw new \InvalidArgumentException('Database name is required (--db)');
171
        }
172
173
        // Throw an exception immediately if we dont have the right mode
174 17
        if (!in_array($input->getOption('mode'), ['queries', 'batch'])) {
175 1
            throw new \InvalidArgumentException('--mode could be only "queries" or "batch"');
176
        }
177
178 16
        $password = $input->getOption('password');
179 16
        if (is_null($password)) {
180 4
            $question = new Question('Password: ');
181 4
            $question->setHidden(true)->setHiddenFallback(false);
182
183 4
            $password = $this->getHelper('question')->ask($input, $output, $question);
184
        }
185
186 16
        $this->input = $input;
187 16
        $this->output = $output;
188
189
        // Anon READER
190 16
        $this->reader = new Reader($input->getOption('config'));
191
192
        // Now work on the DB
193 16
        $this->db = new \Edyan\Neuralyzer\Anonymizer\DB([
194 16
            'driver' => $input->getOption('driver'),
195 16
            'host' => $input->getOption('host'),
196 16
            'dbname' => $input->getOption('db'),
197 16
            'user' => $input->getOption('user'),
198 16
            'password' => $password
199
        ]);
200 15
        $this->db->setConfiguration($this->reader);
201 15
        $this->db->setMode($this->input->getOption('mode'));
202 15
        $this->db->setPretend($this->input->getOption('pretend'));
203 15
        $this->db->setReturnRes($this->input->getOption('sql'));
204
205 15
        $stopwatch = new Stopwatch();
206 15
        $stopwatch->start('Neuralyzer');
207
        // Get tables
208 15
        $table = $input->getOption('table');
209 15
        $tables = empty($table) ? $this->reader->getEntities() : [$table];
210 15
        $hasErrors = false;
211 15
        foreach ($tables as $table) {
212 15
            if (!$this->anonymizeTable($table)) {
213 13
                $hasErrors = true;
214
            }
215
        }
216
217
        // Get memory and execution time information
218 13
        $event = $stopwatch->stop('Neuralyzer');
219 13
        $memory = round($event->getMemory() / 1024 / 1024, 2);
220 13
        $time = round($event->getDuration() / 1000, 2);
221 13
        $time = ($time > 180 ? round($time / 60, 2) . 'mins' : "$time sec");
222
223 13
        $output->writeln("<info>Done in $time using $memory Mb of memory</info>");
224
225 13
        return $hasErrors ? 1 : 0;
226
    }
227
228
    /**
229
     * Anonmyze a specific table and display info about the job
230
     *
231
     * @param  string $table
232
     */
233 15
    private function anonymizeTable(string $table): bool
234
    {
235 15
        $total = $this->getTotal($table);
236 13
        if ($total === 0) {
237 2
            $this->output->writeln("<info>$table is empty</info>");
238
239 2
            return false;
240
        }
241
242 11
        $bar = new ProgressBar($this->output, $total);
243 11
        $bar->setRedrawFrequency($total > 100 ? 100 : 0);
244
245 11
        $this->output->writeln("<info>Anonymizing $table</info>");
246
247
        try {
248 11
            $queries = $this->db->setLimit($total)->processEntity($table, function () use ($bar) {
249 10
                $bar->advance();
250 11
            });
251
        // @codeCoverageIgnoreStart
252
        } catch (\Exception $e) {
253
            $msg = "<error>Error anonymizing $table. Message was : " . $e->getMessage() . "</error>";
254
            $this->output->writeln(PHP_EOL . $msg . PHP_EOL);
255
256
            return false;
257
        }
258
        // @codeCoverageIgnoreEnd
259
260 10
        $this->output->writeln(PHP_EOL);
261
262 10
        if ($this->input->getOption('sql')) {
263 2
            $this->output->writeln('<comment>Queries:</comment>');
264 2
            $this->output->writeln(implode(PHP_EOL, $queries));
265 2
            $this->output->writeln(PHP_EOL);
266
        }
267
268 10
        return true;
269
    }
270
271
272
    /**
273
     * Define the total number of records to process for progress bar
274
     *
275
     * @param  string $table
276
     * @return int
277
     */
278 15
    private function getTotal(string $table): int
279
    {
280 15
        $limit = (int)$this->input->getOption('limit');
281 15
        $config = $this->reader->getEntityConfig($table);
282 15
        if ($config['action'] === 'insert') {
283 2
            return empty($limit) ? 100 : $limit;
284
        }
285
286 13
        $rows = (new DBUtils($this->db->getConn()))->countResults($table);
287 11
        if (empty($limit)) {
288 7
            return $rows;
289
        }
290
291 4
        if (!empty($limit) && $limit > $rows) {
292 2
            return $rows;
293
        }
294
295 2
        return $limit;
296
    }
297
}
298