Test Failed
Branch main (71ef7e)
by Rafael
10:37 queued 05:04
created

YamlSchema::getTypeOf()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 5
nc 3
nop 1
dl 0
loc 9
rs 10
c 1
b 0
f 0
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
use Symfony\Component\Yaml\Yaml;
22
23
abstract class YamlSchema
24
{
25
    public const TYPE_INTEGER = 'integer';
26
    public const TYPE_FLOAT = 'float';
27
    public const TYPE_DECIMAL = 'decimal';
28
    public const TYPE_STRING = 'string';
29
    public const TYPE_TEXT = 'text';
30
    public const TYPE_DATE = 'date';
31
    public const TYPE_TIME = 'time';
32
    public const TYPE_DATETIME = 'datetime';
33
    public const TYPE_BOOLEAN = 'bool';
34
35
    public const TYPES = [
36
        self::TYPE_INTEGER => ['tinyint', 'smallint', 'mediumint', 'int', 'bigint'],
37
        self::TYPE_FLOAT => ['real', 'double'],
38
        self::TYPE_DECIMAL => ['decimal', 'numeric'],
39
        self::TYPE_STRING => ['char', 'varchar'],
40
        self::TYPE_TEXT => ['tinytext', 'text', 'mediumtext', 'longtext', 'blob'],
41
        self::TYPE_DATE => ['date'],
42
        self::TYPE_TIME => ['time'],
43
        self::TYPE_DATETIME => ['datetime', 'timestamp'],
44
        self::TYPE_BOOLEAN => ['boolean'],
45
    ];
46
47
    public const PHP_CACHE_FOLDER = 'models';
48
49
    /**
50
     * Array asociativo con la última lista de tablas leídas de la base de datos.
51
     * Se ha optimizado generándolo como array asociativo.
52
     * Se ha probado cacheando en una tabla yaml, y así es mucho más rápido.
53
     *
54
     * @var null|array
55
     */
56
    private static $tableList = null;
57
    private static $xmlTableList = null;
58
59
    private static function getTablesFrom(string $folder): array
60
    {
61
        $fullFolder = $folder . '/Models/Tables/';
62
        $tables = scandir($fullFolder);
63
        if ($tables === false) {
64
            dump('No existe o no hay datos en ' . $fullFolder);
65
            return [];
66
        }
67
68
        $result = [];
69
        foreach ($tables as $table) {
70
            if ($table !== '.' && $table !== '..' && (substr($table, -5) === '.yaml')) {
71
                $result[substr($table, 0, strlen($table) - 5)] = $fullFolder . $table;
72
            }
73
        }
74
        return $result;
75
    }
76
77
    public static function getTables(): array
78
    {
79
        // TODO: Definir constante
80
        $tableName = BASE_FOLDER . '/tmp/tables.yaml';
81
82
        if (file_exists($tableName)) {
83
            return YAML::parse(file_get_contents($tableName));
84
        }
85
86
        $path = BASE_FOLDER . '/Modules/';
87
88
        $result = [];
89
        $modules = scandir($path);
90
        foreach ($modules as $module) {
91
            if ($module !== '.' && $module !== '..') {
92
                $result = array_merge($result, self::getTablesFrom($path . $module));
93
            }
94
        }
95
96
        $result = array_merge($result, self::getTablesFrom(BASE_FOLDER . '/src'));
97
98
        // file_put_contents($tableName, YAML::dump($result));
99
        return $result;
100
    }
101
102
    /**
103
     * Divide un tipo de dato de la base de datos en sus diferentes partes.
104
     *
105
     * @author  Rafael San José Tovar <[email protected]>
106
     * @version 2022.0903
107
     *
108
     * @param string $originalType
109
     *
110
     * @return array
111
     */
112
    public static function splitType(string $originalType): array
113
    {
114
        $replacesSources = [
115
            'character varying',
116
            // 'timestamp without time zone',
117
            'double precision',
118
        ];
119
        $replacesDestination = [
120
            'varchar',
121
            // 'timestamp',
122
            'double',
123
        ];
124
        $modifiedType = (str_replace($replacesSources, $replacesDestination, $originalType));
125
126
        if ($originalType !== $modifiedType) {
127
            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

127
            /** @scrutinizer ignore-call */ 
128
            debug_message("XML: Uso de '{$originalType}' en lugar de '{$modifiedType}'.");
Loading history...
128
        }
129
        $explode = explode(' ', strtolower($modifiedType));
130
131
        $pos = strpos($explode[0], '(');
132
        if ($pos > 0) {
133
            $begin = $pos + 1;
134
            $end = strpos($explode[0], ')');
135
            $type = substr($explode[0], 0, $pos);
136
            $length = substr($explode[0], $begin, $end - $begin);
137
        } else {
138
            $type = $explode[0];
139
            $length = null;
140
        }
141
142
        $pos = array_search('unsigned', $explode, true);
143
        $unsigned = $pos ? 'yes' : 'no';
144
145
        $pos = array_search('zerofill', $explode, true);
146
        $zerofill = $pos ? 'yes' : 'no';
147
148
        return ['type' => $type, 'length' => $length, 'unsigned' => $unsigned, 'zerofill' => $zerofill];
149
    }
150
151
    /**
152
     * Elimina la lista de tablas leídas de la base de datos, para comprobar si existe si tener que consultar cada vez.
153
     *
154
     * @author  Rafael San José Tovar <[email protected]>
155
     * @version 2022.0903
156
     *
157
     */
158
    public static function clearTableList()
159
    {
160
        self::$tableList = null;
161
    }
162
163
    /**
164
     * Ésto realiza comprobaciones adicionales.
165
     * Por lo que veo, lo único que hace es tratar de convertir una tabla que no es
166
     * InnoDB en InnoDB.
167
     * En otras bases de datos como Postgres no hace nada.
168
     *
169
     * @author  Rafael San José Tovar <[email protected]>
170
     * @version 2022.0903
171
     *
172
     * @param $table_name
173
     *
174
     * @return bool
175
     */
176
    public static function checkTableAux(string $table_name): bool
177
    {
178
        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

178
        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...
179
    }
180
181
    /**
182
     * Retorna el código SQL para actualizar las columnas de una tabla que han sufrido
183
     * modificaciones en su archivo de definición.
184
     *
185
     * @author  Rafael San José Tovar <[email protected]>
186
     * @author  Rafael San José Tovar <[email protected]>
187
     * @version 2022.0903
188
     *
189
     * @version 2022.0903
190
     *
191
     * @param string $table_name
192
     * @param array  $xml_cols
193
     * @param array  $db_cols
194
     *
195
     * @return string
196
     */
197
    public static function compareColumns(string $table_name, array $xml_cols, array $db_cols): string
198
    {
199
        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

199
        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...
200
    }
201
202
    /**
203
     * Retorna el código SQL para actualizar las constraint de una tabla que ha sufrido
204
     * modificaciones en su archivo de definición.
205
     *
206
     * @author  Rafael San José Tovar <[email protected]>
207
     * @version 2022.0903
208
     *
209
     * @param $table_name
210
     * @param $xml_cons
211
     * @param $db_cons
212
     * @param $delete_only
213
     *
214
     * @return string
215
     */
216
    public static function compareConstraints($table_name, $xml_cons, $db_cons, $delete_only = false): string
217
    {
218
        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

218
        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...
219
    }
220
221
    /**
222
     * Retorna el formato de fecha de la base de datos.
223
     *
224
     * @author  Rafael San José Tovar <[email protected]>
225
     * @version 2022.0903
226
     *
227
     * @return string
228
     */
229
    public static function dateStyle(): string
230
    {
231
        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

231
        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...
232
    }
233
234
    /**
235
     * Retorna el literal $str eliminando aquellos caracteres que pueden provocar problemas en
236
     * una consulta SQL.
237
     *
238
     * @author  Rafael San José Tovar <[email protected]>
239
     * @version 2022.0903
240
     *
241
     * @param string $str
242
     *
243
     * @return string
244
     */
245
    public static function escapeString(string $str): string
246
    {
247
        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

247
        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...
248
    }
249
250
    public static function generateTableSql(string $tablename): string
251
    {
252
        $xml = static::getXmlTable($tablename);
253
        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

253
        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...
254
    }
255
256
    public static function generateTableSeedSql(string $tablename): string
257
    {
258
        $seeds = static::getFilesFromPlugins('Model/seed/', '.csv');
259
        if (!isset($seeds[$tablename])) {
260
            return '';
261
        }
262
263
        $filename = $seeds[$tablename];
264
265
        $result = '';
266
267
        $rows = 10; // Indicamos el número de registros que vamos a insertar de una vez
268
        $handle = fopen($filename, "r");
269
        if ($handle === false) {
270
            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

270
            /** @scrutinizer ignore-call */ 
271
            debug_message('No ha sido posible abrir el archivo ' . $filename);
Loading history...
271
            return '';
272
        }
273
274
        // Asumimos que la primera fila es la cabecera...
275
        $header = fgetcsv($handle, 0, ';');
276
        if ($header === false) {
277
            debug_message('No ha sido posible leer la primera línea del archivo ' . $filename);
278
            fclose($handle);
279
            return '';
280
        }
281
282
        $sqlHeader = "INSERT INTO `{$tablename}` (`" . implode('`, `', $header) . '`) VALUES ';
283
        $row = 0;
284
        $sqlData = [];
285
        while (($data = fgetcsv($handle, 0, ';')) !== false) {
286
            // Entrecomillamos lo que no sea null.
287
            foreach ($data as $key => $datum) {
288
                if (mb_strtoupper($datum) !== 'NULL') {
289
                    $data[$key] = "'$datum'";
290
                }
291
            }
292
293
            if ($row % $rows === 0) {
294
                if (count($sqlData) > 0) {
295
                    $result .= ($sqlHeader . implode(', ', $sqlData) . ';' . PHP_EOL);
296
                }
297
                $sqlData = [];
298
            }
299
            $sqlData[] = '(' . implode(', ', $data) . ')';
300
            $row++;
301
        }
302
        if (count($sqlData) > 0) {
303
            $result .= ($sqlHeader . implode(', ', $sqlData) . ';' . PHP_EOL);
304
        }
305
        fclose($handle);
306
307
        return $result;
308
    }
309
310
    /**
311
     * Crea una tabla a partir de su estructura xml.
312
     * Puebla los datos
313
     * Retorna true si consigue crearla correctamente.
314
     * Retorna false si se produce un error durante la creación, o si ya existe.
315
     *
316
     * @author  Rafael San José Tovar <[email protected]>
317
     * @version 2022.0907
318
     *
319
     * @param string $tablename
320
     *
321
     * @return bool
322
     */
323
    public static function generateTable(string $tablename): bool
324
    {
325
        $sql = static::generateTableSql($tablename);
326
        $ok = DB::exec($sql);
327
        if (!$ok) {
328
            return false;
329
        }
330
        $sql = static::generateTableSeedSql($tablename);
331
        return DB::exec($sql);
332
    }
333
334
    /**
335
     * Contiene un array con las columnas de la tabla.
336
     *
337
     * @author  Rafael San José Tovar <[email protected]>
338
     * @version 2022.0903
339
     *
340
     * @param string $table_name
341
     *
342
     * @return array
343
     */
344
    public static function getColumns(string $table_name): array
345
    {
346
        $result = [];
347
        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

347
        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...
348
            $result[$column['name']] = $column;
349
        }
350
        return $result;
351
    }
