Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
73.97% covered (success)
73.97%
162 / 219
60.53% covered (warning)
60.53%
23 / 38
CRAP
0.00% covered (danger)
0.00%
0 / 1
Pdo
73.97% covered (success)
73.97%
162 / 219
60.53% covered (warning)
60.53%
23 / 38
333.17
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 connect
76.92% covered (success)
76.92%
10 / 13
0.00% covered (danger)
0.00%
0 / 1
10.00
 setOptions
85.71% covered (success)
85.71%
12 / 14
0.00% covered (danger)
0.00%
0 / 1
6.10
 hasOptions
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
6
 dbFileExists
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 getDsn
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getType
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 beginTransaction
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 commit
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
1
 rollback
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 inTransaction
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isSuccess
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
3
 setAttribute
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getAttribute
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 query
66.67% covered (warning)
66.67%
14 / 21
0.00% covered (danger)
0.00%
0 / 1
8.81
 prepare
92.31% covered (success)
92.31%
12 / 13
0.00% covered (danger)
0.00%
0 / 1
7.02
 bindParams
68.18% covered (warning)
68.18%
15 / 22
0.00% covered (danger)
0.00%
0 / 1
13.22
 bindParam
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 bindValue
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 bindColumn
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 execute
46.67% covered (warning)
46.67%
7 / 15
0.00% covered (danger)
0.00%
0 / 1
11.46
 fetch
60.00% covered (warning)
60.00%
3 / 5
0.00% covered (danger)
0.00%
0 / 1
5.02
 fetchAll
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 escape
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getLastId
75.00% covered (success)
75.00%
9 / 12
0.00% covered (danger)
0.00%
0 / 1
5.39
 getNumberOfRows
85.71% covered (success)
85.71%
6 / 7
0.00% covered (danger)
0.00%
0 / 1
3.03
 getNumberOfAffectedRows
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getVersion
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getTables
94.12% covered (success)
94.12%
16 / 17
0.00% covered (danger)
0.00%
0 / 1
7.01
 getErrorMessage
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
20
 buildError
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
12
 getNumberOfFields
