Test Failed
Push — main ( b6e144...1a2341 )
by Rafael
05:34
created

Schema::getSeed()   B

Complexity

Conditions 11
Paths 24

Size

Total Lines 56
Code Lines 34

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 11
eloc 34
nc 24
nop 1
dl 0
loc 56
rs 7.3166
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
/**
3
 * Copyright (C) 2022-2023  Rafael San José Tovar   <[email protected]>
4
 *
5
 * This program is free software; you can redistribute it and/or modify
6
 * it under the terms of the GNU General Public License as published by
7
 * the Free Software Foundation; either version 3 of the License, or
8
 * (at your option) any later version.
9
 *
10
 * This program is distributed in the hope that it will be useful,
11
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
 * GNU General Public License for more details.
14
 *
15
 * You should have received a copy of the GNU General Public License
16
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
17
 */
18
19
namespace Alxarafe\Database;
20
21
use Alxarafe\Core\Helpers\Dispatcher;
22
use Alxarafe\Core\Singletons\Config;
23
use Alxarafe\Core\Singletons\Debug;
24
use Alxarafe\Core\Singletons\FlashMessages;
25
use Alxarafe\Core\Singletons\Translator;
26
use DebugBar\DebugBarException;
27
use Symfony\Component\Yaml\Yaml;
28
29
/**
30
 * Class Schema
31
 *
32
 * La clase abstracta Schema, define un esquema de base de datos teórico al que
33
 * se traduce la base de datos real y viceversa, de manera que el código sea
34
 * en la medida de lo posible, no dependiente de la base de datos real.
35
 *
36
 * TODO: ¿La información cacheada se procesa en YamlSchema o no merece la pena?
37
 *
38
 * @author  Rafael San José Tovar <[email protected]>
39
 * @version 2023.0101
40
 *
41
 * @package Alxarafe\Database
42
 */