352
353
    /**
354
     * Retorna un array asociativo con los archivos de la ruta especificada ($path),
355
     * que tengan extensión $extension.
356
     * El índice es el nombre del archivo sin extensión.
357
     *
358
     * @author  Rafael San José Tovar <[email protected]>
359
     * @version 2022.0904
360
     *
361
     * @param string $path
362
     * @param string $extension
363
     *
364
     * @return array
365
     */
366
    private static function getFilesFromPath(string $path, string $extension): array
367
    {
368
        $result = [];
369
370
        if (!file_exists($path)) {
371
            return $result;
372
        }
373
374
        $scanData = scandir($path);
375
        if (!is_array($scanData)) {
0 ignored issues
show
introduced by
The condition is_array($scanData) is always true.
Loading history...
376
            return $result;
377
        }
378
379
        foreach ($scanData as $scan) {
380
            // Excluímos las carpetas . y ..
381
            if (mb_strpos($scan, '.') === 0) {
382
                continue;
383
            }
384
            if (mb_substr($scan, -mb_strlen($extension)) === $extension) {
385
                $result[mb_substr($scan, 0, -mb_strlen($extension))] = constant('BASE_PATH') . '/' . $path . $scan;
386
            }
387
        }
388
389
        return $result;
390
    }
391
392
    /**
393
     * Obtiene todos los archivos de la ruta especificada para el núcleo y plugins.
394
     * En el caso de tablas repetidas, se mantiene el del último plugin activado.
395
     *
396
     * @author  Rafael San José Tovar <[email protected]>
397
     * @version 2022.0904
398
     *
399
     * @param string $folder
400
     * @param string $extension
401
     *
402
     * @return array
403
     */
