Completed
Push — master ( fb988b...6625be )
by Emmanuel
07:10 queued 05:31
created

RunCommand::execute()   C

Complexity

Conditions 12
Paths 196

Size

Total Lines 70
Code Lines 42

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 40
CRAP Score 12.0489

Importance

Changes 0
Metric Value
cc 12
eloc 42
nc 196
nop 2
dl 0
loc 70
ccs 40
cts 43
cp 0.9302
crap 12.0489
rs 6.1666
c 0
b 0
f 0

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