Writer   A
last analyzed

Complexity

Total Complexity 28

Size/Duplication

Total Lines 248
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 73
dl 0
loc 248
ccs 69
cts 69
cp 1
rs 10
c 1
b 0
f 0
wmc 28

11 Methods

Rating   Name   Duplication   Size   Complexity  
A protectCols() 0 5 1
A getTablesList() 0 15 3
A getColsList() 0 26 5
A setProtectedCols() 0 5 1
A getTablesInConf() 0 3 1
A save() 0 7 2
A generateConfFromDB() 0 33 5
A colIgnored() 0 13 4
A setIgnoredTables() 0 5 1
A tableIgnored() 0 9 3
A guessColsAnonType() 0 13 2
1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * neuralyzer : Data Anonymization Library and CLI Tool
7
 *
8
 * PHP Version 7.2
9
 *
10
 * @package edyan/neuralyzer
11
 *
12
 * @author Emmanuel Dyan
13
 * @author Rémi Sauvat
14
 *
15
 * @copyright 2020 Emmanuel Dyan
16
 *
17
 * @license GNU General Public License v2.0
18
 *
19
 * @link https://github.com/edyan/neuralyzer
20
 */
21
22
namespace Edyan\Neuralyzer\Configuration;
23
24
use Edyan\Neuralyzer\Exception\NeuralyzerConfigurationException;
25
use Edyan\Neuralyzer\GuesserInterface;
26
use Edyan\Neuralyzer\Utils\DBUtils;
27
use Symfony\Component\Config\Definition\Processor;
28
use Symfony\Component\Yaml\Yaml;
29
30
/**
31
 * Configuration Writer
32
 *
33
 * @package edyan/neuralyzer
34
 */
