Passed
Push — master ( c103e2...560387 )
by Emmanuel
03:39
created

Writer::generateConfFromDB()   B

Complexity

Conditions 5
Paths 7

Size

Total Lines 28
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 5

Importance

Changes 0
Metric Value
dl 0
loc 28
ccs 14
cts 14
cp 1
rs 8.439
c 0
b 0
f 0
nc 7
cc 5
eloc 14
nop 2
crap 5
1
<?php
2
/**
3
 * neuralyzer : Data Anonymization Library and CLI Tool
4
 *
5
 * PHP Version 7.1
6
 *
7
 * @package edyan/neuralyzer
8
 *
9
 * @author Emmanuel Dyan
10
 * @author Rémi Sauvat
11
 * @copyright 2018 Emmanuel Dyan
12
 * @license GNU General Public License v2.0
13
 *
14
 * @link https://github.com/edyan/neuralyzer
15
 */
16
17
namespace Edyan\Neuralyzer\Configuration;
18
19
use Edyan\Neuralyzer\Anonymizer\DB;
20
use Edyan\Neuralyzer\Exception\NeuralizerConfigurationException;
21
use Edyan\Neuralyzer\GuesserInterface;
22
use Symfony\Component\Yaml\Yaml;
23
24
/**
25
 * Configuration Writer
26
 */
