Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
96.67% covered (success)
96.67%
87 / 90
96.67% covered (success)
96.67%
29 / 30
CRAP
0.00% covered (danger)
0.00%
0 / 1
AbstractAdapter
96.67% covered (success)
96.67%
87 / 90
96.67% covered (success)
96.67%
29 / 30
59
0.00% covered (danger)
0.00%
0 / 1
 __construct
n/a
0 / 0
n/a
0 / 0
0
 connect
n/a
0 / 0
n/a
0 / 0
0
 setOptions
n/a
0 / 0
n/a
0 / 0
0
 getOptions
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasOptions
n/a
0 / 0
n/a
0 / 0
0
 beginTransaction
n/a
0 / 0
n/a
0 / 0
0
 commit
n/a
0 / 0
n/a
0 / 0
0
 rollback
n/a
0 / 0
n/a
0 / 0
0
 getTransactionManager
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isTransaction
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 getTransactionDepth
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 transaction
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
3
 isSuccess
n/a
0 / 0
n/a
0 / 0
0
 select
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
5
 insert
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
5
 update
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
5
 delete
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
5
 executeSql
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
2
 query
n/a
0 / 0
n/a
0 / 0
0
 prepare
n/a
0 / 0
n/a
0 / 0
0
 bindParams
n/a
0 / 0
n/a
0 / 0
0
 execute
n/a
0 / 0
n/a
0 / 0
0
 fetch
n/a
0 / 0
n/a
0 / 0
0
 fetchAll
n/a
0 / 0
n/a
0 / 0
0
 createSql
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 createSchema
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isConnected
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getConnection
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasStatement
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getStatement
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasResult
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getResult
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 listen
83.33% covered (success)
83.33%
15 / 18
0.00% covered (danger)
0.00%
0 / 1
7.23
 getListener
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setProfiler
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getProfiler
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 clearProfiler
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 hasError
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setError
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getError
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 throwError
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
3
 clearError
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 disconnect
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
1
 escape
n/a
0 / 0
n/a
0 / 0
0
 getLastId
n/a
0 / 0
n/a
0 / 0
0
 getNumberOfRows
n/a
0 / 0
n/a
0 / 0
0
 getNumberOfAffectedRows
n/a
0 / 0
n/a
0 / 0
0
 getVersion
n/a
0 / 0
n/a
0 / 0
0
 getTables
