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

RunCommand::getTotal()   A

Complexity

Conditions 6
Paths 5

Size

Total Lines 18
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 6

Importance

Changes 0
Metric Value
eloc 10
dl 0
loc 18
c 0
b 0
f 0
ccs 11
cts 11
cp 1
rs 9.2222
cc 6
nc 5
nop 1
crap 6
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 27
    protected function configure(): void
78
    {
79
        // First command : Test the DB Connexion
80 27
        $this->setName($this->command)
81 27
            ->setDescription('Run Anonymizer')
82 27
            ->setHelp(
83 27
                'This command will connect to a DB and run the anonymizer from a yaml config' . PHP_EOL .
84 27
                "Usage: <info>./bin/neuralyzer {$this->command} -u app -p app -f neuralyzer.yml</info>"
85 27
            )->addOption(
86 27
                'driver',
87 27
                'D',
88 27
                InputOption::VALUE_REQUIRED,
89 27
                'Driver (check Doctrine documentation to have the list)',
90 27
                'pdo_mysql'
91 27
            )->addOption(
92 27
                'host',
93 27
                'H',
94 27
                InputOption::VALUE_REQUIRED,
95 27
                'Host',
96 27
                '127.0.0.1'
97 27
            )->addOption(
98 27
                'db',
99 27
                'd',
100 27
                InputOption::VALUE_REQUIRED,
101 27
                'Database Name'
102 27
            )->addOption(
103 27
                'user',
104 27
                'u',
105 27
                InputOption::VALUE_REQUIRED,
106 27
                'User Name',
107 27
                get_current_user()
108 27
            )->addOption(
109 27
                'password',
110 27
                'p',
111 27
                InputOption::VALUE_REQUIRED,
112 27
                'Password (or prompted)'
113 27
            )->addOption(
114 27
                'config',
115 27
                'c',
116 27
                InputOption::VALUE_REQUIRED,
117 27
                'Configuration File',
118 27
                'neuralyzer.yml'
119 27
            )->addOption(
120 27
                'table',
121 27
                't',
122 27
                InputOption::VALUE_REQUIRED,
123 27
                'Do a single table'
124 27
            )->addOption(
125 27
                'pretend',
126 27
                null,
127 27
                InputOption::VALUE_NONE,
128 27
                "Don't run the queries"
129 27
            )->addOption(
130 27
                'sql',
131 27
                's',
132 27
                InputOption::VALUE_NONE,
133 27
                'Display the SQL'
134 27
            )->addOption(
135 27
                'limit',
136 27
                'l',
137 27
                InputOption::VALUE_REQUIRED,
138 27
                'Limit the number of written records (update or insert). 100 by default for insert'
139 27
            )->addOption(
140 27
                'mode',
141 27
                'm',
142 27
                InputOption::VALUE_REQUIRED,
143 27
                'Set the mode : batch or queries',
144 27
                'batch'
145 27
            )->addOption(
146 27
                'bootstrap',
147 27
                'b',
148 27
                InputOption::VALUE_REQUIRED,
149 27
                'Provide a bootstrap file to load a custom setup before executing the command. Format /path/to/bootstrap.php'
150
            )
151
        ;
152 27
    }
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 void
161
     */
162 17
    protected function execute(InputInterface $input, OutputInterface $output): void
163
    {
164 17
        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 17
        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 16
        if (!in_array($input->getOption('mode'), ['queries', 'batch'])) {
175 1
            throw new \InvalidArgumentException('--mode could be only "queries" or "batch"');
176
        }
177
178 15
        $password = $input->getOption('password');
179 15
        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 15
        $this->input = $input;
187 15
        $this->output = $output;
188
189
        // Anon READER
190 15
        $this->reader = new Reader($input->getOption('config'));
191
192
        // Now work on the DB
193 15
        $this->db = new \Edyan\Neuralyzer\Anonymizer\DB([
194 15
            'driver' => $input->getOption('driver'),
195 15
            'host' => $input->getOption('host'),
196 15
            'dbname' => $input->getOption('db'),
197 15
            'user' => $input->getOption('user'),
198 15
            'password' => $password
199
        ]);
200 14
        $this->db->setConfiguration($this->reader);
201 14
        $this->db->setMode($this->input->getOption('mode'));
202 14
        $this->db->setPretend($this->input->getOption('pretend'));
203 14
        $this->db->setReturnRes($this->input->getOption('sql'));
204
205 14
        $stopwatch = new Stopwatch();
206 14
        $stopwatch->start('Neuralyzer');
207
        // Get tables
208 14
        $table = $input->getOption('table');
209 14
        $tables = empty($table) ? $this->reader->getEntities() : [$table];
210 14
        foreach ($tables as $table) {
211 14
            $this->anonymizeTable($table);
212
        }
213
214
        // Get memory and execution time information
215 12
        $event = $stopwatch->stop('Neuralyzer');
216 12
        $memory = round($event->getMemory() / 1024 / 1024, 2);
217 12
        $time = round($event->getDuration() / 1000, 2);
218 12
        $time = ($time > 180 ? round($time / 60, 2) . 'mins' : "$time sec");
219
220 12
        $output->writeln("<info>Done in $time using $memory Mb of memory</info>");
221 12
    }
222
223
    /**
224
     * Anonmyze a specific table and display info about the job
225
     *
226
     * @param  string $table
227
     */
228 14
    private function anonymizeTable(string $table): void
229
    {
230 14
        $total = $this->getTotal($table);
231 12
        if ($total === 0) {
232 2
            $this->output->writeln("<info>$table is empty</info>");
233 2
            return;
234
        }
235
236 10
        $bar = new ProgressBar($this->output, $total);
237 10
        $bar->setRedrawFrequency($total > 100 ? 100 : 0);
238
239 10
        $this->output->writeln("<info>Anonymizing $table</info>");
240
241
        try {
242 10
            $queries = $this->db->setLimit($total)->processEntity($table, function () use ($bar) {
243 10
                $bar->advance();
244 10
            });
245
        // @codeCoverageIgnoreStart
246
        } catch (\Exception $e) {
247
            $msg = "<error>Error anonymizing $table. Message was : " . $e->getMessage() . "</error>";
248
            $this->output->writeln(PHP_EOL . $msg . PHP_EOL);
249
            return;
250
        }
251
        // @codeCoverageIgnoreEnd
252
253 10
        $this->output->writeln(PHP_EOL);
254
255 10
        if ($this->input->getOption('sql')) {
256 2
            $this->output->writeln('<comment>Queries:</comment>');
257 2
            $this->output->writeln(implode(PHP_EOL, $queries));
258 2
            $this->output->writeln(PHP_EOL);
259
        }
260 10
    }
261
262
263
    /**
264
     * Define the total number of records to process for progress bar
265
     *
266
     * @param  string $table
267
     * @return int
268
     */
269 14
    private function getTotal(string $table): int
270
    {
271 14
        $limit = (int)$this->input->getOption('limit');
272 14
        $config = $this->reader->getEntityConfig($table);
273 14
        if ($config['action'] === 'insert') {
274 2
            return empty($limit) ? 100 : $limit;
275
        }
276
277 12
        $rows = (new DBUtils($this->db->getConn()))->countResults($table);
278 10
        if (empty($limit)) {
279 6
            return $rows;
280
        }
281
282 4
        if (!empty($limit) && $limit > $rows) {
283 2
            return $rows;
284
        }
285
286 2
        return $limit;
287
    }
288
}
289