Completed
Push — master ( 3ac0b4...c8ffe8 )
by Emmanuel
03:01
created

AnonRunCommand::anonymizeTable()   B

Complexity

Conditions 4
Paths 3

Size

Total Lines 24
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 17
CRAP Score 4

Importance

Changes 0
Metric Value
dl 0
loc 24
ccs 17
cts 17
cp 1
rs 8.6845
c 0
b 0
f 0
cc 4
eloc 16
nc 3
nop 1
crap 4
1
<?php
2
/**
3
 * neuralyzer : Data Anonymization Library and CLI Tool
4
 *
5
 * PHP Version 7.0
6
 *
7
 * @author Emmanuel Dyan
8
 * @author Rémi Sauvat
9
 * @copyright 2017 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 Inet\Neuralyzer\Console\Commands;
19
20
use Symfony\Component\Console\Command\Command;
21
use Symfony\Component\Console\Helper\ProgressBar;
22
use Symfony\Component\Console\Input\InputInterface;
23
use Symfony\Component\Console\Input\InputOption;
24
use Symfony\Component\Console\Output\OutputInterface;
25
use Symfony\Component\Console\Question\Question;
26
use Symfony\Component\Stopwatch\Stopwatch;
27
28
/**
29
 * Command to launch an anonymization based on a config file
30
 */
