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

YamlSchema::loadXmlFile()   B

Complexity

Conditions 8
Paths 7

Size

Total Lines 33
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 8
eloc 17
nc 7
nop 1
dl 0
loc 33
rs 8.4444
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
     * Obtiene el tipo genérico del tipo de dato que se le ha pasado.
153
     *
154
     * @author  Rafael San José Tovar <[email protected]>
155
     * @version 2022.0903
156
     *
157
     * @param string $type
158
     *
159
     * @return string
160
     */
161
    public static function getTypeOf(string $type): string
162
    {
163
        foreach (YamlSchema::TYPES as $index => $types) {
164
            if (in_array(strtolower($type), $types)) {
165
                return $index;
166
            }
167
        }
168
        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

168
        /** @scrutinizer ignore-call */ 
169
        debug_message($type . ' not found in DBSchema::getTypeOf()');
Loading history...
169
        return 'text';
170
    }
171
172
    /**
173
     * Elimina la lista de tablas leídas de la base de datos, para comprobar si existe si tener que consultar cada vez.
174
     *
175
     * @author  Rafael San José Tovar <[email protected]>
176
     * @version 2022.0903
177
     *
178
     */
179
    public static function clearTableList()
180
    {
181
        self::$tableList = null;
182
    }
183
184
    /**
185
     * Ésto realiza comprobaciones adicionales.
186
     * Por lo que veo, lo único que hace es tratar de convertir una tabla que no es
187
     * InnoDB en InnoDB.
188
     * En otras bases de datos como Postgres no hace nada.
189
     *
190
     * @author  Rafael San José Tovar <[email protected]>
191
     * @version 2022.0903
192
     *
193
     * @param $table_name
194
     *
195
     * @return bool
196
     */
197
    public static function checkTableAux(string $table_name): bool
198
    {
199
        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

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

220
        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...
221
    }
222
223
    /**
224
     * Retorna el código SQL para actualizar las constraint de una tabla que ha sufrido
225
     * modificaciones en su archivo de definición.
226
     *
227
     * @author  Rafael San José Tovar <[email protected]>
228
     * @version 2022.0903
229
     *
230
     * @param $table_name
231
     * @param $xml_cons
232
     * @param $db_cons
233
     * @param $delete_only
234
     *
235
     * @return string
236
     */
237
    public static function compareConstraints($table_name, $xml_cons, $db_cons, $delete_only = false): string
238
    {
239
        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

239
        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...
240
    }
241
242
    /**
243
     * Retorna el formato de fecha de la base de datos.
244
     *
245
     * @author  Rafael San José Tovar <[email protected]>
246
     * @version 2022.0903
247
     *
248
     * @return string
249
     */
250
    public static function dateStyle(): string
251
    {
252
        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

252
        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...
253
    }
254
255
    /**
256
     * Retorna el literal $str eliminando aquellos caracteres que pueden provocar problemas en
257
     * una consulta SQL.
258
     *
259
     * @author  Rafael San José Tovar <[email protected]>
260
     * @version 2022.0903
261
     *
262
     * @param string $str
263
     *
264
     * @return string
265
     */
266
    public static function escapeString(string $str): string
267
    {
268
        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

268
        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...
269
    }
270
271
    public static function generateTableSql(string $tablename): string
272
    {
273
        $xml = static::getXmlTable($tablename);
274
        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

274
        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...
275
    }
276
277
    public static function generateTableSeedSql(string $tablename): string
