Index::set()   F
last analyzed

Complexity

Conditions 17
Paths 12288

Size

Total Lines 63
Code Lines 37

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 28
CRAP Score 21.157

Importance

Changes 0
Metric Value
eloc 37
dl 0
loc 63
ccs 28
cts 37
cp 0.7568
rs 1.0499
c 0
b 0
f 0
cc 17
nc 12288
nop 1
crap 21.157

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
declare(strict_types=1);
4
5
namespace PhpMyAdmin\Indexes;
6
7
use PhpMyAdmin\Dbal\DatabaseInterface;
8
use PhpMyAdmin\Message;
9
10
use function __;
11
use function array_pop;
12
use function count;
13
use function htmlspecialchars;
14
15
/**
16
 * Index manipulation class
17
 */
18
class Index
19
{
20
    public const PRIMARY = 1;
21
    public const UNIQUE = 2;
22
    public const INDEX = 4;
23
    public const SPATIAL = 8;
24
    public const FULLTEXT = 16;
25
26
    /**
27
     * Class-wide storage container for indexes (caching, singleton)
28
     *
29
     * @var array<string, array<string, array<string, Index>>>
30
     */
31
    private static array $registry = [];
32
33
    /** @var string The name of the schema */
34
    private string $schema = '';
35
36
    /** @var string The name of the table */
37
    private string $table = '';
38
39
    /** @var string The name of the index */
40
    private string $name = '';
41
42
    /**
43
     * Columns in index
44
     *
45
     * @var array<string|int, IndexColumn>
46
     */
47
    private array $columns = [];
48
49
    /**
50
     * The index method used (BTREE, HASH, RTREE).
51
     */
52
    private string $type = '';
53
54
    /**
55
     * The index choice (PRIMARY, UNIQUE, INDEX, SPATIAL, FULLTEXT)
56
     */
57
    private string $choice = '';
58
59
    /**
60
     * Various remarks.
61
     */
62
    private string $remarks = '';
63
64
    /**
65
     * Any comment provided for the index with a COMMENT attribute when the
66
     * index was created.
67
     */
68
    private string $comment = '';
69
70
    /** @var bool false if the index cannot contain duplicates, true if it can. */
71
    private bool $nonUnique = false;
72
73
    /**
74
     * Indicates how the key is packed. NULL if it is not.
75
     */
76
    private string|null $packed = null;
77
78
    /**
79
     * Block size for the index
80
     */
81
    private int $keyBlockSize = 0;
82
83
    /**
84
     * Parser option for the index
85
     */
86
    private string $parser = '';
87
88
    /** @param mixed[] $params parameters */
89 20
    public function __construct(array $params = [])
90
    {
91 20
        $this->set($params);
92
    }
93
94
    /**
95
     * Creates (if not already created) and returns the corresponding Index object
96
     *
97
     * @return Index corresponding Index object
98
     */
99
    public static function singleton(
100
        DatabaseInterface $dbi,
101
        string $schema,
102
        string $table,
103
        string $indexName = '',
104
    ): Index {
105
        self::loadIndexes($dbi, $table, $schema);
106
        if (isset(self::$registry[$schema][$table][$indexName])) {
107
            return self::$registry[$schema][$table][$indexName];
108
        }
109
110
        $index = new Index();
111
        if ($indexName !== '') {
112
            $index->setName($indexName);
113
            self::$registry[$schema][$table][$index->getName()] = $index;
114
        }
115
116
        return $index;
117
    }
118
119
    /**
120
     * returns an array with all indexes from the given table
121
     *
122
     * @return Index[]
123
     */
124
    public static function getFromTable(DatabaseInterface $dbi, string $table, string $schema): array
125
    {
126
        self::loadIndexes($dbi, $table, $schema);
127
128
        return self::$registry[$schema][$table] ?? [];
129
    }
130
131
    /**
132
     * Returns an array with all indexes from the given table of the requested types
133
     *
134
     * @param string $table   table
135
     * @param string $schema  schema
136
     * @param int    $choices choices
137
     *
138
     * @return Index[] array of indexes
139
     */
140
    public static function getFromTableByChoice(string $table, string $schema, int $choices = 31): array
141
    {
142
        $indexes = [];
143
        foreach (self::getFromTable(DatabaseInterface::getInstance(), $table, $schema) as $index) {
0 ignored issues
show
Deprecated Code introduced by
The function PhpMyAdmin\Dbal\DatabaseInterface::getInstance() has been deprecated: Use dependency injection instead. ( Ignorable by Annotation )

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

143
        foreach (self::getFromTable(/** @scrutinizer ignore-deprecated */ DatabaseInterface::getInstance(), $table, $schema) as $index) {

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
144
            if (($choices & self::PRIMARY) && $index->getChoice() === 'PRIMARY') {
145
                $indexes[] = $index;
146
            }
147
148
            if (($choices & self::UNIQUE) && $index->getChoice() === 'UNIQUE') {
149
                $indexes[] = $index;
150
            }
151
152
            if (($choices & self::INDEX) && $index->getChoice() === 'INDEX') {
153
                $indexes[] = $index;
154
            }
155
156
            if (($choices & self::SPATIAL) && $index->getChoice() === 'SPATIAL') {
157
                $indexes[] = $index;
158
            }
159
160
            if ((($choices & self::FULLTEXT) === 0) || $index->getChoice() !== 'FULLTEXT') {
161
                continue;
162
            }
163
164
            $indexes[] = $index;
165
        }
166
167
        return $indexes;
168
    }
169
170
    public static function getPrimary(DatabaseInterface $dbi, string $table, string $schema): Index|null
171
    {
172
        self::loadIndexes($dbi, $table, $schema);
173
174
        return self::$registry[$schema][$table]['PRIMARY'] ?? null;
175
    }
176
177
    /**
178
     * Load index data for table
179
     */
180
    private static function loadIndexes(DatabaseInterface $dbi, string $table, string $schema): void
181
    {
182
        if (isset(self::$registry[$schema][$table])) {
183
            return;
184
        }
185
186
        $rawIndexes = $dbi->getTableIndexes($schema, $table);
187
        foreach ($rawIndexes as $eachIndex) {
188
            $eachIndex['Schema'] = $schema;
189
            $keyName = $eachIndex['Key_name'];
190
            if (! isset(self::$registry[$schema][$table][$keyName])) {
191
                $key = new Index($eachIndex);
192
                self::$registry[$schema][$table][$keyName] = $key;
193
            } else {
194
                $key = self::$registry[$schema][$table][$keyName];
195
            }
196
197
            $key->addColumn($eachIndex);
198
        }
199
    }
200
201
    /**
202
     * Add column to index
203
     *
204
     * @param array<string, string|null> $params column params
205
     */
206 16
    public function addColumn(array $params): void
207
    {
208 16
        $key = $params['Column_name'] ?? $params['Expression'] ?? '';
209 16
        if (isset($params['Expression'])) {
210
            // The Expression only does not make the key unique, add a sequence number
211
            $key .= $params['Seq_in_index'];
212
        }
213
214 16
        if ($key === '') {
215
            return;
216
        }
217
218 16
        $this->columns[$key] = new IndexColumn($params);
219
    }
220
221
    /**
222
     * Adds a list of columns to the index
223
     *
224
     * @param mixed[] $columns array containing details about the columns
225
     */
226 16
    public function addColumns(array $columns): void
227
    {
228 16
        $addedColumns = [];
229
230 16
        if (isset($columns['names'])) {
231
            // coming from form
232
            // $columns[names][]
233
            // $columns[sub_parts][]
234
            foreach ($columns['names'] as $key => $name) {
235
                $subPart = $columns['sub_parts'][$key] ?? '';
236
                $addedColumns[] = ['Column_name' => $name, 'Sub_part' => $subPart];
237
            }
238
        } else {
239
            // coming from SHOW INDEXES
240
            // $columns[][name]
241
            // $columns[][sub_part]
242
            // ...
243 16
            $addedColumns = $columns;
244
        }
245
246 16
        foreach ($addedColumns as $column) {
247 16
            $this->addColumn($column);
248
        }
249
    }
250
251
    /**
252
     * Returns true if $column indexed in this index
253
     *
254
     * @param string $column the column
255
     */
256 4
    public function hasColumn(string $column): bool
257
    {
258 4
        return isset($this->columns[$column]);
259
    }
260
261
    /**
262
     * Sets index details
263
     *
264
     * @param mixed[] $params index details
265
     */
266 20
    public function set(array $params): void
267
    {
268 20
        if (isset($params['columns'])) {
269 8
            $this->addColumns($params['columns']);
270
        }
271
272 20
        if (isset($params['Schema'])) {
273 8
            $this->schema = $params['Schema'];
274
        }
275
276 20
        if (isset($params['Table'])) {
277 8
            $this->table = $params['Table'];
278
        }
279
280 20
        if (isset($params['Key_name'])) {
281 8
            $this->name = $params['Key_name'];
282
        }
283
284 20
        if (isset($params['Index_type'])) {
285
            $this->type = $params['Index_type'];
286
        }
287
288 20
        if (isset($params['Comment'])) {
289 8
            $this->remarks = $params['Comment'];
290
        }
291
292 20
        if (isset($params['Index_comment'])) {
293 8
            $this->comment = $params['Index_comment'];
294
        }
295
296 20
        if (isset($params['Non_unique'])) {
297 8
            $this->nonUnique = (bool) $params['Non_unique'];
298
        }
299
300 20
        if (isset($params['Packed'])) {
301 8
            $this->packed = $params['Packed'];
302
        }
303
304 20
        if (isset($params['Index_choice'])) {
305 8
            $this->choice = $params['Index_choice'];
306 12
        } elseif ($this->name === 'PRIMARY') {
307
            $this->choice = 'PRIMARY';
308 12
        } elseif ($this->type === 'FULLTEXT') {
309
            $this->choice = 'FULLTEXT';
310
            $this->type = '';
311 12
        } elseif ($this->type === 'SPATIAL') {
312
            $this->choice = 'SPATIAL';
313
            $this->type = '';
314 12
        } elseif (! $this->nonUnique) {
315 12
            $this->choice = 'UNIQUE';
316
        } else {
317
            $this->choice = 'INDEX';
318
        }
319
320 20
        if (isset($params['Key_block_size'])) {
321
            $this->keyBlockSize = (int) $params['Key_block_size'];
322
        }
323
324 20
        if (! isset($params['Parser'])) {
325 20
            return;
326
        }
327
328
        $this->parser = $params['Parser'];
329
    }
330
331
    /**
332
     * Returns the number of columns of the index
333
     *
334
     * @return int the number of the columns
335
     */
336 4
    public function getColumnCount(): int
337
    {
338 4
        return count($this->columns);
339
    }
340
341
    /**
342
     * Returns the index comment
343
     *
344
     * @return string index comment
345
     */
346 4
    public function getComment(): string
347
    {
348 4
        return $this->comment;
349
    }
350
351
    /**
352
     * Returns index remarks
353
     *
354
     * @return string index remarks
355
     */
356 4
    public function getRemarks(): string
357
    {
358 4
        return $this->remarks;
359
    }
360
361
    /**
362
     * Return the key block size
363
     */
364
    public function getKeyBlockSize(): int
365
    {
366
        return $this->keyBlockSize;
367
    }
368
369
    /**
370
     * Return the parser
371
     */
372
    public function getParser(): string
373
    {
374
        return $this->parser;
375
    }
376
377
    /**
378
     * Returns concatenated remarks and comment
379
     *
380
     * @return string concatenated remarks and comment
381
     */
382 4
    public function getComments(): string
383
    {
384 4
        $comments = $this->getRemarks();
385 4
        if ($comments !== '') {
386 4
            $comments .= "\n";
387
        }
388
389 4
        $comments .= $this->getComment();
390
391 4
        return $comments;
392
    }
393
394
    /**
395
     * Returns index type (BTREE, HASH, RTREE)
396
     *
397
     * @return string index type
398
     */
399
    public function getType(): string
400
    {
401
        return $this->type;
402
    }
403
404
    /**
405
     * Returns index choice (PRIMARY, UNIQUE, INDEX, SPATIAL, FULLTEXT)
406
     *
407
     * @return string index choice
408
     */
409 4
    public function getChoice(): string
410
    {
411 4
        return $this->choice;
412
    }
413
414
    /**
415
     * Returns a lit of all index types
416
     *
417
     * @return string[] index types
418
     */
419
    public static function getIndexTypes(): array
420
    {
421
        return ['BTREE', 'HASH'];
422
    }
423
424
    public function hasPrimary(): bool
425
    {
426
        return self::getPrimary(DatabaseInterface::getInstance(), $this->table, $this->schema) !== null;
0 ignored issues
show
Deprecated Code introduced by
The function PhpMyAdmin\Dbal\DatabaseInterface::getInstance() has been deprecated: Use dependency injection instead. ( Ignorable by Annotation )

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

426
        return self::getPrimary(/** @scrutinizer ignore-deprecated */ DatabaseInterface::getInstance(), $this->table, $this->schema) !== null;

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
427
    }
428
429
    /**
430
     * Returns how the index is packed
431
     *
432
     * @return string|null how the index is packed
433
     */
434 4
    public function getPacked(): string|null
435
    {
436 4
        return $this->packed;
437
    }
438
439
    /**
440
     * Returns 'No' if the index is not packed,
441
     * how the index is packed if packed
442
     */
443
    public function isPacked(): string
444
    {
445
        if ($this->packed === null) {
446
            return __('No');
447
        }
448
449
        return htmlspecialchars($this->packed);
450
    }
451
452
    /**
453
     * Returns bool false if the index cannot contain duplicates, true if it can
454
     *
455
     * @return bool false if the index cannot contain duplicates, true if it can
456
     */
457 4
    public function getNonUnique(): bool
458
    {
459 4
        return $this->nonUnique;
460
    }
461
462
    /**
463
     * Returns whether the index is a 'Unique' index
464
     *
465
     * @param bool $asText whether to output should be in text
466
     *
467
     * @return string|bool whether the index is a 'Unique' index
468
     */
469 4
    public function isUnique(bool $asText = false): string|bool
470
    {
471 4
        if ($asText) {
472 4
            return $this->nonUnique ? __('No') : __('Yes');
473
        }
474
475 4
        return ! $this->nonUnique;
476
    }
477
478
    /**
479
     * Returns the name of the index
480
     *
481
     * @return string the name of the index
482
     */
483 4
    public function getName(): string
484
    {
485 4
        return $this->name;
486
    }
487
488
    /**
489
     * Sets the name of the index
490
     */
491 4
    public function setName(string $name): void
492
    {
493 4
        $this->name = $name;
494
    }
495
496
    /**
497
     * Returns the columns of the index
498
     *
499
     * @return IndexColumn[]
500
     */
501 4
    public function getColumns(): array
502
    {
503 4
        return $this->columns;
504
    }
505
506
    /**
507
     * Gets the properties in an array for comparison purposes
508
     *
509
     * @return array<string, array<int, array<string, int|string|null>>|string|null>
510
     * @psalm-return array{
511
     *   Packed: string|null,
512
     *   Index_choice: string,
513
     *   columns?: list<array{
514
     *     Column_name: string,
515
     *     Seq_in_index: int,
516
     *     Collation: string|null,
517
     *     Sub_part: int|null,
518
     *     Null: string
519
     *   }>
520
     * }
521
     */
522
    public function getCompareData(): array
523
    {
524
        $data = ['Packed' => $this->packed, 'Index_choice' => $this->choice];
525
526
        foreach ($this->columns as $column) {
527
            $data['columns'][] = $column->getCompareData();
528
        }
529
530
        return $data;
531
    }
532
533
    /**
534
     * Function to check over array of indexes and look for common problems
535
     *
536
     * @param string $table  table name
537
     * @param string $schema schema name
538
     *
539
     * @return string  Output HTML
540
     */
541
    public static function findDuplicates(string $table, string $schema): string
542
    {
543
        $indexes = self::getFromTable(DatabaseInterface::getInstance(), $table, $schema);
0 ignored issues
show
Deprecated Code introduced by
The function PhpMyAdmin\Dbal\DatabaseInterface::getInstance() has been deprecated: Use dependency injection instead. ( Ignorable by Annotation )

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

543
        $indexes = self::getFromTable(/** @scrutinizer ignore-deprecated */ DatabaseInterface::getInstance(), $table, $schema);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
544
545
        $output = '';
546
547
        // count($indexes) < 2:
548
        //   there is no need to check if there less than two indexes
549
        if (count($indexes) < 2) {
550
            return $output;
551
        }
552
553
        // remove last index from stack and ...
554
        while ($whileIndex = array_pop($indexes)) {
555
            // ... compare with every remaining index in stack
556
            foreach ($indexes as $eachIndex) {
557
                if ($eachIndex->getCompareData() !== $whileIndex->getCompareData()) {
558
                    continue;
559
                }
560
561
                // did not find any difference
562
                // so it makes no sense to have this two equal indexes
563
564
                $message = Message::notice(
565
                    __(
566
                        'The indexes %1$s and %2$s seem to be equal and one of them could possibly be removed.',
567
                    ),
568
                );
569
                $message->addParam($eachIndex->getName());
570
                $message->addParam($whileIndex->getName());
571
                $output .= $message->getDisplay();
572
573
                // there is no need to check any further indexes if we have already
574
                // found that this one has a duplicate
575
                continue 2;
576
            }
577
        }
578
579
        return $output;
580
    }
581
}
582