Total Complexity | 48 |
Total Lines | 688 |
Duplicated Lines | 0 % |
Changes | 0 |
Complex classes like AbstractConnection 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 AbstractConnection, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
45 | abstract class AbstractConnection implements ConnectionInterface |
||
46 | { |
||
47 | /** |
||
48 | * @var PDO |
||
49 | */ |
||
50 | protected $pdo; |
||
51 | |||
52 | /** |
||
53 | * @var ProfilerInterface |
||
54 | */ |
||
55 | protected $profiler; |
||
56 | |||
57 | /** |
||
58 | * Proxies to PDO methods created for specific drivers; in particular, |
||
59 | * `sqlite` and `pgsql`. |
||
60 | * |
||
61 | * @param string $name |
||
62 | * @param array $arguments |
||
63 | * |
||
64 | * @return mixed |
||
65 | * @throws BadMethodCallException |
||
66 | */ |
||
67 | public function __call($name, array $arguments) |
||
68 | { |
||
69 | $this->connect(); |
||
70 | |||
71 | if (!method_exists($this->pdo, $name)) { |
||
72 | $class = get_class($this); |
||
73 | $message = "Class '" . $class |
||
74 | . "' does not have a method '" . $name . "'"; |
||
75 | throw new BadMethodCallException($message); |
||
76 | } |
||
77 | |||
78 | return call_user_func_array([$this->pdo, $name], $arguments); |
||
79 | } |
||
80 | |||
81 | /** |
||
82 | * Begins a transaction. If the profiler is enabled, the operation will |
||
83 | * be recorded. |
||
84 | * |
||
85 | * @return bool |
||
86 | */ |
||
87 | public function beginTransaction(): bool |
||
88 | { |
||
89 | $this->connect(); |
||
90 | $this->profiler->start(__FUNCTION__); |
||
91 | $result = $this->pdo->beginTransaction(); |
||
92 | $this->profiler->finish(); |
||
93 | |||
94 | return $result; |
||
95 | } |
||
96 | |||
97 | /** |
||
98 | * Commits the existing transaction. If the profiler is enabled, the |
||
99 | * operation will be recorded. |
||
100 | * |
||
101 | * @return bool |
||
102 | */ |
||
103 | public function commit(): bool |
||
104 | { |
||
105 | $this->connect(); |
||
106 | $this->profiler->start(__FUNCTION__); |
||
107 | $result = $this->pdo->commit(); |
||
108 | $this->profiler->finish(); |
||
109 | |||
110 | return $result; |
||
111 | } |
||
112 | |||
113 | /** |
||
114 | * Connects to the database. |
||
115 | */ |
||
116 | abstract public function connect(): void; |
||
117 | |||
118 | /** |
||
119 | * Disconnects from the database. |
||
120 | */ |
||
121 | abstract public function disconnect(): void; |
||
122 | |||
123 | /** |
||
124 | * Gets the most recent error code. |
||
125 | * |
||
126 | * @return string|null |
||
127 | */ |
||
128 | public function errorCode(): ?string |
||
129 | { |
||
130 | $this->connect(); |
||
131 | |||
132 | return $this->pdo->errorCode(); |
||
133 | } |
||
134 | |||
135 | /** |
||
136 | * Gets the most recent error info. |
||
137 | * |
||
138 | * @return array |
||
139 | */ |
||
140 | public function errorInfo(): array |
||
141 | { |
||
142 | $this->connect(); |
||
143 | |||
144 | return $this->pdo->errorInfo(); |
||
145 | } |
||
146 | |||
147 | /** |
||
148 | * Executes an SQL statement and returns the number of affected rows. If |
||
149 | * the profiler is enabled, the operation will be recorded. |
||
150 | * |
||
151 | * @param string $statement |
||
152 | * |
||
153 | * @return int |
||
154 | */ |
||
155 | public function exec(string $statement): int |
||
163 | } |
||
164 | |||
165 | /** |
||
166 | * Performs a statement and returns the number of affected rows. |
||
167 | * |
||
168 | * @param string $statement |
||
169 | * @param array $values |
||
170 | * |
||
171 | * @return int |
||
172 | * @throws CannotBindValue |
||
173 | */ |
||
174 | public function fetchAffected(string $statement, array $values = []): int |
||
175 | { |
||
176 | $sth = $this->perform($statement, $values); |
||
177 | |||
178 | return $sth->rowCount(); |
||
179 | } |
||
180 | |||
181 | /** |
||
182 | * Fetches a sequential array of rows from the database; the rows are |
||
183 | * returned as associative arrays. |
||
184 | * |
||
185 | * @param string $statement |
||
186 | * @param array $values |
||
187 | * |
||
188 | * @return array |
||
189 | * @throws CannotBindValue |
||
190 | */ |
||
191 | public function fetchAll(string $statement, array $values = []): array |
||
192 | { |
||
193 | return $this->fetchData( |
||
194 | "fetchAll", |
||
195 | [PDO::FETCH_ASSOC], |
||
196 | $statement, |
||
197 | $values |
||
198 | ); |
||
199 | } |
||
200 | |||
201 | /** |
||
202 | * Fetches an associative array of rows from the database; the rows are |
||
203 | * returned as associative arrays, and the array of rows is keyed on the |
||
204 | * first column of each row. |
||
205 | * |
||
206 | * If multiple rows have the same first column value, the last row with |
||
207 | * that value will overwrite earlier rows. This method is more resource |
||
208 | * intensive and should be avoided if possible. |
||
209 | * |
||
210 | * @param string $statement |
||
211 | * @param array $values |
||
212 | * |
||
213 | * @return array |
||
214 | * @throws CannotBindValue |
||
215 | */ |
||
216 | public function fetchAssoc(string $statement, array $values = []): array |
||
217 | { |
||
218 | $sth = $this->perform($statement, $values); |
||
219 | $data = []; |
||
220 | while ($row = $sth->fetch(PDO::FETCH_ASSOC)) { |
||
221 | $data[current($row)] = $row; |
||
222 | } |
||
223 | |||
224 | return $data; |
||
225 | } |
||
226 | |||
227 | /** |
||
228 | * Fetches a column of rows as a sequential array (default first one). |
||
229 | * |
||
230 | * @param string $statement |
||
231 | * @param array $values |
||
232 | * @param int $column |
||
233 | * |
||
234 | * @return array |
||
235 | * @throws CannotBindValue |
||
236 | */ |
||
237 | public function fetchColumn( |
||
238 | string $statement, |
||
239 | array $values = [], |
||
240 | int $column = 0 |
||
241 | ): array { |
||
242 | return $this->fetchData( |
||
243 | "fetchAll", |
||
244 | [PDO::FETCH_COLUMN, $column], |
||
245 | $statement, |
||
246 | $values |
||
247 | ); |
||
248 | } |
||
249 | |||
250 | /** |
||
251 | * Fetches multiple from the database as an associative array. The first |
||
252 | * column will be the index key. The default flags are |
||
253 | * PDO::FETCH_ASSOC | PDO::FETCH_GROUP |
||
254 | * |
||
255 | * @param string $statement |
||
256 | * @param array $values |
||
257 | * @param int $flags |
||
258 | * |
||
259 | * @return array |
||
260 | * @throws CannotBindValue |
||
261 | */ |
||
262 | public function fetchGroup( |
||
263 | string $statement, |
||
264 | array $values = [], |
||
265 | int $flags = PDO::FETCH_ASSOC |
||
266 | ): array { |
||
267 | return $this->fetchData( |
||
268 | "fetchAll", |
||
269 | [PDO::FETCH_GROUP | $flags], |
||
270 | $statement, |
||
271 | $values |
||
272 | ); |
||
273 | } |
||
274 | |||
275 | /** |
||
276 | * Fetches one row from the database as an object where the column values |
||
277 | * are mapped to object properties. |
||
278 | * |
||
279 | * Since PDO injects property values before invoking the constructor, any |
||
280 | * initializations for defaults that you potentially have in your object's |
||
281 | * constructor, will override the values that have been injected by |
||
282 | * `fetchObject`. The default object returned is `\stdClass` |
||
283 | * |
||
284 | * @param string $statement |
||
285 | * @param array $values |
||
286 | * @param string $class |
||
287 | * @param array $arguments |
||
288 | * |
||
289 | * @return object |
||
290 | * @throws CannotBindValue |
||
291 | */ |
||
292 | public function fetchObject( |
||
293 | string $statement, |
||
294 | array $values = [], |
||
295 | string $class = 'stdClass', |
||
296 | array $arguments = [] |
||
297 | ): object { |
||
298 | $sth = $this->perform($statement, $values); |
||
299 | |||
300 | return $sth->fetchObject($class, $arguments); |
||
301 | } |
||
302 | |||
303 | /** |
||
304 | * Fetches a sequential array of rows from the database; the rows are |
||
305 | * returned as objects where the column values are mapped to object |
||
306 | * properties. |
||
307 | * |
||
308 | * Since PDO injects property values before invoking the constructor, any |
||
309 | * initializations for defaults that you potentially have in your object's |
||
310 | * constructor, will override the values that have been injected by |
||
311 | * `fetchObject`. The default object returned is `\stdClass` |
||
312 | * |
||
313 | * @param string $statement |
||
314 | * @param array $values |
||
315 | * @param string $class |
||
316 | * @param array $arguments |
||
317 | * |
||
318 | * @return array |
||
319 | * @throws CannotBindValue |
||
320 | */ |
||
321 | public function fetchObjects( |
||
322 | string $statement, |
||
323 | array $values = [], |
||
324 | string $class = 'stdClass', |
||
325 | array $arguments = [] |
||
326 | ): array { |
||
327 | $sth = $this->perform($statement, $values); |
||
328 | |||
329 | return $sth->fetchAll(PDO::FETCH_CLASS, $class, $arguments); |
||
330 | } |
||
331 | |||
332 | /** |
||
333 | * Fetches one row from the database as an associative array. |
||
334 | * |
||
335 | * @param string $statement |
||
336 | * @param array $values |
||
337 | * |
||
338 | * @return array |
||
339 | * @throws CannotBindValue |
||
340 | */ |
||
341 | public function fetchOne(string $statement, array $values = []): array |
||
342 | { |
||
343 | return $this->fetchData( |
||
344 | "fetch", |
||
345 | [PDO::FETCH_ASSOC], |
||
346 | $statement, |
||
347 | $values |
||
348 | ); |
||
349 | } |
||
350 | |||
351 | /** |
||
352 | * Fetches an associative array of rows as key-value pairs (first column is |
||
353 | * the key, second column is the value). |
||
354 | * |
||
355 | * @param string $statement |
||
356 | * @param array $values |
||
357 | * |
||
358 | * @return array |
||
359 | * @throws CannotBindValue |
||
360 | */ |
||
361 | public function fetchPairs(string $statement, array $values = []): array |
||
362 | { |
||
363 | return $this->fetchData( |
||
364 | "fetchAll", |
||
365 | [PDO::FETCH_KEY_PAIR], |
||
366 | $statement, |
||
367 | $values |
||
368 | ); |
||
369 | } |
||
370 | |||
371 | /** |
||
372 | * Fetches the very first value (i.e., first column of the first row). |
||
373 | * |
||
374 | * @param string $statement |
||
375 | * @param array $values |
||
376 | * |
||
377 | * @return mixed |
||
378 | * @throws CannotBindValue |
||
379 | */ |
||
380 | public function fetchValue(string $statement, array $values = []) |
||
381 | { |
||
382 | $sth = $this->perform($statement, $values); |
||
383 | |||
384 | return $sth->fetchColumn(0); |
||
385 | } |
||
386 | |||
387 | /** |
||
388 | * Return the inner PDO (if any) |
||
389 | * |
||
390 | * @return PDO |
||
391 | */ |
||
392 | public function getAdapter(): PDO |
||
393 | { |
||
394 | $this->connect(); |
||
395 | |||
396 | return $this->pdo; |
||
397 | } |
||
398 | |||
399 | /** |
||
400 | * Retrieve a database connection attribute |
||
401 | * |
||
402 | * @param int $attribute |
||
403 | * |
||
404 | * @return mixed |
||
405 | */ |
||
406 | public function getAttribute($attribute) |
||
407 | { |
||
408 | $this->connect(); |
||
409 | |||
410 | return $this->pdo->getAttribute($attribute); |
||
411 | } |
||
412 | |||
413 | /** |
||
414 | * Return an array of available PDO drivers (empty array if none available) |
||
415 | * |
||
416 | * @return array |
||
417 | */ |
||
418 | public static function getAvailableDrivers(): array |
||
419 | { |
||
420 | return PDO::getAvailableDrivers(); |
||
421 | } |
||
422 | |||
423 | /** |
||
424 | * Return the driver name |
||
425 | * |
||
426 | * @return string |
||
427 | */ |
||
428 | public function getDriverName(): string |
||
429 | { |
||
430 | $this->connect(); |
||
431 | |||
432 | return $this->pdo->getAttribute(PDO::ATTR_DRIVER_NAME); |
||
433 | } |
||
434 | |||
435 | /** |
||
436 | * Returns the Profiler instance. |
||
437 | * |
||
438 | * @return ProfilerInterface |
||
439 | */ |
||
440 | public function getProfiler(): ProfilerInterface |
||
441 | { |
||
442 | return $this->profiler; |
||
443 | } |
||
444 | |||
445 | /** |
||
446 | * Gets the quote parameters based on the driver |
||
447 | * |
||
448 | * @param string $driver |
||
449 | * |
||
450 | * @return array |
||
451 | */ |
||
452 | public function getQuoteNames($driver = ""): array |
||
453 | { |
||
454 | $driver = "" === $driver ? $this->getDriverName() : $driver; |
||
455 | switch ($driver) { |
||
456 | case 'mysql': |
||
457 | return [ |
||
458 | "prefix" => '`', |
||
459 | "suffix" => '`', |
||
460 | "find" => '`', |
||
461 | "replace" => '``', |
||
462 | ]; |
||
463 | |||
464 | case 'sqlsrv': |
||
465 | return [ |
||
466 | "prefix" => '[', |
||
467 | "suffix" => ']', |
||
468 | "find" => ']', |
||
469 | "replace" => '][', |
||
470 | ]; |
||
471 | |||
472 | default: |
||
473 | return [ |
||
474 | "prefix" => '"', |
||
475 | "suffix" => '"', |
||
476 | "find" => '"', |
||
477 | "replace" => '""', |
||
478 | ]; |
||
479 | } |
||
480 | } |
||
481 | |||
482 | /** |
||
483 | * Is a transaction currently active? If the profiler is enabled, the |
||
484 | * operation will be recorded. If the profiler is enabled, the operation |
||
485 | * will be recorded. |
||
486 | * |
||
487 | * @return bool |
||
488 | */ |
||
489 | public function inTransaction(): bool |
||
490 | { |
||
491 | $this->connect(); |
||
492 | $this->profiler->start(__FUNCTION__); |
||
493 | $result = $this->pdo->inTransaction(); |
||
494 | $this->profiler->finish(); |
||
495 | return $result; |
||
496 | } |
||
497 | |||
498 | /** |
||
499 | * Is the PDO connection active? |
||
500 | * |
||
501 | * @return bool |
||
502 | */ |
||
503 | public function isConnected(): bool |
||
504 | { |
||
505 | return (bool) $this->pdo; |
||
506 | } |
||
507 | |||
508 | /** |
||
509 | * Returns the last inserted autoincrement sequence value. If the profiler |
||
510 | * is enabled, the operation will be recorded. |
||
511 | * |
||
512 | * @param string $name |
||
513 | * |
||
514 | * @return string |
||
515 | */ |
||
516 | public function lastInsertId(string $name = null): string |
||
517 | { |
||
518 | $this->connect(); |
||
519 | |||
520 | $this->profiler->start(__FUNCTION__); |
||
521 | $result = $this->pdo->lastInsertId($name); |
||
522 | $this->profiler->finish(); |
||
523 | |||
524 | return $result; |
||
525 | } |
||
526 | |||
527 | /** |
||
528 | * Performs a query with bound values and returns the resulting |
||
529 | * PDOStatement; array values will be passed through `quote()` and their |
||
530 | * respective placeholders will be replaced in the query string. If the |
||
531 | * profiler is enabled, the operation will be recorded. |
||
532 | * |
||
533 | * @param string $statement |
||
534 | * @param array $values |
||
535 | * |
||
536 | * @return PDOStatement |
||
537 | */ |
||
538 | public function perform(string $statement, array $values = []): PDOStatement |
||
539 | { |
||
540 | $this->connect(); |
||
541 | |||
542 | $this->profiler->start(__FUNCTION__); |
||
543 | |||
544 | $sth = $this->prepare($statement); |
||
545 | foreach ($values as $name => $value) { |
||
546 | $this->performBind($sth, $name, $value); |
||
547 | } |
||
548 | $sth->execute(); |
||
549 | |||
550 | $this->profiler->finish($statement, $values); |
||
551 | |||
552 | return $sth; |
||
553 | } |
||
554 | |||
555 | /** |
||
556 | * Prepares an SQL statement for execution. |
||
557 | * |
||
558 | * @param string $statement |
||
559 | * @param array $options |
||
560 | * |
||
561 | * @return PDOStatement|false |
||
562 | */ |
||
563 | public function prepare(string $statement, array $options = []) |
||
564 | { |
||
565 | $this->connect(); |
||
566 | |||
567 | $this->profiler->start(__FUNCTION__); |
||
568 | $sth = $this->pdo->prepare($statement, $options); |
||
569 | $this->profiler->finish($sth->queryString); |
||
570 | |||
571 | return $sth; |
||
|
|||
572 | } |
||
573 | |||
574 | /** |
||
575 | * Queries the database and returns a PDOStatement. If the profiler is |
||
576 | * enabled, the operation will be recorded. |
||
577 | * |
||
578 | * @param string $statement |
||
579 | * @param mixed ...$fetch |
||
580 | * |
||
581 | * @return PDOStatement|false |
||
582 | */ |
||
583 | public function query(string $statement) |
||
584 | { |
||
585 | $this->connect(); |
||
586 | |||
587 | $this->profiler->start(__FUNCTION__); |
||
588 | $sth = call_user_func_array( |
||
589 | [ |
||
590 | $this->pdo, |
||
591 | "query", |
||
592 | ], |
||
593 | func_get_args() |
||
594 | ); |
||
595 | $this->profiler->finish($sth->queryString); |
||
596 | |||
597 | return $sth; |
||
598 | } |
||
599 | |||
600 | /** |
||
601 | * Quotes a value for use in an SQL statement. This differs from |
||
602 | * `PDO::quote()` in that it will convert an array into a string of |
||
603 | * comma-separated quoted values. The default type is `PDO::PARAM_STR` |
||
604 | * |
||
605 | * @param mixed $value |
||
606 | * @param int $type |
||
607 | * |
||
608 | * @return string The quoted value. |
||
609 | */ |
||
610 | public function quote($value, int $type = PDO::PARAM_STR): string |
||
611 | { |
||
612 | $this->connect(); |
||
613 | |||
614 | $quotes = $this->getQuoteNames(); |
||
615 | if (!is_array($value)) { |
||
616 | $value = (string) $value; |
||
617 | |||
618 | return $quotes["prefix"] . $value . $quotes["suffix"]; |
||
619 | } |
||
620 | |||
621 | // quote array values, not keys, then combine with commas |
||
622 | foreach ($value as $key => $element) { |
||
623 | $element = (string) $element; |
||
624 | $value[$key] = $quotes["prefix"] . $element . $quotes["suffix"]; |
||
625 | } |
||
626 | |||
627 | return implode(', ', $value); |
||
628 | } |
||
629 | |||
630 | /** |
||
631 | * Rolls back the current transaction, and restores autocommit mode. If the |
||
632 | * profiler is enabled, the operation will be recorded. |
||
633 | * |
||
634 | * @return bool |
||
635 | */ |
||
636 | public function rollBack(): bool |
||
645 | } |
||
646 | |||
647 | /** |
||
648 | * Set a database connection attribute |
||
649 | * |
||
650 | * @param int $attribute |
||
651 | * @param mixed $value |
||
652 | * |
||
653 | * @return bool |
||
654 | */ |
||
655 | public function setAttribute(int $attribute, $value): bool |
||
656 | { |
||
657 | $this->connect(); |
||
658 | |||
659 | return $this->pdo->setAttribute($attribute, $value); |
||
660 | } |
||
661 | |||
662 | /** |
||
663 | * Sets the Profiler instance. |
||
664 | * |
||
665 | * @param ProfilerInterface $profiler |
||
666 | */ |
||
667 | public function setProfiler(ProfilerInterface $profiler) |
||
670 | } |
||
671 | |||
672 | /** |
||
673 | * Bind a value using the proper PDO::PARAM_* type. |
||
674 | * |
||
675 | * @param PDOStatement $statement |
||
676 | * @param mixed $name |
||
677 | * @param mixed $arguments |
||
678 | */ |
||
679 | protected function performBind(PDOStatement $statement, $name, $arguments): void |
||
680 | { |
||
681 | if (is_int($name)) { |
||
682 | $name++; |
||
683 | } |
||
684 | |||
685 | if (is_array($arguments)) { |
||
686 | $type = $arguments[1] ?? PDO::PARAM_STR; |
||
687 | if ($type === PDO::PARAM_BOOL && is_bool($arguments[0])) { |
||
688 | $arguments[0] = $arguments[0] ? '1' : '0'; |
||
689 | } |
||
690 | |||
691 | $parameters = array_merge([$name], $arguments); |
||
692 | } else { |
||
693 | $parameters = [$name, $arguments]; |
||
694 | } |
||
695 | |||
696 | call_user_func_array( |
||
697 | [ |
||
698 | $statement, |
||
699 | "bindValue", |
||
700 | ], |
||
701 | $parameters |
||
702 | ); |
||
703 | } |
||
704 | |||
705 | /** |
||
706 | * Helper method to get data from PDO based on the method passed |
||
707 | * |
||
708 | * @param string $method |
||
709 | * @param array $arguments |
||
710 | * @param string $statement |
||
711 | * @param array $values |
||
712 | * |
||
713 | * @return array |
||
714 | */ |
||
715 | protected function fetchData( |
||
733 | } |
||
734 | } |
||
735 |