Completed
Push — master ( fb988b...6625be )
by Emmanuel
07:10 queued 05:31
created

DBUtils   A

Complexity

Total Complexity 20

Size/Duplication

Total Lines 231
Duplicated Lines 0 %

Test Coverage

Coverage 97.1%

Importance

Changes 0
Metric Value
eloc 68
dl 0
loc 231
ccs 67
cts 69
cp 0.971
rs 10
c 0
b 0
f 0
wmc 20

11 Methods

Rating   Name   Duplication   Size   Complexity  
A getConn() 0 6 2
A assertTableExists() 0 4 2
A getTableCols() 0 14 2
A getDBHelper() 0 3 1
A configure() 0 10 1
A getRawSQL() 0 8 2
A getEmptyValue() 0 20 2
A getIntegerCast() 0 8 3
A getCondition() 0 26 2
A getPrimaryKey() 0 9 2
A countResults() 0 6 1
1
<?php
2
/**
3
 * neuralyzer : Data Anonymization Library and CLI Tool
4
 *
5
 * PHP Version 7.1
6
 *
7
 * @author    Emmanuel Dyan
8
 * @author    Rémi Sauvat
9
 * @copyright 2018 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 Edyan\Neuralyzer\Utils;
19
20
use Doctrine\DBAL\Configuration as DbalConfiguration;
21
use Doctrine\DBAL\Connection;
22
use Doctrine\DBAL\DriverManager;
23
use Doctrine\DBAL\Query\QueryBuilder;
24
use Edyan\Neuralyzer\Exception\NeuralyzerException;
25
use Edyan\Neuralyzer\Helper\DB as DBHelper;
26
27
/**
28
 * A few generic methods to help interacting with DB
29
 */