71.43% covered (success)
71.43%
5 / 7
0.00% covered (danger)
0.00%
0 / 1
3.21
 closeCursor
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getCountOfFields
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 fetchColumn
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getCountOfRows
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 debugDumpParams
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 exec
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
1<?php
2/**
3 * Pop PHP Framework (http://www.popphp.org/)
4 *
5 * @link       https://github.com/popphp/popphp-framework
6 * @author     Nick Sagona, III <dev@nolainteractive.com>
7 * @copyright  Copyright (c) 2009-2024 NOLA Interactive, LLC. (http://www.nolainteractive.com)
8 * @license    http://www.popphp.org/license     New BSD License
9 */
10
11/**
12 * @namespace
13 */
14namespace Pop\Db\Adapter;
15
16/**
17 * PDO database adapter class
18 *
19 * @category   Pop
20 * @package    Pop\Db
21 * @author     Nick Sagona, III <dev@nolainteractive.com>
22 * @copyright  Copyright (c) 2009-2024 NOLA Interactive, LLC. (http://www.nolainteractive.com)
23 * @license    http://www.popphp.org/license     New BSD License
24 * @version    6.5.0
25 */
26class Pdo extends AbstractAdapter
27{
28
29    /**
30     * PDO DSN
31     * @var ?string
32     */
33    protected ?string $dsn = null;
34
35    /**
36     * PDO type
37     * @var ?string
38     */
39    protected ?string $type = null;
40
41    /**
42     * Statement placeholder
43     * @var ?string
44     */
45    protected ?string $placeholder = null;
46
47    /**
48     * Statement result
49     * @var bool
50     */
51    protected bool $statementResult = false;
52
53    /**
54     * Constructor
55     *
56     * Instantiate the database connection object using PDO
57     *
58     * @param  array $options
59     */
60    public function __construct(array $options = [])
61    {
62        if (!empty($options)) {
63            $this->connect($options);
64        }
65    }
66
67    /**
68     * Connect to the database
69     *
70     * @param  array $options
71     * @return Pdo
72     */
73    public function connect(array $options = []): Pdo
74    {
75        if (!empty($options)) {
76            $this->setOptions($options);
77        } else if (!$this->hasOptions()) {
78            $this->throwError('Error: The proper database credentials were not passed.');
79        }
80
81        try {
82            if ($this->type == 'sqlite') {
83                $this->connection = (isset($this->options['options']) && is_array($this->options['options'])) ?
84                    new \PDO($this->dsn, null, null, $this->options['options']) : new \PDO($this->dsn);
85            } else {
86                $this->connection = (isset($this->options['options']) && is_array($this->options['options'])) ?
87                    new \PDO($this->dsn, $this->options['username'], $this->options['password'], $this->options['options']) :
88                    new \PDO($this->dsn, $this->options['username'], $this->options['password']);
89            }
90        } catch (\PDOException $e) {
91            $this->throwError('PDO Connection Error: ' . $e->getMessage() . ' (#' . $e->getCode() . ')');
92        }
93
94        return $this;
95    }
96
97    /**
98     * Set database connection options
99     *
100     * @param  array $options
101     * @return Pdo
102     */
103    public function setOptions(array $options): Pdo
104    {
105        if (!isset($options['host'])) {
106            $options['host'] = 'localhost';
107        }
108
109        $this->options = $options;
110
111        if (!$this->hasOptions()) {
112            $this->throwError('Error: The proper database credentials were not passed.');
113        }
114
115        $this->type = strtolower($this->options['type']);
116
117        if ($this->type == 'sqlite') {
118            if (!$this->dbFileExists()) {
119                $this->throwError("Error: The database file '" . $this->options['database'] . "'does not exists.");
120            }
121            $this->dsn = $this->type . ':' . $this->options['database'];
122        } else {
123            $this->dsn = ($this->type == 'sqlsrv') ?
124                $this->type . ':Server=' . $this->options['host'] . ';Database=' . $this->options['database'] :
125                $this->type . ':host=' . $this->options['host'] . ';dbname=' . $this->options['database'];
126        }
127
128        return $this;
129    }
130
131    /**
132     * Has database connection options
133     *
134     * @return bool
135     */
136    public function hasOptions(): bool
137    {
138        if (!isset($this->options['type'])) {
139            return false;
140        } else {
141            return (strtolower($this->options['type']) == 'sqlite') ?
142                (isset($this->options['database'])) :
143                (isset($this->options['database']) && isset($this->options['host']) &&
144                    isset($this->options['username']) && isset($this->options['password']));
145        }
146    }
147
148    /**
149     * Does the database file exist
150     *
151     * @return bool
152     */
153    public function dbFileExists(): bool
154    {
155        return (isset($this->options['database']) && file_exists($this->options['database']));
156    }
157
158    /**
159     * Return the DSN
160     *
161     * @return ?string
162     */
163    public function getDsn(): ?string
164    {
165        return $this->dsn;
166    }
167
168    /**
169     * Return the type
170     *
171     * @return ?string
172     */
173    public function getType(): ?string
174    {
175        return $this->type;
176    }
177
178    /**
179     * Begin a transaction
180     *
181     * @return Pdo
182     */
183    public function beginTransaction(): Pdo
184    {
185        $this->getTransactionManager()->enter(
186            beginFunc: function () { $this->connection->beginTransaction(); },
187            savepointFunc: function (string $sp) { $this->query('SAVEPOINT ' . $sp); },
188        );
189
190        return $this;
191    }
192
193    /**
194     * Commit a transaction
195     *
196     * @return Pdo
197     */
198    public function commit(): Pdo
199    {
200        $this->getTransactionManager()->leave(true,
201            commitFunc: function () { $this->connection->commit(); },
202            rollbackFunc: function () { $this->connection->rollBack(); },
203            savepointReleaseFunc: function (string $sp) { $this->query('RELEASE SAVEPOINT ' . $sp); },
204        );
205        return $this;
206    }
207
208    /**
209     * Rollback a transaction
210     *
211     * @return Pdo
212     */
213    public function rollback(): Pdo
214    {
215        $this->getTransactionManager()->leave(false,
216            rollbackFunc: function () { $this->connection->rollBack(); },
217            savepointRollbackFunc: function (string $sp) { $this->query('ROLLBACK TO SAVEPOINT ' . $sp); },
218        );
219
220        return $this;
221    }
222
223    /**
224     * Method checks, whether the transaction is initiated.
225     *
226     * @return bool
227     */
228    public function inTransaction(): bool
229    {
230        return $this->connection->inTransaction();
231    }
232
233    /**
234     * Check if transaction is success
235     *
236     * @return bool
237     */
238    public function isSuccess(): bool
239    {
240        return ((($this->result) || ($this->statementResult)) && (!$this->hasError()));
241    }
242
243    /**
244     * Method sets the value of the request attribute PDO.
245     *
246     * @param  int    $attribute A request attribute
247     * @param  mixed  $value     The value of the attribute request
248     * @return bool
249     */
250    public function setAttribute(int $attribute, mixed $value): bool
251    {
252        return $this->connection->setAttribute($attribute, $value);
253    }
254
255    /**
256     * The method of obtaining the value of the request attribute PDO.
257     *
258     * @param  int $attribute A request attribute
259     * @return string
260     */
261    public function getAttribute(int $attribute): string
262    {
263        return $this->connection->getAttribute($attribute);
264    }
265
266    /**
267     * Execute a SQL query directly
268     *
269     * @param  mixed $sql
270     * @return Pdo
271     */
272    public function query(mixed $sql): Pdo
273    {
274        if ($sql instanceof \Pop\Db\Sql\AbstractSql) {
275            $sql = (string)$sql;
276        }
277
278        $this->statement       = null;
279        $this->statementResult = false;
280
281        $sth = $this->connection->prepare($sql);
282
283        if (!($sth->execute())) {
284            if ($this->profiler !== null) {
285                $this->profiler->addStep();
286                $this->profiler->current->setQuery($sql);
287                $this->profiler->current->addError($this->getErrorMessage($sth->errorInfo()), $sth->errorCode());
288            }
289            $this->buildError($sth->errorCode(), $sth->errorInfo())
290                 ->throwError();
291        } else {
292            if ($this->profiler !== null) {
293                $this->profiler->addStep();
294                $this->profiler->current->setQuery($sql);
295            }
296            $this->result = $sth;
297        }
298
299        if ($this->profiler !== null) {
300            $this->profiler->current->finish();
301            if ($this->profiler->hasDebugger()) {
302                $this->profiler->debugger()->save();
303            }
304        }
305
306        return $this;
307    }
308
309    /**
310     * Prepare a SQL query
311     *
312     * @param  mixed  $sql
313     * @param  ?array $attribs
314     * @return Pdo
315     */
316    public function prepare(mixed $sql, ?array $attribs = null): Pdo
317    {
318        if ($sql instanceof \Pop\Db\Sql\AbstractSql) {
319            $sql = (string)$sql;
320        }
321
322        if (str_contains($sql, '?')) {
323            $this->placeholder = '?';
324        } else if (str_contains($sql, ':')) {
325            $this->placeholder = ':';
326        }
327
328        if ($this->profiler !== null) {
329            $this->profiler->addStep();
330            $this->profiler->current->setQuery($sql);
331        }
332
333        if (($attribs !== null) && is_array($attribs)) {
334            $this->statement = $this->connection->prepare($sql, $attribs);
335        } else {
336            $this->statement = $this->connection->prepare($sql);
337        }
338
339        return $this;
340    }
341
342    /**
343     * Bind parameters to a prepared SQL query
344     *
345     * @param  array $params
346     * @return Pdo
347     */
348    public function bindParams(array $params): Pdo
349    {
350        if ($this->profiler !== null) {
351            $this->profiler->current->addParams($params);
352        }
353
354        if ($this->placeholder == '?') {
355            $i = 1;
356            foreach ($params as $dbColumnName => $dbColumnValue) {
357                if (is_array($dbColumnValue)) {
358                    foreach ($dbColumnValue as $k => $dbColumnVal) {
359                        ${$dbColumnName . ($k + 1)} = $dbColumnVal;
360                        $this->statement->bindParam($i, ${$dbColumnName . ($k + 1)});
361                        $i++;
362
363                    }
364                } else {
365                    ${$dbColumnName} = $dbColumnValue;
366                    $this->statement->bindParam($i, ${$dbColumnName});
367                    $i++;
368                }
369            }
370        } else if ($this->placeholder == ':') {
371            foreach ($params as $dbColumnName => $dbColumnValue) {
372                if (is_array($dbColumnValue)) {
373                    foreach ($dbColumnValue as $k => $dbColumnVal) {
374                        ${$dbColumnName} = $dbColumnVal;
375                        $this->statement->bindParam(':' . $dbColumnName . ($k + 1), ${$dbColumnName});
376                    }
377                } else {
378                    ${$dbColumnName} = $dbColumnValue;
379                    $this->statement->bindParam(':' . $dbColumnName, ${$dbColumnName});
380                }
381            }
382        }
383
384        return $this;
385    }
386
387    /**
388     * Bind a parameter for a prepared SQL query
389     *
390     * @param  mixed $param
391     * @param  mixed $value
392     * @param  int   $dataType
393     * @param  ?int  $length
394     * @param  mixed $options
395     * @return Pdo
396     */
397    public function bindParam(mixed $param, mixed &$value, int $dataType = \PDO::PARAM_STR, ?int $length = null, mixed $options = null): Pdo
398    {
399        if ($this->profiler !== null) {
400            $this->profiler->current->addParam($param, $value);
401        }
402        $this->statement->bindParam($param, $value, $dataType, (int)$length, $options);
403        return $this;
404    }
405
406    /**
407     * Bind a value for a prepared SQL query
408     *
409     * @param  mixed $param
410     * @param  mixed $value
411     * @param  int   $dataType
412     * @return Pdo
413     */
414    public function bindValue(mixed $param, mixed $value, int $dataType = \PDO::PARAM_STR): Pdo
415    {
416        if ($this->profiler !== null) {
417            $this->profiler->current->addParam($param, $value);
418        }
419        $this->statement->bindValue($param, $value, $dataType);
420        return $this;
421    }
422
423    /**
424     *  Bind a column to a PHP variable.
425     *
426     * @param  mixed $column    Number of the column (1-indexed) or name of the column in the result set.
427     * @param  mixed $param     Name of the PHP variable to which the column will be bound.
428     * @param  int   $dataType  Data type of the parameter, specified by the PDO::PARAM_* constants.
429     * @return Pdo
430     */
431    public function bindColumn(mixed $column, mixed $param, int $dataType = \PDO::PARAM_STR): Pdo
432    {
433        $this->statement->bindColumn($column, $param, $dataType);
434        return $this;
435    }
436
437    /**
438     * Execute a prepared SQL query
439     *
440     * @return Pdo
441     */
442    public function execute(): Pdo
443    {
444        if ($this->statement === null) {
445            $this->throwError('Error: The database statement resource is not currently set.');
446        }
447
448        $this->statementResult = $this->statement->execute();
449
450        if ($this->statement->errorCode() != 0) {
451            if ($this->profiler !== null) {
452                $this->profiler->current->addError(
453                    $this->getErrorMessage($this->statement->errorInfo()), $this->statement->errorCode()
454                );
455            }
456            $this->buildError($this->statement->errorCode(), $this->statement->errorInfo())
457                 ->throwError();
458        }
459
460        if ($this->profiler !== null) {
461            $this->profiler->current->finish();
462            if ($this->profiler->hasDebugger()) {
463                $this->profiler->debugger()->save();
464            }
465        }
466
467        return $this;
468    }
469
470    /**
471     * Fetch and return a row from the result
472     *
473     * @param  int $dataType  Data type of the parameter, specified by the PDO::PARAM_* constants.
474     * @return mixed
475     */
476    public function fetch(int $dataType = \PDO::FETCH_ASSOC): mixed
477    {
478        if (($this->statement !== null) && ($this->statementResult !== false)) {
479            return $this->statement->fetch($dataType);
480        } else {
481            if ($this->result === null) {
482                $this->throwError('Error: The database statement resource is not currently set.');
483            }
484            return $this->result->fetch($dataType);
485        }
486    }
487
488    /**
489     * Fetch and return all rows from the result
490     *
491     * @param  int $dataType  Data type of the parameter, specified by the PDO::PARAM_* constants.
492     * @return array
493     */
494    public function fetchAll(int $dataType = \PDO::FETCH_ASSOC): array
495    {
496        return $this->statement->fetchAll($dataType);
497    }
498
499    /**
500     * Escape the value
501     *
502     * @param  ?string $value
503     * @return string
504     */
505    public function escape(?string $value = null): string
506    {
507        return substr($this->connection->quote($value), 1, -1);
508    }
509
510    /**
511     * Return the last ID of the last query
512     *
513     * @return int
514     */
515    public function getLastId(): int
516    {
517        $id = 0;
518
519        // If pgsql
520        if ($this->type == 'pgsql') {
521            $this->query("SELECT lastval();");
522            if ($this->result !== null) {
523                $insertRow = $this->result->fetch();
524                $id        = $insertRow[0];
525            }
526        // Else, if sqlsrv
527        } else if ($this->type == 'sqlsrv') {
528            $this->query('SELECT SCOPE_IDENTITY() as Current_Identity');
529            $row = $this->fetch();
530            $id  = (isset($row['Current_Identity'])) ? $row['Current_Identity'] : 0;
531        // Else, just get the last insert ID
532        } else {
533            $id = $this->connection->lastInsertId();
534        }
535
536        return $id;
537    }
538
539    /**
540     * Return the number of rows from the last query
541     *
542     * @throws Exception
543     * @return int
544     */
545    public function getNumberOfRows(): int
546    {
547        $count = 0;
548
549        if ($this->result !== null) {
550            $count = $this->result->rowCount();
551        } else if ($this->statement !== null) {
552            $count = $this->statement->rowCount();
553        } else {
554            $this->throwError('Error: The database statement resource is not currently set.');
555        }
556
557        return $count;
558    }
559
560    /**
561     * Return the number of affected rows from the last query
562     *
563     * @throws Exception
564     * @return int
565     */
566    public function getNumberOfAffectedRows(): int
567    {
568        return $this->getNumberOfRows();
569    }
570
571    /**
572     * Return the database version
573     *
574     * @return string
575     */
576    public function getVersion(): string
577    {
578        return 'PDO ' . substr($this->dsn, 0, strpos($this->dsn, ':')) . ' ' .
579        $this->connection->getAttribute(\PDO::ATTR_SERVER_VERSION);
580    }
581
582    /**
583     * Return the tables in the database
584     *
585     * @return array
586     */
587    public function getTables(): array
588    {
589        $tables = [];
590
591        if (stripos($this->dsn, 'sqlite') !== false) {
592            $sql = "SELECT name FROM sqlite_master WHERE type IN ('table', 'view') AND name NOT LIKE 'sqlite_%' " .
593                "UNION ALL SELECT name FROM sqlite_temp_master WHERE type IN ('table', 'view') ORDER BY 1";
594
595            $this->query($sql);
596            while (($row = $this->fetch())) {
597                $tables[] = $row['name'];
598            }
599        } else {
600            if (stripos($this->dsn, 'pgsql') !== false) {
601                $sql = "SELECT table_name FROM information_schema.tables WHERE table_schema = 'public'";
602            } else if (stripos($this->dsn, 'sqlsrv') !== false) {
603                $sql = "SELECT name FROM " . $this->database . ".sysobjects WHERE xtype = 'U'";
604            } else {
605                $sql = 'SHOW TABLES';
606            }
607            $this->query($sql);
608            while (($row = $this->fetch())) {
609                foreach($row as $value) {
610                    $tables[] = $value;
611                }
612            }
613        }
614
615        return $tables;
616    }
617
618    /**
619     * Get the error message
620     *
621     * @param  mixed $errorInfo
622     * @return ?string
623     */
624    protected function getErrorMessage(mixed $errorInfo): ?string
625    {
626        if (is_array($errorInfo)) {
627            $errorMessage = null;
628            if (isset($errorInfo[1])) {
629                $errorMessage .= $errorInfo[1];
630            }
631            if (isset($errorInfo[2])) {
632                $errorMessage .= ' : ' . $errorInfo[2];
633            }
634        } else {
635            $errorMessage = $errorInfo;
636        }
637
638        return $errorMessage;
639    }
640
641    /**
642     * Build the error
643     *
644     * @param  ?string $code
645     * @param  ?array  $info
646     * @return Pdo
647     */
648    protected function buildError(?string $code = null, ?array $info = null): Pdo
649    {
650        if (($code === null) && ($info === null)) {
651            $errorCode = $this->connection->errorCode();
652            $errorInfo = $this->connection->errorInfo();
653        } else {
654            $errorCode = $code;
655            $errorInfo = $info;
656        }
657
658        $this->setError('Error: ' . $errorCode . ' => ' . $this->getErrorMessage($errorInfo));
659        return $this;
660    }
661
662    /**
663     * Return the number of fields in the result.
664     *
665     * @throws Exception
666     * @return int
667     */
668    public function getNumberOfFields(): int
669    {
670        $count = 0;
671
672        if ($this->result !== null) {
673            $count = $this->result->columnCount();
674        } else if ($this->statement !== null) {
675            $count = $this->statement->columnCount();
676        } else {
677            $this->throwError('Error: The database statement resource is not currently set.');
678        }
679
680        return $count;
681    }
682
683    /**
684     * Method closes the cursor, translating the request in the ready state.
685     *
686     * @return bool
687     */
688    public function closeCursor(): bool
689    {
690        return $this->statement->closeCursor();
691    }
692
693    /**
694     * The method returns the number of columns in the result set.
695     *
696     * @return int
697     */
698    public function getCountOfFields(): int
699    {
700        return $this->statement->columnCount();
701    }
702
703    /**
704     * The method receives data of one column from the next row of the result set.
705     *
706     * @param  int $num The number of the table column
707     * @return mixed
708     */
709    public function fetchColumn(int $num = null): mixed
710    {
711        return $this->statement->fetchColumn($num);
712    }
713
714    /**
715     * The method returns the number of rows modified by the last SQL query.
716     *
717     * @return int
718     */
719    public function getCountOfRows(): int
720    {
721        return $this->statement->rowCount();
722    }
723
724    /**
725     * The method displays information about the prepared SQL command for debugging purposes.
726     *
727     * @param  bool $debug
728     * @return string
729     */
730    public function debugDumpParams(bool $debug = false): bool|string
731    {
732        ob_start();
733        $this->statement->debugDumpParams();
734        $result = ob_get_contents();
735        ob_end_clean();
736        return (!$debug) ?: $result;
737    }
738
739    /**
740     * The method runs an SQL query for execution and returns the number of rows affected during execution.
741     *
742     * @param  mixed $sql The SQL statement to be prepared and run
743     * @return Pdo
744     */
745    public function exec(mixed $sql): Pdo
746    {
747        if (!($this->connection->exec($sql))) {
748            $this->throwError('Error: The database statement resource is not currently set.');
749        }
750
751        return $this;
752    }
753
754}