Passed
Push — main ( 6a63fe...cd781c )
by Thierry
07:05
created

ExportAdmin   F

Complexity

Total Complexity 102

Size/Duplication

Total Lines 530
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 277
dl 0
loc 530
rs 2
c 1
b 0
f 0
wmc 102

8 Methods

Rating   Name   Duplication   Size   Complexity  
A dumpCsv() 0 11 6
C getExportOptions() 0 120 11
F dumpData() 0 65 20
B dumpTable() 0 30 11
F dumpTablesAndViews() 0 64 23
B dumpRoutinesAndEvents() 0 47 11
A convertToString() 0 9 6
F exportDatabases() 0 68 14

How to fix   Complexity   

Complex Class

Complex classes like ExportAdmin often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use ExportAdmin, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Lagdo\DbAdmin\DbAdmin;
4
5
use Exception;
6
7
/**
8
 * Admin export functions
9
 */
10
class ExportAdmin extends AbstractAdmin
11
{
12
    /**
13
     * The databases to dump
14
     *
15
     * @var array
16
     */
17
    protected $databases;
18
19
    /**
20
     * The tables to dump
21
     *
22
     * @var array
23
     */
24
    protected $tables;
25
26
    /**
27
     * The dump options
28
     *
29
     * @var array
30
     */
31
    protected $options;
32
33
    /**
34
     * The queries generated by the dump
35
     *
36
     * @var array
37
     */
38
    protected $queries = [];
39
40
    /**
41
     * Get data for export
42
     *
43
     * @param string $database      The database name
44
     * @param string $table
45
     *
46
     * @return array
47
     */
48
    public function getExportOptions(string $database, string $table = '')
49
    {
50
        // From dump.inc.php
51
        $db_style = ['', 'USE', 'DROP+CREATE', 'CREATE'];
52
        $table_style = ['', 'DROP+CREATE', 'CREATE'];
53
        $data_style = ['', 'TRUNCATE+INSERT', 'INSERT'];
54
        if ($this->driver->jush() == 'sql') { //! use insertOrUpdate() in all drivers
55
            $data_style[] = 'INSERT+UPDATE';
56
        }
57
        // \parse_str($_COOKIE['adminer_export'], $row);
58
        // if(!$row) {
59
        $row = [
60
                'output' => 'text',
61
                'format' => 'sql',
62
                'db_style' => ($database != '' ? '' : 'CREATE'),
63
                'table_style' => 'DROP+CREATE',
64
                'data_style' => 'INSERT',
65
            ];
66
        // }
67
        // if(!isset($row['events'])) { // backwards compatibility
68
        $row['routines'] = $row['events'] = ($table == '');
69
        $row['triggers'] = $row['table_style'];
70
        // }
71
        $options = [
72
            'output' => [
73
                'label' => $this->trans->lang('Output'),
74
                'options' => $this->util->dumpOutput(),
75
                'value' => $row['output'],
76
            ],
77
            'format' => [
78
                'label' => $this->trans->lang('Format'),
79
                'options' => $this->util->dumpFormat(),
80
                'value' => $row['format'],
81
            ],
82
            'table_style' => [
83
                'label' => $this->trans->lang('Tables'),
84
                'options' => $table_style,
85
                'value' => $row['table_style'],
86
            ],
87
            'auto_increment' => [
88
                'label' => $this->trans->lang('Auto Increment'),
89
                'value' => 1,
90
                'checked' => $row['autoIncrement'] ?? false,
91
            ],
92
            'data_style' => [
93
                'label' => $this->trans->lang('Data'),
94
                'options' => $data_style,
95
                'value' => $row['data_style'],
96
            ],
97
        ];
98
        if ($this->driver->jush() !== 'sqlite') {
99
            $options['db_style'] = [
100
                'label' => $this->trans->lang('Database'),
101
                'options' => $db_style,
102
                'value' => $row['db_style'],
103
            ];
104
            if ($this->driver->support('routine')) {
105
                $options['routines'] = [
106
                    'label' => $this->trans->lang('Routines'),
107
                    'value' => 1,
108
                    'checked' => $row['routines'],
109
                ];
110
            }
111
            if ($this->driver->support('event')) {
112
                $options['events'] = [
113
                    'label' => $this->trans->lang('Events'),
114
                    'value' => 1,
115
                    'checked' => $row['events'],
116
                ];
117
            }
118
        }
119
        if ($this->driver->support('trigger')) {
120
            $options['triggers'] = [
121
                'label' => $this->trans->lang('Triggers'),
122
                'value' => 1,
123
                'checked' => $row['triggers'],
124
            ];
125
        }
126
127
        $results = [
128
            'options' => $options,
129
            'prefixes' => [],
130
        ];
131
        if (($database)) {
132
            $tables = [
133
                'headers' => [$this->trans->lang('Tables'), $this->trans->lang('Data')],
134
                'details' => [],
135
            ];
136
            $tables_list = $this->driver->tables();
137
            foreach ($tables_list as $name => $type) {
138
                $prefix = \preg_replace('~_.*~', '', $name);
139
                //! % may be part of table name
140
                // $checked = ($TABLE == "" || $TABLE == (\substr($TABLE, -1) == "%" ? "$prefix%" : $name));
141
                // $results['prefixes'][$prefix]++;
142
143
                $tables['details'][] = \compact('prefix', 'name', 'type'/*, 'checked'*/);
144
            }
145
            $results['tables'] = $tables;
146
        } else {
147
            $databases = [
148
                'headers' => [$this->trans->lang('Database'), $this->trans->lang('Data')],
149
                'details' => [],
150
            ];
151
            $databases_list = $this->driver->databases(false) ?? [];
152
            foreach ($databases_list as $name) {
153
                if (!$this->driver->isInformationSchema($name)) {
154
                    $prefix = \preg_replace('~_.*~', '', $name);
155
                    // $results['prefixes'][$prefix]++;
156
157
                    $databases['details'][] = \compact('prefix', 'name');
158
                }
159
            }
160
            $results['databases'] = $databases;
161
        }
162
163
        $results['options'] = $options;
164
        $results['labels'] = [
165
            'export' => $this->trans->lang('Export'),
166
        ];
167
        return $results;
168
    }
169
170
    /**
171
     * Dump routines and events in the connected database
172
     *
173
     * @param string $database      The database name
174
     *
175
     * @return void
176
     */
177
    protected function dumpRoutinesAndEvents(string $database)
178
    {
179
        // From dump.inc.php
180
        $style = $this->options["db_style"];
181
        $queries = [];
182
183
        if ($this->options["routines"]) {
184
            $sql = "SHOW FUNCTION STATUS WHERE Db = " . $this->driver->quote($database);
185
            foreach ($this->driver->rows($sql) as $row) {
186
                $sql = "SHOW CREATE FUNCTION " . $this->driver->escapeId($row["Name"]);
187
                $create = $this->admin->removeDefiner($this->driver->result($sql, 2));
188
                $queries[] = $this->driver->setUtf8mb4($create);
189
                if ($style != 'DROP+CREATE') {
190
                    $queries[] = "DROP FUNCTION IF EXISTS " . $this->driver->escapeId($row["Name"]) . ";;";
191
                }
192
                $queries[] = "$create;;\n";
193
            }
194
            $sql = "SHOW PROCEDURE STATUS WHERE Db = " . $this->driver->quote($database);
195
            foreach ($this->driver->rows($sql) as $row) {
196
                $sql = "SHOW CREATE PROCEDURE " . $this->driver->escapeId($row["Name"]);
197
                $create = $this->admin->removeDefiner($this->driver->result($sql, 2));
198
                $queries[] = $this->driver->setUtf8mb4($create);
199
                if ($style != 'DROP+CREATE') {
200
                    $queries[] = "DROP PROCEDURE IF EXISTS " . $this->driver->escapeId($row["Name"]) . ";;";
201
                }
202
                $queries[] = "$create;;\n";
203
            }
204
        }
205
206
        if ($this->options["events"]) {
207
            foreach ($this->driver->rows("SHOW EVENTS") as $row) {
208
                $sql = "SHOW CREATE EVENT " . $this->driver->escapeId($row["Name"]);
209
                $create = $this->admin->removeDefiner($this->driver->result($sql, 3));
210
                $queries[] = $this->driver->setUtf8mb4($create);
211
                if ($style != 'DROP+CREATE') {
212
                    $queries[] = "DROP EVENT IF EXISTS " . $this->driver->escapeId($row["Name"]) . ";;";
213
                }
214
                $queries[] = "$create;;\n";
215
            }
216
        }
217
218
        if (\count($queries) > 0) {
219
            $this->queries[] = "DELIMITER ;;\n";
220
            foreach ($queries as $query) {
221
                $this->queries[] = $query;
222
            }
223
            $this->queries[] = "DELIMITER ;;\n";
224
        }
225
    }
226
227
    /**
228
     * Print CSV row
229
     *
230
     * @param array  $row
231
     *
232
     * @return void
233
     */
234
    protected function dumpCsv(array $row)
235
    {
236
        // From functions.inc.php
237
        foreach ($row as $key => $val) {
238
            if (\preg_match('~["\n,;\t]|^0|\.\d*0$~', $val) || $val === "") {
239
                $row[$key] = '"' . \str_replace('"', '""', $val) . '"';
240
            }
241
        }
242
        $separator = $this->options["format"] == "csv" ? "," :
243
            ($this->options["format"] == "tsv" ? "\t" : ";");
244
        $this->queries[] = \implode($separator, $row);
245
    }
246
247
    /**
248
     * Convert a value to string
249
     *
250
     * @param mixed  $val
251
     * @param object $field
252
     *
253
     * @return string
254
     */
255
    protected function convertToString($val, $field)
256
    {
257
        // From functions.inc.php
258
        if ($val === null) {
259
            return "NULL";
260
        }
261
        return $this->driver->unconvertField($field, \preg_match($this->driver->numberRegex(), $field->type) &&
262
            !\preg_match('~\[~', $field->fullType) && \is_numeric($val) ?
263
            $val : $this->driver->quote(($val === false ? 0 : $val)));
264
    }
265
266
    /**
267
     * Export table structure
268
     *
269
     * @param string $table
270
     * @param string $style
271
     * @param int    $is_view       0 table, 1 view, 2 temporary view table
272
     *
273
     * @return null prints data
274
     */
275
    protected function dumpTable(string $table, string $style, int $is_view = 0)
276
    {
277
        // From adminer.inc.php
278
        if ($this->options['format'] != "sql") {
279
            $this->queries[] = "\xef\xbb\xbf"; // UTF-8 byte order mark
280
            if ($style) {
281
                $this->dumpCsv(\array_keys($this->driver->fields($table)));
282
            }
283
            return;
284
        }
285
286
        if ($is_view == 2) {
287
            $fields = [];
288
            foreach ($this->driver->fields($table) as $name => $field) {
289
                $fields[] = $this->driver->escapeId($name) . ' ' . $field->fullType;
290
            }
291
            $create = "CREATE TABLE " . $this->driver->table($table) . " (" . \implode(", ", $fields) . ")";
292
        } else {
293
            $create = $this->driver->sqlForCreateTable($table, $this->options['auto_increment'], $style);
294
        }
295
        $this->driver->setUtf8mb4($create);
296
        if ($style && $create) {
297
            if ($style == "DROP+CREATE" || $is_view == 1) {
298
                $this->queries[] = "DROP " . ($is_view == 2 ? "VIEW" : "TABLE") .
299
                    " IF EXISTS " . $this->driver->table($table) . ';';
300
            }
301
            if ($is_view == 1) {
302
                $create = $this->admin->removeDefiner($create);
303
            }
304
            $this->queries[] = $create . ';';
305
        }
306
    }
307
308
    /** Export table data
309
     *
310
     * @param string
311
     * @param string
312
     * @param string
313
     *
314
     * @return null prints data
315
     */
316
    protected function dumpData($table, $style, $query)
317
    {
318
        $fields = [];
319
        $max_packet = ($this->driver->jush() == "sqlite" ? 0 : 1048576); // default, minimum is 1024
320
        if ($style) {
321
            if ($this->options["format"] == "sql") {
322
                if ($style == "TRUNCATE+INSERT") {
323
                    $this->queries[] = $this->driver->sqlForTruncateTable($table) . ";\n";
324
                }
325
                $fields = $this->driver->fields($table);
326
            }
327
            $statement = $this->driver->query($query, 1); // 1 - MYSQLI_USE_RESULT //! enum and set as numbers
328
            if ($statement) {
329
                $insert = "";
330
                $buffer = "";
331
                $keys = [];
332
                $suffix = "";
333
                $fetch_function = ($table != '' ? 'fetchAssoc' : 'fetchRow');
334
                while ($row = $statement->$fetch_function()) {
335
                    if (empty($keys)) {
336
                        $values = [];
337
                        foreach ($row as $val) {
338
                            $field = $statement->fetchField();
339
                            $keys[] = $field->name();
340
                            $key = $this->driver->escapeId($field->name());
341
                            $values[] = "$key = VALUES($key)";
342
                        }
343
                        $suffix = ";\n";
344
                        if ($style == "INSERT+UPDATE") {
345
                            $suffix = "\nON DUPLICATE KEY UPDATE " . \implode(", ", $values) . ";\n";
346
                        }
347
                    }
348
                    if ($this->options["format"] != "sql") {
349
                        if ($style == "table") {
350
                            $this->dumpCsv($keys);
351
                            $style = "INSERT";
352
                        }
353
                        $this->dumpCsv($row);
354
                    } else {
355
                        if (!$insert) {
356
                            $insert = "INSERT INTO " . $this->driver->table($table) . " (" .
357
                                \implode(", ", \array_map(function ($key) {
358
                                    return $this->driver->escapeId($key);
359
                                }, $keys)) . ") VALUES";
360
                        }
361
                        foreach ($row as $key => $val) {
362
                            $field = $fields[$key];
363
                            $row[$key] = $this->convertToString($val, $field);
364
                        }
365
                        $s = ($max_packet ? "\n" : " ") . "(" . \implode(",\t", $row) . ")";
366
                        if (!$buffer) {
367
                            $buffer = $insert . $s;
368
                        } elseif (\strlen($buffer) + 4 + \strlen($s) + \strlen($suffix) < $max_packet) { // 4 - length specification
369
                            $buffer .= ",$s";
370
                        } else {
371
                            $this->queries[] = $buffer . $suffix;
372
                            $buffer = $insert . $s;
373
                        }
374
                    }
375
                }
376
                if ($buffer) {
377
                    $this->queries[] = $buffer . $suffix;
378
                }
379
            } elseif ($this->options["format"] == "sql") {
380
                $this->queries[] = "-- " . \str_replace("\n", " ", $this->driver->error()) . "\n";
381
            }
382
        }
383
    }
384
385
    /**
386
     * Dump tables and views in the connected database
387
     *
388
     * @param string $database      The database name
389
     *
390
     * @return array
391
     */
392
    protected function dumpTablesAndViews(string $database)
393
    {
394
        if (!$this->options["table_style"] && !$this->options["data_style"]) {
395
            return [];
396
        }
397
398
        $dbDumpTable = $this->tables['list'] === '*' &&
399
            \in_array($database, $this->databases["list"]);
400
        $dbDumpData = \in_array($database, $this->databases["data"]);
401
        $views = [];
402
        $dbTables = $this->driver->tableStatuses(true);
403
        foreach ($dbTables as $table => $tableStatus) {
404
            $dumpTable = $dbDumpTable || \in_array($table, $this->tables['list']);
405
            $dumpData = $dbDumpData || \in_array($table, $this->tables["data"]);
406
            if ($dumpTable || $dumpData) {
407
                // if($ext == "tar")
408
                // {
409
                //     $tmp_file = new TmpFile;
410
                //     ob_start([$tmp_file, 'write'], 1e5);
411
                // }
412
413
                $this->dumpTable(
414
                    $table,
415
                    ($dumpTable ? $this->options["table_style"] : ""),
416
                    ($this->driver->isView($tableStatus) ? 2 : 0)
417
                );
418
                if ($this->driver->isView($tableStatus)) {
419
                    $views[] = $table;
420
                } elseif ($dumpData) {
421
                    $fields = $this->driver->fields($table);
422
                    $query = "SELECT *" . $this->driver->convertFields($fields, $fields) .
423
                        " FROM " . $this->driver->table($table);
424
                    $this->dumpData($table, $this->options["data_style"], $query);
425
                }
426
                if ($this->options['is_sql'] && $this->options["triggers"] && $dumpTable &&
427
                    ($triggers = $this->driver->sqlForCreateTrigger($table))) {
428
                    $this->queries[] = "DELIMITER ;";
429
                    $this->queries[] = $triggers;
430
                    $this->queries[] = "DELIMITER ;";
431
                }
432
433
                // if($ext == "tar")
434
                // {
435
                //     ob_end_flush();
436
                //     tar_file((DB != "" ? "" : "$db/") . "$table.csv", $tmp_file);
437
                // } else
438
                if ($this->options['is_sql']) {
439
                    $this->queries[] = '';
440
                }
441
            }
442
        }
443
444
        // add FKs after creating tables (except in MySQL which uses SET FOREIGN_KEY_CHECKS=0)
445
        if ($this->driver->support('fkeys_sql')) {
446
            foreach ($dbTables as $table => $tableStatus) {
447
                $dumpTable = true; // (DB == "" || \in_array($table, $this->options["tables"]));
448
                if ($dumpTable && !$this->driver->isView($tableStatus)) {
449
                    $this->queries[] = $this->driver->sqlForForeignKeys($table);
450
                }
451
            }
452
        }
453
454
        foreach ($views as $view) {
455
            $this->dumpTable($view, $this->options["table_style"], 1);
456
        }
457
458
        // if($ext == "tar") {
459
        //     $this->queries[] = pack("x512");
460
        // }
461
    }
462
463
    /**
464
     * Export databases
465
     *
466
     * @param array  $databases     The databases to dump
467
     * @param array  $tables        The tables to dump
468
     * @param array  $options       The export options
469
     *
470
     * @return array
471
     */
472
    public function exportDatabases(array $databases, array $tables, array $options)
473
    {
474
        // From dump.inc.php
475
        // $tables = array_flip($options["tables"]) + array_flip($options["data"]);
476
        // $ext = dump_headers((count($tables) == 1 ? key($tables) : DB), (DB == "" || count($tables) > 1));
477
        $options['is_sql'] = \preg_match('~sql~', $options["format"]);
478
        $this->databases = $databases;
479
        $this->tables = $tables;
480
        $this->options = $options;
481
482
        $headers = null;
483
        if ($this->options['is_sql']) {
484
            $headers = [
485
                'version' => $this->driver->version(),
486
                'driver' => $this->driver->name(),
487
                'server' => \str_replace("\n", " ", $this->driver->serverInfo()),
488
                'sql' => false,
489
                'data_style' => false,
490
            ];
491
            if ($this->driver->jush() == "sql") {
492
                $headers['sql'] = true;
493
                if (isset($options["data_style"])) {
494
                    $headers['data_style'] = true;
495
                }
496
                // Set some options in database server
497
                $this->driver->query("SET time_zone = '+00:00'");
498
                $this->driver->query("SET sql_mode = ''");
499
            }
500
        }
501
502
        $style = $options["db_style"];
503
504
        foreach (\array_unique(\array_merge($databases['list'], $databases['data'])) as $database) {
505
            // $this->util->dumpDatabase($database);
506
            try {
507
                $this->driver->connect($database, '');
508
                $sql = "SHOW CREATE DATABASE " . $this->driver->escapeId($database);
509
                if ($this->options['is_sql'] && \preg_match('~CREATE~', $style) &&
510
                    ($create = $this->driver->result($sql, 1))) {
511
                    $this->driver->setUtf8mb4($create);
512
                    if ($style == "DROP+CREATE") {
513
                        $this->queries[] = "DROP DATABASE IF EXISTS " . $this->driver->escapeId($database) . ";";
514
                    }
515
                    $this->queries[] = $create . ";\n";
516
                }
517
                if ($this->options['is_sql']) {
518
                    if ($style) {
519
                        if (($query = $this->driver->sqlForUseDatabase($database))) {
520
                            $this->queries[] = $query . ";";
521
                        }
522
                        $this->queries[] = ''; // Empty line
523
                    }
524
525
                    $this->dumpRoutinesAndEvents($database);
526
                }
527
528
                $this->dumpTablesAndViews($database);
529
            }
530
            catch (\Exception $e) {}
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
531
        }
532
533
        if ($this->options['is_sql']) {
534
            $this->queries[] = "-- " . $this->driver->result("SELECT NOW()");
535
        }
536
537
        return [
538
            'headers' => $headers,
539
            'queries' => $this->queries,
540
        ];
541
    }
542
}
543