Total Complexity | 104 |
Total Lines | 570 |
Duplicated Lines | 0 % |
Coverage | 94.44% |
Changes | 0 |
Complex classes like BaseDBAL often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use BaseDBAL, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
21 | abstract class BaseDBAL implements DBAL |
||
22 | { |
||
23 | use ConvertObjectToStringMethod; |
||
1 ignored issue
–
show
|
|||
24 | |||
25 | /** @var LoggerInterface */ |
||
26 | protected $logger; |
||
27 | |||
28 | /** |
||
29 | * Settings object |
||
30 | * @var Settings |
||
31 | */ |
||
32 | protected $settings; |
||
33 | |||
34 | /** |
||
35 | * Contains the transaction level to do nested transactions |
||
36 | * @var int |
||
37 | */ |
||
38 | protected $transactionLevel = 0; |
||
39 | |||
40 | /** |
||
41 | * Contains the "prevent commit state" of transactions |
||
42 | * @var bool |
||
43 | */ |
||
44 | protected $preventCommit = false; |
||
45 | |||
46 | /** |
||
47 | * @param Settings $settings |
||
48 | * @param LoggerInterface|null $logger If null then a NullLogger will be used |
||
49 | */ |
||
50 | 388 | public function __construct(Settings $settings, LoggerInterface $logger = null) |
|
51 | { |
||
52 | 388 | $this->settings = $settings; |
|
53 | 388 | $this->setLogger($logger ?? new NullLogger()); |
|
54 | } |
||
55 | |||
56 | /** |
||
57 | * Destructor - force call disconnect |
||
58 | */ |
||
59 | 11 | public function __destruct() |
|
60 | { |
||
61 | 11 | $this->disconnect(); |
|
62 | } |
||
63 | |||
64 | /** |
||
65 | * Implement the transaction begin command |
||
66 | */ |
||
67 | 150 | protected function commandTransactionBegin(): void |
|
68 | { |
||
69 | 150 | $this->execute('BEGIN TRANSACTION', 'Cannot start transaction'); |
|
70 | } |
||
71 | |||
72 | /** |
||
73 | * Implement the transaction commit command |
||
74 | */ |
||
75 | 218 | protected function commandTransactionCommit(): void |
|
76 | { |
||
77 | 218 | $this->execute('COMMIT', 'Cannot commit transaction'); |
|
78 | } |
||
79 | |||
80 | /** |
||
81 | * Implement the transaction rollback command |
||
82 | */ |
||
83 | 3 | protected function commandTransactionRollback(): void |
|
84 | { |
||
85 | 3 | $this->execute('ROLLBACK', 'Cannot rollback transaction'); |
|
86 | } |
||
87 | |||
88 | /** |
||
89 | * Implement the savepoint command |
||
90 | * @param string $name |
||
91 | */ |
||
92 | 4 | protected function commandSavepoint(string $name): void |
|
93 | { |
||
94 | 4 | $this->execute("SAVEPOINT $name", "Cannot create savepoint $name"); |
|
95 | } |
||
96 | |||
97 | /** |
||
98 | * Implement the release savepoint command |
||
99 | * @param string $name |
||
100 | */ |
||
101 | 4 | protected function commandReleaseSavepoint(string $name): void |
|
102 | { |
||
103 | 4 | $this->execute("RELEASE SAVEPOINT $name", "Cannot release savepoint $name"); |
|
104 | } |
||
105 | |||
106 | /** |
||
107 | * Implement the rollback to savepoint command |
||
108 | * @param string $name |
||
109 | */ |
||
110 | 2 | protected function commandRollbackToSavepoint(string $name): void |
|
113 | } |
||
114 | |||
115 | 8 | final public function getTransactionLevel(): int |
|
116 | { |
||
117 | 8 | return $this->transactionLevel; |
|
118 | } |
||
119 | |||
120 | 293 | final public function transBegin(): void |
|
121 | { |
||
122 | 293 | $this->logger->info('-- TRANSACTION BEGIN'); |
|
123 | 293 | if (0 === $this->transactionLevel) { |
|
124 | 293 | $this->commandTransactionBegin(); |
|
125 | } else { |
||
126 | 8 | $this->commandSavepoint("LEVEL_{$this->transactionLevel}"); |
|
127 | } |
||
128 | 293 | $this->transactionLevel = $this->transactionLevel + 1; |
|
129 | } |
||
130 | |||
131 | 293 | final public function transCommit(): void |
|
132 | { |
||
133 | 293 | $this->logger->info('-- TRANSACTION COMMIT'); |
|
134 | // reduce the transaction level |
||
135 | 293 | if (0 === $this->transactionLevel) { |
|
136 | 4 | trigger_error('Try to call commit without a transaction', E_USER_NOTICE); |
|
137 | 4 | return; |
|
138 | } |
||
139 | 293 | $this->transactionLevel = $this->transactionLevel - 1; |
|
140 | // do commit or savepoint |
||
141 | 293 | if (0 === $this->transactionLevel) { |
|
142 | 293 | if ($this->transPreventCommit()) { |
|
143 | 4 | $this->transactionLevel = 1; |
|
144 | 4 | trigger_error('Try to call final commit with prevent commit enabled', E_USER_ERROR); |
|
145 | } |
||
146 | 293 | $this->commandTransactionCommit(); |
|
147 | } else { |
||
148 | 8 | $this->commandReleaseSavepoint("LEVEL_{$this->transactionLevel}"); |
|
149 | } |
||
150 | } |
||
151 | |||
152 | 8 | final public function transRollback(): void |
|
153 | { |
||
154 | 8 | $this->logger->info('-- TRANSACTION ROLLBACK '); |
|
155 | // reduce the transaction level |
||
156 | 8 | if (0 === $this->transactionLevel) { |
|
157 | 4 | trigger_error('Try to call rollback without a transaction', E_USER_NOTICE); |
|
158 | 4 | return; |
|
159 | } |
||
160 | 4 | $this->transactionLevel = $this->transactionLevel - 1; |
|
161 | // do rollback or savepoint |
||
162 | 4 | if (0 === $this->transactionLevel) { |
|
163 | 4 | $this->commandTransactionRollback(); |
|
164 | } else { |
||
165 | 4 | $this->commandRollbackToSavepoint("LEVEL_{$this->transactionLevel}"); |
|
166 | } |
||
167 | } |
||
168 | |||
169 | 293 | final public function transPreventCommit(bool $preventCommit = null): bool |
|
170 | { |
||
171 | 293 | if (null === $preventCommit) { |
|
172 | 293 | return $this->preventCommit; |
|
173 | } |
||
174 | 8 | $previous = $this->preventCommit; |
|
175 | 8 | $this->preventCommit = $preventCommit; |
|
176 | 8 | return $previous; |
|
177 | } |
||
178 | |||
179 | 9 | final public function sqlTable(string $tableName, string $asTable = ''): string |
|
180 | { |
||
181 | 9 | return $this->sqlTableEscape($this->settings->get('prefix', '') . $tableName, $asTable); |
|
182 | } |
||
183 | |||
184 | 4 | final public function sqlField(string $fieldName, string $asFieldName = ''): string |
|
185 | { |
||
186 | 4 | return $fieldName . (('' !== $asFieldName) ? ' AS ' . $this->sqlFieldEscape($asFieldName) : ''); |
|
187 | } |
||
188 | |||
189 | 325 | final public function sqlQuote( |
|
190 | $variable, |
||
191 | string $commonType = CommonTypes::TTEXT, |
||
192 | bool $includeNull = false |
||
193 | ): string { |
||
194 | 325 | if (is_object($variable)) { |
|
195 | 8 | $variable = $this->convertObjectToString($variable); |
|
196 | } |
||
197 | 325 | if ($includeNull && null === $variable) { |
|
198 | 16 | return 'NULL'; |
|
199 | } |
||
200 | 325 | if (! is_scalar($variable) && ! is_null($variable)) { |
|
201 | throw new InvalidArgumentException('Value is something that cannot be parsed as scalar'); |
||
202 | } |
||
203 | // CommonTypes::TTEXT is here because is the most common used type |
||
204 | 325 | if (CommonTypes::TTEXT === $commonType) { |
|
205 | 301 | return "'" . $this->sqlString($variable ?? '') . "'"; |
|
206 | } |
||
207 | 321 | if (CommonTypes::TINT === $commonType) { |
|
208 | 317 | return $this->sqlQuoteParseNumber($variable, true); |
|
209 | } |
||
210 | 301 | if (CommonTypes::TNUMBER === $commonType) { |
|
211 | 297 | return $this->sqlQuoteParseNumber($variable, false); |
|
212 | } |
||
213 | 301 | if (CommonTypes::TBOOL === $commonType) { |
|
214 | 297 | return ($variable) ? '1' : '0'; |
|
215 | } |
||
216 | 301 | if (CommonTypes::TDATE === $commonType) { |
|
217 | 8 | return "'" . date('Y-m-d', (int) $variable) . "'"; |
|
218 | } |
||
219 | 301 | if (CommonTypes::TTIME === $commonType) { |
|
220 | 8 | return "'" . date('H:i:s', (int) $variable) . "'"; |
|
221 | } |
||
222 | 301 | if (CommonTypes::TDATETIME === $commonType) { |
|
223 | 301 | return "'" . date('Y-m-d H:i:s', (int) $variable) . "'"; |
|
224 | } |
||
225 | 8 | return "'" . $this->sqlString((string) $variable) . "'"; |
|
226 | } |
||
227 | |||
228 | /** |
||
229 | * @param scalar|null $value |
||
230 | * @param bool $asInteger |
||
231 | * @return string |
||
232 | */ |
||
233 | 317 | private function sqlQuoteParseNumber($value, bool $asInteger): string |
|
234 | { |
||
235 | 317 | return (new NumericParser())->parseAsEnglish((string) $value, $asInteger); |
|
236 | } |
||
237 | |||
238 | 24 | final public function sqlQuoteIn( |
|
239 | array $values, |
||
240 | string $commonType = CommonTypes::TTEXT, |
||
241 | bool $includeNull = false |
||
242 | ): string { |
||
243 | 24 | if ([] === $values) { |
|
244 | 4 | throw new RuntimeException('The array of values passed to DBAL::sqlQuoteIn is empty'); |
|
245 | } |
||
246 | 20 | return '(' |
|
247 | 20 | . implode(', ', array_map(function ($value) use ($commonType, $includeNull): string { |
|
248 | 20 | return $this->sqlQuote($value, $commonType, $includeNull); |
|
249 | 20 | }, array_unique($values))) |
|
250 | 20 | . ')'; |
|
251 | } |
||
252 | |||
253 | 12 | final public function sqlIn( |
|
254 | string $field, |
||
255 | array $values, |
||
256 | string $commonType = CommonTypes::TTEXT, |
||
257 | bool $positive = true, |
||
258 | bool $includeNull = false |
||
259 | ): string { |
||
260 | 12 | if (! $positive) { |
|
261 | 4 | trigger_error( |
|
262 | 4 | __METHOD__ . ' with argument $positive = false is deprecated, use DBAL::sqlNotIn', |
|
263 | 4 | E_USER_NOTICE |
|
264 | 4 | ); |
|
265 | 4 | return $this->sqlNotIn($field, $values, $commonType, $includeNull); |
|
266 | } |
||
267 | 8 | if ([] === $values) { |
|
268 | 4 | return '0 = 1'; |
|
269 | } |
||
270 | 4 | return $field . ' IN ' . $this->sqlQuoteIn($values, $commonType, $includeNull); |
|
271 | } |
||
272 | |||
273 | 12 | final public function sqlNotIn( |
|
274 | string $field, |
||
275 | array $values, |
||
276 | string $commonType = CommonTypes::TTEXT, |
||
277 | bool $includeNull = false |
||
278 | ): string { |
||
279 | 12 | if ([] === $values) { |
|
280 | 4 | return '1 = 1'; |
|
281 | } |
||
282 | 8 | return $field . ' NOT IN ' . $this->sqlQuoteIn($values, $commonType, $includeNull); |
|
283 | } |
||
284 | |||
285 | 8 | final public function sqlIsNull(string $field, bool $positive = true): string |
|
286 | { |
||
287 | 8 | if (! $positive) { |
|
288 | 4 | trigger_error( |
|
289 | 4 | __METHOD__ . ' with argument $positive = false is deprecated, use DBAL::sqlIsNotNull', |
|
290 | 4 | E_USER_NOTICE |
|
291 | 4 | ); |
|
292 | 4 | return $this->sqlIsNotNull($field); |
|
293 | } |
||
294 | 4 | return $field . ' IS NULL'; |
|
295 | } |
||
296 | |||
297 | 8 | final public function sqlIsNotNull(string $field): string |
|
298 | { |
||
299 | 8 | return $field . ' IS NOT NULL'; |
|
300 | } |
||
301 | |||
302 | 4 | final public function sqlBetweenQuote( |
|
303 | string $field, |
||
304 | $lowerBound, |
||
305 | $upperBound, |
||
306 | string $commonType = CommonTypes::TTEXT |
||
307 | ): string { |
||
308 | 4 | return $field |
|
309 | 4 | . ' BETWEEN ' . $this->sqlQuote($lowerBound, $commonType) |
|
310 | 4 | . ' AND ' . $this->sqlQuote($upperBound, $commonType) |
|
311 | 4 | ; |
|
312 | } |
||
313 | |||
314 | 4 | final public function sqlIfNull(string $fieldName, string $nullValue): string |
|
315 | { |
||
316 | 4 | return 'IFNULL(' . $fieldName . ', ' . $nullValue . ')'; |
|
317 | } |
||
318 | |||
319 | 4 | final public function sqlLikeSearch( |
|
320 | string $fieldName, |
||
321 | string $searchTerms, |
||
322 | bool $matchAnyTerm = true, |
||
323 | string $termsSeparator = ' ' |
||
324 | ): string { |
||
325 | 4 | if ('' === $termsSeparator) { |
|
326 | throw new InvalidArgumentException('Arguments to explode terms must not be an empty string'); |
||
327 | } |
||
328 | 4 | return implode( |
|
329 | 4 | ($matchAnyTerm) ? ' OR ' : ' AND ', |
|
330 | 4 | array_map(function (string $term) use ($fieldName): string { |
|
331 | 4 | return '(' . $this->sqlLike($fieldName, $term) . ')'; |
|
332 | 4 | }, array_unique(array_filter(explode($termsSeparator, $searchTerms)))) |
|
333 | 4 | ); |
|
334 | } |
||
335 | |||
336 | /** |
||
337 | * Executes a query and return the number of affected rows |
||
338 | * |
||
339 | * @param string $query |
||
340 | * @return int|false FALSE if the query fails |
||
341 | */ |
||
342 | abstract protected function queryAffectedRows(string $query); |
||
343 | |||
344 | /** |
||
345 | * Return the last error message from the driver |
||
346 | * @return string |
||
347 | */ |
||
348 | abstract protected function getLastErrorMessage(): string; |
||
349 | |||
350 | 293 | final public function execute(string $query, string $exceptionMessage = '') |
|
351 | { |
||
352 | 293 | $return = $this->queryAffectedRows($query); |
|
353 | 293 | if (false === $return) { |
|
354 | 4 | if ('' !== $exceptionMessage) { |
|
355 | 4 | $previous = $this->getLastErrorMessage() ? new RuntimeException($this->getLastErrorMessage()) : null; |
|
356 | 4 | throw new QueryException($exceptionMessage, $query, 0, $previous); |
|
357 | } |
||
358 | return false; |
||
359 | } |
||
360 | |||
361 | 293 | $this->logger->info("-- AffectedRows: $return"); |
|
362 | 293 | return $return; |
|
363 | } |
||
364 | |||
365 | 4 | final public function query(string $query) |
|
366 | { |
||
367 | 4 | trigger_error(__METHOD__ . ' is deprecated, use queryResult instead', E_USER_DEPRECATED); |
|
368 | 4 | return $this->queryResult($query); |
|
369 | } |
||
370 | |||
371 | 70 | final public function queryOne(string $query, $default = false) |
|
372 | { |
||
373 | 70 | return current($this->queryRow($query) ?: [$default]); |
|
374 | } |
||
375 | |||
376 | 82 | final public function queryRow(string $query) |
|
377 | { |
||
378 | 82 | $result = $this->queryResult($query); |
|
379 | 82 | if (false === $result) { |
|
380 | 8 | return false; |
|
381 | } |
||
382 | |||
383 | 74 | $row = $result->fetchRow(); |
|
384 | 74 | if (false === $row) { |
|
385 | 8 | return false; |
|
386 | } |
||
387 | |||
388 | 66 | return $row; |
|
389 | } |
||
390 | |||
391 | 12 | final public function queryValues(string $query, array $overrideTypes = []) |
|
392 | { |
||
393 | 12 | $recordset = $this->queryRecordset($query, '', [], $overrideTypes); |
|
394 | 12 | if (false === $recordset) { |
|
395 | 4 | return false; |
|
396 | } |
||
397 | 8 | if ($recordset->eof()) { |
|
398 | 4 | return false; |
|
399 | } |
||
400 | 4 | return $recordset->values; |
|
401 | } |
||
402 | |||
403 | 24 | final public function queryArray(string $query) |
|
404 | { |
||
405 | 24 | $result = $this->queryResult($query); |
|
406 | 24 | if (false === $result) { |
|
407 | 8 | return false; |
|
408 | } |
||
409 | |||
410 | 16 | $return = []; |
|
411 | 16 | while (false !== $row = $result->fetchRow()) { |
|
412 | 12 | $return[] = $row; |
|
413 | } |
||
414 | 16 | return $return; |
|
415 | } |
||
416 | |||
417 | 12 | final public function queryArrayValues(string $query, array $overrideTypes = []) |
|
418 | { |
||
419 | 12 | $recordset = $this->queryRecordset($query, '', [], $overrideTypes); |
|
420 | 12 | if (false === $recordset) { |
|
421 | 4 | return false; |
|
422 | } |
||
423 | |||
424 | 8 | $return = []; |
|
425 | 8 | while (! $recordset->eof()) { |
|
426 | 4 | $return[] = $recordset->values; |
|
427 | 4 | $recordset->moveNext(); |
|
428 | } |
||
429 | 8 | return $return; |
|
430 | } |
||
431 | |||
432 | 16 | final public function queryArrayKey(string $query, string $keyField, string $keyPrefix = '') |
|
433 | { |
||
434 | 16 | $result = $this->queryResult($query); |
|
435 | 16 | if (false === $result) { |
|
436 | 4 | return false; |
|
437 | } |
||
438 | |||
439 | 12 | $return = []; |
|
440 | /** @noinspection PhpAssignmentInConditionInspection */ |
||
441 | 12 | while ($row = $result->fetchRow()) { |
|
442 | 8 | if (! array_key_exists($keyField, $row)) { |
|
443 | 4 | return false; |
|
444 | } |
||
445 | 4 | $return[$keyPrefix . $row[$keyField]] = $row; |
|
446 | } |
||
447 | 8 | return $return; |
|
448 | } |
||
449 | |||
450 | 12 | final public function queryPairs( |
|
463 | } |
||
464 | |||
465 | 28 | final public function queryArrayOne(string $query, string $field = '') |
|
490 | } |
||
491 | |||
492 | 12 | final public function queryOnString(string $query, string $default = '', string $separator = ', '): string |
|
493 | { |
||
494 | 12 | $array = $this->queryArrayOne($query); |
|
495 | 12 | if (false === $array) { |
|
496 | 4 | return $default; |
|
497 | } |
||
498 | 8 | return implode($separator, $array); |
|
499 | } |
||
500 | |||
501 | 27 | final public function queryRecordset( |
|
514 | } |
||
515 | |||
516 | 42 | final public function createRecordset( |
|
517 | string $query, |
||
518 | string $overrideEntity = '', |
||
519 | array $overrideKeys = [], |
||
520 | array $overrideTypes = [] |
||
521 | ): Recordset { |
||
522 | try { |
||
523 | 42 | $recordset = new Recordset($this); |
|
524 | 42 | $recordset->query($query, $overrideEntity, $overrideKeys, $overrideTypes); |
|
525 | 12 | } catch (Throwable $exception) { |
|
526 | 12 | throw new QueryException('Unable to create a valid Recordset', $query, 0, $exception); |
|
527 | } |
||
528 | 30 | return $recordset; |
|
529 | } |
||
530 | |||
531 | final public function queryPager( |
||
532 | string $querySelect, |
||
533 | string $queryCount = '', |
||
534 | int $page = 1, |
||
535 | int $recordsPerPage = 20 |
||
536 | ) { |
||
537 | try { |
||
538 | return $this->createPager($querySelect, $queryCount, $page, $recordsPerPage); |
||
539 | } catch (Throwable $exception) { |
||
540 | $this->logger->error("DBAL::queryPager failure running $querySelect"); |
||
541 | return false; |
||
542 | } |
||
543 | } |
||
544 | |||
545 | 4 | final public function createPager( |
|
568 | } |
||
569 | |||
570 | 33 | final public function getLastMessage(): string |
|
571 | { |
||
572 | 33 | if ($this->isConnected()) { |
|
573 | 33 | return $this->getLastErrorMessage(); |
|
574 | } |
||
575 | return ''; |
||
576 | } |
||
577 | |||
578 | final public function createQueryException(string $query, Throwable $previous = null): QueryException |
||
579 | { |
||
580 | return new QueryException($this->getLastMessage() ?: 'Database error', $query, 0, $previous); |
||
581 | } |
||
582 | |||
583 | 7 | final public function getLogger(): LoggerInterface |
|
586 | } |
||
587 | |||
588 | 388 | final public function setLogger(LoggerInterface $logger): void |
|
591 | } |
||
592 | } |
||
593 |