27
class Writer
28
{
29
    /**
30
     * List of tables patterns to ignore
31
     *
32
     * @var array
33
     */
34
    protected $ignoredTables = [];
35
36
    /**
37
     * Should I protect the cols ? That will also protect the Primary Keys
38
     *
39
     * @var boolean
40
     */
41
    protected $protectCols = true;
42
43
    /**
44
     * List the cols to protected. Could containe regexp
45
     *
46
     * @var array
47
     */
48
    protected $protectedCols = ['id', 'parent_id'];
49
50
    /**
51
     * Store the tables added to the conf
52
     *
53
     * @var array
54
     */
55
    protected $tablesInConf = [];
56
57
    /**
58
     * Doctrine conection handler
59
     * @var \Doctrine\DBAL\Connection
60
     */
61
    private $conn;
62
63
64
    /**
65
     * Generate the configuration by reading tables + cols
66
     *
67
     * @param  DB               $db
68
     * @param  GuesserInterface $guesser
69
     * @return array
70
     */
71 12
    public function generateConfFromDB(DB $db, GuesserInterface $guesser): array
72
    {
73 12
        $this->conn = $db->getConn();
74
75
        // First step : get the list of tables
76 12
        $tables = $this->getTablesList();
77 11
        if (empty($tables)) {
78 3
            throw new NeuralizerConfigurationException('No tables to read in that database');
79
        }
80
81
        // For each table, read the cols and guess the Faker
82 8
        $data = [];
83 8
        foreach ($tables as $table) {
84 8
            $cols = $this->getColsList($table);
85
            // No cols because all are ignored ?
86 7
            if (empty($cols)) {
87 1
                continue;
88
            }
89
90 6
            $data[$table]['cols'] = $this->guessColsAnonType($table, $cols, $guesser);
91
        }
92
93 7
        if (empty($data)) {
94 1
            throw new NeuralizerConfigurationException('All tables or fields have been ignored');
95
        }
96
97 6
        return ['guesser_version' => $guesser->getVersion(), 'entities' => $data];
98
    }
99
100
101
    /**
102
     * Get Tables List added to the conf
103
     *
104
     * @return array
105
     */
106 1
    public function getTablesInConf(): array
107
    {
108 1
        return $this->tablesInConf;
109
    }
110
111
112
    /**
113
     * Set a flat to protect cols (Primary Key is protected by default)
114
     *
115
     * @param bool
116
     */
117 8
    public function protectCols(bool $protectCols): Writer
118
    {
119 8
        $this->protectCols = $protectCols;
120
121 8
        return $this;
122
    }
123
124
125
    /**
126
     * Save the data to the file as YAML
127
     *
128
     * @param array  $data
129
     * @param string $filename
130
     */
131 5
    public function save(array $data, string $filename)
132
    {
133 5
        if (!is_writeable(dirname($filename))) {
134 1
            throw new NeuralizerConfigurationException(dirname($filename) . ' is not writeable.');
135
        }
136
137 4
        file_put_contents($filename, Yaml::dump($data, 4));
138 4
    }
139
140
141
    /**
142
     * Set protected cols
143
     *
144
     * @param array $ignoredTables
145
     */
146 6
    public function setIgnoredTables(array $ignoredTables): Writer
147
    {
148 6
        $this->ignoredTables = $ignoredTables;
149
150 6
        return $this;
151
    }
152
153
154
    /**
155
     * Set protected cols
156
     *
157
     * @param array $protectedCols
158
     */
159 5
    public function setProtectedCols(array $protectedCols): Writer
160
    {
161 5
        $this->protectedCols = $protectedCols;
162
163 5
        return $this;
164
    }
165
166
167
    /**
168
     * Check if that col has to be ignored
169
     *
170
     * @param  string $table
171
     * @param  string $col
172
     * @param  bool   $isPrimary
0 ignored issues
show
Bug introduced by
There is no parameter named $isPrimary. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
173
     * @return bool
174
     */
175 7
    protected function colIgnored(string $table, string $col): bool
176
    {
177 7
        if ($this->protectCols === false) {
178 2
            return false;
179
        }
180
181 5
        foreach ($this->protectedCols as $protectedCol) {
182 5
            if (preg_match("/^$protectedCol\$/", $table . '.' . $col)) {
183 5
                return true;
184
            }
185
        }
186
187 4
        return false;
188
    }
189
190
191
    /**
192
     * Get the cols lists from a connection + table
193
     *
194
     * @param  string  $table
195
     * @return array
196
     */
197 8
    protected function getColsList(string $table): array
198
    {
199 8
        $schema = $this->conn->getSchemaManager();
200 8
        $tableDetails = $schema->listTableDetails($table);
201
        // No primary ? Exception !
202 8
        if ($tableDetails->hasPrimaryKey() === false) {
203 1
            throw new NeuralizerConfigurationException("Can't work with $table, it has no primary key.");
204
        }
205 7
        $primaryKey = $tableDetails->getPrimaryKey()->getColumns()[0];
206
207 7
        $cols = $schema->listTableColumns($table);
208 7
        $colsInfo = [];
209 7
        foreach ($cols as $col) {
210
            // If the col has to be ignored: just leave
211 7
            if ($primaryKey === $col->getName() || $this->colIgnored($table, $col->getName())) {
212 7
                continue;
213
            }
214
215 6
            $colsInfo[] = [
216 6
                'name' => $col->getName(),
217 6
                'type' => strtolower((string) $col->getType()),
218 6
                'len' => $col->getLength(),
219
            ];
220
        }
221
222 7
        return $colsInfo;
223
    }
224
225
226
    /**
227
     * Get the table lists from a connection
228
     *
229
     * @return array
230
     */
231 12
    protected function getTablesList(): array
232
    {
233 12
        $schemaManager = $this->conn->getSchemaManager();
234
235 11
        $tablesInDB = $schemaManager->listTables();
236 11
        $tables = [];
237 11
        foreach ($tablesInDB as $table) {
238 10
            if ($this->tableIgnored($table->getName())) {
239 2
                continue;
240
            }
241
242 8
            $tables[] = $this->tablesInConf[] = $table->getName();
243
        }
244
245 11
        return array_values($tables);
246
    }
247
248
249
    /**
250
     * Guess the cols with the guesser
251
     *
252
     * @param  string           $table
253
     * @param  array            $cols
254
     * @param  GuesserInterface $guesser
255
     * @return array
256
     */
257 6
    protected function guessColsAnonType(string $table, array $cols, GuesserInterface $guesser): array
258
    {
259 6
        $mapping = [];
260 6
        foreach ($cols as $props) {
261 6
            $mapping[$props['name']] = $guesser->mapCol($table, $props['name'], $props['type'], $props['len']);
262
        }
263
264 6
        return $mapping;
265
    }
266
267
268
    /**
269
     * Check if that table has to be ignored
270
     *
271
     * @param  string $table
272
     * @return bool
273
     */
274 10
    protected function tableIgnored(string $table): bool
275
    {
276 10
        foreach ($this->ignoredTables as $ignoredTable) {
277 2
            if (preg_match("/^$ignoredTable\$/", $table)) {
278 2
                return true;
279
            }
280
        }
281
282 8
        return false;
283
    }
284
}
285