278
    {
279
        $seeds = static::getFilesFromPlugins('Model/seed/', '.csv');
280
        if (!isset($seeds[$tablename])) {
281
            return '';
282
        }
283
284
        $filename = $seeds[$tablename];
285
286
        $result = '';
287
288
        $rows = 10; // Indicamos el número de registros que vamos a insertar de una vez
289
        $handle = fopen($filename, "r");
290
        if ($handle === false) {
291
            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

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

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

538
        $xml = simplexml_load_string(file_get_contents($filename, /** @scrutinizer ignore-type */ FILE_USE_INCLUDE_PATH));
Loading history...
539
        if (!$xml) {
0 ignored issues
show
introduced by
$xml is of type SimpleXMLElement, thus it always evaluated to true.
Loading history...
540
            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...
541
        }
542
543
        if (!isset($xml->incluye) || $xml->incluye->count() === 0) {
544
            return $xml;
545
        }
546
547
        // Si hay un apartado "incluye", hay que incluir las rutas
548
        foreach ($xml->incluye->children() as $item) {
549
            $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

549
            $includeFilename = './' . trim(reset(/** @scrutinizer ignore-type */ $item));
Loading history...
550
551
            $xmlParent = simplexml_load_string(file_get_contents($includeFilename, FILE_USE_INCLUDE_PATH));
552
            if (!$xmlParent) {
553
                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...
554
            }
555
556
            $xml = self::mergeXml($xmlParent, $xml);
557
        }
558
559
        return $xml;
560
    }
561
562
    /**
563
     * Normaliza la información de una columna con los datos que se le han pasado del XML.
564
     *
565
     * @author  Rafael San José Tovar <[email protected]>
566
     * @version 2022.0905
567
     *
568
     * @param \SimpleXMLElement $col
569
     *
570
     * @return array
571
     */
572
    private static function getXmlColumn(\SimpleXMLElement $col): array
573
    {
574
        $column = [];
575
        $key = (string) $col->nombre;
576
577
        $column['nombre'] = $key;
578
        $column['tipo'] = (string) $col->tipo;
579
580
        $column['nulo'] = 'YES';
581
        if ($col->nulo && mb_strtolower($col->nulo) == 'no') {
582
            $column['nulo'] = 'NO';
583
        }
584
585
        if (empty($col->defecto)) {
586
            $column['defecto'] = null;
587
        } else {
588
            $column['defecto'] = (string) $col->defecto;
589
        }
590
591
        /**
592
         * Pueden existir otras definiciones de limitaciones físicas como min y max
593
         * De existir, tienen que ser contempladas en el método test y tener mayor peso que
594
         * la limitación en plantilla.
595
         */
596
        foreach (['min', 'max'] as $field) {
597
            if (isset($col->{$field})) {
598
                $column[$field] = (string) $col->{$field};
599
            }
600
        }
601
602
        if (isset($col->description)) {
603
            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

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

765
            /** @scrutinizer ignore-call */ 
766
            debug_message('No se ha podido guardar el XML de ' . $tablename . ' en la YAML caché.');
Loading history...
766
        }
767
        XnetDebugBar::stopTimer('creaxml' . $tablename);
768
        return $result;
769
    }
770
771
    /**
772
     * Obtiene un array asociativo con las columnas de la tabla.
773
     * Es igual que getColumns, sólo que el índice del array es el nombre de la columna.
774
     *
775
     * @author     Rafael San José Tovar <[email protected]>
776
     * @version    2022.0903
777
     *
778
     * @param string $table_name
779
     *
780
     * @return array
781
     *
782
     * @deprecated Utilice getColumns, ya es lo mismo
783
     */
784
    public static function getColumnsAssociativeArray(string $table_name): array
785
    {
786
        return static::getColumns($table_name);
787
    }
788
789
    public static function getConstraints(string $table_name, bool $extended = false): array
790
    {
791
        if ($extended) {
792
            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

792
            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...
793
        }
794
795
        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

795
        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...
796
    }
797
798
    public function deleteConstraint(string $table_name, string $constraint_name): bool
799
    {
800
        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

800
        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...
801
    }
802
803
    public function getIndexes($table_name)
804
    {
805
        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

805
        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...
806
    }
807
808
    public static function getSelects()
809
    {
810
        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

810
        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...
811
    }
812
813
    public static function getTransactions()
814
    {
815
        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

815
        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...
816
    }
817
818
    public static function lastval()
819
    {
820
        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

820
        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...
821
    }
822
823
    public function select_limit($sql, $limit = null, $offset = 0)
824
    {
825
        if ($limit === null) {
826
            $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

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

828
        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...
829
    }
830
831
    public function sql_to_int($col_name)
832
    {
833
        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

833
        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...
834
    }
835
836
    /**
837
     * Obtiene un array asociativo indexado por el nombre de cada una de las tablas
838
     * de la base de datos. El valor de cada elemento se pone a true, pero lo que
839
     * realmente importa es el índice, pues se verifica si el índice está, que es
840
     * más rápido que buscar.
841
     * El array se cachea en self::$tableList para las próximas peticiones.
842
     *
843
     * @author  Rafael San José Tovar <[email protected]>
844
     * @version 2022.0904
845
     *
846
     * @return array
847
     */
848
    private static function listTables(): array
849
    {
850
        if (isset(self::$tableList)) {
851
            return self::$tableList;
852
        }
853
854
        $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

854
        /** @scrutinizer ignore-call */ 
855
        $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...
855
        self::$tableList = [];
856
        foreach ($items as $item) {
857
            self::$tableList[$item['name']] = true;
858
        }
859
        return self::$tableList;
860
    }
861
862
    /**
863
     * Obtiene un array asociativo indexado por el nombre de las tablas de la base
864
     * de datos, tomadas de los archivos XML de definición.
865
     * El array contiene la ruta completa al archivo XML correspondiente.
866
     * El array se cachea en self::$xmlTableList para las siguientes peticiones,
867
     * pero además, la búsqueda en el sistema de archivos también se cachea en
868
     * un archivo yaml, que sólo se regenera al limpiar caché.
869
     *
870
     * @author  Rafael San José Tovar <[email protected]>
871
     * @version 2022.0904
872
     *
873
     * @return array
874
     */
875
    public static function listXmlTables($force = false): array
876
    {
877
        if (isset(self::$xmlTableList) && !$force) {
878
            return self::$xmlTableList;
879
        }
880
881
        self::$xmlTableList = static::getYamlXmlFiles();
882
        return self::$xmlTableList;
883
    }
884
885
    /**
886
     * Retorna TRUE si la tabla $name existe en la base de datos.
887
     *
888
     * @author  Rafael San José Tovar <[email protected]>
889
     * @version 2022.0904
890
     *
891
     * @param $name
892
     *
893
     * @return bool
894
     */
895
    public static function tableExists($name)
896
    {
897
        $list = self::listTables();
898
        return isset($list[$name]);
899
    }
900
901
    /**
902
     * Actualiza el juego de caracteres y cómo se aplican las comparaciones.
903
     *
904
     * @author  Rafael San José Tovar <[email protected]>
905
     * @version 2022.0903
906
     *
907
     * @param string $charset
908
     * @param string $collation
909
     *
910
     * @return bool
911
     */
912
    public static function updateCollation(string $charset = 'utf8mb4', string $collation = 'utf8mb4_bin'): bool
913
    {
914
        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

914
        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...
915
    }
916
917
    /**
918
     * Realiza una búsqueda sin distinguir case ni tildes.
919
     *
920
     * @author  Rafael San José Tovar <[email protected]>
921
     * @version 2022.0904
922
     *
923
     * @param $col_name
924
     * @param $search
925
     * @param $splitWord
926
     *
927
     * @return string
928
     */
929
    public function search_diacritic_insensitive($col_name, $search, $splitWord = '')
930
    {
931
        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

931
        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...
932
    }
933
}
934