Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
98.11% |
156 / 159 |
|
84.21% |
16 / 19 |
CRAP | |
0.00% |
0 / 1 |
Db | |
98.11% |
156 / 159 |
|
84.21% |
16 / 19 |
88 | |
0.00% |
0 / 1 |
connect | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
2 | |||
mysqlConnect | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
pdoConnect | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
pgsqlConnect | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
sqlsrvConnect | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
sqliteConnect | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
check | |
100.00% |
11 / 11 |
|
100.00% |
1 / 1 |
3 | |||
executeSql | |
98.08% |
51 / 52 |
|
0.00% |
0 / 1 |
28 | |||
executeSqlFile | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
getAvailableAdapters | |
100.00% |
13 / 13 |
|
100.00% |
1 / 1 |
2 | |||
isAvailable | |
100.00% |
24 / 24 |
|
100.00% |
1 / 1 |
9 | |||
setDb | |
100.00% |
9 / 9 |
|
100.00% |
1 / 1 |
5 | |||
getDb | |
94.74% |
18 / 19 |
|
0.00% |
0 / 1 |
15.03 | |||
hasDb | |
100.00% |
14 / 14 |
|
100.00% |
1 / 1 |
12 | |||
addClassToTable | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
hasClassToTable | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
setDefaultDb | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
db | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getAll | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 |
1 | <?php |
2 | /** |
3 | * Pop PHP Framework (https://www.popphp.org/) |
4 | * |
5 | * @link https://github.com/popphp/popphp-framework |
6 | * @author Nick Sagona, III <dev@noladev.com> |
7 | * @copyright Copyright (c) 2009-2025 NOLA Interactive, LLC. |
8 | * @license https://www.popphp.org/license New BSD License |
9 | */ |
10 | |
11 | /** |
12 | * @namespace |
13 | */ |
14 | namespace Pop\Db; |
15 | |
16 | /** |
17 | * Db class |
18 | * |
19 | * @category Pop |
20 | * @package Pop\Db |
21 | * @author Nick Sagona, III <dev@noladev.com> |
22 | * @copyright Copyright (c) 2009-2025 NOLA Interactive, LLC. |
23 | * @license https://www.popphp.org/license New BSD License |
24 | * @version 6.6.5 |
25 | */ |
26 | class Db |
27 | { |
28 | |
29 | /** |
30 | * Database connection(s) |
31 | * @var array |
32 | */ |
33 | protected static array $db = ['default' => null]; |
34 | |
35 | /** |
36 | * Database connection class to table relationship |
37 | * @var array |
38 | */ |
39 | protected static array $classToTable = []; |
40 | |
41 | /** |
42 | * Method to connect to a database and return the database adapter object |
43 | * |
44 | * @param string $adapter |
45 | * @param array $options |
46 | * @param string $prefix |
47 | * @throws Exception |
48 | * @return Adapter\AbstractAdapter |
49 | */ |
50 | public static function connect(string $adapter, array $options, string $prefix = '\Pop\Db\Adapter\\'): Adapter\AbstractAdapter |
51 | { |
52 | $class = $prefix . ucfirst(strtolower($adapter)); |
53 | |
54 | if (!class_exists($class)) { |
55 | throw new Exception('Error: The database adapter ' . $class . ' does not exist.'); |
56 | } |
57 | |
58 | return new $class($options); |
59 | } |
60 | |
61 | /** |
62 | * Method to connect to a MySQL database and return the MySQL database adapter object |
63 | * |
64 | * @param array $options |
65 | * @param string $prefix |
66 | * @throws Exception |
67 | * @return Adapter\Mysql|Adapter\AbstractAdapter |
68 | */ |
69 | public static function mysqlConnect(array $options, string $prefix = '\Pop\Db\Adapter\\'): Adapter\Mysql|Adapter\AbstractAdapter |
70 | { |
71 | return self::connect('mysql', $options, $prefix); |
72 | } |
73 | |
74 | /** |
75 | * Method to connect to a PDO database and return the PDO database adapter object |
76 | * |
77 | * @param array $options |
78 | * @param string $prefix |
79 | * @throws Exception |
80 | * @return Adapter\Pdo|Adapter\AbstractAdapter |
81 | */ |
82 | public static function pdoConnect(array $options, string $prefix = '\Pop\Db\Adapter\\'): Adapter\Pdo|Adapter\AbstractAdapter |
83 | { |
84 | return self::connect('pdo', $options, $prefix); |
85 | } |
86 | |
87 | /** |
88 | * Method to connect to a PostgreSQL database and return the PostgreSQL database adapter object |
89 | * |
90 | * @param array $options |
91 | * @param string $prefix |
92 | * @throws Exception |
93 | * @return Adapter\Pgsql|Adapter\AbstractAdapter |
94 | */ |
95 | public static function pgsqlConnect(array $options, string $prefix = '\Pop\Db\Adapter\\'): Adapter\Pgsql|Adapter\AbstractAdapter |
96 | { |
97 | return self::connect('pgsql', $options, $prefix); |
98 | } |
99 | |
100 | /** |
101 | * Method to connect to a SQL Server database and return the SQL Server database adapter object |
102 | * |
103 | * @param array $options |
104 | * @param string $prefix |
105 | * @throws Exception |
106 | * @return Adapter\Sqlsrv|Adapter\AbstractAdapter |
107 | */ |
108 | public static function sqlsrvConnect(array $options, string $prefix = '\Pop\Db\Adapter\\'): Adapter\AbstractAdapter|Adapter\Sqlsrv |
109 | { |
110 | return self::connect('sqlsrv', $options, $prefix); |
111 | } |
112 | |
113 | /** |
114 | * Method to connect to a SQLite database and return the SQLite database adapter object |
115 | * |
116 | * @param array $options |
117 | * @param string $prefix |
118 | * @throws Exception |
119 | * @return Adapter\Sqlite|Adapter\AbstractAdapter |
120 | */ |
121 | public static function sqliteConnect(array $options, string $prefix = '\Pop\Db\Adapter\\'): Adapter\Sqlite|Adapter\AbstractAdapter |
122 | { |
123 | return self::connect('sqlite', $options, $prefix); |
124 | } |
125 | |
126 | /** |
127 | * Check the database connection |
128 | * |
129 | * @param string $adapter |
130 | * @param array $options |
131 | * @param string $prefix |
132 | * @return mixed |
133 | */ |
134 | public static function check(string $adapter, array $options, string $prefix = '\Pop\Db\Adapter\\'): mixed |
135 | { |
136 | $result = true; |
137 | $class = $prefix . ucfirst(strtolower($adapter)); |
138 | $error = ini_get('error_reporting'); |
139 | |
140 | error_reporting(E_ERROR); |
141 | |
142 | try { |
143 | if (!class_exists($class)) { |
144 | $result = "Error: The database adapter '" . $class . "' does not exist."; |
145 | } else { |
146 | $db = new $class($options); |
147 | } |
148 | } catch (\Exception $e) { |
149 | $result = $e->getMessage(); |
150 | } |
151 | |
152 | error_reporting((int)$error); |
153 | |
154 | return $result; |
155 | } |
156 | |
157 | /** |
158 | * Execute SQL |
159 | * |
160 | * @param string $sql |
161 | * @param mixed $adapter |
162 | * @param array $options |
163 | * @param string $prefix |
164 | * @throws Exception |
165 | * @return int |
166 | */ |
167 | public static function executeSql( |
168 | string $sql, mixed $adapter, array $options = [], string $prefix = '\Pop\Db\Adapter\\' |
169 | ): int |
170 | { |
171 | $affectedRows = 0; |
172 | |
173 | if (is_string($adapter)) { |
174 | $adapter = ucfirst(strtolower($adapter)); |
175 | $class = $prefix . $adapter; |
176 | |
177 | if (!class_exists($class)) { |
178 | throw new Exception('Error: The database adapter ' . $class . ' does not exist.'); |
179 | } |
180 | |
181 | // If Sqlite |
182 | if (($adapter == 'Sqlite') || |
183 | (($adapter == 'Pdo') && isset($options['type'])) && (strtolower($options['type']) == 'sqlite')) { |
184 | if (!file_exists($options['database'])) { |
185 | touch($options['database']); |
186 | chmod($options['database'], 0777); |
187 | } |
188 | if (!file_exists($options['database'])) { |
189 | throw new Exception('Error: Could not create the database file.'); |
190 | } |
191 | } |
192 | |
193 | $db = new $class($options); |
194 | } else { |
195 | $db = $adapter; |
196 | } |
197 | |
198 | $lines = explode("\n", $sql); |
199 | $statements = []; |
200 | |
201 | if (count($lines) > 0) { |
202 | // Remove any comments, parse prefix if available |
203 | $insideComment = false; |
204 | foreach ($lines as $i => $line) { |
205 | if (empty($line)) { |
206 | unset($lines[$i]); |
207 | } else { |
208 | if (isset($options['prefix'])) { |
209 | $lines[$i] = str_replace('[{prefix}]', $options['prefix'], trim($line)); |
210 | } |
211 | if ($insideComment) { |
212 | if (str_ends_with($line, '*/')) { |
213 | $insideComment = false; |
214 | } |
215 | unset($lines[$i]); |
216 | } else { |
217 | if ((str_starts_with($line, '-')) || (str_starts_with($line, '#'))) { |
218 | unset($lines[$i]); |
219 | } else if (str_starts_with($line, '/*')) { |
220 | $line = trim($line); |
221 | if ((!str_ends_with($line, '*/')) && (!str_ends_with($line, '*/;'))) { |
222 | $insideComment = true; |
223 | } |
224 | unset($lines[$i]); |
225 | } else if (strrpos($line, '--') !== false) { |
226 | $lines[$i] = substr($line, 0, strrpos($line, '--')); |
227 | } else if (strrpos($line, '/*') !== false) { |
228 | $lines[$i] = substr($line, 0, strrpos($line, '/*')); |
229 | } |
230 | } |
231 | } |
232 | } |
233 | |
234 | $lines = array_values(array_filter($lines)); |
235 | $currentStatement = null; |
236 | |
237 | // Assemble statements based on ; delimiter |
238 | foreach ($lines as $i => $line) { |
239 | $currentStatement .= ($currentStatement !== null) ? ' ' . $line : $line; |
240 | if (str_ends_with($line, ';')) { |
241 | $statements[] = $currentStatement; |
242 | $currentStatement = null; |
243 | } |
244 | } |
245 | |
246 | if (!empty($statements)) { |
247 | foreach ($statements as $statement) { |
248 | if (!empty($statement)) { |
249 | $db->query($statement); |
250 | $affectedRows += $db->getNumberOfAffectedRows(); |
251 | } |
252 | } |
253 | } |
254 | } |
255 | |
256 | return $affectedRows; |
257 | } |
258 | |
259 | /** |
260 | * Execute SQL |
261 | * |
262 | * @param string $sqlFile |
263 | * @param mixed $adapter |
264 | * @param array $options |
265 | * @param string $prefix |
266 | * @throws Exception |
267 | * @return int |
268 | */ |
269 | public static function executeSqlFile( |
270 | string $sqlFile, mixed $adapter, array $options = [], string $prefix = '\Pop\Db\Adapter\\' |
271 | ): int |
272 | { |
273 | if (!file_exists($sqlFile)) { |
274 | throw new Exception("Error: The SQL file '" . $sqlFile . "' does not exist."); |
275 | } |
276 | |
277 | return self::executeSql(file_get_contents($sqlFile), $adapter, $options, $prefix); |
278 | } |
279 | |
280 | /** |
281 | * Get the available database adapters |
282 | * |
283 | * @return array |
284 | */ |
285 | public static function getAvailableAdapters(): array |
286 | { |
287 | $pdoDrivers = (class_exists('Pdo', false)) ? \PDO::getAvailableDrivers() : []; |
288 | |
289 | return [ |
290 | 'mysqli' => (class_exists('mysqli', false)), |
291 | 'pdo' => [ |
292 | 'mysql' => (in_array('mysql', $pdoDrivers)), |
293 | 'pgsql' => (in_array('pgsql', $pdoDrivers)), |
294 | 'sqlite' => (in_array('sqlite', $pdoDrivers)), |
295 | 'sqlsrv' => (in_array('sqlsrv', $pdoDrivers)) |
296 | ], |
297 | 'pgsql' => (function_exists('pg_connect')), |
298 | 'sqlite' => (class_exists('Sqlite3', false)), |
299 | 'sqlsrv' => (function_exists('sqlsrv_connect')) |
300 | ]; |
301 | } |
302 | |
303 | /** |
304 | * Determine if a database adapter is available |
305 | * |
306 | * @param string $adapter |
307 | * @return bool |
308 | */ |
309 | public static function isAvailable(string $adapter): bool |
310 | { |
311 | $adapter = strtolower($adapter); |
312 | $result = false; |
313 | $type = null; |
314 | |
315 | $pdoDrivers = (class_exists('Pdo', false)) ? \PDO::getAvailableDrivers() : []; |
316 | if (str_contains($adapter, 'pdo_')) { |
317 | $type = substr($adapter, 4); |
318 | $adapter = 'pdo'; |
319 | } |
320 | |
321 | switch ($adapter) { |
322 | case 'mysql': |
323 | case 'mysqli': |
324 | $result = (class_exists('mysqli', false)); |
325 | break; |
326 | case 'pdo': |
327 | $result = (in_array($type, $pdoDrivers)); |
328 | break; |
329 | case 'pgsql': |
330 | $result = (function_exists('pg_connect')); |
331 | break; |
332 | case 'sqlite': |
333 | $result = (class_exists('Sqlite3', false)); |
334 | break; |
335 | case 'sqlsrv': |
336 | $result = (function_exists('sqlsrv_connect')); |
337 | break; |
338 | } |
339 | |
340 | return $result; |
341 | } |
342 | |
343 | /** |
344 | * Set DB adapter |
345 | * |
346 | * @param Adapter\AbstractAdapter $db |
347 | * @param ?string $class |
348 | * @param ?string $prefix |
349 | * @param bool $isDefault |
350 | * @return void |
351 | */ |
352 | public static function setDb(Adapter\AbstractAdapter $db, ?string $class = null, ?string $prefix = null, bool $isDefault = false): void |
353 | { |
354 | if ($prefix !== null) { |
355 | self::$db[$prefix] = $db; |
356 | } |
357 | |
358 | if ($class !== null) { |
359 | self::$db[$class] = $db; |
360 | $record = new $class(); |
361 | if ($record instanceof Record) { |
362 | self::$classToTable[$class] = $record->getFullTable(); |
363 | } |
364 | } |
365 | |
366 | if ($isDefault) { |
367 | self::$db['default'] = $db; |
368 | } |
369 | } |
370 | |
371 | /** |
372 | * Get DB adapter |
373 | * |
374 | * @param ?string $class |
375 | * @throws Exception |
376 | * @return Adapter\AbstractAdapter |
377 | */ |
378 | public static function getDb(?string $class = null): Adapter\AbstractAdapter |
379 | { |
380 | $dbAdapter = null; |
381 | |
382 | // Check for database adapter assigned to a full class name |
383 | if (($class !== null) && isset(self::$db[$class])) { |
384 | $dbAdapter = self::$db[$class]; |
385 | // Check for database adapter assigned to a namespace |
386 | } else if ($class !== null) { |
387 | foreach (self::$db as $prefix => $adapter) { |
388 | if (str_starts_with($class, $prefix)) { |
389 | $dbAdapter = $adapter; |
390 | } |
391 | } |
392 | } |
393 | |
394 | // Check if class is actual table name |
395 | if (($dbAdapter === null) && ($class !== null) && in_array($class, self::$classToTable)) { |
396 | $class = array_search($class, self::$classToTable); |
397 | // Direct match |
398 | if (isset(self::$db[$class])) { |
399 | $dbAdapter = self::$db[$class]; |
400 | // Check prefixes |
401 | } else { |
402 | foreach (self::$db as $prefix => $adapter) { |
403 | if (str_starts_with($class, $prefix)) { |
404 | $dbAdapter = $adapter; |
405 | } |
406 | } |
407 | } |
408 | } |
409 | |
410 | if (($dbAdapter === null) && isset(self::$db['default'])) { |
411 | $dbAdapter = self::$db['default']; |
412 | } |
413 | |
414 | if ($dbAdapter === null) { |
415 | throw new Exception('No database adapter was found.'); |
416 | } |
417 | |
418 | return $dbAdapter; |
419 | } |
420 | |
421 | /** |
422 | * Check for a DB adapter |
423 | * |
424 | * @param ?string $class |
425 | * @return bool |
426 | */ |
427 | public static function hasDb(?string $class = null): bool |
428 | { |
429 | $result = false; |
430 | |
431 | if (($class !== null) && isset(self::$db[$class])) { |
432 | $result = true; |
433 | } else if ($class !== null) { |
434 | foreach (self::$db as $prefix => $adapter) { |
435 | if (str_starts_with($class, $prefix)) { |
436 | $result = true; |
437 | } |
438 | } |
439 | } |
440 | |
441 | if ((!$result) && ($class !== null) && in_array($class, self::$classToTable)) { |
442 | $table = array_search($class, self::$classToTable); |
443 | if (isset(self::$db[$table])) { |
444 | $result = true; |
445 | } |
446 | } |
447 | |
448 | if ((!$result) && isset(self::$db['default'])) { |
449 | $result = true; |
450 | } |
451 | |
452 | return $result; |
453 | } |
454 | |
455 | /** |
456 | * Add class-to-table relationship |
457 | * |
458 | * @param string $class |
459 | * @param string $table |
460 | * @return void |
461 | */ |
462 | public static function addClassToTable(string $class, string $table): void |
463 | { |
464 | self::$classToTable[$class] = $table; |
465 | } |
466 | |
467 | /** |
468 | * Check if class-to-table relationship exists |
469 | * |
470 | * @param string $class |
471 | * @return bool |
472 | */ |
473 | public static function hasClassToTable(string $class): bool |
474 | { |
475 | return isset(self::$classToTable[$class]); |
476 | } |
477 | |
478 | /** |
479 | * Set DB adapter |
480 | * |
481 | * @param Adapter\AbstractAdapter $db |
482 | * @param ?string $class |
483 | * @param ?string $prefix |
484 | * @return void |
485 | */ |
486 | public static function setDefaultDb(Adapter\AbstractAdapter $db, ?string $class = null, ?string $prefix = null): void |
487 | { |
488 | self::setDb($db, $class, $prefix, true); |
489 | } |
490 | |
491 | /** |
492 | * Get DB adapter (alias) |
493 | * |
494 | * @param ?string $class |
495 | * @throws Exception |
496 | * @return Adapter\AbstractAdapter |
497 | */ |
498 | public static function db(?string $class = null): Adapter\AbstractAdapter |
499 | { |
500 | return self::getDb($class); |
501 | } |
502 | |
503 | /** |
504 | * Get all DB adapters |
505 | * |
506 | * @return array |
507 | */ |
508 | public static function getAll(): array |
509 | { |
510 | return self::$db; |
511 | } |
512 | |
513 | } |