Completed
Push — master ( 129964...3ac0b4 )
by Emmanuel
06:34
created

Writer::colIgnored()   B

Complexity

Conditions 5
Paths 4

Size

Total Lines 14
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 5

Importance

Changes 0
Metric Value
dl 0
loc 14
ccs 4
cts 4
cp 1
rs 8.8571
c 0
b 0
f 0
nc 4
cc 5
eloc 7
nop 3
crap 5
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\InetAnonConfigurationException;
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 7
     */
62
    public function protectCols(bool $protect): Writer
63 7
    {
64
        $this->protectCols = $protect;
65 7
66
        return $this;
67
    }
68
69
    /**
70
     * Set protected cols
71
     *
72
     * @param array $cols
73 5
     */
74
    public function setProtectedCols(array $cols): Writer
75 5
    {
76
        $this->protectedCols = $cols;
77 5
78
        return $this;
79
    }
80
81
    /**
82
     * Set protected cols
83
     *
84
     * @param array $tables
85 5
     */
86
    public function setIgnoredTables(array $tables): Writer
87 5
    {
88
        $this->ignoredTables = $tables;
89 5
90
        return $this;
91
    }
92
93
    /**
94
     * Get Tables List added to the conf
95
     *
96
     * @return array
97 1
     */
98
    public function getTablesInConf(): array
99 1
    {
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 11
     */
111
    public function generateConfFromDB(\PDO $pdo, GuesserInterface $guesser): array
112
    {
113
114 11
        // First step : get the list of tables
115 11
        $tables = $this->getTablesList($pdo);
116 3
        if (empty($tables)) {
117
            throw new InetAnonConfigurationException('No tables to read in that database');
118
        }
119
120 8
        // For each table, read the cols and guess the Faker
121 8
        $data = [];
122 8
        foreach ($tables as $table) {
123
            $cols = $this->getColsList($pdo, $table);
124 7
            // No cols because all are ignored ?
125 1
            if (empty($cols)) {
126
                continue;
127
            }
128 6
129 7
            $data[$table]['cols'] = $this->guessColsAnonType($table, $cols, $guesser);
130
        }
131 7
132 1
        if (empty($data)) {
133
            throw new InetAnonConfigurationException('All tables or fields have been ignored');
134
        }
135 6
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 5
     */
145
    public function save(array $data, string $filename)
146 5
    {
147 1
        if (!is_writeable(dirname($filename))) {
148
            throw new InetAnonConfigurationException(dirname($filename) . ' is not writeable.');
149
        }
150 4
151 4
        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 10
     *
161
     * @return bool
162 10
     */
163 2
    protected function colIgnored(string $table, string $col, bool $isPrimary): bool
164 2
    {
165
        if ($this->protectCols === false) {
166 8
            return false;
167
        }
168 8
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 8
     *
181
     * @param \PDO $pdo
182 8
     *
183 2
     * @return array
184
     */
185
    protected function getTablesList(\PDO $pdo): array
186 6
    {
187 6
        $result = $pdo->query('SHOW TABLES');
188 5
189
        $tables = $result->fetchAll(\PDO::FETCH_COLUMN);
190 5
        foreach ($tables as $key => $val) {
191
            if ($this->tableIgnored($val)) {
192 5
                unset($tables[$key]);
193
                continue;
194
            }
195
196
            $this->tablesInConf[] = $val;
197
        }
198
199
        return array_values($tables);
200
    }
201
202 11
    /**
203
     * Get the cols lists from a connection + table
204 11
     *
205
     * @param \PDO   $pdo
206 11
     * @param string $table
207 11
     *
208 10
     * @return array
209 2
     */
210 2
    protected function getColsList(\PDO $pdo, string $table): array
211
    {
212
        $result = $pdo->query("SHOW COLUMNS FROM $table");
213 8
214 11
        $cols = $result->fetchAll(\PDO::FETCH_ASSOC);
215
        $colsInfo = [];
216 11
217
        $primary = 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
                $primary = true;
223
            }
224
225
            // If the col has to be ignored: just leave
226
            if ($this->colIgnored($table, $col['Field'], $isPrimary)) {
227 8
                continue;
228
            }
229 8
230
            $length = null;
231 8
            $type = $col['Type'];
232 8
            preg_match('|^(.*)\((.*)\)|', $col['Type'], $colProps);
233
            if (!empty($colProps)) {
234 8
                $type = $colProps[1];
235 8
                $length = $colProps[2];
236
            }
237 8
238 8
            $colsInfo[] = [
239 7
                'name' => $col['Field'],
240 7
                'type' => $type,
241
                'len'  => $length,
242
                'id'   => $isPrimary,
243 8
            ];
244 5
        }
245
246
        // No primary ? Exception !
247 7
        if ($primary === false) {
248 7
            throw new InetAnonConfigurationException("Not able to work with $table, it has no primary key.");
249 7
        }
250 7
251 4
        return $colsInfo;
252 4
    }
253 4
254
    /**
255 7
     * Guess the cols with the guesser
256 7
     *
257 7
     * @param string                            $table
258 7
     * @param array                             $cols
259 7
     * @param GuesserInterface $guesser
260
     *
261 8
     * @return array
262
     */
263
    protected function guessColsAnonType(string $table, array $cols, GuesserInterface $guesser): array
264 8
    {
265 1
        $mapping = [];
266
        foreach ($cols as $props) {
267
            $mapping[$props['name']] = $guesser->mapCol($table, $props['name'], $props['type'], $props['len']);
268 7
        }
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 6
    protected function tableIgnored(string $table): bool
281
    {
282 6
        foreach ($this->ignoredTables as $ignoredTable) {
283 6
            if (preg_match("/^$ignoredTable\$/", $table)) {
284 6
                return true;
285 6
            }
286 6
        }
287 6
288 6
        return false;
289 6
    }
290
}
291