31
class AnonRunCommand extends Command
32
{
33
    /**
34
     * Anonymizer DB Interface
35
     * @var Inet\Neuralyzer\Anonymizer\DB
36
     */
37
    private $anon;
38
39
    /**
40
     * Set the command shortcut to be used in configuration
41
     *
42
     * @var string
43
     */
44
    private $command = 'run';
45
46
    /**
47
     * @var InputInterface
48
     */
49
    private $input;
50
51
    /**
52
     * PDO Object Initialized
53
     * @var \PDO
54
     */
55
    private $pdo;
56
57
    /**
58
     * @var InputInterface
59
     */
60
    private $output;
61
62
    /**
63
     * Configure the command
64
     *
65
     * @return void
66
     */
67 12
    protected function configure()
68
    {
69
        // First command : Test the DB Connexion
70 12
        $this->setName($this->command)
71 12
             ->setDescription(
72 12
                 'Generate configuration for the Anonymizer'
73 12
             )->setHelp(
74 12
                 'This command will connect to a DB and run the anonymizer from a yaml config' . PHP_EOL .
75 12
                 "Usage: ./bin/anon <info>{$this->command} -u app -p app -f anon.yml</info>"
76 12
             )->addOption(
77 12
                 'host',
78 12
                 null,
79 12
                 InputOption::VALUE_REQUIRED,
80 12
                 'Host',
81 12
                 '127.0.0.1'
82 12
             )->addOption(
83 12
                 'db',
84 12
                 'd',
85 12
                 InputOption::VALUE_REQUIRED,
86 12
                 'Database Name'
87 12
             )->addOption(
88 12
                 'user',
89 12
                 'u',
90 12
                 InputOption::VALUE_REQUIRED,
91 12
                 'User Name',
92
                 get_current_user()
93 12
             )->addOption(
94 12
                 'password',
95 12
                 'p',
96 12
                 InputOption::VALUE_REQUIRED,
97 12
                 'Password (or prompted)'
98 12
             )->addOption(
99 12
                 'config',
100 12
                 'c',
101 12
                 InputOption::VALUE_REQUIRED,
102 12
                 'Configuration File',
103 12
                 'anon.yml'
104 12
             )->addOption(
105 12
                 'pretend',
106 12
                 null,
107 12
                 InputOption::VALUE_NONE,
108 12
                 "Don't run the queries"
109 12
             )->addOption(
110 12
                 'sql',
111 12
                 null,
112 12
                 InputOption::VALUE_NONE,
113 12
                 'Display the SQL'
114
             );
115 12
    }
116
117
    /**
118
     * Execute the command
119
     *
120
     * @param InputInterface  $input
121
     * @param OutputInterface $output
122
     *
123
     * @return void
124
     */
125 6
    protected function execute(InputInterface $input, OutputInterface $output)
126
    {
127 6
        $this->input = $input;
128 6
        $this->output = $output;
0 ignored issues
show
Documentation Bug introduced by
It seems like $output of type object<Symfony\Component...Output\OutputInterface> is incompatible with the declared type object<Symfony\Component...e\Input\InputInterface> of property $output.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
129
130 6
        $dbName = $input->getOption('db');
131
        // Throw an exception immediately if we dont have the required DB parameter
132 6
        if (empty($dbName)) {
133 1
            throw new \InvalidArgumentException('Database name is required (--db)');
134
        }
135
136 5
        $password = $input->getOption('password');
137 5
        if (is_null($password)) {
138 2
            $password = $this->askPassword();
139
        }
140
141
        try {
142 5
            $this->pdo = new \PDO(
143 5
                "mysql:dbname=$dbName;host=" . $input->getOption('host'),
144 5
                $input->getOption('user'),
145
                $password
146
            );
147 4
            $this->pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
148 1
        } catch (\Exception $e) {
149 1
            throw new \RuntimeException("Can't connect to the database. Check your credentials");
150
        }
151
152
        // Anon READER
153 4
        $reader = new \Inet\Neuralyzer\Configuration\Reader($input->getOption('config'));
154
155
        // Now work on the DB
156 4
        $this->anon = new \Inet\Neuralyzer\Anonymizer\DB($this->pdo);
0 ignored issues
show
Documentation Bug introduced by
It seems like new \Inet\Neuralyzer\Anonymizer\DB($this->pdo) of type object<Inet\Neuralyzer\Anonymizer\DB> is incompatible with the declared type object<Inet\Neuralyzer\C...uralyzer\Anonymizer\DB> of property $anon.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
157 4
        $this->anon->setConfiguration($reader);
158
159 4
        $stopwatch = new Stopwatch();
160 4
        $stopwatch->start('Neuralyzer');
161
        // Get tables
162 4
        $tables = $reader->getEntities();
163 4
        foreach ($tables as $table) {
164 4
            $this->anonymizeTable($table, $input, $output);
0 ignored issues
show
Unused Code introduced by
The call to AnonRunCommand::anonymizeTable() has too many arguments starting with $input.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
165
        }
166
167
        // Get memory and execution time information
168 3
        $event = $stopwatch->stop('Neuralyzer');
169 3
        $memory = round($event->getMemory() / 1024 / 1024, 2);
170 3
        $time = round($event->getDuration() / 1000, 2);
171 3
        $time = ($time > 180 ? round($time / 60, 2) . 'mins' : "$time sec");
172
173 3
        $output->writeln("<info>Done in $time using $memory Mb of memory</info>");
174 3
    }
175
176
    /**
177
     * Prepare a question and return its result
178
     * @return string
179
     */
180 2
    private function askPassword(): string
181
    {
182 2
        $helper = $this->getHelper('question');
183 2
        $question = new Question('Password: ');
184 2
        $question->setHidden(true);
185 2
        $question->setHiddenFallback(false);
186
187 2
        return $helper->ask($this->input, $this->output, $question);
188
    }
189
190
    /**
191
     * Anonmyze a specific table and display info about the job
192
     *
193
     * @param  string $table
194
     */
195 4
    private function anonymizeTable(string $table)
196
    {
197 4
        $total = $this->countRecords($table);
198 3
        if ($total === 0) {
199 1
            $this->output->writeln("<info>$table is empty</info>");
0 ignored issues
show
Bug introduced by
The method writeln() does not seem to exist on object<Symfony\Component...e\Input\InputInterface>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
200 1
            return;
201
        }
202
203 2
        $bar = new ProgressBar($this->output, $total);
0 ignored issues
show
Documentation introduced by
$this->output is of type object<Symfony\Component...e\Input\InputInterface>, but the function expects a object<Symfony\Component...Output\OutputInterface>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
204 2
        $bar->setRedrawFrequency($total > 100 ? 100 : 0);
205
206 2
        $this->output->writeln("<info>Anonymizing $table</info>");
0 ignored issues
show
Bug introduced by
The method writeln() does not seem to exist on object<Symfony\Component...e\Input\InputInterface>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
207 2
        $queries = $this->anon->processEntity($table, function () use ($bar) {
208 2
            $bar->advance();
209 2
        }, $this->input->getOption('pretend'), $this->input->getOption('sql'));
210
211 2
        $this->output->writeln(PHP_EOL);
0 ignored issues
show
Bug introduced by
The method writeln() does not seem to exist on object<Symfony\Component...e\Input\InputInterface>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
212
213 2
        if ($this->input->getOption('sql')) {
214 1
            $this->output->writeln('<comment>Queries:</comment>');
0 ignored issues
show
Bug introduced by
The method writeln() does not seem to exist on object<Symfony\Component...e\Input\InputInterface>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
215 1
            $this->output->writeln(implode(PHP_EOL, $queries));
0 ignored issues
show
Bug introduced by
The method writeln() does not seem to exist on object<Symfony\Component...e\Input\InputInterface>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
216 1
            $this->output->writeln(PHP_EOL);
0 ignored issues
show
Bug introduced by
The method writeln() does not seem to exist on object<Symfony\Component...e\Input\InputInterface>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
217
        }
218 2
    }
219
220
    /**
221
     * Count records on a table
222
     * @param  string $table
223
     * @return int
224
     */
225 4
    private function countRecords(string $table): int
226
    {
227
        try {
228 4
            $result = $this->pdo->query("SELECT COUNT(1) FROM $table");
229 1
        } catch (\Exception $e) {
230 1
            $msg = "Could not count records in table '$table' defined in your config";
231 1
            throw new \InvalidArgumentException($msg);
232
        }
233
234 3
        $data = $result->fetchAll(\PDO::FETCH_COLUMN);
235
236 3
        return (int)$data[0];
237
    }
238
}
239