n/a
0 / 0
n/a
0 / 0
0
 hasTable
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
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
16use Pop\Db\Sql;
17use Pop\Db\Adapter\Transaction;
18use Pop\Utils\CallableObject;
19
20/**
21 * Db abstract adapter class
22 *
23 * @category   Pop
24 * @package    Pop\Db
25 * @author     Nick Sagona, III <dev@nolainteractive.com>
26 * @copyright  Copyright (c) 2009-2024 NOLA Interactive, LLC. (http://www.nolainteractive.com)
27 * @license    http://www.popphp.org/license     New BSD License
28 * @version    6.5.0
29 */
30abstract class AbstractAdapter implements AdapterInterface
31{
32
33    /**
34     * Database connection options
35     * @var array
36     */
37    protected array $options = [];
38
39    /**
40     * Database connection object/resource
41     * @var mixed
42     */
43    protected mixed $connection = null;
44
45    /**
46     * Statement object/resource
47     * @var mixed
48     */
49    protected mixed $statement = null;
50
51    /**
52     * Result object/resource
53     * @var mixed
54     */
55    protected mixed $result = null;
56
57    /**
58     * Error string/object/resource
59     * @var mixed
60     */
61    protected mixed $error = null;
62
63    /**
64     * Query listener object/resource
65     * @var mixed
66     */
67    protected mixed $listener = null;
68
69    /**
70     * Query profiler
71     * @var ?Profiler\Profiler
72     */
73    protected ?Profiler\Profiler $profiler = null;
74
75    /**
76     * Transaction manager
77     * @var Transaction\Manager|null
78     */
79    protected ?Transaction\Manager $transactionManager = null;
80
81    /**
82     * Constructor
83     *
84     * Instantiate the database adapter object
85     *
86     * @param  array $options
87     */
88    abstract public function __construct(array $options = []);
89
90    /**
91     * Connect to the database
92     *
93     * @param  array $options
94     * @return AbstractAdapter
95     */
96    abstract public function connect(array $options = []): AbstractAdapter;
97
98    /**
99     * Set database connection options
100     *
101     * @param  array $options
102     * @return AbstractAdapter
103     */
104    abstract public function setOptions(array $options): AbstractAdapter;
105
106    /**
107     * Get database connection options
108     *
109     * @return array
110     */
111    public function getOptions(): array
112    {
113        return $this->options;
114    }
115
116    /**
117     * Has database connection options
118     *
119     * @return bool
120     */
121    abstract public function hasOptions(): bool;
122
123    /**
124     * Begin a transaction
125     *
126     * @return AbstractAdapter
127     */
128    abstract public function beginTransaction(): AbstractAdapter;
129
130    /**
131     * Commit a transaction
132     *
133     * @return AbstractAdapter
134     */
135    abstract public function commit(): AbstractAdapter;
136
137    /**
138     * Rollback a transaction
139     *
140     * @return AbstractAdapter
141     */
142    abstract public function rollback(): AbstractAdapter;
143
144    /**
145     * Return the transaction manager object, initialize on first use
146     *
147     * @return Transaction\Manager
148     */
149    protected function getTransactionManager(): Transaction\Manager
150    {
151        return ($this->transactionManager ??= new Transaction\Manager());
152    }
153
154    /**
155     * Check if adapter is in the middle of an open transaction
156     *
157     * @return bool
158     */
159    public function isTransaction(): bool
160    {
161        return !is_null($this->transactionManager) && $this->transactionManager->isTransaction();
162    }
163
164    /**
165     * Get transaction depth
166     *
167     * @return int
168     */
169    public function getTransactionDepth(): int
170    {
171        return is_null($this->transactionManager) ? 0 : $this->transactionManager->getTransactionDepth();
172    }
173
174    /**
175     * Execute complete transaction with the DB adapter
176     *
177     * @param  mixed $callable
178     * @param  mixed $params
179     * @throws \Exception
180     * @return void
181     */
182    public function transaction(mixed $callable, mixed $params = null): void
183    {
184        if (!($callable instanceof CallableObject)) {
185            $callable = new CallableObject($callable, $params);
186        }
187
188        try {
189            $this->beginTransaction();
190            $callable->call();
191            $this->commit();
192        } catch (\Exception $e) {
193            $this->rollback();
194            throw $e;
195        }
196    }
197
198    /**
199     * Check if transaction is success
200     *
201     * @return bool
202     */
203    abstract public function isSuccess(): bool;
204
205    /**
206     * Directly execute a SELECT SQL query or prepared statement and return the results
207     *
208     * @param  string|Sql $sql
209     * @param  array      $params
210     * @throws Exception
211     * @return array
212     */
213    public function select(string|Sql $sql, array $params = []): array
214    {
215        if ((is_string($sql) && !str_starts_with(strtolower(trim($sql)), 'select')) ||
216            (($sql instanceof Sql) && !($sql->hasSelect()))) {
217            throw new Exception('Error: The SQL statement is not a valid SELECT statement.');
218        }
219
220        $this->executeSql($sql, $params);
221        return $this->fetchAll();
222    }
223
224    /**
225     * Directly execute an INSERT SQL query or prepared statement and return the results
226     *
227     * @param  string|Sql $sql
228     * @param  array      $params
229     * @throws Exception
230     * @return int
231     */
232    public function insert(string|Sql $sql, array $params = []): int
233    {
234        if ((is_string($sql) && !str_starts_with(strtolower(trim($sql)), 'insert')) ||
235            (($sql instanceof Sql) && !($sql->hasInsert()))) {
236            throw new Exception('Error: The SQL statement is not a valid INSERT statement.');
237        }
238
239        $this->executeSql($sql, $params);
240        return $this->getNumberOfAffectedRows();
241    }
242
243    /**
244     * Directly execute an UPDATE SQL query or prepared statement and return the results
245     *
246     * @param  string|Sql $sql
247     * @param  array      $params
248     * @throws Exception
249     * @return int
250     */
251    public function update(string|Sql $sql, array $params = []): int
252    {
253        if ((is_string($sql) && !str_starts_with(strtolower(trim($sql)), 'update')) ||
254            (($sql instanceof Sql) && !($sql->hasUpdate()))) {
255            throw new Exception('Error: The SQL statement is not a valid UPDATE statement.');
256        }
257
258        $this->executeSql($sql, $params);
259        return $this->getNumberOfAffectedRows();
260    }
261
262    /**
263     * Directly execute a DELETE SQL query or prepared statement and return the results
264     *
265     * @param  string|Sql $sql
266     * @param  array      $params
267     * @throws Exception
268     * @return int
269     */
270    public function delete(string|Sql $sql, array $params = []): int
271    {
272        if ((is_string($sql) && !str_starts_with(strtolower(trim($sql)), 'delete')) ||
273            (($sql instanceof Sql) && !($sql->hasDelete()))) {
274            throw new Exception('Error: The SQL statement is not a valid DELETE statement.');
275        }
276
277        $this->executeSql($sql, $params);
278        return $this->getNumberOfAffectedRows();
279    }
280
281    /**
282     * Execute a SQL query or prepared statement with params
283     *
284     * @param  string|Sql $sql
285     * @param  array      $params
286     * @return AbstractAdapter
287     */
288    public function executeSql(string|Sql $sql, array $params = []): AbstractAdapter
289    {
290        if (!empty($params)) {
291            $this->prepare($sql)
292                ->bindParams($params)
293                ->execute();
294        } else {
295            $this->query($sql);
296        }
297
298        return $this;
299    }
300
301    /**
302     * Execute a SQL query directly
303     *
304     * @param  mixed $sql
305     * @return AbstractAdapter
306     */
307    abstract public function query(mixed $sql): AbstractAdapter;
308
309    /**
310     * Prepare a SQL query
311     *
312     * @param  mixed $sql
313     * @return AbstractAdapter
314     */
315    abstract public function prepare(mixed $sql): AbstractAdapter;
316
317    /**
318     * Bind parameters to a prepared SQL query
319     *
320     * @param  array $params
321     * @return AbstractAdapter
322     */
323    abstract public function bindParams(array $params): AbstractAdapter;
324
325    /**
326     * Execute a prepared SQL query
327     *
328     * @return AbstractAdapter
329     */
330    abstract public function execute(): AbstractAdapter;
331
332    /**
333     * Fetch and return a row from the result
334     *
335     * @return mixed
336     */
337    abstract public function fetch(): mixed;
338
339    /**
340     * Fetch and return all rows from the result
341     *
342     * @return array
343     */
344    abstract public function fetchAll(): array;
345
346    /**
347     * Create SQL builder
348     *
349     * @return Sql
350     */
351    public function createSql(): Sql
352    {
353        return new Sql($this);
354    }
355
356    /**
357     * Create Schema builder
358     *
359     * @return Sql\Schema
360     */
361    public function createSchema(): Sql\Schema
362    {
363        return new Sql\Schema($this);
364    }
365
366    /**
367     * Determine whether or not connected
368     *
369     * @return bool
370     */
371    public function isConnected(): bool
372    {
373        return ($this->connection !== null);
374    }
375
376    /**
377     * Get the connection object/resource
378     *
379     * @return mixed
380     */
381    public function getConnection(): mixed
382    {
383        return $this->connection;
384    }
385
386    /**
387     * Determine whether or not a statement resource exists
388     *
389     * @return bool
390     */
391    public function hasStatement(): bool
392    {
393        return ($this->statement !== null);
394    }
395
396    /**
397     * Get the statement object/resource
398     *
399     * @return mixed
400     */
401    public function getStatement(): mixed
402    {
403        return $this->statement;
404    }
405
406    /**
407     * Determine whether or not a result resource exists
408     *
409     * @return bool
410     */
411    public function hasResult(): bool
412    {
413        return ($this->result !== null);
414    }
415
416    /**
417     * Get the result object/resource
418     *
419     * @return mixed
420     */
421    public function getResult(): mixed
422    {
423        return $this->result;
424    }
425
426    /**
427     * Add query listener to the adapter
428     *
429     * @param  mixed             $listener
430     * @param  mixed             $params
431     * @param  Profiler\Profiler $profiler
432     * @return mixed
433     */
434    public function listen(mixed $listener, mixed $params = null, Profiler\Profiler $profiler = new Profiler\Profiler()): mixed
435    {
436        $this->profiler = $profiler;
437
438        if (!($listener instanceof CallableObject)) {
439            $this->listener = new CallableObject($listener, [$this->profiler]);
440            if ($params !== null) {
441                if (is_array($params)) {
442                    $this->listener->addParameters($params);
443                } else {
444                    $this->listener->addParameter($params);
445                }
446            }
447        } else {
448            $this->listener = $listener;
449            if ($params !== null) {
450                if (is_array($params)) {
451                    array_unshift($params, $this->profiler);
452                } else {
453                    $params = [$this->profiler, $params];
454                }
455                $this->listener->addParameters($params);
456            } else {
457                $this->listener->addNamedParameter('profiler', $this->profiler);
458            }
459        }
460
461        $handler = $this->listener->call();
462        if ($this->profiler->hasDebugger()) {
463            $this->profiler->getDebugger()->addHandler($handler);
464        }
465
466        return $handler;
467    }
468
469    /**
470     * Get query listener
471     *
472     * @return mixed
473     */
474    public function getListener(): mixed
475    {
476        return $this->listener;
477    }
478
479    /**
480     * Set query profiler
481     *
482     * @param  Profiler\Profiler $profiler
483     * @return AbstractAdapter
484     */
485    public function setProfiler(Profiler\Profiler $profiler): AbstractAdapter
486    {
487        $this->profiler = $profiler;
488        return $this;
489    }
490
491    /**
492     * Get query profiler
493     *
494     * @return Profiler\Profiler|null
495     */
496    public function getProfiler(): Profiler\Profiler|null
497    {
498        return $this->profiler;
499    }
500
501    /**
502     * Clear query profiler
503     *
504     * @return AbstractAdapter
505     */
506    public function clearProfiler(): AbstractAdapter
507    {
508        unset($this->profiler);
509        $this->profiler = null;
510        return $this;
511    }
512
513    /**
514     * Determine whether or not there is an error
515     *
516     * @return bool
517     */
518    public function hasError(): bool
519    {
520        return ($this->error !== null);
521    }
522
523    /**
524     * Set the error
525     *
526     * @param  string $error
527     * @return AbstractAdapter
528     */
529    public function setError(string $error): AbstractAdapter
530    {
531        $this->error = $error;
532        return $this;
533    }
534
535    /**
536     * Get the error
537     *
538     * @return mixed
539     */
540    public function getError(): mixed
541    {
542        return $this->error;
543    }
544
545    /**
546     * Throw a database error exception
547     *
548     * @param  ?string $error
549     * @throws Exception
550     * @return void
551     */
552    public function throwError(?string $error = null): void
553    {
554        if ($error !== null) {
555            $this->setError($error);
556        }
557        if ($this->error !== null) {
558            throw new Exception($this->error);
559        }
560    }
561
562    /**
563     * Clear the error
564     *
565     * @return AbstractAdapter
566     */
567    public function clearError(): AbstractAdapter
568    {
569        $this->error = null;
570        return $this;
571    }
572
573    /**
574     * Disconnect from the database
575     *
576     * @return void
577     */
578    public function disconnect(): void
579    {
580        unset($this->connection);
581        unset($this->statement);
582        unset($this->result);
583        unset($this->error);
584
585        $this->connection = null;
586        $this->result     = null;
587        $this->statement  = null;
588        $this->error      = null;
589    }
590
591    /**
592     * Escape the value
593     *
594     * @param  ?string $value
595     * @return string
596     */
597    abstract public function escape(?string $value = null): string;
598
599    /**
600     * Return the last ID of the last query
601     *
602     * @return int
603     */
604    abstract public function getLastId(): int;
605
606    /**
607     * Return the number of rows from the last query
608     *
609     * @return int
610     */
611    abstract public function getNumberOfRows(): int;
612
613    /**
614     * Return the number of affected rows from the last query
615     *
616     * @return int
617     */
618    abstract public function getNumberOfAffectedRows(): int;
619
620    /**
621     * Return the database version
622     *
623     * @return string
624     */
625    abstract public function getVersion(): string;
626
627    /**
628     * Return the tables in the database
629     *
630     * @return array
631     */
632    abstract public function getTables(): array;
633
634    /**
635     * Return if the database has a table
636     *
637     * @param  string  $table
638     * @return bool
639     */
640    public function hasTable(string $table): bool
641    {
642        return (in_array($table, $this->getTables()));
643    }
644
645}