Test Failed
Push — main ( a8a1f8...d105a1 )
by Rafael
11:47
created

DBSchema::getXmlColumn()   F

Complexity

Conditions 27
Paths > 20000

Size

Total Lines 144
Code Lines 90

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 27
eloc 90
nc 104640
nop 1
dl 0
loc 144
rs 0
c 1
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * Copyright (C) 2022-2023  Rafael San José Tovar   <[email protected]>
4
 *
5
 * This program is free software; you can redistribute it and/or modify
6
 * it under the terms of the GNU General Public License as published by
7
 * the Free Software Foundation; either version 3 of the License, or
8
 * (at your option) any later version.
9
 *
10
 * This program is distributed in the hope that it will be useful,
11
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
 * GNU General Public License for more details.
14
 *
15
 * You should have received a copy of the GNU General Public License
16
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
17
 */
18
19
namespace Alxarafe\Database;
20
21
abstract class DBSchema
22
{
23
    public const TYPE_INTEGER = 'integer';
24
    public const TYPE_FLOAT = 'float';
25
    public const TYPE_DECIMAL = 'decimal';
26
    public const TYPE_STRING = 'string';
27
    public const TYPE_TEXT = 'text';
28
    public const TYPE_DATE = 'date';
29
    public const TYPE_TIME = 'time';
30
    public const TYPE_DATETIME = 'datetime';
31
    public const TYPE_BOOLEAN = 'bool';
32
33
    public const TYPES = [
34
        self::TYPE_INTEGER => ['tinyint', 'smallint', 'mediumint', 'int', 'bigint'],
35
        self::TYPE_FLOAT => ['real', 'double'],
36
        self::TYPE_DECIMAL => ['decimal', 'numeric'],
37
        self::TYPE_STRING => ['char', 'varchar'],
38
        self::TYPE_TEXT => ['tinytext', 'text', 'mediumtext', 'longtext', 'blob'],
39
        self::TYPE_DATE => ['date'],
40
        self::TYPE_TIME => ['time'],
41
        self::TYPE_DATETIME => ['datetime', 'timestamp'],
42
        self::TYPE_BOOLEAN => ['boolean'],
43
    ];
44
45
    public const PHP_CACHE_FOLDER = 'models';
46
47
    /**
48
     * Array asociativo con la última lista de tablas leídas de la base de datos.
49
     * Se ha optimizado generándolo como array asociativo.
50
     * Se ha probado cacheando en una tabla yaml, y así es mucho más rápido.
51
     *
52
     * @var null|array
53
     */
54
    private static $tableList = null;
55
    private static $xmlTableList = null;
56
57
    /**
58
     * Divide un tipo de dato de la base de datos en sus diferentes partes.
59
     *
60
     * @author  Rafael San José Tovar <[email protected]>
61
     * @version 2022.0903
62
     *
63
     * @param string $originalType
64
     *
65
     * @return array
66
     */
67
    public static function splitType(string $originalType): array
68
    {
69
        $replacesSources = [
70
            'character varying',
71
            // 'timestamp without time zone',
72
            'double precision',
73
        ];
74
        $replacesDestination = [
75
            'varchar',
76
            // 'timestamp',
77
            'double',
78
        ];
79
        $modifiedType = (str_replace($replacesSources, $replacesDestination, $originalType));
80
81
        if ($originalType !== $modifiedType) {
82
            debug_message("XML: Uso de '{$originalType}' en lugar de '{$modifiedType}'.");
0 ignored issues
show
Bug introduced by
The function debug_message was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

82
            /** @scrutinizer ignore-call */ 
83
            debug_message("XML: Uso de '{$originalType}' en lugar de '{$modifiedType}'.");
Loading history...
83
        }
84
        $explode = explode(' ', strtolower($modifiedType));
85
86
        $pos = strpos($explode[0], '(');
87
        if ($pos > 0) {
88
            $begin = $pos + 1;
89
            $end = strpos($explode[0], ')');
90
            $type = substr($explode[0], 0, $pos);
91
            $length = substr($explode[0], $begin, $end - $begin);
92
        } else {
93
            $type = $explode[0];
94
            $length = null;
95
        }
96
97
        $pos = array_search('unsigned', $explode, true);
98
        $unsigned = $pos ? 'yes' : 'no';
99
100
        $pos = array_search('zerofill', $explode, true);
101
        $zerofill = $pos ? 'yes' : 'no';
102
103
        return ['type' => $type, 'length' => $length, 'unsigned' => $unsigned, 'zerofill' => $zerofill];
104
    }
105
106
    /**
107
     * Obtiene el tipo genérico del tipo de dato que se le ha pasado.
108
     *
109
     * @author  Rafael San José Tovar <[email protected]>
110
     * @version 2022.0903
111
     *
112
     * @param string $type
113
     *
114
     * @return string
115
     */
116
    public static function getTypeOf(string $type): string
117
    {
118
        foreach (DBSchema::TYPES as $index => $types) {
119
            if (in_array(strtolower($type), $types)) {
120
                return $index;
121
            }
122
        }
123
        debug_message($type . ' not found in DBSchema::getTypeOf()');
0 ignored issues
show
Bug introduced by
The function debug_message was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

123
        /** @scrutinizer ignore-call */ 
124
        debug_message($type . ' not found in DBSchema::getTypeOf()');
Loading history...
124
        return 'text';
125
    }
126
127
    /**
128
     * Elimina la lista de tablas leídas de la base de datos, para comprobar si existe si tener que consultar cada vez.
129
     *
130
     * @author  Rafael San José Tovar <[email protected]>
131
     * @version 2022.0903
132
     *
133
     */
134
    public static function clearTableList()
135
    {
136
        self::$tableList = null;
137
    }
138
139
    /**
140
     * Ésto realiza comprobaciones adicionales.
141
     * Por lo que veo, lo único que hace es tratar de convertir una tabla que no es
142
     * InnoDB en InnoDB.
143
     * En otras bases de datos como Postgres no hace nada.
144
     *
145
     * @author  Rafael San José Tovar <[email protected]>
146
     * @version 2022.0903
147
     *
148
     * @param $table_name
149
     *
150
     * @return bool
151
     */
152
    public static function checkTableAux(string $table_name): bool
153
    {
154
        return DB::$engine->check_table_aux($table_name);
0 ignored issues
show
Bug introduced by
The method check_table_aux() does not exist on Alxarafe\Database\Engine. ( Ignorable by Annotation )

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

154
        return DB::$engine->/** @scrutinizer ignore-call */ check_table_aux($table_name);

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...
155
    }
156
157
    /**
158
     * Retorna el código SQL para actualizar las columnas de una tabla que han sufrido
159
     * modificaciones en su archivo de definición.
160
     *
161
     * @author  Rafael San José Tovar <[email protected]>
162
     * @author  Rafael San José Tovar <[email protected]>
163
     * @version 2022.0903
164
     *
165
     * @version 2022.0903
166
     *
167
     * @param string $table_name
168
     * @param array  $xml_cols
169
     * @param array  $db_cols
170
     *
171
     * @return string
172
     */
173
    public static function compareColumns(string $table_name, array $xml_cols, array $db_cols): string
174
    {
175
        return DB::$engine->compare_columns($table_name, $xml_cols, $db_cols);
0 ignored issues
show
Bug introduced by
The method compare_columns() does not exist on Alxarafe\Database\Engine. ( Ignorable by Annotation )

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

175
        return DB::$engine->/** @scrutinizer ignore-call */ compare_columns($table_name, $xml_cols, $db_cols);

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...
176
    }
177
178
    /**
179
     * Retorna el código SQL para actualizar las constraint de una tabla que ha sufrido
180
     * modificaciones en su archivo de definición.
181
     *
182
     * @author  Rafael San José Tovar <[email protected]>
183
     * @version 2022.0903
184
     *
185
     * @param $table_name
186
     * @param $xml_cons
187
     * @param $db_cons
188
     * @param $delete_only
189
     *
190
     * @return string
191
     */
192
    public static function compareConstraints($table_name, $xml_cons, $db_cons, $delete_only = false): string
193
    {
194
        return DB::$engine->compare_constraints($table_name, $xml_cons, $db_cons, $delete_only);
0 ignored issues
show
Bug introduced by
The method compare_constraints() does not exist on Alxarafe\Database\Engine. ( Ignorable by Annotation )

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

194
        return DB::$engine->/** @scrutinizer ignore-call */ compare_constraints($table_name, $xml_cons, $db_cons, $delete_only);

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...
195
    }
196
197
    /**
198
     * Retorna el formato de fecha de la base de datos.
199
     *
200
     * @author  Rafael San José Tovar <[email protected]>
201
     * @version 2022.0903
202
     *
203
     * @return string
204
     */
205
    public static function dateStyle(): string
206
    {
207
        return DB::$engine->date_style();
0 ignored issues
show
Bug introduced by
The method date_style() does not exist on Alxarafe\Database\Engine. ( Ignorable by Annotation )

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

207
        return DB::$engine->/** @scrutinizer ignore-call */ date_style();

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...
208
    }
209
210
    /**
211
     * Retorna el literal $str eliminando aquellos caracteres que pueden provocar problemas en
212
     * una consulta SQL.
213
     *
214
     * @author  Rafael San José Tovar <[email protected]>
215
     * @version 2022.0903
216
     *
217
     * @param string $str
218
     *
219
     * @return string
220
     */
221
    public static function escapeString(string $str): string
222
    {
223
        return "'" . DB::$engine->escape_string($str) . "'";
0 ignored issues
show
Bug introduced by
The method escape_string() does not exist on Alxarafe\Database\Engine. ( Ignorable by Annotation )

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

223
        return "'" . DB::$engine->/** @scrutinizer ignore-call */ escape_string($str) . "'";

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...
224
    }
225
226
    public static function generateTableSql(string $tablename): string
227
    {
228
        $xml = static::getXmlTable($tablename);
229
        return DB::$engine->generate_table($tablename, $xml['columns'], $xml['constraints']);
0 ignored issues
show
Bug introduced by
The method generate_table() does not exist on Alxarafe\Database\Engine. ( Ignorable by Annotation )

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

229
        return DB::$engine->/** @scrutinizer ignore-call */ generate_table($tablename, $xml['columns'], $xml['constraints']);

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...
230
    }
231
232
    public static function generateTableSeedSql(string $tablename): string
233
    {
234
        $seeds = static::getFilesFromPlugins('Model/seed/', '.csv');
235
        if (!isset($seeds[$tablename])) {
236
            return '';
237
        }
238
239
        $filename = $seeds[$tablename];
240
241
        $result = '';
242
243
        $rows = 10; // Indicamos el número de registros que vamos a insertar de una vez
244
        $handle = fopen($filename, "r");
245
        if ($handle === false) {
246
            debug_message('No ha sido posible abrir el archivo ' . $filename);
0 ignored issues
show
Bug introduced by
The function debug_message was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

246
            /** @scrutinizer ignore-call */ 
247
            debug_message('No ha sido posible abrir el archivo ' . $filename);
Loading history...
247
            return '';
248
        }
249
250
        // Asumimos que la primera fila es la cabecera...
251
        $header = fgetcsv($handle, 0, ';');
252
        if ($header === false) {
253
            debug_message('No ha sido posible leer la primera línea del archivo ' . $filename);
254
            fclose($handle);
255
            return '';
256
        }
257
258
        $sqlHeader = "INSERT INTO `{$tablename}` (`" . implode('`, `', $header) . '`) VALUES ';
259
        $row = 0;
260
        $sqlData = [];
261
        while (($data = fgetcsv($handle, 0, ';')) !== false) {
262
            // Entrecomillamos lo que no sea null.
263
            foreach ($data as $key => $datum) {
264
                if (mb_strtoupper($datum) !== 'NULL') {
265
                    $data[$key] = "'$datum'";
266
                }
267
            }
268
269
            if ($row % $rows === 0) {
270
                if (count($sqlData) > 0) {
271
                    $result .= ($sqlHeader . implode(', ', $sqlData) . ';' . PHP_EOL);
272
                }
273
                $sqlData = [];
274
            }
275
            $sqlData[] = '(' . implode(', ', $data) . ')';
276
            $row++;
277
        }
278
        if (count($sqlData) > 0) {
279
            $result .= ($sqlHeader . implode(', ', $sqlData) . ';' . PHP_EOL);
280
        }
281
        fclose($handle);
282
283
        return $result;
284
    }
285
286
    /**
287
     * Crea una tabla a partir de su estructura xml.
288
     * Puebla los datos
289
     * Retorna true si consigue crearla correctamente.
290
     * Retorna false si se produce un error durante la creación, o si ya existe.
291
     *
292
     * @author  Rafael San José Tovar <[email protected]>
293
     * @version 2022.0907
294
     *
295
     * @param string $tablename
296
     *
297
     * @return bool
298
     */
299
    public static function generateTable(string $tablename): bool
300
    {
301
        $sql = static::generateTableSql($tablename);
302
        $ok = DB::exec($sql);
303
        if (!$ok) {
304
            return false;
305
        }
306
        $sql = static::generateTableSeedSql($tablename);
307
        return DB::exec($sql);
308
    }
309
310
    /**
311
     * Contiene un array con las columnas de la tabla.
312
     *
313
     * @author  Rafael San José Tovar <[email protected]>
314
     * @version 2022.0903
315
     *
316
     * @param string $table_name
317
     *
318
     * @return array
319
     */
320
    public static function getColumns(string $table_name): array
321
    {
322
        $result = [];
323
        foreach (DB::$engine->get_columns($table_name) as $column) {
0 ignored issues
show
Bug introduced by
The method get_columns() does not exist on Alxarafe\Database\Engine. ( Ignorable by Annotation )

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

323
        foreach (DB::$engine->/** @scrutinizer ignore-call */ get_columns($table_name) as $column) {

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...
324
            $result[$column['name']] = $column;
325
        }
326
        return $result;
327
    }
328
329
    /**
330
     * Retorna un array asociativo con los archivos de la ruta especificada ($path),
331
     * que tengan extensión $extension.
332
     * El índice es el nombre del archivo sin extensión.
333
     *
334
     * @author  Rafael San José Tovar <[email protected]>
335
     * @version 2022.0904
336
     *
337
     * @param string $path
338
     * @param string $extension
339
     *
340
     * @return array
341
     */
342
    private static function getFilesFromPath(string $path, string $extension): array
343
    {
344
        $result = [];
345
346
        if (!file_exists($path)) {
347
            return $result;
348
        }
349
350
        $scanData = scandir($path);
351
        if (!is_array($scanData)) {
0 ignored issues
show
introduced by
The condition is_array($scanData) is always true.
Loading history...
352
            return $result;
353
        }
354
355
        foreach ($scanData as $scan) {
356
            // Excluímos las carpetas . y ..
357
            if (mb_strpos($scan, '.') === 0) {
358
                continue;
359
            }
360
            if (mb_substr($scan, -mb_strlen($extension)) === $extension) {
361
                $result[mb_substr($scan, 0, -mb_strlen($extension))] = constant('BASE_PATH') . '/' . $path . $scan;
362
            }
363
        }
364
365
        return $result;
366
    }
367
368
    /**
369
     * Obtiene todos los archivos de la ruta especificada para el núcleo y plugins.
370
     * En el caso de tablas repetidas, se mantiene el del último plugin activado.
371
     *
372
     * @author  Rafael San José Tovar <[email protected]>
373
     * @version 2022.0904
374
     *
375
     * @param string $folder
376
     * @param string $extension
377
     *
378
     * @return array
379
     */
380
    public static function getFilesFromPlugins(string $folder, string $extension): array
381
    {
382
        $result = [];
383
384
        // Ruta de los xml en formato antiguo
385
        $path = $folder;
386
        $result = array_merge($result, static::getFilesFromPath($path, $extension));
387
388
        // Ruta de los xml en formato nuevo
389
        $path = 'src/Xnet/' . $folder;
390
        $result = array_merge($result, static::getFilesFromPath($path, $extension));
391
392
        foreach (Version::getEnabledPluginsArray() as $plugin) {
0 ignored issues
show
Bug introduced by
The type Alxarafe\Database\Version was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
393
            $path = 'plugins/' . $plugin . '/' . mb_strtolower($folder);
394
            $result = array_merge($result, static::getFilesFromPath($path, $extension));
395
396
            $path = 'plugins/' . $plugin . '/' . $folder;
397
            $result = array_merge($result, static::getFilesFromPath($path, $extension));
398
        }
399
400
        return $result;
401
    }
402
403
    /**
404
     * Carga el listado de archivos XML del archivo YAML existente.
405
     * Si el archivo YAML no existe, genera la información y lo crea.
406
     * Retorna un array asociativo con el contenido de dicho archivo.
407
     *
408
     * @author  Rafael San José Tovar <[email protected]>
409
     * @version 2022.0904
410
     *
411
     * @return array
412
     */
413
    private static function getYamlXmlFiles(): array
414
    {
415
        $result = XnetPhpFileCache::loadYamlFile(XnetConfig::PHP_CACHE_FOLDER, 'tables');
0 ignored issues
show
Bug introduced by
The type Alxarafe\Database\XnetConfig was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
Bug introduced by
The type Alxarafe\Database\XnetPhpFileCache was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
416
        if (!empty($result)) {
417
            return $result;
418
        }
419
420
        $result = static::getFilesFromPlugins('Model/table/', '.xml');
421
        XnetPhpFileCache::saveYamlFile(XnetConfig::PHP_CACHE_FOLDER, 'tables', $result);
422
        return $result;
423
    }
424
425
    /**
426
     * Retorna la ruta del archivo xml de configuración de la tabla $tablename
427
     *
428
     * @author  Rafael San José Tovar <[email protected]>
429
     * @version 2022.0903
430
     *
431
     * @param string $tablename
432
     *
433
     * @return string
434
     */
435
    public static function getXmlPath(string $tablename): ?string
436
    {
437
        $files = static::listXmlTables();
438
        if (isset($files[$tablename])) {
439
            return $files[$tablename];
440
        }
441
        return null;
442
    }
443
444
    /**
445
     * Incluye el archivo XML $child dentro de $parent, y retorna el resultado.
446
     *
447
     * @author  Rafael San José Tovar <[email protected]>
448
     * @version 2022.0903
449
     *
450
     * @param \SimpleXMLElement $parent
451
     * @param \SimpleXMLElement $child
452
     *
453
     * @return \SimpleXMLElement
454
     */
455
    private static function mergeXml(\SimpleXMLElement $parent, \SimpleXMLElement $child): \SimpleXMLElement
456
    {
457
        foreach (['columna', 'restriccion'] as $toMerge) {
458
            foreach ($child->{$toMerge} as $item) {
459
                $childItem = $parent->addChild($toMerge, $item);
460
                foreach ($item->children() as $child) {
461
                    $childItem->addChild($child->getName(), reset($child));
462
                }
463
                // Si es una relación extendida, tiene que ser nullable para poder desactivar el plugin
464
                if (!isset($childItem->nulo) && reset($childItem->tipo) === 'relationship') {
465
                    $childItem->addChild('nulo', 'YES');
466
                }
467
            }
468
        }
469
        return $parent;
470
    }
471
472
    /**
473
     * Carga el archivo XML $filename, incluyendo sus dependencias de otros XML
474
     *
475
     * @author  Rafael San José Tovar <[email protected]>
476
     * @version 2022.0810
477
     *
478
     * @param string $tablename
479
     *
480
     * @return \SimpleXMLElement
481
     */
482
    private static function loadXmlFile(string $tablename): \SimpleXMLElement
483
    {
484
        $filename = self::getXmlPath($tablename);
485
        if (empty($filename)) {
486
            die($tablename . ' XML not found');
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
Bug Best Practice introduced by
In this branch, the function will implicitly return null which is incompatible with the type-hinted return SimpleXMLElement. Consider adding a return statement or allowing null as return value.

For hinted functions/methods where all return statements with the correct type are only reachable via conditions, ?null? gets implicitly returned which may be incompatible with the hinted type. Let?s take a look at an example:

interface ReturnsInt {
    public function returnsIntHinted(): int;
}

class MyClass implements ReturnsInt {
    public function returnsIntHinted(): int
    {
        if (foo()) {
            return 123;
        }
        // here: null is implicitly returned
    }
}
Loading history...
487
        }
488
489
        if (!file_exists($filename)) {
490
            die('Archivo ' . $filename . ' no encontrado.');
0 ignored issues
show
Bug Best Practice introduced by
In this branch, the function will implicitly return null which is incompatible with the type-hinted return SimpleXMLElement. Consider adding a return statement or allowing null as return value.

For hinted functions/methods where all return statements with the correct type are only reachable via conditions, ?null? gets implicitly returned which may be incompatible with the hinted type. Let?s take a look at an example:

interface ReturnsInt {
    public function returnsIntHinted(): int;
}

class MyClass implements ReturnsInt {
    public function returnsIntHinted(): int
    {
        if (foo()) {
            return 123;
        }
        // here: null is implicitly returned
    }
}
Loading history...
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
491
        }
492
493
        $xml = simplexml_load_string(file_get_contents($filename, FILE_USE_INCLUDE_PATH));
0 ignored issues
show
Bug introduced by
Alxarafe\Database\FILE_USE_INCLUDE_PATH of type integer is incompatible with the type boolean expected by parameter $use_include_path of file_get_contents(). ( Ignorable by Annotation )

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

493
        $xml = simplexml_load_string(file_get_contents($filename, /** @scrutinizer ignore-type */ FILE_USE_INCLUDE_PATH));
Loading history...
494
        if (!$xml) {
0 ignored issues
show
introduced by
$xml is of type SimpleXMLElement, thus it always evaluated to true.
Loading history...
495
            die('Error al leer el archivo ' . $filename);
0 ignored issues
show
Bug Best Practice introduced by
In this branch, the function will implicitly return null which is incompatible with the type-hinted return SimpleXMLElement. Consider adding a return statement or allowing null as return value.

For hinted functions/methods where all return statements with the correct type are only reachable via conditions, ?null? gets implicitly returned which may be incompatible with the hinted type. Let?s take a look at an example:

interface ReturnsInt {
    public function returnsIntHinted(): int;
}

class MyClass implements ReturnsInt {
    public function returnsIntHinted(): int
    {
        if (foo()) {
            return 123;
        }
        // here: null is implicitly returned
    }
}
Loading history...
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
496
        }
497
498
        if (!isset($xml->incluye) || $xml->incluye->count() === 0) {
499
            return $xml;
500
        }
501
502
        // Si hay un apartado "incluye", hay que incluir las rutas
503
        foreach ($xml->incluye->children() as $item) {
504
            $includeFilename = './' . trim(reset($item));
0 ignored issues
show
Bug introduced by
It seems like $item can also be of type null; however, parameter $array of reset() does only seem to accept array|object, maybe add an additional type check? ( Ignorable by Annotation )

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

504
            $includeFilename = './' . trim(reset(/** @scrutinizer ignore-type */ $item));
Loading history...
505
506
            $xmlParent = simplexml_load_string(file_get_contents($includeFilename, FILE_USE_INCLUDE_PATH));
507
            if (!$xmlParent) {
508
                die('Error al leer el archivo ' . $includeFilename);
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
Bug Best Practice introduced by
In this branch, the function will implicitly return null which is incompatible with the type-hinted return SimpleXMLElement. Consider adding a return statement or allowing null as return value.

For hinted functions/methods where all return statements with the correct type are only reachable via conditions, ?null? gets implicitly returned which may be incompatible with the hinted type. Let?s take a look at an example:

interface ReturnsInt {
    public function returnsIntHinted(): int;
}

class MyClass implements ReturnsInt {
    public function returnsIntHinted(): int
    {
        if (foo()) {
            return 123;
        }
        // here: null is implicitly returned
    }
}
Loading history...
509
            }
510
511
            $xml = self::mergeXml($xmlParent, $xml);
512
        }
513
514
        return $xml;
515
    }
516
517
    /**
518
     * Normaliza la información de una columna con los datos que se le han pasado del XML.
519
     *
520
     * @author  Rafael San José Tovar <[email protected]>
521
     * @version 2022.0905
522
     *
523
     * @param \SimpleXMLElement $col
524
     *
525
     * @return array
526
     */
527
    private static function getXmlColumn(\SimpleXMLElement $col): array
528
    {
529
        $column = [];
530
        $key = (string) $col->nombre;
531
532
        $column['nombre'] = $key;
533
        $column['tipo'] = (string) $col->tipo;
534
535
        $column['nulo'] = 'YES';
536
        if ($col->nulo && mb_strtolower($col->nulo) == 'no') {
537
            $column['nulo'] = 'NO';
538
        }
539
540
        if (empty($col->defecto)) {
541
            $column['defecto'] = null;
542
        } else {
543
            $column['defecto'] = (string) $col->defecto;
544
        }
545
546
        /**
547
         * Pueden existir otras definiciones de limitaciones físicas como min y max
548
         * De existir, tienen que ser contempladas en el método test y tener mayor peso que
549
         * la limitación en plantilla.
550
         */
551
        foreach (['min', 'max'] as $field) {
552
            if (isset($col->{$field})) {
553
                $column[$field] = (string) $col->{$field};
554
            }
555
        }
556
557
        if (isset($col->description)) {
558
            debug_message('Cambie la etiqueta <description> por comentario en ' . $col->nombre . ' de ' . $tablename);
0 ignored issues
show
Bug introduced by
The function debug_message was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

558
            /** @scrutinizer ignore-call */ 
559
            debug_message('Cambie la etiqueta <description> por comentario en ' . $col->nombre . ' de ' . $tablename);
Loading history...
Comprehensibility Best Practice introduced by
The variable $tablename seems to be never defined.
Loading history...
559
            $column['comentario'] = (string) $col->description;
560
        } elseif (isset($col->comment)) {
561
            debug_message('Cambie la etiqueta <comment> por comentario en ' . $col->nombre . ' de ' . $tablename);
562
            $column['comentario'] = (string) $col->comment;
563
        } elseif (isset($col->comentario)) {
564
            $column['comentario'] = (string) $col->comentario;
565
        }
566
567
        // Aquí vienen los datos adicionales...
568
569
        switch ($col->tipo) {
570
            case 'serial':
571
                $colType = constant('FS_DB_INTEGER');
572
                break;
573
            case 'autoincrement':
574
            case 'relationship':
575
                $colType = constant('DB_INDEX_TYPE');
576
                break;
577
            case 'boolean':
578
                $colType = 'tinyint(1) unsigned';
579
                break;
580
            default:
581
                $colType = (string) $col->tipo;
582
        }
583
        $typeArray = static::splitType($colType);
584
        $type = $typeArray['type'];
585
        $length = $typeArray['length'];
586
        $unsigned = $typeArray['unsigned'] === 'yes';
587
        $zerofill = $typeArray['zerofill'] === 'yes';
0 ignored issues
show
Unused Code introduced by
The assignment to $zerofill is dead and can be removed.
Loading history...
588
        $genericType = static::getTypeOf($type);
589
590
        $column['realtype'] = $type;
591
        $column['generictype'] = $genericType;
592
593
        if (isset($col->defecto)) {
594
            $column['default'] = trim($col->defecto, " \"'`");
595
        }
596
597
        switch ($genericType) {
598
            case 'string':
599
                $column['maxlength'] = $length;
600
                break;
601
            case 'integer':
602
                /**
603
                 * Lo primero es ver la capacidad física máxima según el tipo de dato.
604
                 */
605
                $bytes = 4;
606
                switch ($type) {
607
                    case 'tinyint':
608
                        $bytes = 1;
609
                        break;
610
                    case 'smallint':
611
                        $bytes = 2;
612
                        break;
613
                    case 'mediumint':
614
                        $bytes = 3;
615
                        break;
616
                    case 'int':
617
                        $bytes = 4;
618
                        break;
619
                    case 'bigint':
620
                        $bytes = 8;
621
                        break;
622
                }
623
                $bits = 8 * (int) $bytes;
624
                $physicalMaxLength = 2 ** $bits;
625
626
                /**
627
                 * $minDataLength y $maxDataLength contendrán el mínimo y máximo valor que puede contener el campo.
628
                 */
629
                $minDataLength = $unsigned ? 0 : -$physicalMaxLength / 2;
630
                $maxDataLength = ($unsigned ? $physicalMaxLength : $physicalMaxLength / 2) - 1;
631
632
                /**
633
                 * De momento, se asignan los límites máximos por el tipo de dato.
634
                 * En $min y $max, iremos arrastrando los límites conforme se vayan comprobando.
635
                 * $min nunca podrá ser menor que $minDataLength.
636
                 * $max nunca podrá ser mayor que $maxDataLength.
637
                 */
638
                $min = $minDataLength;
639
                $max = $maxDataLength;
640
641
                /**
642
                 * Se puede hacer una limitación física Se puede haber definido en el xml un min y un max.
643
                 * A todos los efectos, lo definido en el XML como min o max se toma como limitación
644
                 * física del campo.
645
                 */
646
                if (isset($col->min)) {
647
                    $minXmlLength = $col->min;
648
                    if ($minXmlLength > $minDataLength) {
649
                        $min = $minXmlLength;
650
                    } else {
651
                        debug_message("({$key}): Se ha especificado un min {$minXmlLength} en el XML, pero por el tipo de datos, el mínimo es {$minDataLength}.");
652
                    }
653
                }
654
                if (isset($col->max)) {
655
                    $maxXmlLength = $col->max;
656
                    if ($maxXmlLength < $maxDataLength) {
657
                        $max = $maxXmlLength;
658
                    } else {
659
                        debug_message("({$key}): Se ha especificado un min {$maxXmlLength} en el XML, pero por el tipo de datos, el máximo es {$maxDataLength}.");
660
                    }
661
                }
662
663
                $column['min'] = $min;
664
                $column['max'] = $max;
665
                break;
666
            default:
667
                // ???
668
        }
669
670
        return $column;
671
    }
672
673
    /**
674
     * Retorna un array con el contenido del archivo XML de la tabla seleccionada.
675
     *
676
     * @author  Rafael San José Tovar <[email protected]>
677
     * @version 2022.0904
678
     *
679
     * @param string $tablename
680
     *
681
     * @return array[]
682
     */
683
    public static function getXmlTable(string $tablename): array
684
    {
685
        XnetDebugBar::startTimer('leexml' . $tablename, 'Leer archivo ' . $tablename);
0 ignored issues
show
Bug introduced by
The type Alxarafe\Database\XnetDebugBar was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
686
        $result = XnetPhpFileCache::loadYamlFile(static::PHP_CACHE_FOLDER, $tablename);
687
        XnetDebugBar::stopTimer('leexml' . $tablename);
688
689
        if (!empty($result)) {
690
            return $result;
691
        }
692
693
        XnetDebugBar::startTimer('creaxml' . $tablename, 'Crear archivo ' . $tablename);
694
695
        $xml = self::loadXmlFile($tablename);
696
697
        $columns = [];
698
        $constraints = [];
699
        if ($xml->columna) {
700
            foreach ($xml->columna as $col) {
701
                $columns[] = static::getXmlColumn($col);
702
            }
703
        }
704
705
        if ($xml->restriccion) {
706
            $i = 0;
707
            foreach ($xml->restriccion as $col) {
708
                $constraints[$i]['nombre'] = (string) $col->nombre;
709
                $constraints[$i]['consulta'] = (string) $col->consulta;
710
                $i++;
711
            }
712
        }
713
714
        $result = [
715
            'columns' => $columns,
716
            'constraints' => $constraints,
717
        ];
718
719
        if (!XnetPhpFileCache::saveYamlFile(static::PHP_CACHE_FOLDER, $tablename, $result)) {
720
            debug_message('No se ha podido guardar el XML de ' . $tablename . ' en la YAML caché.');
0 ignored issues
show
Bug introduced by
The function debug_message was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

720
            /** @scrutinizer ignore-call */ 
721
            debug_message('No se ha podido guardar el XML de ' . $tablename . ' en la YAML caché.');
Loading history...
721
        }
722
        XnetDebugBar::stopTimer('creaxml' . $tablename);
723
        return $result;
724
    }
725
726
    /**
727
     * Obtiene un array asociativo con las columnas de la tabla.
728
     * Es igual que getColumns, sólo que el índice del array es el nombre de la columna.
729
     *
730
     * @author     Rafael San José Tovar <[email protected]>
731
     * @version    2022.0903
732
     *
733
     * @param string $table_name
734
     *
735
     * @return array
736
     *
737
     * @deprecated Utilice getColumns, ya es lo mismo
738
     */
739
    public static function getColumnsAssociativeArray(string $table_name): array
740
    {
741
        return static::getColumns($table_name);
742
    }
743
744
    public static function getConstraints(string $table_name, bool $extended = false): array
745
    {
746
        if ($extended) {
747
            return DB::$engine->get_constraints_extended($table_name);
0 ignored issues
show
Bug introduced by
The method get_constraints_extended() does not exist on Alxarafe\Database\Engine. ( Ignorable by Annotation )

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

747
            return DB::$engine->/** @scrutinizer ignore-call */ get_constraints_extended($table_name);

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...
748
        }
749
750
        return DB::$engine->get_constraints($table_name);
0 ignored issues
show
Bug introduced by
The method get_constraints() does not exist on Alxarafe\Database\Engine. ( Ignorable by Annotation )

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

750
        return DB::$engine->/** @scrutinizer ignore-call */ get_constraints($table_name);

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...
751
    }
752
753
    public function deleteConstraint(string $table_name, string $constraint_name): bool
754
    {
755
        return DB::$engine->delete_constraint($table_name, $constraint_name);
0 ignored issues
show
Bug introduced by
The method delete_constraint() does not exist on Alxarafe\Database\Engine. ( Ignorable by Annotation )

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

755
        return DB::$engine->/** @scrutinizer ignore-call */ delete_constraint($table_name, $constraint_name);

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...
756
    }
757
758
    public function getIndexes($table_name)
759
    {
760
        return DB::$engine->get_indexes($table_name);
0 ignored issues
show
Bug introduced by
The method get_indexes() does not exist on Alxarafe\Database\Engine. ( Ignorable by Annotation )

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

760
        return DB::$engine->/** @scrutinizer ignore-call */ get_indexes($table_name);

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...
761
    }
762
763
    public static function getSelects()
764
    {
765
        return DB::$engine->get_selects();
0 ignored issues
show
Bug introduced by
The method get_selects() does not exist on Alxarafe\Database\Engine. Did you maybe mean select()? ( Ignorable by Annotation )

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

765
        return DB::$engine->/** @scrutinizer ignore-call */ get_selects();

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...
766
    }
767
768
    public static function getTransactions()
769
    {
770
        return DB::$engine->get_transactions();
0 ignored issues
show
Bug introduced by
The method get_transactions() does not exist on Alxarafe\Database\Engine. ( Ignorable by Annotation )

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

770
        return DB::$engine->/** @scrutinizer ignore-call */ get_transactions();

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...
771
    }
772
773
    public static function lastval()
774
    {
775
        return DB::$engine->lastval();
0 ignored issues
show
Bug introduced by
The method lastval() does not exist on Alxarafe\Database\Engine. ( Ignorable by Annotation )

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

775
        return DB::$engine->/** @scrutinizer ignore-call */ lastval();

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...
776
    }
777
778
    public function select_limit($sql, $limit = null, $offset = 0)
779
    {
780
        if ($limit === null) {
781
            $limit = get_item_limit();
0 ignored issues
show
Bug introduced by
The function get_item_limit was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

781
            $limit = /** @scrutinizer ignore-call */ get_item_limit();
Loading history...
782
        }
783
        return DB::$engine->select_limit($sql, $limit, $offset);
0 ignored issues
show
Bug introduced by
The method select_limit() does not exist on Alxarafe\Database\Engine. Did you maybe mean select()? ( Ignorable by Annotation )

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

783
        return DB::$engine->/** @scrutinizer ignore-call */ select_limit($sql, $limit, $offset);

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...
784
    }
785
786
    public function sql_to_int($col_name)
787
    {
788
        return DB::$engine->sql_to_int($col_name);
0 ignored issues
show
Bug introduced by
The method sql_to_int() does not exist on Alxarafe\Database\Engine. ( Ignorable by Annotation )

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

788
        return DB::$engine->/** @scrutinizer ignore-call */ sql_to_int($col_name);

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...
789
    }
790
791
    /**
792
     * Obtiene un array asociativo indexado por el nombre de cada una de las tablas
793
     * de la base de datos. El valor de cada elemento se pone a true, pero lo que
794
     * realmente importa es el índice, pues se verifica si el índice está, que es
795
     * más rápido que buscar.
796
     * El array se cachea en self::$tableList para las próximas peticiones.
797
     *
798
     * @author  Rafael San José Tovar <[email protected]>
799
     * @version 2022.0904
800
     *
801
     * @return array
802
     */
803
    private static function listTables(): array
804
    {
805
        if (isset(self::$tableList)) {
806
            return self::$tableList;
807
        }
808
809
        $items = DB::$engine->list_tables();
0 ignored issues
show
Bug introduced by
The method list_tables() does not exist on Alxarafe\Database\Engine. ( Ignorable by Annotation )

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

809
        /** @scrutinizer ignore-call */ 
810
        $items = DB::$engine->list_tables();

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...
810
        self::$tableList = [];
811
        foreach ($items as $item) {
812
            self::$tableList[$item['name']] = true;
813
        }
814
        return self::$tableList;
815
    }
816
817
    /**
818
     * Obtiene un array asociativo indexado por el nombre de las tablas de la base
819
     * de datos, tomadas de los archivos XML de definición.
820
     * El array contiene la ruta completa al archivo XML correspondiente.
821
     * El array se cachea en self::$xmlTableList para las siguientes peticiones,
822
     * pero además, la búsqueda en el sistema de archivos también se cachea en
823
     * un archivo yaml, que sólo se regenera al limpiar caché.
824
     *
825
     * @author  Rafael San José Tovar <[email protected]>
826
     * @version 2022.0904
827
     *
828
     * @return array
829
     */
830
    public static function listXmlTables($force = false): array
831
    {
832
        if (isset(self::$xmlTableList) && !$force) {
833
            return self::$xmlTableList;
834
        }
835
836
        self::$xmlTableList = static::getYamlXmlFiles();
837
        return self::$xmlTableList;
838
    }
839
840
    /**
841
     * Retorna TRUE si la tabla $name existe en la base de datos.
842
     *
843
     * @author  Rafael San José Tovar <[email protected]>
844
     * @version 2022.0904
845
     *
846
     * @param $name
847
     *
848
     * @return bool
849
     */
850
    public static function tableExists($name)
851
    {
852
        $list = self::listTables();
853
        return isset($list[$name]);
854
    }
855
856
    /**
857
     * Actualiza el juego de caracteres y cómo se aplican las comparaciones.
858
     *
859
     * @author  Rafael San José Tovar <[email protected]>
860
     * @version 2022.0903
861
     *
862
     * @param string $charset
863
     * @param string $collation
864
     *
865
     * @return bool
866
     */
867
    public static function updateCollation(string $charset = 'utf8mb4', string $collation = 'utf8mb4_bin'): bool
868
    {
869
        return DB::$engine->update_collation($charset, $collation);
0 ignored issues
show
Bug introduced by
The method update_collation() does not exist on Alxarafe\Database\Engine. ( Ignorable by Annotation )

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

869
        return DB::$engine->/** @scrutinizer ignore-call */ update_collation($charset, $collation);

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...
870
    }
871
872
    /**
873
     * Realiza una búsqueda sin distinguir case ni tildes.
874
     *
875
     * @author  Rafael San José Tovar <[email protected]>
876
     * @version 2022.0904
877
     *
878
     * @param $col_name
879
     * @param $search
880
     * @param $splitWord
881
     *
882
     * @return string
883
     */
884
    public function search_diacritic_insensitive($col_name, $search, $splitWord = '')
885
    {
886
        return DB::$engine->search_diacritic_insensitive($col_name, $search, $splitWord);
0 ignored issues
show
Bug introduced by
The method search_diacritic_insensitive() does not exist on Alxarafe\Database\Engine. ( Ignorable by Annotation )

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

886
        return DB::$engine->/** @scrutinizer ignore-call */ search_diacritic_insensitive($col_name, $search, $splitWord);

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...
887
    }
888
}
889