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

Writer::colIgnored()   B

Complexity

Conditions 5
Paths 4

Size

Total Lines 14
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 30

Importance

Changes 0
Metric Value
dl 0
loc 14
ccs 0
cts 12
cp 0
rs 8.8571
c 0
b 0
f 0
cc 5
eloc 7
nc 4
nop 3
crap 30
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\Configuration;
19
20
use Inet\Neuralyzer\GuesserInterface;
21
use Inet\Neuralyzer\Exception\NeuralizerConfigurationException;
22
use Symfony\Component\Yaml\Yaml;
23
24
/**
25
 * Configuration Writer
26
 */
27
class Writer
28
{
29
    /**
30
     * Should I protect the cols ? That will also protect the Primary Keys
31
     *
32
     * @var boolean
33
     */
34
    protected $protectCols = true;
35
36
    /**
37
     * List the cols to protected. Could containe regexp
38
     *
39
     * @var array
40
     */
41
    protected $protectedCols = ['id', 'parent_id'];
42
43
    /**
44
     * List of tables patterns to ignore
45
     *
46
     * @var array
47
     */
48
    protected $ignoredTables = [];
49
50
    /**
51
     * Store the tables added to the conf
52
     *
53
     * @var array
54
     */
55
    protected $tablesInConf = [];
56
57
    /**
58
     * Set Change Id to ask the writer to ignore (or not) the protectedCols
59
     *
60
     * @param    bool
61
     */
62
    public function protectCols(bool $protect): Writer
63
    {
64
        $this->protectCols = $protect;
65
66
        return $this;
67
    }
68
69
    /**
70
     * Set protected cols
71
     *
72
     * @param array $cols
73
     */
74
    public function setProtectedCols(array $cols): Writer
75
    {
76
        $this->protectedCols = $cols;
77
78
        return $this;
79
    }
80
81
    /**
82
     * Set protected cols
83
     *
84
     * @param array $tables
85
     */
86
    public function setIgnoredTables(array $tables): Writer
87
    {
88
        $this->ignoredTables = $tables;
89
90
        return $this;
91
    }
92
93
    /**
94
     * Get Tables List added to the conf
95
     *
96
     * @return array
97
     */
98
    public function getTablesInConf(): array
99
    {
100
        return $this->tablesInConf;
101
    }
102
103
    /**
104
     * Generate the configuration by reading tables + cols
105
     *
106
     * @param \PDO                              $pdo
107
     * @param GuesserInterface $guesser
108
     *
109
     * @return array
110
     */
111
    public function generateConfFromDB(\PDO $pdo, GuesserInterface $guesser): array
112
    {
113
114
        // First step : get the list of tables
115
        $tables = $this->getTablesList($pdo);
116
        if (empty($tables)) {
117
            throw new NeuralizerConfigurationException('No tables to read in that database');
118
        }
119
120
        // For each table, read the cols and guess the Faker
121
        $data = [];
122
        foreach ($tables as $table) {
123
            $cols = $this->getColsList($pdo, $table);
124
            // No cols because all are ignored ?
125
            if (empty($cols)) {
126
                continue;
127
            }
128
129
            $data[$table]['cols'] = $this->guessColsAnonType($table, $cols, $guesser);
130
        }
131
132
        if (empty($data)) {
133
            throw new NeuralizerConfigurationException('All tables or fields have been ignored');
134
        }
135
136
        return ['guesser_version' => $guesser->getVersion(), 'entities' => $data];
137
    }
138
139
    /**
140
     * Save the data to the file as YAML
141
     *
142
     * @param array  $data
143
     * @param string $filename
144
     */
145
    public function save(array $data, string $filename)
146
    {
147
        if (!is_writeable(dirname($filename))) {
148
            throw new NeuralizerConfigurationException(dirname($filename) . ' is not writeable.');
149
        }
150
151
        file_put_contents($filename, Yaml::dump($data, 4));
152
    }
153
154
    /**
155
     * Check if that col has to be ignored
156
     *
157
     * @param string $table
158
     * @param string $col
159
     * @param bool   $isPrimary
160
     *
161
     * @return bool
162
     */
163
    protected function colIgnored(string $table, string $col, bool $isPrimary): bool
164
    {
165
        if ($this->protectCols === false) {
166
            return false;
167
        }
168
169
        foreach ($this->protectedCols as $protectedCol) {
170
            if ($isPrimary || preg_match("/^$protectedCol\$/", $table. '.' . $col)) {
171
                return true;
172
            }
173
        }
174
175
        return false;
176
    }
177
178
    /**
179
     * Get the table lists from a connection
180
     *
181
     * @param \PDO $pdo
182
     *
183
     * @return array
184
     */
185
    protected function getTablesList(\PDO $pdo): array
186
    {
187
        $result = $pdo->query("SHOW FULL TABLES WHERE Table_type = 'BASE TABLE'");
188
189
        $tables = $result->fetchAll(\PDO::FETCH_COLUMN);
190
        foreach ($tables as $key => $val) {
191
            if ($this->tableIgnored($val)) {
192
                unset($tables[$key]);
193
                continue;
194
            }
195
196
            $this->tablesInConf[] = $val;
197
        }
198
199
        return array_values($tables);
200
    }
201
202
    /**
203
     * Get the cols lists from a connection + table
204
     *
205
     * @param \PDO   $pdo
206
     * @param string $table
207
     *
208
     * @return array
209
     */
210
    protected function getColsList(\PDO $pdo, string $table): array
211
    {
212
        $result = $pdo->query("SHOW COLUMNS FROM $table");
213
214
        $cols = $result->fetchAll(\PDO::FETCH_ASSOC);
215
        $colsInfo = [];
216
217
        $hasPrimary = false;
218
        foreach ($cols as $col) {
219
            // get the info that we found a primary key
220
            $isPrimary = ($col['Key'] === 'PRI'  ? true : false);
221
            if ($isPrimary) {
222
                $hasPrimary = true;
223
            }
224
225
            // If the col has to be ignored: just leave
226
            if ($this->colIgnored($table, $col['Field'], $isPrimary)) {
227
                continue;
228
            }
229
230
            $length = null;
231
            $type = $col['Type'];
232
            preg_match('|^(.*)\((.*)\)|', $col['Type'], $colProps);
233
            if (!empty($colProps)) {
234
                $type = $colProps[1];
235
                $length = $colProps[2];
236
            }
237
238
            $colsInfo[] = [
239
                'name' => $col['Field'],
240
                'type' => $type,
241
                'len'  => $length,
242
                'id'   => $isPrimary,
243
            ];
244
        }
245
246
        // No primary ? Exception !
247
        if ($hasPrimary === false) {
248
            throw new NeuralizerConfigurationException("Can't work with $table, it has no primary key.");
249
        }
250
251
        return $colsInfo;
252
    }
253
254
    /**
255
     * Guess the cols with the guesser
256
     *
257
     * @param string                            $table
258
     * @param array                             $cols
259
     * @param GuesserInterface $guesser
260
     *
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
     * Check if that table has to be ignored
275
     *
276
     * @param string $table
277
     *
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