Test Failed
Push — master ( 359bf7...72eda2 )
by Emmanuel
02:11
created

Writer::generateConfFromDB()   B

Complexity

Conditions 5
Paths 7

Size

Total Lines 32
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 30

Importance

Changes 0
Metric Value
dl 0
loc 32
rs 8.439
c 0
b 0
f 0
ccs 0
cts 24
cp 0
cc 5
eloc 17
nc 7
nop 2
crap 30
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
    public function generateConfFromDB(DB $db, GuesserInterface $guesser): array
72
    {
73
        $this->conn = $db->getConn();
74
75
        // First step : get the list of tables
76
        $tables = $this->getTablesList();
77
        if (empty($tables)) {
78
            throw new NeuralizerConfigurationException('No tables to read in that database');
79
        }
80
81
        // For each table, read the cols and guess the Faker
82
        $data = [];
83
        foreach ($tables as $table) {
84
            $cols = $this->getColsList($table);
85
            // No cols because all are ignored ?
86
            if (empty($cols)) {
87
                continue;
88
            }
89
90
            $data[$table]['cols'] = $this->guessColsAnonType($table, $cols, $guesser);
91
        }
92
93
        if (empty($data)) {
94
            throw new NeuralizerConfigurationException('All tables or fields have been ignored');
95
        }
96
97
        return [
98
            'guesser_version' => $guesser->getVersion(),
99
            'language' => 'en_US',
100
            'entities' => $data
101
        ];
102
    }
103
104
105
    /**
106
     * Get Tables List added to the conf
107
     *
108
     * @return array
109
     */
110
    public function getTablesInConf(): array
111
    {
112
        return $this->tablesInConf;
113
    }
114
115
116
    /**
117
     * Set a flat to protect cols (Primary Key is protected by default)
118
     *
119
     * @param  bool   $protectCols
120
     * @return Writer
121
     */
122
    public function protectCols(bool $protectCols): Writer
123
    {
124
        $this->protectCols = $protectCols;
125
126
        return $this;
127
    }
128
129
130
    /**
131
     * Save the data to the file as YAML
132
     *
133
     * @param array  $data
134
     * @param string $filename
135
     */
136
    public function save(array $data, string $filename)
137
    {
138
        if (!is_writeable(dirname($filename))) {
139
            throw new NeuralizerConfigurationException(dirname($filename) . ' is not writeable.');
140
        }
141
142
        file_put_contents($filename, Yaml::dump($data, 4));
143
    }
144
145
146
    /**
147
     * Set protected cols
148
     *
149
     * @param array $ignoredTables
150
     * @return Writer
151
     */
152
    public function setIgnoredTables(array $ignoredTables): Writer
153
    {
154
        $this->ignoredTables = $ignoredTables;
155
156
        return $this;
157
    }
158
159
160
    /**
161
     * Set protected cols
162
     *
163
     * @param array $protectedCols
164
     * @return Writer
165
     */
166
    public function setProtectedCols(array $protectedCols): Writer
167
    {
168
        $this->protectedCols = $protectedCols;
169
170
        return $this;
171
    }
172
173
174
    /**
175
     * Check if that col has to be ignored
176
     *
177
     * @param  string $table
178
     * @param  string $col
179
     * @return bool
180
     */
181
    protected function colIgnored(string $table, string $col): bool
182
    {
183
        if ($this->protectCols === false) {
184
            return false;
185
        }
186
187
        foreach ($this->protectedCols as $protectedCol) {
188
            if (preg_match("/^$protectedCol\$/", $table . '.' . $col)) {
189
                return true;
190
            }
191
        }
192
193
        return false;
194
    }
195
196
197
    /**
198
     * Get the cols lists from a connection + table
199
     *
200
     * @param  string  $table
201
     * @return array
202
     */
203
    protected function getColsList(string $table): array
204
    {
205
        $schema = $this->conn->getSchemaManager();
206
        $tableDetails = $schema->listTableDetails($table);
207
        // No primary ? Exception !
208
        if ($tableDetails->hasPrimaryKey() === false) {
209
            throw new NeuralizerConfigurationException("Can't work with $table, it has no primary key.");
210
        }
211
        $primaryKey = $tableDetails->getPrimaryKey()->getColumns()[0];
212
213
        $cols = $schema->listTableColumns($table);
214
        $colsInfo = [];
215
        foreach ($cols as $col) {
216
            // If the col has to be ignored: just leave
217
            if ($primaryKey === $col->getName() || $this->colIgnored($table, $col->getName())) {
218
                continue;
219
            }
220
221
            $colsInfo[] = [
222
                'name' => $col->getName(),
223
                'type' => strtolower((string) $col->getType()),
224
                'len' => $col->getLength(),
225
            ];
226
        }
227
228
        return $colsInfo;
229
    }
230
231
232
    /**
233
     * Get the table lists from a connection
234
     *
235
     * @return array
236
     */
237
    protected function getTablesList(): array
238
    {
239
        $schemaManager = $this->conn->getSchemaManager();
240
241
        $tablesInDB = $schemaManager->listTables();
242
        $tables = [];
243
        foreach ($tablesInDB as $table) {
244
            if ($this->tableIgnored($table->getName())) {
245
                continue;
246
            }
247
248
            $tables[] = $this->tablesInConf[] = $table->getName();
249
        }
250
251
        return array_values($tables);
252
    }
253
254
255
    /**
256
     * Guess the cols with the guesser
257
     *
258
     * @param  string           $table
259
     * @param  array            $cols
260
     * @param  GuesserInterface $guesser
261
     * @return array
262
     */
263
    protected function guessColsAnonType(string $table, array $cols, GuesserInterface $guesser): array
264
    {
265
        $mapping = [];
266
        foreach ($cols as $props) {
267
            $mapping[$props['name']] = $guesser->mapCol($table, $props['name'], $props['type'], $props['len']);
268
        }
269
270
        return $mapping;
271
    }
272
273
274
    /**
275
     * Check if that table has to be ignored
276
     *
277
     * @param  string $table
278
     * @return bool
279
     */
280
    protected function tableIgnored(string $table): bool
281
    {
282
        foreach ($this->ignoredTables as $ignoredTable) {
283
            if (preg_match("/^$ignoredTable\$/", $table)) {
284
                return true;
285
            }
286
        }
287
288
        return false;
289
    }
290
}
291