Completed
Push — master ( a1f222...7ceccc )
by Emmanuel
03:50
created

DBUtils::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

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