Completed
Push — master ( 3a5c6d...7f7360 )
by Emmanuel
02:10
created

AnonRunCommand   A

Complexity

Total Complexity 12

Size/Duplication

Total Lines 191
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 10

Test Coverage

Coverage 87.88%

Importance

Changes 0
Metric Value
wmc 12
lcom 2
cbo 10
dl 0
loc 191
ccs 87
cts 99
cp 0.8788
rs 10
c 0
b 0
f 0

4 Methods

Rating   Name   Duplication   Size   Complexity  
A configure() 0 49 1
B execute() 0 43 4
B anonymizeTable() 0 31 5
A countRecords() 0 13 2
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
/**
30
 * Command to launch an anonymization based on a config file
31
 */
32
class AnonRunCommand extends Command
33
{
34
    use DBTrait;
35
36
    /**
37
     * Anonymizer DB Interface
38
     * @var Inet\Neuralyzer\Anonymizer\DB
39
     */
40
    private $anon;
41
42
    /**
43
     * Set the command shortcut to be used in configuration
44
     *
45
     * @var string
46
     */
47
    private $command = 'run';
48
49
    /**
50
     * @var InputInterface
51
     */
52
    private $input;
53
54
    /**
55
     * @var OutputInterface
56
     */
57
    private $output;
58
59
60
    /**
61
     * Configure the command
62
     *
63
     * @return void
64
     */
65 4
    protected function configure()
66
    {
67
        // First command : Test the DB Connexion
68 4
        $this->setName($this->command)
69 4
             ->setDescription(
70 4
                 'Generate configuration for the Anonymizer'
71 4
             )->setHelp(
72 4
                 'This command will connect to a DB and run the anonymizer from a yaml config' . PHP_EOL .
73 4
                 "Usage: ./bin/anon <info>{$this->command} -u app -p app -f anon.yml</info>"
74 4
             )->addOption(
75 4
                 'host',
76 4
                 null,
77 4
                 InputOption::VALUE_REQUIRED,
78 4
                 'Host',
79 4
                 '127.0.0.1'
80 4
             )->addOption(
81 4
                 'db',
82 4
                 'd',
83 4
                 InputOption::VALUE_REQUIRED,
84 4
                 'Database Name'
85 4
             )->addOption(
86 4
                 'user',
87 4
                 'u',
88 4
                 InputOption::VALUE_REQUIRED,
89 4
                 'User Name',
90
                 get_current_user()
91 4
             )->addOption(
92 4
                 'password',
93 4
                 'p',
94 4
                 InputOption::VALUE_REQUIRED,
95 4
                 'Password (or prompted)'
96 4
             )->addOption(
97 4
                 'config',
98 4
                 'c',
99 4
                 InputOption::VALUE_REQUIRED,
100 4
                 'Configuration File',
101 4
                 'anon.yml'
102 4
             )->addOption(
103 4
                 'pretend',
104 4
                 null,
105 4
                 InputOption::VALUE_NONE,
106 4
                 "Don't run the queries"
107 4
             )->addOption(
108 4
                 'sql',
109 4
                 null,
110 4
                 InputOption::VALUE_NONE,
111 4
                 'Display the SQL'
112
             );
113 4
    }
114
115
    /**
116
     * Execute the command
117
     *
118
     * @param InputInterface  $input
119
     * @param OutputInterface $output
120
     *
121
     * @return void
122
     */
123 4
    protected function execute(InputInterface $input, OutputInterface $output)
124
    {
125 4
        $password = $input->getOption('password');
126 4
        if (is_null($password)) {
127
            $question = new Question('Password: ');
128
            $question->setHidden(true)->setHiddenFallback(false);
129
130
            $password = $this->getHelper('question')->ask($input, $output, $question);
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Symfony\Component\Console\Helper\HelperInterface as the method ask() does only exist in the following implementations of said interface: Symfony\Component\Console\Helper\QuestionHelper, Symfony\Component\Consol...r\SymfonyQuestionHelper.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
131
        }
132
133 4
        $this->connectToDB(
134 4
            $input->getOption('host'),
135 4
            $input->getOption('db'),
136 4
            $input->getOption('user'),
137
            $password
138
        );
139
140 2
        $this->input = $input;
141 2
        $this->output = $output;
142
143
        // Anon READER
144 2
        $reader = new \Inet\Neuralyzer\Configuration\Reader($input->getOption('config'));
145
146
        // Now work on the DB
147 2
        $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...
148 2
        $this->anon->setConfiguration($reader);
149
150 2
        $stopwatch = new Stopwatch();
151 2
        $stopwatch->start('Neuralyzer');
152
        // Get tables
153 2
        $tables = $reader->getEntities();
154 2
        foreach ($tables as $table) {
155 2
            $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...
156
        }
157
158
        // Get memory and execution time information
159 1
        $event = $stopwatch->stop('Neuralyzer');
160 1
        $memory = round($event->getMemory() / 1024 / 1024, 2);
161 1
        $time = round($event->getDuration() / 1000, 2);
162 1
        $time = ($time > 180 ? round($time / 60, 2) . 'mins' : "$time sec");
163
164 1
        $output->writeln("<info>Done in $time using $memory Mb of memory</info>");
165 1
    }
166
167
    /**
168
     * Anonmyze a specific table and display info about the job
169
     *
170
     * @param  string $table
171
     */
172 2
    private function anonymizeTable(string $table)
173
    {
174 2
        $total = $this->countRecords($table);
175 1
        if ($total === 0) {
176
            $this->output->writeln("<info>$table is empty</info>");
177
            return;
178
        }
179
180 1
        $bar = new ProgressBar($this->output, $total);
181 1
        $bar->setRedrawFrequency($total > 100 ? 100 : 0);
182
183 1
        $this->output->writeln("<info>Anonymizing $table</info>");
184
185
        try {
186 1
            $queries = $this->anon->processEntity($table, function () use ($bar) {
187 1
                $bar->advance();
188 1
            }, $this->input->getOption('pretend'), $this->input->getOption('sql'));
189
        } catch (\Exception $e) {
190
            $msg = "<error>Error anonymizing $table. Message was : " . $e->getMessage() . "</error>";
191
            $this->output->writeln(PHP_EOL . $msg . PHP_EOL);
192
            return;
193
        }
194
195 1
        $this->output->writeln(PHP_EOL);
196
197 1
        if ($this->input->getOption('sql')) {
198
            $this->output->writeln('<comment>Queries:</comment>');
199
            $this->output->writeln(implode(PHP_EOL, $queries));
200
            $this->output->writeln(PHP_EOL);
201
        }
202 1
    }
203
204
    /**
205
     * Count records on a table
206
     * @param  string $table
207
     * @return int
208
     */
209 2
    private function countRecords(string $table): int
210
    {
211
        try {
212 2
            $result = $this->pdo->query("SELECT COUNT(1) FROM $table");
213 1
        } catch (\Exception $e) {
214 1
            $msg = "Could not count records in table '$table' defined in your config";
215 1
            throw new \InvalidArgumentException($msg);
216
        }
217
218 1
        $data = $result->fetchAll(\PDO::FETCH_COLUMN);
219
220 1
        return (int)$data[0];
221
    }
222
}
223