Test Failed
Push — main ( f24735...83b0dc )
by Rafael
11:53
created

Engine::_getEngines()   A

Complexity

Conditions 5
Paths 3

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 30

Importance

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

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

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