404
    public static function getFilesFromPlugins(string $folder, string $extension): array
405
    {
406
        $result = [];
407
408
        // Ruta de los xml en formato antiguo
409
        $path = $folder;
410
        $result = array_merge($result, static::getFilesFromPath($path, $extension));
411
412
        // Ruta de los xml en formato nuevo
413
        $path = 'src/Xnet/' . $folder;
414
        $result = array_merge($result, static::getFilesFromPath($path, $extension));
415
416
        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...
417
            $path = 'plugins/' . $plugin . '/' . mb_strtolower($folder);
418
            $result = array_merge($result, static::getFilesFromPath($path, $extension));
419
420
            $path = 'plugins/' . $plugin . '/' . $folder;
421
            $result = array_merge($result, static::getFilesFromPath($path, $extension));
422
        }
423
424
        return $result;
425
    }
426
427
    /**
428
     * Carga el listado de archivos XML del archivo YAML existente.
429
     * Si el archivo YAML no existe, genera la información y lo crea.
430
     * Retorna un array asociativo con el contenido de dicho archivo.
431
     *
432
     * @author  Rafael San José Tovar <[email protected]>
433
     * @version 2022.0904
434
     *
435
     * @return array
436
     */
437
    private static function getYamlXmlFiles(): array
438
    {
439
        $result = XnetPhpFileCache::loadYamlFile(XnetConfig::PHP_CACHE_FOLDER, 'tables');
0 ignored issues
show
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...
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...
440
        if (!empty($result)) {
441
            return $result;
442
        }
443
444
        $result = static::getFilesFromPlugins('Model/table/', '.xml');
445
        XnetPhpFileCache::saveYamlFile(XnetConfig::PHP_CACHE_FOLDER, 'tables', $result);
446
        return $result;
447
    }