35
class Writer
36
{
37
    /**
38
     * List of tables patterns to ignore
39
     *
40
     * @var array
41
     */
42
    protected $ignoredTables = [];
43
44
    /**
45
     * Should I protect the cols ? That will also protect the Primary Keys
46
     *
47
     * @var bool
48
     */
49
    protected $protectCols = true;
50
51
    /**
52
     * List the cols to protected. Could contain regexp
53
     *
54
     * @var array
55
     */
56
    protected $protectedCols = ['id', 'parent_id'];
57
58
    /**
59
     * Store the tables added to the conf
60
     *
61
     * @var array
62
     */
63
    protected $tablesInConf = [];
64
65
    /**
66
     * Doctrine connexion handler
67
     *
68
     * @var \Doctrine\DBAL\Connection
69
     */
70
    private $conn;
71
72
    /**
73
     * Generate the configuration by reading tables + cols
74 12
     *
75
     * @return array
76 12
     *
77
     * @throws NeuralyzerConfigurationException
78
     */
79 12
    public function generateConfFromDB(DBUtils $dbUtils, GuesserInterface $guesser): array
80 11
    {
81 3
        $this->conn = $dbUtils->getConn();
82
83
        // First step : get the list of tables
84
        $tables = $this->getTablesList();
85 8
        if (empty($tables)) {
86 8
            throw new NeuralyzerConfigurationException('No tables to read in that database');
87 8
        }
88
89 7
        // For each table, read the cols and guess the Faker
90 1
        $data = [];
91
        foreach ($tables as $table) {
92
            $cols = $this->getColsList($table);
93 6
            // No cols because all are ignored ?
94
            if (empty($cols)) {
95
                continue;
96 7
            }
97 1
98
            $data[$table]['cols'] = $this->guessColsAnonType($table, $cols, $guesser);
99
        }
100
101 6
        if (empty($data)) {
102
            throw new NeuralyzerConfigurationException('All tables or fields have been ignored');
103
        }
104 6
105
        $config = [
106 6
            'entities' => $data,
107
        ];
108
109
        $processor = new Processor();
110
111
        return $processor->processConfiguration(new ConfigDefinition(), [$config]);
112
    }
113
114
    /**
115 1
     * Get Tables List added to the conf
116
     *
117 1
     * @return array
118
     */
119
    public function getTablesInConf(): array
120
    {
121
        return $this->tablesInConf;
122
    }
123
124
    /**
125
     * Set a flat to protect cols (Primary Key is protected by default)
126
     */
127 8
    public function protectCols(bool $protectCols): Writer
128
    {
129 8
        $this->protectCols = $protectCols;
130
131 8
        return $this;
132
    }
133
134
    /**
135
     * Save the data to the file as YAML
136
     *
137
     * @param array $data
138
     *
139
     * @throws NeuralyzerConfigurationException
140
     */
141
    public function save(array $data, string $filename): void
142 5
    {
143
        if (! is_writeable(dirname($filename))) {
144 5
            throw new NeuralyzerConfigurationException(dirname($filename) . ' is not writable.');
145 1
        }
146
147
        file_put_contents($filename, Yaml::dump($data, 4));
148 4
    }
149 4
150
    /**
151
     * Set protected cols
152
     *
153
     * @param array $ignoredTables
154
     */
155
    public function setIgnoredTables(array $ignoredTables): Writer
156
    {
157
        $this->ignoredTables = $ignoredTables;
158 6
159
        return $this;
160 6
    }
161
162 6
    /**
163
     * Set protected cols
164
     *
165
     * @param array $protectedCols
166
     */
167
    public function setProtectedCols(array $protectedCols): Writer
168
    {
169
        $this->protectedCols = $protectedCols;
170
171
        return $this;
172 5
    }
173
174 5
    /**
175
     * Check if that col has to be ignored
176 5
     */
177
    protected function colIgnored(string $table, string $col): bool
178
    {
179
        if ($this->protectCols === false) {
180
            return false;
181
        }
182
183
        foreach ($this->protectedCols as $protectedCol) {
184
            if (preg_match("/^${protectedCol}\$/", $table . '.' . $col)) {
185
                return true;
186
            }
187 7
        }
188
189 7
        return false;
190 2
    }
191
192
    /**
193 5
     * Get the cols lists from a connection + table
194 5
     *
195 5
     * @return array
196
     *
197
     * @throws NeuralyzerConfigurationException
198
     */
199 4
    protected function getColsList(string $table): array
200
    {
201
        $schema = $this->conn->getSchemaManager();
202
        $tableDetails = $schema->listTableDetails($table);
203
        // No primary ? Exception !
204
        if ($tableDetails->hasPrimaryKey() === false) {
205
            throw new NeuralyzerConfigurationException("Can't work with ${table}, it has no primary key.");
206
        }
207
        $primaryKey = $tableDetails->getPrimaryKey()->getColumns()[0];
208
209
        $cols = $schema->listTableColumns($table);
210 8
        $colsInfo = [];
211
        foreach ($cols as $col) {
212 8
            // If the col has to be ignored: just leave
213 8
            if ($primaryKey === $col->getName() || $this->colIgnored($table, $col->getName())) {
214
                continue;
215 8
            }
216 1
217
            $colsInfo[] = [
218 7
                'name' => $col->getName(),
219
                'type' => ltrim(strtolower((string) $col->getType()), '\\'),
220 7
                'len' => $col->getLength(),
221 7
            ];
222 7
        }
223
224 7
        return $colsInfo;
225 7
    }
226
227
    /**
228 6
     * Get the table lists from a connection
229 6
     *
230 6
     * @return array
231 6
     */
232
    protected function getTablesList(): array
233
    {
234
        $schemaManager = $this->conn->getSchemaManager();
235 7
236
        $tablesInDB = $schemaManager->listTables();
237
        $tables = [];
238
        foreach ($tablesInDB as $table) {
239
            if ($this->tableIgnored($table->getName())) {
240
                continue;
241
            }
242
243
            $tables[] = $this->tablesInConf[] = $table->getName();
244 12
        }
245
246 12
        return array_values($tables);
247
    }
248 11
249 11
    /**
250 11
     * Guess the cols with the guesser
251 10
     *
252 2
     * @param  array            $cols
253
     *
254
     * @return array
255 8
     */
256
    protected function guessColsAnonType(string $table, array $cols, GuesserInterface $guesser): array
257
    {
258 11
        $mapping = [];
259
        foreach ($cols as $props) {
260
            $mapping[$props['name']] = $guesser->mapCol(
261
              $table,
262
              $props['name'],
263
              $props['type'],
264
              $props['len']
265
            );
266
        }
267
268
        return $mapping;
269
    }
270 6
271
    /**
272 6
     * Check if that table has to be ignored
273 6
     */
274 6
    protected function tableIgnored(string $table): bool
275
    {
276
        foreach ($this->ignoredTables as $ignoredTable) {
277 6
            if (preg_match("/^${ignoredTable}\$/", $table)) {
278
                return true;
279
            }
280
        }
281
282
        return false;
283
    }
284
}
285