Issues (17)

src/Driver.php (1 issue)

1
<?php
2
3
namespace Lagdo\DbAdmin\Driver;
4
5
use Lagdo\DbAdmin\Driver\Utils\Utils;
6
use Lagdo\DbAdmin\Driver\Entity\ConfigEntity;
7
use Lagdo\DbAdmin\Driver\Exception\AuthException;
8
use Lagdo\DbAdmin\Driver\Entity\TableFieldEntity;
9
10
use function in_array;
11
use function is_object;
12
use function preg_match;
13
use function version_compare;
14
use function array_flip;
15
use function preg_quote;
16
use function str_replace;
17
use function strtr;
18
use function implode;
19
use function preg_replace;
20
use function preg_match_all;
21
use function trim;
22
23
abstract class Driver implements DriverInterface
24
{
25
    use ErrorTrait;
26
    use ConfigTrait;
0 ignored issues
show
The trait Lagdo\DbAdmin\Driver\ConfigTrait requires some properties which are not provided by Lagdo\DbAdmin\Driver\Driver: $jush, $numberRegex, $grouping, $editFunctions, $inout, $version, $unsigned, $enumLength, $actions, $functions, $types, $operators
Loading history...
27
    use ConnectionTrait;
28
    use ServerTrait;
29
    use TableTrait;
30
    use DatabaseTrait;
31
    use QueryTrait;
32
    use GrammarTrait;
33
34
    /**
35
     * @var Utils
36
     */
37
    protected $utils;
38
39
    /**
40
     * The constructor
41
     *
42
     * @param Utils $utils
43
     * @param array $options
44
     */
45
    public function __construct(Utils $utils, array $options)
46
    {
47
        $this->utils = $utils;
48
        $this->config = new ConfigEntity($utils->trans, $options);
49
        $this->beforeConnection();
50
        // Create and set the main connection.
51
        $this->mainConnection = $this->createConnection();
52
    }
53
54
    /**
55
     * @inheritDoc
56
     * @throws AuthException
57
     */
58
    public function open(string $database, string $schema = '')
59
    {
60
        if (!$this->connection->open($database, $schema)) {
61
            throw new AuthException($this->error());
62
        }
63
        $this->config->database = $database;
64
        $this->config->schema = $schema;
65
66
        $this->afterConnection();
67
        return $this->connection;
68
    }
69
70
    /**
71
     * @inheritDoc
72
     * @throws AuthException
73
     */
74
    public function connect(string $database, string $schema = '')
75
    {
76
        $this->createConnection();
77
        return $this->open($database, $schema);
78
    }
79
80
    /**
81
     * @inheritDoc
82
     */
83
    public function minVersion(string $version, string $mariaDb = '')
84
    {
85
        $info = $this->connection->serverInfo();
86
        if ($mariaDb && preg_match('~([\d.]+)-MariaDB~', $info, $match)) {
87
            $info = $match[1];
88
            $version = $mariaDb;
89
        }
90
        return (version_compare($info, $version) >= 0);
91
    }
92
93
    /**
94
     * @inheritDoc
95
     */
96
    public function support(string $feature)
97
    {
98
        return in_array($feature, $this->config->features);
99
    }
100
101
    /**
102
     * @inheritDoc
103
     */
104
    public function charset()
105
    {
106
        // SHOW CHARSET would require an extra query
107
        return ($this->minVersion('5.5.3', 0) ? 'utf8mb4' : 'utf8');
108
    }
109
110
    /**
111
     * @inheritDoc
112
     */
113
    public function setUtf8mb4(string $create)
114
    {
115
        static $set = false;
116
        // possible false positive
117
        if (!$set && preg_match('~\butf8mb4~i', $create)) {
118
            $set = true;
119
            return 'SET NAMES ' . $this->charset() . ";\n\n";
120
        }
121
        return '';
122
    }
123
124
    /**
125
     * @inheritDoc
126
     */
127
    public function execute(string $query)
128
    {
129
        $this->utils->history->save($query);
130
        return $this->connection->query($query);
131
    }
132
133
    /**
134
     * @inheritDoc
135
     */
136
    public function queries()
137
    {
138
        return $this->utils->history->queries();
139
    }
140
141
    /**
142
     * @inheritDoc
143
     */
144
    public function applyQueries(string $query, array $tables, $escape = null)
145
    {
146
        if (!$escape) {
147
            $escape = function ($table) {
148
                return $this->escapeTableName($table);
149
            };
150
        }
151
        foreach ($tables as $table) {
152
            if (!$this->execute("$query " . $escape($table))) {
153
                return false;
154
            }
155
        }
156
        return true;
157
    }
158
159
    /**
160
     * @inheritDoc
161
     */
162
    public function values(string $query, int $column = 0)
163
    {
164
        $statement = $this->execute($query);
165
        if (!is_object($statement)) {
166
            return [];
167
        }
168
        $values = [];
169
        while ($row = $statement->fetchRow()) {
170
            $values[] = $row[$column];
171
        }
172
        return $values;
173
    }
174
175
    /**
176
     * @inheritDoc
177
     */
178
    public function colValues(string $query, string $column)
179
    {
180
        $statement = $this->execute($query);
181
        if (!is_object($statement)) {
182
            return [];
183
        }
184
        $values = [];
185
        while ($row = $statement->fetchAssoc()) {
186
            $values[] = $row[$column];
187
        }
188
        return $values;
189
    }
190
191
    /**
192
     * @inheritDoc
193
     */
194
    public function keyValues(string $query, int $keyColumn = 0, int $valueColumn = 1)
195
    {
196
        $statement = $this->execute($query);
197
        if (!is_object($statement)) {
198
            return [];
199
        }
200
        $values = [];
201
        while ($row = $statement->fetchRow()) {
202
            $values[$row[$keyColumn]] = $row[$valueColumn];
203
        }
204
        return $values;
205
    }
206
207
    /**
208
     * @inheritDoc
209
     */
210
    public function rows(string $query)
211
    {
212
        $statement = $this->execute($query);
213
        if (!is_object($statement)) { // can return true
214
            return [];
215
        }
216
        $rows = [];
217
        while ($row = $statement->fetchAssoc()) {
218
            $rows[] = $row;
219
        }
220
        return $rows;
221
    }
222
223
    /**
224
     * Escape column key used in where()
225
     *
226
     * @param string
227
     *
228
     * @return string
229
     */
230
    public function escapeKey(string $key): string
231
    {
232
        if (preg_match('(^([\w(]+)(' . str_replace('_', '.*',
233
                preg_quote($this->escapeId('_'))) . ')([ \w)]+)$)', $key, $match)) {
234
            //! columns looking like functions
235
            return $match[1] . $this->escapeId($this->unescapeId($match[2])) . $match[3]; //! SQL injection
236
        }
237
        return $this->escapeId($key);
238
    }
239
240
    /**
241
     * Escape or unescape string to use inside form []
242
     *
243
     * @param string $idf
244
     * @param bool $back
245
     *
246
     * @return string
247
     */
248
    public function bracketEscape(string $idf, bool $back = false): string
249
    {
250
        // escape brackets inside name='x[]'
251
        static $trans = [':' => ':1', ']' => ':2', '[' => ':3', '"' => ':4'];
252
        return strtr($idf, ($back ? array_flip($trans) : $trans));
253
    }
254
255
    /**
256
     * Filter length value including enums
257
     *
258
     * @param string $length
259
     *
260
     * @return string
261
     */
262
    public function processLength(string $length): string
263
    {
264
        if (!$length) {
265
            return '';
266
        }
267
        $enumLength = $this->enumLength();
268
        if (preg_match("~^\\s*\\(?\\s*$enumLength(?:\\s*,\\s*$enumLength)*+\\s*\\)?\\s*\$~", $length) &&
269
            preg_match_all("~$enumLength~", $length, $matches)) {
270
            return '(' . implode(',', $matches[0]) . ')';
271
        }
272
        return preg_replace('~^[0-9].*~', '(\0)', preg_replace('~[^-0-9,+()[\]]~', '', $length));
273
    }
274
275
    /**
276
     * Create SQL string from field type
277
     *
278
     * @param TableFieldEntity $field
279
     * @param string $collate
280
     *
281
     * @return string
282
     */
283
    private function processType(TableFieldEntity $field, string $collate = 'COLLATE'): string
284
    {
285
        $collation = '';
286
        if (preg_match('~char|text|enum|set~', $field->type) && $field->collation) {
287
            $collation = " $collate " . $this->quote($field->collation);
288
        }
289
        $sign = '';
290
        if (preg_match($this->numberRegex(), $field->type) &&
291
            in_array($field->unsigned, $this->unsigned())) {
292
            $sign = ' ' . $field->unsigned;
293
        }
294
        return ' ' . $field->type . $this->processLength($field->length) . $sign . $collation;
295
    }
296
297
    /**
298
     * Create SQL string from field
299
     *
300
     * @param TableFieldEntity $field Basic field information
301
     * @param TableFieldEntity $typeField Information about field type
302
     *
303
     * @return array
304
     */
305
    public function processField(TableFieldEntity $field, TableFieldEntity $typeField): array
306
    {
307
        $onUpdate = '';
308
        if (preg_match('~timestamp|datetime~', $field->type) && $field->onUpdate) {
309
            $onUpdate = ' ON UPDATE ' . $field->onUpdate;
310
        }
311
        $comment = '';
312
        if ($this->support('comment') && $field->comment !== '') {
313
            $comment = ' COMMENT ' . $this->quote($field->comment);
314
        }
315
        $null = $field->null ? ' NULL' : ' NOT NULL'; // NULL for timestamp
316
        $autoIncrement = $field->autoIncrement ? $this->getAutoIncrementModifier() : null;
317
        return [$this->escapeId(trim($field->name)), $this->processType($typeField),
318
            $null, $this->getDefaultValueClause($field), $onUpdate, $comment, $autoIncrement];
319
    }
320
}
321