448
449
    /**
450
     * Retorna la ruta del archivo xml de configuración de la tabla $tablename
451
     *
452
     * @author  Rafael San José Tovar <[email protected]>
453
     * @version 2022.0903
454
     *
455
     * @param string $tablename
456
     *
457
     * @return string
458
     */
459
    public static function getXmlPath(string $tablename): ?string
460
    {
461
        $files = static::listXmlTables();
462
        if (isset($files[$tablename])) {
463
            return $files[$tablename];
464
        }
465
        return null;
466
    }
467
468
    /**
469
     * Incluye el archivo XML $child dentro de $parent, y retorna el resultado.
470
     *
471
     * @author  Rafael San José Tovar <[email protected]>
472
     * @version 2022.0903
473
     *
474
     * @param \SimpleXMLElement $parent
475
     * @param \SimpleXMLElement $child
476
     *
477
     * @return \SimpleXMLElement
478
     */
479
    private static function mergeXml(\SimpleXMLElement $parent, \SimpleXMLElement $child): \SimpleXMLElement
480
    {
481
        foreach (['columna', 'restriccion'] as $toMerge) {
482
            foreach ($child->{$toMerge} as $item) {
483
                $childItem = $parent->addChild($toMerge, $item);
484
                foreach ($item->children() as $child) {
485
                    $childItem->addChild($child->getName(), reset($child));
486
                }
487
                // Si es una relación extendida, tiene que ser nullable para poder desactivar el plugin
488
                if (!isset($childItem->nulo) && reset($childItem->tipo) === 'relationship') {
489
                    $childItem->addChild('nulo', 'YES');
490
                }
491
            }
492
        }
493
        return $parent;
494
    }
