Test Failed
Push — main ( 31a78f...703d27 )
by Rafael
10:23
created

Engine::getEngines()   A

Complexity

Conditions 5
Paths 3

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 6
nc 3
nop 0
dl 0
loc 10
rs 9.6111
c 0
b 0
f 0
1
<?php
2
/**
3
 * Alxarafe. Development of PHP applications in a flash!
4
 * Copyright (C) 2023 Alxarafe <[email protected]>
5
 */
6
7
namespace Alxarafe\Database;
8
9
use Alxarafe\Core\Singletons\Config;
10
use Alxarafe\Core\Singletons\Debug;
11
use DebugBar\DataCollector\PDO\PDOCollector;
12
use DebugBar\DataCollector\PDO\TraceablePDO;
13
use PDO;
14
use PDOException;
15
use PDOStatement;
16
17
/**
18
 * Class Engine
19
 *
20
 * Proporciona soporte genérico a bases de datos usando PDO.
21
 * Cada motor que desee desarrollarse extenderá esta clase y añadirá sus particularidades.
22
 * Se usa desde la clase estática DB.
23
 *
24
 * @author  Rafael San José Tovar <[email protected]>
25
 * @version 2023.0108
26
 *
27
 * @package Alxarafe\Database
28
 */
29
abstract class Engine
30
{
31
    /**
32
     * Ruta en la que se encuentran los motores de la base de datos
33
     */
34
    const ENGINES_FOLDER = BASE_DIR . '/src/Database/Engines';
35
36
    /**
37
     * Data Source Name
38
     *
39
     * @var string
40
     */
41
    protected static string $dsn;
42
43
    /**
44
     * Array con los parámetros de acceso a la base de datos
45
     *
46
     * @var array
47
     */
48
    protected static array $dbConfig;
49
50
    /**
51
     * Instancia PDO
52
     *
53
     * @var PDO
54
     */
55
    protected static PDO $dbHandler;
56
57
    /**
58
     * Instancia PDOStatement
59
     *
60
     * @var PDOStatement|false
61
     */
62
    protected static $statement;
63
64
    /**
65
     * Indica si la base de datos soporta SAVEPOINT en las transacciones.
66
     * Si la base de datos no lo usa, el descendiente deberá de ponerlo a "false".
67
     * SAVEPOINT es una forma de anidar transacciones.
68
     *
69
     * @source https://coderwall.com/p/rml5fa/nested-pdo-transactions
70
     *
71
     * @var bool
72
     */
73
    protected static bool $savePointsSupport = true;
74
75
    /**
76
     * Indica el número de transacciones que están siendo ejecutadas.
77
     *
78
     * @var int
79
     */
80
    protected static int $transactionDepth = 0;
81
82
    /**
83
     * Engine constructor
84
     *
85
     * @param array $dbConfig
86
     */
87
    public function __construct(array $dbConfig)
88
    {
89
        self::$dbConfig = $dbConfig;
90
    }
91
92
    /**
93
     * Engine destructor. Cancela las transacciones pendientes.
94
     */
95
    public function ___destruct()
96
    {
97
        $this->rollbackTransactions();
0 ignored issues
show
Bug introduced by
The method rollbackTransactions() does not exist on Alxarafe\Database\Engine. Did you maybe mean _rollbackTransactions()? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

97
        $this->/** @scrutinizer ignore-call */ 
98
               rollbackTransactions();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
98
    }
99
100
    /**
101
     * Obtiene un array con los motores de base de datos disponibles.
102
     *
103
     * TODO: La extracción de archivos de una ruta por su extensión se usa en más
104
     *       de una ocasión. Habría que plantearse un método estático que lo facilite.
105
     *
106
     * @author  Rafael San José Tovar <[email protected]>
107
     * @version 2023.0108
108
     *
109
     * @return array
110
     */
111
    public static function _getEngines(): array
112
    {
113
        $engines = scandir(self::ENGINES_FOLDER);
114
        $ret = [];
115
        foreach ($engines as $engine) {
116
            if ($engine != '.' && $engine != '..' && substr($engine, -4) == '.php') {
117
                $ret[] = substr($engine, 0, strlen($engine) - 4);
118
            }
119
        }
120
        return $ret;
121
    }
122
123
    /**
124
     * Retorna el nombre físico de la tabla. Estará en minúsculas y contendrá el prefijo de
125
     * la base de datos.
126
     *
127
     * @author  Rafael San José Tovar <[email protected]>
128
     * @version 2023.0108
129
     *
130
     * @param $tablename
131
     *
132
     * @return string
133
     */
134
    public static function getTableName($tableName): string
135
    {
136
        return DB::$dbPrefix . strtolower($tableName);
137
    }
138
139
    /**
140
     * Cancela las transacciones pendientes.
141
     *
142
     * @author  Rafael San José Tovar <[email protected]>
143
     * @version 2023.0108
144
     *
145
     */
146
    final public static function _rollbackTransactions()
147
    {
148
        while (self::$transactionDepth > 0) {
149
            self::rollback();
0 ignored issues
show
Bug introduced by
The method rollback() does not exist on Alxarafe\Database\Engine. Did you maybe mean _rollbackTransactions()? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

149
            self::/** @scrutinizer ignore-call */ 
150
                  rollback();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
150
        }
151
    }
152
153
    /**
154
     * Cancela la transacción en curso
155
     *
156
     * @author  Rafael San José Tovar <[email protected]>
157
     * @version 2023.0108
158
     *
159
     * @return bool
160
     */
161
    final public static function _rollBack(): bool
162
    {
163
        $ret = true;
164
165
        if (self::$transactionDepth == 0) {
166
            throw new PDOException('Rollback error : There is no transaction started');
167
        }
168
169
        Debug::sqlMessage('Rollback, savepoint LEVEL' . self::$transactionDepth);
170
        self::$transactionDepth--;
171
172
        if (self::$transactionDepth == 0 || !self::$savePointsSupport) {
173
            $ret = self::$dbHandler->rollBack();
174
        } else {
175
            self::exec('ROLLBACK TO SAVEPOINT LEVEL' . self::$transactionDepth);
176
        }
177
178
        return $ret;
179
    }
180
181
    /**
182
     * Ejecuta una sentencia SQL (INSERT, UPDATE o DELETE).
183
     *
184
     * @author  Rafael San José Tovar <[email protected]>
185
     * @version 2023.0108
186
     *
187
     * @param string $query
188
     *
189
     * @return bool
190
     */
191
    final public static function exec(string $query): bool
192
    {
193
        self::$statement = self::$dbHandler->prepare($query);
194
        if (self::$statement != null && self::$statement) {
195
            return self::$statement->execute([]);
196
        }
197
        return false;
198
    }
199
200
    /**
201
     * Retorna el ID del último registro insertado. Vacío si no ha habido inserción.
202
     *
203
     * @author  Rafael San José Tovar <[email protected]>
204
     * @version 2023.0108
205
     *
206
     * @return string
207
     */
208
    final public static function _getLastInserted(): string
209
    {
210
        $data = self::select('SELECT @@identity AS id');
211
        if (count($data) > 0) {
212
            return $data[0]['id'];
213
        }
214
        return '';
215
    }
216
217
    /**
218
     * Ejecuta el comando SELECT de SQL retornando la respuesta en un array.
219
     * Si no hay datos, retorna un array vacío.
220
     * En caso de error, retorna "false".
221
     *
222
     * @author  Rafael San José Tovar <[email protected]>
223
     * @version 2023.0108
224
     *
225
     * @param string $query
226
     *
227
     * @return array|false
228
     */
229
    public static function select(string $query)
230
    {
231
        self::$statement = self::$dbHandler->prepare($query);
232
        if (self::$statement != null && self::$statement && self::$statement->execute([])) {
233
            return self::$statement->fetchAll(PDO::FETCH_ASSOC);
234
        }
235
        return [];
236
    }
237
238
    /**
239
     * Comprueba si hay una conexión activa a la base de datos.
240
     *
241
     * @author  Rafael San José Tovar <[email protected]>
242
     * @version 2023.0108
243
     *
244
     * @return bool
245
     */
246
    public static function checkConnection(): bool
247
    {
248
        return (self::$dbHandler != null);
249
    }
250
251
    /**
252
     * Establece conexión con la base de datos.
253
     * Si ya existía una conexión, simplemente retorna éxito.
254
     * Retorna true si existe conexión, asignando el handler a self::$dbHandler.
255
     *
256
     * @author  Rafael San José Tovar <[email protected]>
257
     *
258
     * @param array $config
259
     *
260
     * @return bool
261
     */
262
    public static function connect(array $config = []): bool
263
    {
264
        if (isset(self::$dbHandler)) {
265
            Debug::sqlMessage("PDO: Already connected " . self::$dsn);
266
            return true;
267
        }
268
        Debug::sqlMessage("PDO: " . self::$dsn);
269
        try {
270
            // Logs SQL queries. You need to wrap your PDO object into a DebugBar\DataCollector\PDO\TraceablePDO object.
271
            // http://phpdebugbar.com/docs/base-collectors.html
272
            self::$dbHandler = new TraceablePDO(new PDO(self::$dsn, self::$dbConfig['dbUser'], self::$dbConfig['dbPass'], $config));
273
            Debug::addCollector(new PDOCollector(self::$dbHandler));
274
        } catch (PDOException $e) {
275
            Debug::addException($e);
276
            return false;
277
        }
278
        return isset(self::$dbHandler);
279
    }
280
281
    /**
282
     * Prepara una sentencia para su ejecución y la retorna.
283
     *
284
     * @source https://php.net/manual/en/pdo.prepare.php
285
     *
286
     * @author  Rafael San José Tovar <[email protected]>
287
     * @version 2023.0108
288
     *
289
     * @param string $sql
290
     * @param array  $options
291
     *
292
     * @return bool
293
     */
294
    final public static function _prepare(string $sql, array $options = []): bool
295
    {
296
        if (!isset(self::$dbHandler)) {
297
            return false;
298
        }
299
        self::$statement = self::$dbHandler->prepare($sql, $options);
300
        return (self::$statement != false);
301
    }
302
303
    /**
304
     * Ejecuta una sentencia SQL previamente preparada.
305
     *
306
     * @source https://php.net/manual/en/pdostatement.execute.php
307
     *
308
     * @author  Rafael San José Tovar <[email protected]>
309
     * @version 2023.0108
310
     *
311
     * @param array $inputParameters
312
     *
313
     * @return bool
314
     */
315
    final public static function _execute(array $inputParameters = []): bool
316
    {
317
        if (!isset(self::$statement) || !self::$statement) {
318
            return false;
319
        }
320
        return self::$statement->execute($inputParameters);
321
    }
322
323
    /**
324
     * Inicia una transacción
325
     *
326
     * @source https://www.ibm.com/support/knowledgecenter/es/SSEPGG_9.1.0/com.ibm.db2.udb.apdv.php.doc/doc/t0023166.htm
327
     *
328
     * @author  Rafael San José Tovar <[email protected]>
329
     * @version 2023.0108
330
     *
331
     * @return bool
332
     */
333
    final public static function _beginTransaction(): bool
334
    {
335
        if (self::$transactionDepth == 0 || !self::$savePointsSupport) {
336
            $ret = self::$dbHandler->beginTransaction();
337
        } else {
338
            $ret = self::exec('SAVEPOINT LEVEL' . self::$transactionDepth);
339
        }
340
341
        if (!$ret) {
342
            return false;
343
        }
344
        self::$transactionDepth++;
345
        Debug::sqlMessage('Transaction started, savepoint LEVEL' . self::$transactionDepth . ' saved');
346
347
        return true;
348
    }
349
350
    /**
351
     * Confirma la transacción en curso
352
     *
353
     * @author  Rafael San José Tovar <[email protected]>
354
     * @version 2023.0108
355
     *
356
     * @return bool
357
     */
358
    final public static function _commit(): bool
359
    {
360
        $ret = true;
361
362
        Debug::sqlMessage('Commit, savepoint LEVEL' . self::$transactionDepth);
363
        self::$transactionDepth--;
364
365
        if (self::$transactionDepth == 0 || !self::$savePointsSupport) {
366
            $ret = self::$dbHandler->commit();
367
        } else {
368
            self::exec('RELEASE SAVEPOINT LEVEL' . self::$transactionDepth);
369
        }
370
371
        return $ret;
372
    }
373
}
374