Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
98.08% |
153 / 156 |
|
84.21% |
16 / 19 |
CRAP | |
0.00% |
0 / 1 |
Db | |
98.08% |
153 / 156 |
|
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 | |
97.96% |
48 / 49 |
|
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 (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; |
15 | |
16 | /** |
17 | * Db 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 | */ |
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 | return $result; |
154 | } |
155 | |
156 | /** |
157 | * Execute SQL |
158 | * |
159 | * @param string $sql |
160 | * @param mixed $adapter |
161 | * @param array $options |
162 | * @param string $prefix |
163 | * @throws Exception |
164 | * @return void |
165 | */ |
166 | public static function executeSql(string $sql, mixed $adapter, array $options = [], string $prefix = '\Pop\Db\Adapter\\'): void |
167 | { |
168 | if (is_string($adapter)) { |
169 | $adapter = ucfirst(strtolower($adapter)); |
170 | $class = $prefix . $adapter; |
171 | |
172 | if (!class_exists($class)) { |
173 | throw new Exception('Error: The database adapter ' . $class . ' does not exist.'); |
174 | } |
175 | |
176 | // If Sqlite |
177 | if (($adapter == 'Sqlite') || |
178 | (($adapter == 'Pdo') && isset($options['type'])) && (strtolower($options['type']) == 'sqlite')) { |
179 | if (!file_exists($options['database'])) { |
180 | touch($options['database']); |
181 | chmod($options['database'], 0777); |
182 | } |
183 | if (!file_exists($options['database'])) { |
184 | throw new Exception('Error: Could not create the database file.'); |
185 | } |
186 | } |
187 | |
188 | $db = new $class($options); |
189 | } else { |
190 | $db = $adapter; |
191 | } |
192 | |
193 | $lines = explode("\n", $sql); |
194 | $statements = []; |
195 | |
196 | if (count($lines) > 0) { |
197 | // Remove any comments, parse prefix if available |
198 | $insideComment = false; |
199 | foreach ($lines as $i => $line) { |
200 | if (empty($line)) { |
201 | unset($lines[$i]); |
202 | } else { |
203 | if (isset($options['prefix'])) { |
204 | $lines[$i] = str_replace('[{prefix}]', $options['prefix'], trim($line)); |
205 | } |
206 | if ($insideComment) { |
207 | if (str_ends_with($line, '*/')) { |
208 | $insideComment = false; |
209 | } |
210 | unset($lines[$i]); |
211 | } else { |
212 | if ((str_starts_with($line, '-')) || (str_starts_with($line, '#'))) { |
213 | unset($lines[$i]); |
214 | } else if (str_starts_with($line, '/*')) { |
215 | $line = trim($line); |
216 | if ((!str_ends_with($line, '*/')) && (!str_ends_with($line, '*/;'))) { |
217 | $insideComment = true; |
218 | } |
219 | unset($lines[$i]); |
220 | } else if (strrpos($line, '--') !== false) { |
221 | $lines[$i] = substr($line, 0, strrpos($line, '--')); |
222 | } else if (strrpos($line, '/*') !== false) { |
223 | $lines[$i] = substr($line, 0, strrpos($line, '/*')); |
224 | } |
225 | } |
226 | } |
227 | } |
228 | |
229 | $lines = array_values(array_filter($lines)); |
230 | $currentStatement = null; |
231 | |
232 | // Assemble statements based on ; delimiter |
233 | foreach ($lines as $i => $line) { |
234 | $currentStatement .= ($currentStatement !== null) ? ' ' . $line : $line; |
235 | if (str_ends_with($line, ';')) { |
236 | $statements[] = $currentStatement; |
237 | $currentStatement = null; |
238 | } |
239 | } |
240 | |
241 | if (!empty($statements)) { |
242 | foreach ($statements as $statement) { |
243 | if (!empty($statement)) { |
244 | $db->query($statement); |
245 | } |
246 | } |
247 | } |
248 | } |
249 | } |
250 | |
251 | /** |
252 | * Execute SQL |
253 | * |
254 | * @param string $sqlFile |
255 | * @param mixed $adapter |
256 | * @param array $options |
257 | * @param string $prefix |
258 | * @throws Exception |
259 | * @return void |
260 | */ |
261 | public static function executeSqlFile(string $sqlFile, mixed $adapter, array $options = [], string $prefix = '\Pop\Db\Adapter\\'): void |
262 | { |
263 | if (!file_exists($sqlFile)) { |
264 | throw new Exception("Error: The SQL file '" . $sqlFile . "' does not exist."); |
265 | } |
266 | |
267 | self::executeSql(file_get_contents($sqlFile), $adapter, $options, $prefix); |
268 | } |
269 | |
270 | /** |
271 | * Get the available database adapters |
272 | * |
273 | * @return array |
274 | */ |
275 | public static function getAvailableAdapters(): array |
276 | { |
277 | $pdoDrivers = (class_exists('Pdo', false)) ? \PDO::getAvailableDrivers() : []; |
278 | |
279 | return [ |
280 | 'mysqli' => (class_exists('mysqli', false)), |
281 | 'pdo' => [ |
282 | 'mysql' => (in_array('mysql', $pdoDrivers)), |
283 | 'pgsql' => (in_array('pgsql', $pdoDrivers)), |
284 | 'sqlite' => (in_array('sqlite', $pdoDrivers)), |
285 | 'sqlsrv' => (in_array('sqlsrv', $pdoDrivers)) |
286 | ], |
287 | 'pgsql' => (function_exists('pg_connect')), |
288 | 'sqlite' => (class_exists('Sqlite3', false)), |
289 | 'sqlsrv' => (function_exists('sqlsrv_connect')) |
290 | ]; |
291 | } |
292 | |
293 | /** |
294 | * Determine if a database adapter is available |
295 | * |
296 | * @param string $adapter |
297 | * @return bool |
298 | */ |
299 | public static function isAvailable(string $adapter): bool |
300 | { |
301 | $adapter = strtolower($adapter); |
302 | $result = false; |
303 | $type = null; |
304 | |
305 | $pdoDrivers = (class_exists('Pdo', false)) ? \PDO::getAvailableDrivers() : []; |
306 | if (str_contains($adapter, 'pdo_')) { |
307 | $type = substr($adapter, 4); |
308 | $adapter = 'pdo'; |
309 | } |
310 | |
311 | switch ($adapter) { |
312 | case 'mysql': |
313 | case 'mysqli': |
314 | $result = (class_exists('mysqli', false)); |
315 | break; |
316 | case 'pdo': |
317 | $result = (in_array($type, $pdoDrivers)); |
318 | break; |
319 | case 'pgsql': |
320 | $result = (function_exists('pg_connect')); |
321 | break; |
322 | case 'sqlite': |
323 | $result = (class_exists('Sqlite3', false)); |
324 | break; |
325 | case 'sqlsrv': |
326 | $result = (function_exists('sqlsrv_connect')); |
327 | break; |
328 | } |
329 | |
330 | return $result; |
331 | } |
332 | |
333 | /** |
334 | * Set DB adapter |
335 | * |
336 | * @param Adapter\AbstractAdapter $db |
337 | * @param ?string $class |
338 | * @param ?string $prefix |
339 | * @param bool $isDefault |
340 | * @return void |
341 | */ |
342 | public static function setDb(Adapter\AbstractAdapter $db, ?string $class = null, ?string $prefix = null, bool $isDefault = false): void |
343 | { |
344 | if ($prefix !== null) { |
345 | self::$db[$prefix] = $db; |
346 | } |
347 | |
348 | if ($class !== null) { |
349 | self::$db[$class] = $db; |
350 | $record = new $class(); |
351 | if ($record instanceof Record) { |
352 | self::$classToTable[$class] = $record->getFullTable(); |
353 | } |
354 | } |
355 | |
356 | if ($isDefault) { |
357 | self::$db['default'] = $db; |
358 | } |
359 | } |
360 | |
361 | /** |
362 | * Get DB adapter |
363 | * |
364 | * @param ?string $class |
365 | * @throws Exception |
366 | * @return Adapter\AbstractAdapter |
367 | */ |
368 | public static function getDb(?string $class = null): Adapter\AbstractAdapter |
369 | { |
370 | $dbAdapter = null; |
371 | |
372 | // Check for database adapter assigned to a full class name |
373 | if (($class !== null) && isset(self::$db[$class])) { |
374 | $dbAdapter = self::$db[$class]; |
375 | // Check for database adapter assigned to a namespace |
376 | } else if ($class !== null) { |
377 | foreach (self::$db as $prefix => $adapter) { |
378 | if (str_starts_with($class, $prefix)) { |
379 | $dbAdapter = $adapter; |
380 | } |
381 | } |
382 | } |
383 | |
384 | // Check if class is actual table name |
385 | if (($dbAdapter === null) && ($class !== null) && in_array($class, self::$classToTable)) { |
386 | $class = array_search($class, self::$classToTable); |
387 | // Direct match |
388 | if (isset(self::$db[$class])) { |
389 | $dbAdapter = self::$db[$class]; |
390 | // Check prefixes |
391 | } else { |
392 | foreach (self::$db as $prefix => $adapter) { |
393 | if (str_starts_with($class, $prefix)) { |
394 | $dbAdapter = $adapter; |
395 | } |
396 | } |
397 | } |
398 | } |
399 | |
400 | if (($dbAdapter === null) && isset(self::$db['default'])) { |
401 | $dbAdapter = self::$db['default']; |
402 | } |
403 | |
404 | if ($dbAdapter === null) { |
405 | throw new Exception('No database adapter was found.'); |
406 | } |
407 | |
408 | return $dbAdapter; |
409 | } |
410 | |
411 | /** |
412 | * Check for a DB adapter |
413 | * |
414 | * @param ?string $class |
415 | * @return bool |
416 | */ |
417 | public static function hasDb(?string $class = null): bool |
418 | { |
419 | $result = false; |
420 | |
421 | if (($class !== null) && isset(self::$db[$class])) { |
422 | $result = true; |
423 | } else if ($class !== null) { |
424 | foreach (self::$db as $prefix => $adapter) { |
425 | if (str_starts_with($class, $prefix)) { |
426 | $result = true; |
427 | } |
428 | } |
429 | } |
430 | |
431 | if ((!$result) && ($class !== null) && in_array($class, self::$classToTable)) { |
432 | $table = array_search($class, self::$classToTable); |
433 | if (isset(self::$db[$table])) { |
434 | $result = true; |
435 | } |
436 | } |
437 | |
438 | if ((!$result) && isset(self::$db['default'])) { |
439 | $result = true; |
440 | } |
441 | |
442 | return $result; |
443 | } |
444 | |
445 | /** |
446 | * Add class-to-table relationship |
447 | * |
448 | * @param string $class |
449 | * @param string $table |
450 | * @return void |
451 | */ |
452 | public static function addClassToTable(string $class, string $table): void |
453 | { |
454 | self::$classToTable[$class] = $table; |
455 | } |
456 | |
457 | /** |
458 | * Check if class-to-table relationship exists |
459 | * |
460 | * @param string $class |
461 | * @return bool |
462 | */ |
463 | public static function hasClassToTable(string $class): bool |
464 | { |
465 | return isset(self::$classToTable[$class]); |
466 | } |
467 | |
468 | /** |
469 | * Set DB adapter |
470 | * |
471 | * @param Adapter\AbstractAdapter $db |
472 | * @param ?string $class |
473 | * @param ?string $prefix |
474 | * @return void |
475 | */ |
476 | public static function setDefaultDb(Adapter\AbstractAdapter $db, ?string $class = null, ?string $prefix = null): void |
477 | { |
478 | self::setDb($db, $class, $prefix, true); |
479 | } |
480 | |
481 | /** |
482 | * Get DB adapter (alias) |
483 | * |
484 | * @param ?string $class |
485 | * @throws Exception |
486 | * @return Adapter\AbstractAdapter |
487 | */ |
488 | public static function db(?string $class = null): Adapter\AbstractAdapter |
489 | { |
490 | return self::getDb($class); |
491 | } |
492 | |
493 | /** |
494 | * Get all DB adapters |
495 | * |
496 | * @return array |
497 | */ |
498 | public static function getAll(): array |
499 | { |
500 | return self::$db; |
501 | } |
502 | |
503 | } |