495
496
    /**
497
     * Carga el archivo XML $filename, incluyendo sus dependencias de otros XML
498
     *
499
     * @author  Rafael San José Tovar <[email protected]>
500
     * @version 2022.0810
501
     *
502
     * @param string $tablename
503
     *
504
     * @return \SimpleXMLElement
505
     */
506
    private static function loadXmlFile(string $tablename): \SimpleXMLElement
507
    {
508
        $filename = self::getXmlPath($tablename);
509
        if (empty($filename)) {
510
            die($tablename . ' XML not found');
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...
511
        }
512
513
        if (!file_exists($filename)) {
514
            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...
515
        }
516
517
        $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

517
        $xml = simplexml_load_string(file_get_contents($filename, /** @scrutinizer ignore-type */ FILE_USE_INCLUDE_PATH));
Loading history...
518
        if (!$xml) {
0 ignored issues
show
introduced by
$xml is of type SimpleXMLElement, thus it always evaluated to true.
Loading history...
519
            die('Error al leer el archivo ' . $filename);
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...
520
        }
521
522
        if (!isset($xml->incluye) || $xml->incluye->count() === 0) {
523
            return $xml;
524
        }
525
526
        // Si hay un apartado "incluye", hay que incluir las rutas
527
        foreach ($xml->incluye->children() as $item) {
528
            $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

528
            $includeFilename = './' . trim(reset(/** @scrutinizer ignore-type */ $item));
Loading history...
529
530
            $xmlParent = simplexml_load_string(file_get_contents($includeFilename, FILE_USE_INCLUDE_PATH));
531
            if (!$xmlParent) {
532
                die('Error al leer el archivo ' . $includeFilename);
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...
533
            }
534
535
            $xml = self::mergeXml($xmlParent, $xml);
536
        }
537
538
        return $xml;
539
    }
540
541
    /**
542
     * Normaliza la información de una columna con los datos que se le han pasado del XML.
543
     *
544
     * @author  Rafael San José Tovar <[email protected]>
545
     * @version 2022.0905
546
     *
547
     * @param \SimpleXMLElement $col
548
     *
549
     * @return array
550
     */
551
    private static function getXmlColumn(\SimpleXMLElement $col): array
