Passed
Push — main ( faeaf0...5f5b5b )
by Thierry
36:26 queued 28:38
created

Driver::processLength()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 11
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 7
c 0
b 0
f 0
nc 3
nop 1
dl 0
loc 11
rs 10
1
<?php
2
3
namespace Lagdo\DbAdmin\Driver;
4
5
use Lagdo\DbAdmin\Driver\Db\ConnectionInterface;
6
use Lagdo\DbAdmin\Driver\Db\ServerInterface;
7
use Lagdo\DbAdmin\Driver\Db\DatabaseInterface;
8
use Lagdo\DbAdmin\Driver\Db\TableInterface;
9
use Lagdo\DbAdmin\Driver\Db\QueryInterface;
10
use Lagdo\DbAdmin\Driver\Db\GrammarInterface;
11
use Lagdo\DbAdmin\Driver\Utils\Utils;
12
use Lagdo\DbAdmin\Driver\Entity\ConfigEntity;
13
use Lagdo\DbAdmin\Driver\Exception\AuthException;
14
use Lagdo\DbAdmin\Driver\Entity\TableFieldEntity;
15
16
use function in_array;
17
use function is_object;
18
use function preg_match;
19
use function version_compare;
20
use function array_flip;
21
use function preg_quote;
22
use function str_replace;
23
use function strtr;
24
use function implode;
25
use function preg_replace;
26
use function preg_match_all;
27
use function trim;
28
29
abstract class Driver implements DriverInterface
30
{
31
    use ErrorTrait;
32
    use ConfigTrait;
0 ignored issues
show
introduced by
The trait Lagdo\DbAdmin\Driver\ConfigTrait requires some properties which are not provided by Lagdo\DbAdmin\Driver\Driver: $onActions, $jush, $numberRegex, $grouping, $editFunctions, $inout, $version, $unsigned, $enumLength, $functions, $types, $operators
Loading history...
33
    use ConnectionTrait;
34
    use ServerTrait;
35
    use TableTrait;
36
    use DatabaseTrait;
37
    use QueryTrait;
38
    use GrammarTrait;
39
40
    /**
41
     * @var Utils
42
     */
43
    protected $utils;
44
45
    /**
46
     * @var ServerInterface
47
     */
48
    protected $server;
49
50
    /**
51
     * @var DatabaseInterface
52
     */
53
    protected $database;
54
55
    /**
56
     * @var TableInterface
57
     */
58
    protected $table;
59
60
    /**
61
     * @var QueryInterface
62
     */
63
    protected $query;
64
65
    /**
66
     * @var GrammarInterface
67
     */
68
    protected $grammar;
69
70
    /**
71
     * @var ConnectionInterface
72
     */
73
    protected $connection;
74
75
    /**
76
     * @var ConnectionInterface
77
     */
78
    protected $mainConnection;
79
80
    /**
81
     * @var ConfigEntity
82
     */
83
    protected $config;
84
85
    /**
86
     * The constructor
87
     *
88
     * @param Utils $utils
89
     * @param array $options
90
     */
91
    public function __construct(Utils $utils, array $options)
92
    {
93
        $this->utils = $utils;
94
        $this->config = new ConfigEntity($utils->trans, $options);
95
        $this->beforeConnectConfig();
96
        // Create and set the main connection.
97
        $this->mainConnection = $this->createConnection();
98
    }
99
100
    /**
101
     * Set driver config
102
     *
103
     * @return void
104
     */
105
    abstract protected function beforeConnectConfig();
106
107
    /**
108
     * Set driver config
109
     *
110
     * @return void
111
     */
112
    abstract protected function afterConnectConfig();
113
114
    /**
115
     * Create a connection to the server, based on the config and available packages
116
     *
117
     * @return ConnectionInterface|null
118
     */
119
    abstract protected function createConnection();
120
121
    /**
122
     * @param ConnectionInterface $connection
123
     *
124
     * @return Driver
125
     */
126
    public function useConnection(ConnectionInterface $connection)
127
    {
128
        $this->connection = $connection;
129
        return $this;
130
    }
131
132
    /**
133
     * @return Driver
134
     */
135
    public function useMainConnection()
136
    {
137
        $this->connection = $this->mainConnection;
138
        return $this;
139
    }
140
141
    /**
142
     * @inheritDoc
143
     */
144
    public function support(string $feature)
145
    {
146
        return in_array($feature, $this->config->features);
147
    }
148
149
    /**
150
     * @inheritDoc
151
     * @throws AuthException
152
     */
153
    public function open(string $database, string $schema = '')
154
    {
155
        if (!$this->connection->open($database, $schema)) {
156
            throw new AuthException($this->error());
157
        }
158
        $this->config->database = $database;
159
        $this->config->schema = $schema;
160
161
        $this->afterConnectConfig();
162
        return $this->connection;
163
    }
164
165
    /**
166
     * @inheritDoc
167
     * @throws AuthException
168
     */
169
    public function connect(string $database, string $schema = '')
170
    {
171
        $this->createConnection();
172
        return $this->open($database, $schema);
173
    }
174
175
    /**
176
     * @inheritDoc
177
     */
178
    public function minVersion(string $version, string $mariaDb = '')
179
    {
180
        $info = $this->connection->serverInfo();
181
        if ($mariaDb && preg_match('~([\d.]+)-MariaDB~', $info, $match)) {
182
            $info = $match[1];
183
            $version = $mariaDb;
184
        }
185
        return (version_compare($info, $version) >= 0);
186
    }
187
188
    /**
189
     * @inheritDoc
190
     */
191
    public function charset()
192
    {
193
        // SHOW CHARSET would require an extra query
194
        return ($this->minVersion('5.5.3', 0) ? 'utf8mb4' : 'utf8');
195
    }
196
197
    /**
198
     * @inheritDoc
199
     */
200
    public function begin()
201
    {
202
        $result = $this->connection->query("BEGIN");
203
        return $result !== false;
204
    }
205
206
    /**
207
     * @inheritDoc
208
     */
209
    public function commit()
210
    {
211
        $result = $this->connection->query("COMMIT");
212
        return $result !== false;
213
    }
214
215
    /**
216
     * @inheritDoc
217
     */
218
    public function rollback()
219
    {
220
        $result = $this->connection->query("ROLLBACK");
221
        return $result !== false;
222
    }
223
224
    /**
225
     * @inheritDoc
226
     */
227
    public function setUtf8mb4(string $create)
228
    {
229
        static $set = false;
230
        // possible false positive
231
        if (!$set && preg_match('~\butf8mb4~i', $create)) {
232
            $set = true;
233
            return 'SET NAMES ' . $this->charset() . ";\n\n";
234
        }
235
        return '';
236
    }
237
238
    /**
239
     * @inheritDoc
240
     */
241
    public function execute(string $query)
242
    {
243
        $this->utils->history->save($query);
244
        return $this->connection->query($query);
245
    }
246
247
    /**
248
     * @inheritDoc
249
     */
250
    public function queries()
251
    {
252
        return $this->utils->history->queries();
253
    }
254
255
    /**
256
     * @inheritDoc
257
     */
258
    public function applyQueries(string $query, array $tables, $escape = null)
259
    {
260
        if (!$escape) {
261
            $escape = function ($table) {
262
                return $this->escapeTableName($table);
263
            };
264
        }
265
        foreach ($tables as $table) {
266
            if (!$this->execute("$query " . $escape($table))) {
267
                return false;
268
            }
269
        }
270
        return true;
271
    }
272
273
    /**
274
     * @inheritDoc
275
     */
276
    public function values(string $query, int $column = 0)
277
    {
278
        $statement = $this->execute($query);
279
        if (!is_object($statement)) {
280
            return [];
281
        }
282
        $values = [];
283
        while ($row = $statement->fetchRow()) {
284
            $values[] = $row[$column];
285
        }
286
        return $values;
287
    }
288
289
    /**
290
     * @inheritDoc
291
     */
292
    public function colValues(string $query, string $column)
293
    {
294
        $statement = $this->execute($query);
295
        if (!is_object($statement)) {
296
            return [];
297
        }
298
        $values = [];
299
        while ($row = $statement->fetchAssoc()) {
300
            $values[] = $row[$column];
301
        }
302
        return $values;
303
    }
304
305
    /**
306
     * @inheritDoc
307
     */
308
    public function keyValues(string $query, int $keyColumn = 0, int $valueColumn = 1)
309
    {
310
        $statement = $this->execute($query);
311
        if (!is_object($statement)) {
312
            return [];
313
        }
314
        $values = [];
315
        while ($row = $statement->fetchRow()) {
316
            $values[$row[$keyColumn]] = $row[$valueColumn];
317
        }
318
        return $values;
319
    }
320
321
    /**
322
     * @inheritDoc
323
     */
324
    public function rows(string $query)
325
    {
326
        $statement = $this->execute($query);
327
        if (!is_object($statement)) { // can return true
328
            return [];
329
        }
330
        $rows = [];
331
        while ($row = $statement->fetchAssoc()) {
332
            $rows[] = $row;
333
        }
334
        return $rows;
335
    }
336
337
    /**
338
     * Escape column key used in where()
339
     *
340
     * @param string
341
     *
342
     * @return string
343
     */
344
    public function escapeKey(string $key): string
345
    {
346
        if (preg_match('(^([\w(]+)(' . str_replace('_', '.*',
347
                preg_quote($this->escapeId('_'))) . ')([ \w)]+)$)', $key, $match)) {
348
            //! columns looking like functions
349
            return $match[1] . $this->escapeId($this->unescapeId($match[2])) . $match[3]; //! SQL injection
350
        }
351
        return $this->escapeId($key);
352
    }
353
354
    /**
355
     * Escape or unescape string to use inside form []
356
     *
357
     * @param string $idf
358
     * @param bool $back
359
     *
360
     * @return string
361
     */
362
    public function bracketEscape(string $idf, bool $back = false): string
363
    {
364
        // escape brackets inside name='x[]'
365
        static $trans = [':' => ':1', ']' => ':2', '[' => ':3', '"' => ':4'];
366
        return strtr($idf, ($back ? array_flip($trans) : $trans));
367
    }
368
369
    /**
370
     * Filter length value including enums
371
     *
372
     * @param string $length
373
     *
374
     * @return string
375
     */
376
    public function processLength(string $length): string
377
    {
378
        if (!$length) {
379
            return '';
380
        }
381
        $enumLength = $this->enumLength();
382
        if (preg_match("~^\\s*\\(?\\s*$enumLength(?:\\s*,\\s*$enumLength)*+\\s*\\)?\\s*\$~", $length) &&
383
            preg_match_all("~$enumLength~", $length, $matches)) {
384
            return '(' . implode(',', $matches[0]) . ')';
385
        }
386
        return preg_replace('~^[0-9].*~', '(\0)', preg_replace('~[^-0-9,+()[\]]~', '', $length));
387
    }
388
389
    /**
390
     * Create SQL string from field type
391
     *
392
     * @param TableFieldEntity $field
393
     * @param string $collate
394
     *
395
     * @return string
396
     */
397
    private function processType(TableFieldEntity $field, string $collate = 'COLLATE'): string
398
    {
399
        $collation = '';
400
        if (preg_match('~char|text|enum|set~', $field->type) && $field->collation) {
401
            $collation = " $collate " . $this->quote($field->collation);
402
        }
403
        $sign = '';
404
        if (preg_match($this->numberRegex(), $field->type) &&
405
            in_array($field->unsigned, $this->unsigned())) {
406
            $sign = ' ' . $field->unsigned;
407
        }
408
        return ' ' . $field->type . $this->processLength($field->length) . $sign . $collation;
409
    }
410
411
    /**
412
     * Create SQL string from field
413
     *
414
     * @param TableFieldEntity $field Basic field information
415
     * @param TableFieldEntity $typeField Information about field type
416
     *
417
     * @return array
418
     */
419
    public function processField(TableFieldEntity $field, TableFieldEntity $typeField): array
420
    {
421
        $onUpdate = '';
422
        if (preg_match('~timestamp|datetime~', $field->type) && $field->onUpdate) {
423
            $onUpdate = ' ON UPDATE ' . $field->onUpdate;
424
        }
425
        $comment = '';
426
        if ($this->support('comment') && $field->comment !== '') {
427
            $comment = ' COMMENT ' . $this->quote($field->comment);
428
        }
429
        $null = $field->null ? ' NULL' : ' NOT NULL'; // NULL for timestamp
430
        $autoIncrement = $field->autoIncrement ? $this->getAutoIncrementModifier() : null;
431
        return [$this->escapeId(trim($field->name)), $this->processType($typeField),
432
            $null, $this->getDefaultValueClause($field), $onUpdate, $comment, $autoIncrement];
433
    }
434
}
435