Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
96.67% |
87 / 90 |
|
96.67% |
29 / 30 |
CRAP | |
0.00% |
0 / 1 |
AbstractAdapter | |
96.67% |
87 / 90 |
|
96.67% |
29 / 30 |
59 | |
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% |
1 / 1 |
|
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% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
isTransaction | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
2 | |||
getTransactionDepth | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
2 | |||
transaction | |
100.00% |
8 / 8 |
|
100.00% |
1 / 1 |
3 | |||
isSuccess | n/a |
0 / 0 |
n/a |
0 / 0 |
0 | |||||
select | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
5 | |||
insert | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
5 | |||
update | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
5 | |||
delete | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
5 | |||
executeSql | |
100.00% |
6 / 6 |
|
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% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
createSchema | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
isConnected | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getConnection | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
hasStatement | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getStatement | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
hasResult | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getResult | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
listen | |
83.33% |
15 / 18 |
|
0.00% |
0 / 1 |
7.23 | |||
getListener | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
setProfiler | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
getProfiler | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
clearProfiler | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
hasError | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
setError | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
getError | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
throwError | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
3 | |||
clearError | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
disconnect | |
100.00% |
8 / 8 |
|
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% |
1 / 1 |
|
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 | */ |
14 | namespace Pop\Db\Adapter; |
15 | |
16 | use Pop\Db\Sql; |
17 | use Pop\Db\Adapter\Transaction; |
18 | use 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 | */ |
30 | abstract 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 | } |