552
    {
553
        $column = [];
554
        $key = (string) $col->nombre;
555
556
        $column['nombre'] = $key;
557
        $column['tipo'] = (string) $col->tipo;
558
559
        $column['nulo'] = 'YES';
560
        if ($col->nulo && mb_strtolower($col->nulo) == 'no') {
561
            $column['nulo'] = 'NO';
562
        }
563
564
        if (empty($col->defecto)) {
565
            $column['defecto'] = null;
566
        } else {
567
            $column['defecto'] = (string) $col->defecto;
568
        }
569
570
        /**
571
         * Pueden existir otras definiciones de limitaciones físicas como min y max
572
         * De existir, tienen que ser contempladas en el método test y tener mayor peso que
573
         * la limitación en plantilla.
574
         */
575
        foreach (['min', 'max'] as $field) {
576
            if (isset($col->{$field})) {
577
                $column[$field] = (string) $col->{$field};
578
            }
579
        }
580
581
        if (isset($col->description)) {
582
            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

582
            /** @scrutinizer ignore-call */ 
583
            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...
583
            $column['comentario'] = (string) $col->description;
584
        } elseif (isset($col->comment)) {
585
            debug_message('Cambie la etiqueta <comment> por comentario en ' . $col->nombre . ' de ' . $tablename);
586
            $column['comentario'] = (string) $col->comment;
587
        } elseif (isset($col->comentario)) {
588
            $column['comentario'] = (string) $col->comentario;
589
        }
590
591
        // Aquí vienen los datos adicionales...
592
593
        switch ($col->tipo) {
594
            case 'serial':
595
                $colType = constant('FS_DB_INTEGER');
596
                break;
597
            case 'autoincrement':
598
            case 'relationship':
599
                $colType = constant('DB_INDEX_TYPE');
600
                break;
601
            case 'boolean':
602
                $colType = 'tinyint(1) unsigned';
603
                break;
604
            default:
605
                $colType = (string) $col->tipo;
606
        }
607
        $typeArray = static::splitType($colType);
608
        $type = $typeArray['type'];
609
        $length = $typeArray['length'];
610
        $unsigned = $typeArray['unsigned'] === 'yes';
611
        $zerofill = $typeArray['zerofill'] === 'yes';
0 ignored issues
show
Unused Code introduced by
The assignment to $zerofill is dead and can be removed.
Loading history...
612
        $genericType = Schema::getTypeOf($type);
613
614
        $column['realtype'] = $type;
615
        $column['generictype'] = $genericType;
616
617
        if (isset($col->defecto)) {
618
            $column['default'] = trim($col->defecto, " \"'`");
619
        }
620
621
        switch ($genericType) {
622
            case 'string':
623
                $column['maxlength'] = $length;
624
                break;
625
            case 'integer':
626
                /**
627
                 * Lo primero es ver la capacidad física máxima según el tipo de dato.
628
                 */
629
                $bytes = 4;
630
                switch ($type) {
631
                    case 'tinyint':
632
                        $bytes = 1;
633
                        break;
634
                    case 'smallint':
635
                        $bytes = 2;
636
                        break;
637
                    case 'mediumint':
638
                        $bytes = 3;
639
                        break;
640
                    case 'int':
641
                        $bytes = 4;
642
                        break;
643
                    case 'bigint':
644
                        $bytes = 8;
645
                        break;
646
                }
647
                $bits = 8 * (int) $bytes;
648
                $physicalMaxLength = 2 ** $bits;
649
650
                /**
651
                 * $minDataLength y $maxDataLength contendrán el mínimo y máximo valor que puede contener el campo.
652
                 */
653
                $minDataLength = $unsigned ? 0 : -$physicalMaxLength / 2;
654
                $maxDataLength = ($unsigned ? $physicalMaxLength : $physicalMaxLength / 2) - 1;
655
656
                /**
657
                 * De momento, se asignan los límites máximos por el tipo de dato.
658
                 * En $min y $max, iremos arrastrando los límites conforme se vayan comprobando.
659
                 * $min nunca podrá ser menor que $minDataLength.
660
                 * $max nunca podrá ser mayor que $maxDataLength.
661
                 */
662
                $min = $minDataLength;
663
                $max = $maxDataLength;
664
665
                /**
666
                 * Se puede hacer una limitación física Se puede haber definido en el xml un min y un max.
667
                 * A todos los efectos, lo definido en el XML como min o max se toma como limitación
668
                 * física del campo.
669
                 */
670
                if (isset($col->min)) {
671
                    $minXmlLength = $col->min;
672
                    if ($minXmlLength > $minDataLength) {
673
                        $min = $minXmlLength;
674
                    } else {
675
                        debug_message("({$key}): Se ha especificado un min {$minXmlLength} en el XML, pero por el tipo de datos, el mínimo es {$minDataLength}.");
676
                    }
677
                }
678
                if (isset($col->max)) {
679
                    $maxXmlLength = $col->max;
680
                    if ($maxXmlLength < $maxDataLength) {
681
                        $max = $maxXmlLength;
682
                    } else {
683
                        debug_message("({$key}): Se ha especificado un min {$maxXmlLength} en el XML, pero por el tipo de datos, el máximo es {$maxDataLength}.");
684
                    }
685
                }
686
687
                $column['min'] = $min;
688
                $column['max'] = $max;
689
                break;
690
            default:
691
                // ???
692
        }
693
694
        return $column;
695
    }