30
class DBUtils
31
{
32
    /**
33
     * Doctrine DB Adapter
34
     *
35
     * @var Connection
36
     */
37
    private $conn;
38
39
    /**
40
     * A helper for the current driver
41
     *
42
     * @var DBHelper\AbstractDBHelper
43
     */
44
    private $dbHelper;
45
46
47
    /**
48
     * Set the connection (dependency)
49
     *
50
     * @param array $params
51
     *
52
     * @return void
53
     * @throws \Doctrine\DBAL\DBALException
54
     */
55 68
    public function configure(array $params): void
56
    {
57 68
        $dbHelperClass = DBHelper\DriverGuesser::getDBHelper($params['driver']);
58
59
        // Set specific options
60 68
        $params['driverOptions'] = $dbHelperClass::getDriverOptions();
61 68
        $this->conn = DriverManager::getConnection($params, new DbalConfiguration());
62 68
        $this->conn->setFetchMode(\Doctrine\DBAL\FetchMode::ASSOCIATIVE);
63
64 68
        $this->dbHelper = new $dbHelperClass($this->conn);
65 68
    }
66
67
    /**
68
     * @return DBHelper\AbstractDBHelper
69
     */
70 33
    public function getDBHelper(): DBHelper\AbstractDBHelper
71
    {
72 33
        return $this->dbHelper;
73
    }
74
75
    /**
76
     * Get Doctrine Connection
77
     *
78
     * @return Connection
79
     */
80 40
    public function getConn(): Connection
81
    {
82 40
        if (empty($this->conn)) {
83 1
            throw new \RuntimeException('Make sure you have called $dbUtils->configure($params) first');
84
        }
85 39
        return $this->conn;
86
    }
87
88
    /**
89
     * Do a simple count for a table
90
     *
91
     * @param  string $table
92
     *
93
     * @return int
94
     */
95 23
    public function countResults(string $table): int
96
    {
97 23
        $queryBuilder = $this->conn->createQueryBuilder();
98 23
        $rows = $queryBuilder->select('COUNT(1)')->from($table)->execute();
99
100 21
        return (int) $rows->fetch(\Doctrine\DBAL\FetchMode::NUMERIC)[0];
101
    }
102
103
104
    /**
105
     * Identify the primary key for a table
106
     *
107
     * @param string $table
108
     *
109
     * @return string
110
     * @throws NeuralyzerException
111
     */
112 29
    public function getPrimaryKey(string $table): string
113
    {
114 29
        $schema = $this->conn->getSchemaManager();
115 29
        $tableDetails = $schema->listTableDetails($table);
116 29
        if ($tableDetails->hasPrimaryKey() === false) {
117 2
            throw new NeuralyzerException("Can't find a primary key for '{$table}'");
118
        }
119
120 27
        return $tableDetails->getPrimaryKey()->getColumns()[0];
121
    }
122
123
124
    /**
125
     * Retrieve columns list for a table with type and length
126
     *
127
     * @param  string $table
128
     *
129
     * @return array $cols
130
     */
131 30
    public function getTableCols(string $table): array
132
    {
133 30
        $schema = $this->conn->getSchemaManager();
134 30
        $tableCols = $schema->listTableColumns($table);
135 30
        $cols = [];
136 30
        foreach ($tableCols as $col) {
137 30
            $cols[$col->getName()] = [
138 30
                'length' => $col->getLength(),
139 30
                'type' => $col->getType(),
140 30
                'unsigned' => $col->getUnsigned(),
141
            ];
142
        }
143
144 30
        return $cols;
145
    }
146
147
148
    /**
149
     * To debug, build the final SQL (can be approximate)
150
     *
151
     * @param  QueryBuilder $queryBuilder
152
     *
153
     * @return string
154
     */
155 10
    public function getRawSQL(QueryBuilder $queryBuilder)
156
    {
157 10
        $sql = $queryBuilder->getSQL();
158 10
        foreach ($queryBuilder->getParameters() as $parameter => $value) {
159 9
            $sql = str_replace($parameter, "'$value'", $sql);
160
        }
161
162 10
        return $sql;
163
    }
164
165
    /**
166
     * Make sure a table exists
167
     *
168
     * @param  string $table [description]
169
     * @throws NeuralyzerException
170
     */
171 30
    public function assertTableExists(string $table): void
172
    {
173 30
        if ($this->conn->getSchemaManager()->tablesExist([$table]) === false) {
174 2
            throw new NeuralyzerException("Table $table does not exist");
175
        }
176 28
    }
177
178
179
    /**
180
     * Build the condition by casting the value if needed
181
     *
182
     * @param  string $field
183
     * @param  array  $fieldConf Various values about the field
184
     *
185
     * @return string
186
     */
187 13
    public function getCondition(string $field, array $fieldConf): string
188
    {
189 13
        $type = strtolower($fieldConf['type']);
190 13
        $unsigned = $fieldConf['unsigned'];
191
192 13
        $integerCast = $this->getIntegerCast($unsigned);
193
194 13
        $condition = "(CASE $field WHEN NULL THEN NULL ELSE :$field END)";
195
196
        $typeToCast = [
197 13
            'date' => 'DATE',
198 13
            'datetime' => 'DATE',
199 13
            'time' => 'TIME',
200 13
            'smallint' => $integerCast,
201 13
            'integer' => $integerCast,
202 13
            'bigint' => $integerCast,
203 13
            'float' => 'DECIMAL',
204 13
            'decimal' => 'DECIMAL',
205
        ];
206
207
        // No cast required
208 13
        if (!array_key_exists($type, $typeToCast)) {
209 11
            return $condition;
210
        }
211
212 12
        return "CAST($condition AS {$typeToCast[$type]})";
213
    }
214
215
216
    /**
217
     * Gives an empty value according to the field (example : numeric = 0)
218
     *
219
     * @param  string $type
220
     *
221
     * @return mixed
222
     */
223 7
    public function getEmptyValue(string $type)
224
    {
225 7
        $type = strtolower($type);
226
        $typeToValue = [
227 7
            'date' => '1970-01-01',
228
            'datetime' => '1970-01-01 00:00:00',
229
            'time' => '00:00:00',
230
            'smallint' => 0,
231
            'integer' => 0,
232
            'bigint' => 0,
233
            'float' => 0,
234
            'decimal' => 0,
235
        ];
236
237
        // Value is simply an empty string
238 7
        if (!array_key_exists($type, $typeToValue)) {
239 7
            return '';
240
        }
241
242
        return $typeToValue[$type];
243
    }
244
245
246
    /**
247
     * Get the right CAST for an INTEGER
248
     *
249
     * @param  bool $unsigned
250
     *
251
     * @return string
252
     */
253 13
    private function getIntegerCast(bool $unsigned): string
254
    {
255 13
        $driver = $this->conn->getDriver();
256 13
        if (strpos($driver->getName(), 'mysql')) {
257
            return $unsigned === true ? 'UNSIGNED' : 'SIGNED';
258
        }
259
260 13
        return 'INTEGER';
261
    }
262
}
263