Completed
Push — master ( c8ffe8...ab4d51 )
by Emmanuel
01:35
created

AnonRunCommand::askPassword()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 9
ccs 6
cts 6
cp 1
rs 9.6666
c 0
b 0
f 0
cc 1
eloc 6
nc 1
nop 0
crap 1
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 6
        $this->connectToDB();
130
131
        // Anon READER
132 4
        $reader = new \Inet\Neuralyzer\Configuration\Reader($input->getOption('config'));
133
134
        // Now work on the DB
135 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...
136 4
        $this->anon->setConfiguration($reader);
137
138 4
        $stopwatch = new Stopwatch();
139 4
        $stopwatch->start('Neuralyzer');
140
        // Get tables
141 4
        $tables = $reader->getEntities();
142 4
        foreach ($tables as $table) {
143 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...
144
        }
145
146
        // Get memory and execution time information
147 3
        $event = $stopwatch->stop('Neuralyzer');
148 3
        $memory = round($event->getMemory() / 1024 / 1024, 2);
149 3
        $time = round($event->getDuration() / 1000, 2);
150 3
        $time = ($time > 180 ? round($time / 60, 2) . 'mins' : "$time sec");
151
152 3
        $output->writeln("<info>Done in $time using $memory Mb of memory</info>");
153 3
    }
154
155
    /**
156
     * Prepare a question and return its result
157
     * @return string
158
     */
159 2
    private function askPassword(): string
160
    {
161 2
        $helper = $this->getHelper('question');
162 2
        $question = new Question('Password: ');
163 2
        $question->setHidden(true);
164 2
        $question->setHiddenFallback(false);
165
166 2
        return $helper->ask($this->input, $this->output, $question);
167
    }
168
169
    /**
170
     * Anonmyze a specific table and display info about the job
171
     *
172
     * @param  string $table
173
     */
174 4
    private function anonymizeTable(string $table)
175
    {
176 4
        $total = $this->countRecords($table);
177 3
        if ($total === 0) {
178 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...
179 1
            return;
180
        }
181
182 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...
183 2
        $bar->setRedrawFrequency($total > 100 ? 100 : 0);
184
185 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...
186 2
        $queries = $this->anon->processEntity($table, function () use ($bar) {
187 2
            $bar->advance();
188 2
        }, $this->input->getOption('pretend'), $this->input->getOption('sql'));
189
190 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...
191
192 2
        if ($this->input->getOption('sql')) {
193 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...
194 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...
195 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...
196
        }
197 2
    }
198
199
    /**
200
     * Initialize a PDO Object
201
     */
202 6
    private function connectToDB()
203
    {
204 6
        $dbName = $this->input->getOption('db');
205
        // Throw an exception immediately if we dont have the required DB parameter
206 6
        if (empty($dbName)) {
207 1
            throw new \InvalidArgumentException('Database name is required (--db)');
208
        }
209
210 5
        $password = $this->input->getOption('password');
211 5
        if (is_null($password)) {
212 2
            $password = $this->askPassword();
213
        }
214
215 5
        $host = $this->input->getOption('host');
216 5
        $user = $this->input->getOption('user');
217
        try {
218 5
            $this->pdo = new \PDO("mysql:dbname=$dbName;host=" . $host, $user, $password);
219 4
            $this->pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
220 1
        } catch (\Exception $e) {
221 1
            throw new \RuntimeException("Can't connect to the database. Check your credentials");
222
        }
223 4
    }
224
225
    /**
226
     * Count records on a table
227
     * @param  string $table
228
     * @return int
229
     */
230 4
    private function countRecords(string $table): int
231
    {
232
        try {
233 4
            $result = $this->pdo->query("SELECT COUNT(1) FROM $table");
234 1
        } catch (\Exception $e) {
235 1
            $msg = "Could not count records in table '$table' defined in your config";
236 1
            throw new \InvalidArgumentException($msg);
237
        }
238
239 3
        $data = $result->fetchAll(\PDO::FETCH_COLUMN);
240
241 3
        return (int)$data[0];
242
    }
243
}
244