696
697
    /**
698
     * Retorna un array con el contenido del archivo XML de la tabla seleccionada.
699
     *
700
     * @author  Rafael San José Tovar <[email protected]>
701
     * @version 2022.0904
702
     *
703
     * @param string $tablename
704
     *
705
     * @return array[]
706
     */
707
    public static function getXmlTable(string $tablename): array
708
    {
709
        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...
710
        $result = XnetPhpFileCache::loadYamlFile(static::PHP_CACHE_FOLDER, $tablename);
711
        XnetDebugBar::stopTimer('leexml' . $tablename);
712
713
        if (!empty($result)) {
714
            return $result;
715
        }
716
717
        XnetDebugBar::startTimer('creaxml' . $tablename, 'Crear archivo ' . $tablename);
718
719
        $xml = self::loadXmlFile($tablename);
720
721
        $columns = [];
722
        $constraints = [];
723
        if ($xml->columna) {
724
            foreach ($xml->columna as $col) {
725
                $columns[] = static::getXmlColumn($col);
726
            }
727
        }
728
729
        if ($xml->restriccion) {
730
            $i = 0;
731
            foreach ($xml->restriccion as $col) {
732
                $constraints[$i]['nombre'] = (string) $col->nombre;
733
                $constraints[$i]['consulta'] = (string) $col->consulta;
734
                $i++;
735
            }
736
        }
737
738
        $result = [
739
            'columns' => $columns,
740
            'constraints' => $constraints,
741
        ];
742
743
        if (!XnetPhpFileCache::saveYamlFile(static::PHP_CACHE_FOLDER, $tablename, $result)) {
744
            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

744
            /** @scrutinizer ignore-call */ 
745
            debug_message('No se ha podido guardar el XML de ' . $tablename . ' en la YAML caché.');
Loading history...
745
        }
746
        XnetDebugBar::stopTimer('creaxml' . $tablename);
747
        return $result;
748
    }
749
750
    public static function getConstraints(string $table_name, bool $extended = false): array
751
    {
752
        if ($extended) {
753
            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

753
            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...
754
        }
755
756
        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

756
        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...
757
    }
758
759
    public function deleteConstraint(string $table_name, string $constraint_name): bool
760
    {
761
        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

761
        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...
762
    }
763
764
    public function getIndexes($table_name)
765
    {
766
        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

766
        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...
767
    }
768
769
    public static function getSelects()
770
    {
771
        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

771
        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...
772
    }
773
774
    public static function getTransactions()
775
    {
776
        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

776
        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...
777
    }
778
779
    public static function lastval()
780
    {
781
        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

781
        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...
782
    }
783
784
    public function select_limit($sql, $limit = null, $offset = 0)
785
    {
786
        if ($limit === null) {
787
            $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

787
            $limit = /** @scrutinizer ignore-call */ get_item_limit();
Loading history...
788
        }
789
        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

789
        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...
790
    }
791
792
    public function sql_to_int($col_name)
793
    {
794
        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

794
        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...
795
    }
796
797
    /**
798
     * Obtiene un array asociativo indexado por el nombre de cada una de las tablas
799
     * de la base de datos. El valor de cada elemento se pone a true, pero lo que
800
     * realmente importa es el índice, pues se verifica si el índice está, que es
801
     * más rápido que buscar.
802
     * El array se cachea en self::$tableList para las próximas peticiones.
803
     *
804
     * @author  Rafael San José Tovar <[email protected]>
805
     * @version 2022.0904
806
     *
807
     * @return array
808
     */
809
    private static function listTables(): array
810
    {
811
        if (isset(self::$tableList)) {
812
            return self::$tableList;
813
        }
814
815
        $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

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

875
        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...
876
    }
877
878
    /**
879
     * Realiza una búsqueda sin distinguir case ni tildes.
880
     *
881
     * @author  Rafael San José Tovar <[email protected]>
882
     * @version 2022.0904
883
     *
884
     * @param $col_name
885
     * @param $search
886
     * @param $splitWord
887
     *
888
     * @return string
889
     */
890
    public function search_diacritic_insensitive($col_name, $search, $splitWord = '')
891
    {
892
        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

892
        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...
893
    }
894
}
895