43
class Schema
44
{
45
    /**
46
     * Tipo entero. Número sin decimales.
47
     */
48
    public const TYPE_INTEGER = 'integer';
49
50
    /**
51
     * Tipo real o coma flotante. Número con decimales. Puede dar problema con redondeos.
52
     */
53
    public const TYPE_FLOAT = 'float';
54
55
    /**
56
     * Tipo numérico de coma fija. Número con N decimales y precisión absoluta.
57
     * Es igual que un integer, pero se asume que un número determinado de dígitos son decimales.
58
     */
59
    public const TYPE_DECIMAL = 'decimal';
60
61
    /**
62
     * Tipo cadena de texto
63
     */
64
    public const TYPE_STRING = 'string';
65
66
    /**
67
     * Tipo bloque de texto
68
     */
69
    public const TYPE_TEXT = 'text';
70
71
    /**
72
     * Tipo fecha
73
     */
74
    public const TYPE_DATE = 'date';
75
76
    /**
77
     * Tipo hora
78
     */
79
    public const TYPE_TIME = 'time';
80
81
    /**
82
     * Tipo fecha + hora.
83
     * TODO: Hay que revisar el tema de la zona horaria.
84
     *       De lógica, siempre se debe de almacenar como UTC y convertir al guardar y leer.
85
     */
86
    public const TYPE_DATETIME = 'datetime';
87
88
    /**
89
     * Tipo lógico: TRUE o FALSE.
90
     */
91
    public const TYPE_BOOLEAN = 'bool';
92
93
    /**
94
     * Retorno de carro y salto de línea
95
     */
96
    const CRLF = "\r\n";
97
98
    /**
99
     * Contiene la definición ampliada de la estructura de la base de datos.
100
     *
101
     * @var array
102
     */
103
    public static array $bbddStructure;
104
105
    public static function checkDatabaseStructure()
106
    {
107
        foreach (YamlSchema::getTables() as $key => $table) {
108
            if (!file_exists($table)) {
109
                Debug::message('No existe la tabla ' . $table);
110
            }
111
            Debug::message("Verificando la tabla $key, definida en $table.");
112
            if (!static::checkStructure($key, $table)) {
113
                FlashMessages::setError('Error al comprobar la estructura de la tabla ' . $table);
114
            }
115
        }
116
    }
117
118
    /**
119
     * Return true if $tableName exists in database
120
     *
121
     * @param string $tableName
122
     *
123
     * @return bool
124
     * @throws DebugBarException
125
     */
126
    public static function tableExists($tableName): bool
127
    {
128
        $tableNameWithPrefix = DB::$dbPrefix . $tableName;
129
        $dbName = DB::$dbName;
130
        $sql = "SELECT COUNT(*) AS Total FROM information_schema.tables WHERE table_schema = '{$dbName}' AND table_name='{$tableNameWithPrefix}'";
131
132
        $data = Engine::select($sql);
133
        $result = reset($data);
134
135
        return $result['Total'] === '1';
136
    }
137
138
    private static function getFieldsAndIndexes($tableName, $path): array
0 ignored issues
show
Unused Code introduced by
The parameter $tableName is not used and could be removed. ( Ignorable by Annotation )

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

138
    private static function getFieldsAndIndexes(/** @scrutinizer ignore-unused */ $tableName, $path): array

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The method getFieldsAndIndexes() is not used, and could be removed.

This check looks for private methods that have been defined, but are not used inside the class.

Loading history...
139
    {
140
        $data = Yaml::parseFile($path);
141
142
        $result = [];
143
        foreach ($data['fields'] ?? [] as $key => $datum) {
144
            $datum['key'] = $key;
145
            $result['fields'][$key]['db'] = DB::normalizeFromYaml($datum);
146
            $result['fields'][$key]['info'] = Schema::normalize($datum);
0 ignored issues
show
Bug introduced by
The method normalize() does not exist on Alxarafe\Database\Schema. ( Ignorable by Annotation )

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

146
            /** @scrutinizer ignore-call */ 
147
            $result['fields'][$key]['info'] = Schema::normalize($datum);

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...
147
            if ($result['fields'][$key]['type'] === 'autoincrement') {
148
                // TODO: Ver cómo tendría que ser la primary key
149
                $result['indexes']['primary'] = $key;
150
            }
151
        }
152
        foreach ($data['indexes'] ?? [] as $key => $datum) {
153
            $datum['key'] = $key;
154
            $result['indexes'][$key] = $datum;
155
        }
156
157
        /*
158
        Igual conviene crear una clase:
159
        - DBSchema (con los datos de la base de datos real)
160
        - DefinedSchema (con los datos definidos)
161
        y que Schema cree o adapte según los datos de ambas. Que cada una lleve lo suyo
162
163
        Que el resultado se guarde en el yaml y que se encargue de realizar las conversines
164
    oportunas siempre que no suponga una pérdida de datos.
165
        */
166
167
        return $result;
168
    }
169
170
    private static function getFields($tableName): array
0 ignored issues
show
Unused Code introduced by
The method getFields() is not used, and could be removed.

This check looks for private methods that have been defined, but are not used inside the class.

Loading history...
171
    {
172
        $yamlSourceFilename = self::$tables[$tableName];
0 ignored issues
show
Bug Best Practice introduced by
The property tables does not exist on Alxarafe\Database\Schema. Did you maybe forget to declare it?
Loading history...
173
        if (!file_exists($yamlSourceFilename)) {
174
            dump('No existe el archivo ' . $yamlSourceFilename);
175
        }
176
177
        $data = Yaml::parseFile($yamlSourceFilename);
178
179
        $result = [];
180
        foreach ($data as $key => $datum) {
181
            $datum['key'] = $key;
182
            $result[$key] = Schema::normalize($datum);
183
        }
184
185
        /*
186
        Igual conviene crear una clase:
187
        - DBSchema (con los datos de la base de datos real)
188
        - DefinedSchema (con los datos definidos)
189
        y que Schema cree o adapte según los datos de ambas. Que cada una lleve lo suyo
190
191
        Que el resultado se guarde en el yaml y que se encargue de realizar las conversines
192
    oportunas siempre que no suponga una pérdida de datos.
193
        */
194
195
        return $result;
196
    }
197
198
    private static function getIndexes($tableName): array
0 ignored issues
show
Unused Code introduced by
The method getIndexes() is not used, and could be removed.

This check looks for private methods that have been defined, but are not used inside the class.

Loading history...
Unused Code introduced by
The parameter $tableName is not used and could be removed. ( Ignorable by Annotation )

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

198
    private static function getIndexes(/** @scrutinizer ignore-unused */ $tableName): array

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
199
    {
200
        $result = [];
201
        return $result;
202
    }
203
204
    private static function getRelated($tableName): array
0 ignored issues
show
Unused Code introduced by
The method getRelated() is not used, and could be removed.

This check looks for private methods that have been defined, but are not used inside the class.

Loading history...
Unused Code introduced by
The parameter $tableName is not used and could be removed. ( Ignorable by Annotation )

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

204
    private static function getRelated(/** @scrutinizer ignore-unused */ $tableName): array

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
205
    {
206
        $result = [];
207
        return $result;
208
    }
209
210
    private static function checkTable(string $tableName, string $path, bool $create = true): array
0 ignored issues
show
Unused Code introduced by
The parameter $create is not used and could be removed. ( Ignorable by Annotation )

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

210
    private static function checkTable(string $tableName, string $path, /** @scrutinizer ignore-unused */ bool $create = true): array

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $tableName is not used and could be removed. ( Ignorable by Annotation )

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

210
    private static function checkTable(/** @scrutinizer ignore-unused */ string $tableName, string $path, bool $create = true): array

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
211
    {
212
        $yaml = Yaml::parseFile($path);
213
        $fields = $yaml['fields'] ?? [];
214
215
        $data = [];
216
        foreach ($fields as $key => $field) {
217
            $field['key'] = $key;
218
            $schema = DB::yamlFieldToSchema($field);
219
            $data[$key]['db'] = DB::yamlFieldToDb($schema);
220
            $data[$key]['schema'] = $schema;
221
        }
222
223
        $indexes = $yaml['indexes'] ?? [];
224
225
        return [
226
            'fields' => $data,
227
            'indexes' => $indexes,
228
        ];
229
    }
230
231
    /**
232
     * Comprueba la estructura de la tabla y la crea si no existe y así se solicita.
233
     * Si los datos de la estructura no están en la caché, los regenera y almacena.
234
     * Al regenerar los datos para la caché, también realiza una verificación de
235
     * la estructura por si hay cambios que aplicar en la misma.
236
     *
237
     * TODO: Es mejor que haya un checkStructure que genere TODAS las tablas e índices
238
     * Ese checkstructure se debe de generar tras limpiar caché.
239
     * La caché deberá de limpiarse cada vez que se active o desactive un módulo.
240
     * El último paso de la generación de tablas, sería comprobar las dependencias
241
     * de tablas para saber cuántas tablas usan una constraint de cada tabla para poder
242
     * realizar cambios en la base de datos y tener una visión más nítida de la misma en
243
     * cualquier momento, si bien, esa estructura no será clara hasta que no se hayan leído
244
     * todas, y si hay un cambio entre medias, pues igual la única solución viable es
245
     * determinarlo por la propia base de datos.
246
     *
247
     * @author  Rafael San José Tovar <[email protected]>
248
     * @version 2023.0105
249
     *
250
     * @param string $tableName
251
     * @param string $path
252
     * @param bool   $create
253
     *
254
     * @return bool
255
     * @throws DebugBarException
256
     */
257
    private static function checkStructure(string $tableName, string $path, bool $create = true): bool
258
    {
259
        // Si el dato ya ha sido cargado, retornamos porque no hay nada que hacer.
260
        if (!empty(self::$bbddStructure[$tableName])) {
261
            return true;
262
        }
263
264
        // Si no está, pero está cacheado, se recupera de la caché y se retorna.
265
        self::$bbddStructure[$tableName] = YamlSchema::loadCacheYamlFile(YamlSchema::YAML_CACHE_TABLES_DIR, $tableName);
266
        if (!empty(self::$bbddStructure[$tableName])) {
267
            return true;
268
        }
269
270
        // Si no está cacheado, entonces hay que comprobar si hay cambios en la estructura y regenerarla.
271
        self::$bbddStructure[$tableName] = self::checkTable($tableName, $path, $create);
272
273
        if (DB::tableExists($tableName)) {
274
            Debug::message('La tabla ' . $tableName . ' existe');
275
            if (!self::updateTable($tableName)) {
276
                FlashMessages::setError(Translator::trans('table_creation_error', ['%tablename%' => $tableName]));
277
            }
278
        } else {
279
            Debug::message('La tabla ' . $tableName . ' NO existe');
280
            if (!self::createTable($tableName)) {
281
                FlashMessages::setError(Translator::trans('table_creation_error', ['%tablename%' => $tableName]));
282
            }
283
        }
284
285
        return true;
286
        die('Por aquí vamos ahora...');
0 ignored issues
show
Unused Code introduced by
ExitNode is not reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

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...
287
288
        if (!YamlSchema::saveCacheYamlFile(YamlSchema::YAML_CACHE_TABLES_DIR, $tableName, self::$bbddStructure[$tableName])) {
289
            Debug::message('No se ha podido guardar la información de caché para la tabla ' . $tableName);
290
            return false;
291
        }
292
        return true;
293
    }
294
295
    /**
296
     * Obtiene el tipo genérico del tipo de dato que se le ha pasado.
297
     *
298
     * @author  Rafael San José Tovar <[email protected]>
299
     * @version 2023.0101
300
     *
301
     * @param string $type
302
     *
303
     * @return string
304
     */
305
    public static function getTypeOf(string $type): string
306
    {
307
        foreach (DB::getDataTypes() as $index => $types) {
308
            if (in_array(strtolower($type), $types)) {
309
                return $index;
310
            }
311
        }
312
        Debug::message($type . ' not found in DBSchema::getTypeOf()');
313
        return 'text';
314
    }
315
316
    private static function splitType(string $originalType): array
0 ignored issues
show
Unused Code introduced by
The method splitType() is not used, and could be removed.

This check looks for private methods that have been defined, but are not used inside the class.

Loading history...
317
    {
318
        $replacesSources = [
319
            'character varying',
320
            // 'timestamp without time zone',
321
            'double precision',
322
        ];
323
        $replacesDestination = [
324
            'varchar',
325
            // 'timestamp',
326
            'double',
327
        ];
328
        $modifiedType = (str_replace($replacesSources, $replacesDestination, $originalType));
329
330
        if ($originalType !== $modifiedType) {
331
            Debug::message("XML: Uso de '{$originalType}' en lugar de '{$modifiedType}'.");
332
        }
333
        $explode = explode(' ', strtolower($modifiedType));
334
335
        $pos = strpos($explode[0], '(');
336
        if ($pos > 0) {
337
            $begin = $pos + 1;
338
            $end = strpos($explode[0], ')');
339
            $type = substr($explode[0], 0, $pos);
340
            $length = substr($explode[0], $begin, $end - $begin);
341
        } else {
342
            $type = $explode[0];
343
            $length = null;
344
        }
345
346
        $pos = array_search('unsigned', $explode, true);
347
        $unsigned = $pos ? 'yes' : 'no';
348
349
        $pos = array_search('zerofill', $explode, true);
350
        $zerofill = $pos ? 'yes' : 'no';
351
352
        return ['type' => $type, 'length' => $length, 'unsigned' => $unsigned, 'zerofill' => $zerofill];
353
    }
354
355
    /**
356
     * Create a table in the database.
357
     * Build the default fields, indexes and values defined in the model.
358
     *
359
     * @param string $tableName
360
     *
361
     * @return bool
362
     * @throws DebugBarException
363
     */
364
365
    private static function createTable(string $tableName): bool
366
    {
367
        $tabla = self::$bbddStructure[$tableName];
368
        $sql = self::createFields($tableName, $tabla['fields']);
369
370
        foreach ($tabla['indexes'] as $name => $index) {
371
            $sql .= self::createIndex($tableName, $name, $index);
372
        }
373
374
        if (isset($tabla['values'])) {
375
            $sql .= self::setValues($tableName, $tabla['values']);
376
        } else {
377
            $sql .= self::getSeed($tableName);
378
        }
379
380
        return Engine::exec($sql);
381
    }
382
383
    private static function getSeed($tableName): string
384
    {
385
        $tableNameWithPrefix = DB::$dbPrefix . $tableName;
386
387
        $seeds = Dispatcher::getFiles('Seeds', 'csv');
388
389
        if (!isset($seeds[$tableName])) {
390
            return '';
391
        }
392
393
        $filename = $seeds[$tableName];
394
        if (!file_exists($filename)) {
395
            return '';
396
        }
397
398
        $rows = 10; // Indicamos el número de registros que vamos a insertar de una vez
399
        $handle = fopen($filename, "r");
400
        if ($handle === false) {
401
            FlashMessages::addError('No ha sido posible abrir el archivo ' . $filename);
0 ignored issues
show
Bug introduced by
The method addError() does not exist on Alxarafe\Core\Singletons\FlashMessages. ( Ignorable by Annotation )

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

401
            FlashMessages::/** @scrutinizer ignore-call */ 
402
                           addError('No ha sido posible abrir el archivo ' . $filename);

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...
402
            return '';
403
        }
404
405
        // Asumimos que la primera fila es la cabecera...
406
        $header = fgetcsv($handle, 0, ';');
407
        if ($header === false) {
408
            FlashMessages::addError('No ha sido posible leer la primera línea del archivo ' . $filename);
409
            fclose($handle);
410
            return '';
411
        }
412
413
        $sqlHeader = "INSERT INTO `{$tableNameWithPrefix}` (`" . implode('`, `', $header) . '`) VALUES ';
414
        $row = 0;
415
        $sqlData = [];
416
        while (($data = fgetcsv($handle, 0, ';')) !== false) {
417
            // Entrecomillamos lo que no sea null.
418
            foreach ($data as $key => $datum) {
419
                if (mb_strtoupper($datum) !== 'NULL') {
420
                    $data[$key] = "'$datum'";
421
                }
422
            }
423
424
            if ($row % $rows === 0) {
425
                if (count($sqlData) > 0) {
426
                    $result .= ($sqlHeader . implode(', ', $sqlData) . ';' . PHP_EOL);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $result seems to be never defined.
Loading history...
427
                }
428
                $sqlData = [];
429
            }
430
            $sqlData[] = '(' . implode(', ', $data) . ')';
431
            $row++;
432
        }
433
        if (count($sqlData) > 0) {
434
            $result .= ($sqlHeader . implode(', ', $sqlData) . ';' . PHP_EOL);
435
        }
436
        fclose($handle);
437
438
        return $result;
439
    }
440
441
    private static function updateField(string $tableName, string $fieldName, array $structure): string
0 ignored issues
show
Unused Code introduced by
The method updateField() is not used, and could be removed.

This check looks for private methods that have been defined, but are not used inside the class.

Loading history...
442
    {
443
        dump([
444
            'tablename' => $tableName,
445
            'fieldname' => $fieldName,
446
            'new structure' => self::$bbddStructure[$tableName]['fields'][$fieldName],
447
            'structure' => $structure,
448
        ]);
449
        return '';
450
    }
451
452
    private static function updateTable(string $tableName): bool
453
    {
454
        $yamlStructure = self::$bbddStructure[$tableName];
455
        $dbStructure = DB::getColumns($tableName);
456
457
        $changes = [];
458
        foreach ($yamlStructure['fields'] as $field => $newStructure) {
459
            $oldDb = $dbStructure[$field];
460
            $newDb = $newStructure['db'];
461
462
            $dif = array_diff($oldDb, $newDb);
463
            if (count($dif) > 0) {
464
                $changes[] = DB::modify($tableName, $oldDb, $newDb);
465
            }
466
        }
467
468
        if (empty($changes)) {
469
            return true;
470
        }
471
472
        // dump(['changes in ' . $tableName => $changes]);
473
        $result = true;
474
        foreach ($changes as $change) {
475
            $result = $result && Engine::exec($change);
476
        }
477
        return $result;
478
    }
479
480
    /**
481
     * Build the SQL statement to create the fields in the table.
482
     * It can also create the primary key if the auto_increment attribute is defined.
483
     *
484
     * @param string $tablename
485
     * @param array  $fieldList
486
     *
487
     * @return string
488
     */
489
    protected static function createFields(string $tablename, array $fieldList): string
490
    {
491
        $tablenameWithPrefix = DB::$dbPrefix . $tablename;
492
493
        $sql = "CREATE TABLE $tablenameWithPrefix ( ";
494
        foreach ($fieldList as $index => $column) {
495
            $col = $column['schema'];
496
            if (!isset($col['dbtype'])) {
497
                die('Tipo no especificado en createTable ' . $index);
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 string. 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...
498
            }
499
500
            $sql .= '`' . $index . '` ' . $col['dbtype'];
501
            $nulo = isset($col['null']) && $col['null'];
502
503
            if (strtolower($col['type']) === 'autoincrement') {
504
                $nulo = false;
505
                $sql .= ' PRIMARY KEY AUTO_INCREMENT';
506
            }
507
508
            $sql .= ($nulo ? '' : ' NOT') . ' NULL';
509
510
            $tmpDefecto = $col['default'] ?? null;
511
            $defecto = '';
512
            if (isset($tmpDefecto)) {
513
                if ($tmpDefecto == 'CURRENT_TIMESTAMP') {
514
                    $defecto = "$tmpDefecto";
515
                } else {
516
                    $defecto = "'$tmpDefecto'";
517
                }
518
            } else {
519
                if ($nulo) {
520
                    $defecto = 'NULL';
521
                }
522
            }
523
524
            if ($defecto != '') {
525
                $sql .= ' DEFAULT ' . $defecto;
526
            }
527
528
            $sql .= ', ';
529
        }
530
        $sql = substr($sql, 0, -2); // Quitamos la coma y el espacio del final
531
        $sql .= ') ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;' . self::CRLF;
532
533
        return $sql;
534
    }
535
536
    /**
537
     * Create the SQL statements for the construction of one index.
538
     * In the case of the primary index, it is not necessary if it is auto_increment.
539
     *
540
     * TODO:
541
     *
542
     * Moreover, it should not be defined if it is auto_increment because it would
543
     * generate an error when it already exists.
544
     *
545
     * @param string $tableName
546
     * @param string $indexname
547
     * @param array  $indexData
548
     *
549
     * @return string
550
     */
551
    protected static function createIndex($tableName, $indexname, $indexData)
552
    {
553
        $tableNameWithPrefix = DB::$dbPrefix . $tableName;
554
555
        $sql = "ALTER TABLE $tableNameWithPrefix ADD CONSTRAINT $indexname ";
556
557
        $command = '';
558
        // https://www.w3schools.com/sql/sql_primarykey.asp
559
        // ALTER TABLE Persons ADD CONSTRAINT PK_Person PRIMARY KEY (ID,LastName);
560
        if (isset($indexData['primary'])) {
561
            $command = 'PRIMARY KEY ';
562
            $fields = $indexData['primary'];
563
        }
564
565
        // https://www.w3schools.com/sql/sql_create_index.asp
566
        // CREATE INDEX idx_pname ON Persons (LastName, FirstName);
567
        if (isset($indexData['index'])) {
568
            $command = 'INDEX ';
569
            $fields = $indexData['index'];
570
        }
571
572
        // https://www.w3schools.com/sql/sql_unique.asp
573
        // ALTER TABLE Persons ADD CONSTRAINT UC_Person UNIQUE (ID,LastName);
574
        if (isset($indexData['unique'])) {
575
            $command = 'UNIQUE INDEX ';
576
            $fields = $indexData['column'];
577
        }
578
579
        if ($command == '') {
580
            // https://www.w3schools.com/sql/sql_foreignkey.asp
581
            // ALTER TABLE Orders ADD CONSTRAINT FK_PersonOrder FOREIGN KEY (PersonID) REFERENCES Persons(PersonID);
582
            if (isset($indexData['FOREIGN'])) {
583
                $command = 'FOREIGN KEY ';
584
                $foreignField = $indexData['FOREIGN'];
585
                if (isset($indexData['REFERENCES'])) {
586
                    $references = $indexData['REFERENCES'];
587
                    if (!is_array($references)) {
588
                        die('Esperaba un array en REFERENCES: ' . $tableNameWithPrefix . '/' . $indexname);
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...
589
                    }
590
                    if (count($references) != 1) {
591
                        die('Esperaba un array de 1 elemento en REFERENCES: ' . $tableNameWithPrefix . '/' . $indexname);
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...
592
                    }
593
                    $refTable = key($references);
594
                    $fields = '(' . implode(',', $references) . ')';
595
                } else {
596
                    die('FOREIGN necesita REFERENCES en ' . $tableNameWithPrefix . '/' . $indexname);
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...
597
                }
598
599
                $sql .= $command . ' ' . $foreignField . ' REFERENCES ' . $refTable . $fields;
600
601
                if (isset($indexData['ON']) && is_array($indexData['ON'])) {
602
                    foreach ($indexData['ON'] as $key => $value) {
603
                        $sql .= ' ON ' . $key . ' ' . $value . ', ';
604
                    }
605
                    $sql = substr($sql, 0, -2); // Quitamos el ', ' de detrás
606
                }
607
            }
608
        } else {
609
            if (is_array($fields)) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $fields does not seem to be defined for all execution paths leading up to this point.
Loading history...
610
                $fields = '(' . implode(',', $fields) . ')';
611
            } else {
612
                $fields = "($fields)";
613
            }
614
615
            if ($command == 'INDEX ') {
616
                $sql = "CREATE INDEX {$indexname} ON {$tableNameWithPrefix}" . $fields;
617
            } else {
618
                $sql .= $command . ' ' . $fields;
619
            }
620
        }
621
622
        return $sql . ';' . self::CRLF;
623
    }
624
625
    /**
626
     * Create the SQL statements to fill the table with default data.
627
     *
628
     * @param string $tableName
629
     * @param array  $values
630
     *
631
     * @return string
632
     */
633
    protected static function setValues(string $tableName, array $values): string
634
    {
635
        $tablenameWithPrefix = DB::$dbPrefix . $tableName;
636
637
        $sql = "INSERT INTO $tablenameWithPrefix ";
638
        $header = true;
639
        foreach ($values as $value) {
640
            $fields = "(";
641
            $datos = "(";
642
            foreach ($value as $fname => $fvalue) {
643
                $fields .= $fname . ", ";
644
                $datos .= "'$fvalue'" . ", ";
645
            }
646
            $fields = substr($fields, 0, -2) . ") ";
647
            $datos = substr($datos, 0, -2) . "), ";
648
649
            if ($header) {
650
                $sql .= $fields . " VALUES ";
651
                $header = false;
652
            }
653
654
            $sql .= $datos;
655
        }
656
657
        return substr($sql, 0, -2) . self::CRLF;
658
    }
659
}
660