Passed
Push — master ( 3ccc44...cef086 )
by Emmanuel
02:16 queued 15s
created

Writer::protectCols()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

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