Exporter   F
last analyzed

Complexity

Total Complexity 152

Size/Duplication

Total Lines 951
Duplicated Lines 0 %

Importance

Changes 4
Bugs 1 Features 0
Metric Value
eloc 355
c 4
b 1
f 0
dl 0
loc 951
rs 2
wmc 152

39 Methods

Rating   Name   Duplication   Size   Complexity  
B getColumnStmt() 0 22 9
A getTableWhere() 0 10 3
B process() 0 71 9
A limit() 0 5 1
A getDatabaseStructureTriggers() 0 6 3
A exportEvents() 0 5 2
A where() 0 5 1
A getTableColumnTypes() 0 26 3
A exportFunctions() 0 5 2
A getDumpFileHeader() 0 31 5
C listValues() 0 70 12
A getDatabaseStructureTables() 0 17 5
A getDatabaseStructureViews() 0 17 5
A exportTriggers() 0 5 2
A createStandInTable() 0 12 2
A prepareColumnValues() 0 14 3
B endListValues() 0 29 7
A exportTables() 0 16 6
B prepareListValues() 0 30 7
A matches() 0 14 5
A getViewStructureTable() 0 20 4
A exportViews() 0 19 6
A getColumnNames() 0 14 3
A getDatabaseStructureEvents() 0 6 3
A exportProcedures() 0 5 2
A getTableStructure() 0 27 5
A getFunctionStructure() 0 16 3
A getTriggerStructure() 0 12 3
A getDatabaseStructureProcedures() 0 6 3
A getEventStructure() 0 16 3
B escape() 0 17 7
A transformTableRow() 0 3 1
A getDumpFileFooter() 0 15 3
A getDatabaseStructureFunctions() 0 6 3
A getProcedureStructure() 0 15 3
A getViewStructureView() 0 19 3
A setTableWheres() 0 3 1
A setTableLimits() 0 3 1
A getTableLimit() 0 13 3

How to fix   Complexity   

Complex Class

Complex classes like Exporter 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 Exporter, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/**
4
 * This file is part of dimtrovich/db-dumper".
5
 *
6
 * (c) 2024 Dimitri Sitchet Tomkeu <[email protected]>
7
 *
8
 * For the full copyright and license information, please view
9
 * the LICENSE file that was distributed with this source code.
10
 */
11
12
namespace Dimtrovich\DbDumper;
13
14
use Dimtrovich\DbDumper\Exceptions\Exception;
15
use PDO;
16
17
/**
18
 * @method void onTableExport(callable(string $tableName, int $rowCount) $callback)
19
 */
20
class Exporter
21
{
22
    use Dumper;
0 ignored issues
show
introduced by
The trait Dimtrovich\DbDumper\Dumper requires some properties which are not provided by Dimtrovich\DbDumper\Exporter: $init_commands, $compress
Loading history...
23
24
    /**
25
     * List of registered tables
26
     */
27
    private array $tables = [];
28
29
    /**
30
     * List of columns types for tables [$tableName => [$column => $type]]
31
     */
32
    private array $tableColumnTypes = [];
33
34
    /**
35
     * List of registered views
36
     */
37
    private array $views = [];
38
39
    /**
40
     * List of registered triggers
41
     */
42
    private array $triggers = [];
43
44
    /**
45
     * List of registered procedures
46
     */
47
    private array $procedures = [];
48
49
    /**
50
     * List of registered functions
51
     */
52
    private array $functions = [];
53
54
    /**
55
     * List of registered events
56
     */
57
    private array $events = [];
58
59
    /**
60
     * @var callable
61
     */
62
    private $transformTableRowCallable;
63
64
    /**
65
     * Keyed on table name, with the value as the conditions.
66
     * e.g. - 'users' => 'date_registered > NOW() - INTERVAL 6 MONTH'
67
     */
68
    private array $tableWheres = [];
69
70
    private array $tableLimits = [];
71
72
    /**
73
     * Primary function, triggers dumping.
74
     *
75
     * @param string $filename Name of file to write sql dump to
76
     */
77
    public function process(string $filename = 'php://stdout')
78
    {
79
        // Create output file
80
        $this->compressor->open($filename);
81
82
        // Write some basic info to output file
83
        $this->compressor->write($this->getDumpFileHeader());
84
85
        // initiate a transaction at global level to create a consistent snapshot
86
        if ($this->option->single_transaction) {
87
            if ('' !== $setupTransaction = $this->adapter->setupTransaction()) {
88
                $this->pdo->exec($setupTransaction);
89
            }
90
            if ('' !== $startTransaction = $this->adapter->startTransaction()) {
91
                $this->pdo->exec($startTransaction);
92
            }
93
        }
94
95
        // Store server settings and use sanner defaults to dump
96
        $this->compressor->write($this->adapter->backupParameters());
97
98
        if ($this->option->databases) {
99
            $this->compressor->write($this->adapter->getDatabaseHeader($this->database));
100
101
            if ($this->option->add_drop_database) {
102
                $this->compressor->write($this->adapter->addDropDatabase($this->database));
103
            }
104
        }
105
106
        // Get table, view, trigger, procedures, functions and events structures from database.
107
        $this->getDatabaseStructureTables();
108
        $this->getDatabaseStructureViews();
109
        $this->getDatabaseStructureTriggers();
110
        $this->getDatabaseStructureProcedures();
111
        $this->getDatabaseStructureFunctions();
112
        $this->getDatabaseStructureEvents();
113
114
        if ($this->option->databases) {
115
            $this->compressor->write($this->adapter->databases($this->database));
116
        }
117
118
        // If there still are some tables/views in include-tables array,
119
        // that means that some tables or views weren't found.
120
        // Give proper error and exit.
121
        // This check will be removed once include-tables supports regexps.
122
        if ($this->option->include_tables !== []) {
123
            $name = implode(',', $this->option->include_tables);
124
125
            throw Exception::tableNotFound($name);
126
        }
127
128
        $this->exportTables();
129
        $this->exportTriggers();
130
        $this->exportFunctions();
131
        $this->exportProcedures();
132
        $this->exportViews();
133
        $this->exportEvents();
134
135
        // Restore saved parameters.
136
        $this->compressor->write($this->adapter->restoreParameters());
137
138
        // end transaction
139
        if ($this->option->single_transaction) {
140
            $this->pdo->exec($this->adapter->commitTransaction());
141
        }
142
143
        // Write some stats to output file.
144
        $this->compressor->write($this->getDumpFileFooter());
145
146
        // Close output file.
147
        $this->compressor->close();
148
    }
149
150
    /**
151
     * Keyed by table name, with the value as the conditions:
152
     * e.g. 'users' => 'date_registered > NOW() - INTERVAL 6 MONTH AND deleted=0'
153
     */
154
    public function setTableWheres(array $tableWheres)
155
    {
156
        $this->tableWheres = $tableWheres;
157
    }
158
159
    /**
160
     * @return bool|mixed
161
     */
162
    public function getTableWhere(string $tableName)
163
    {
164
        if (! empty($this->tableWheres[$tableName])) {
165
            return $this->tableWheres[$tableName];
166
        }
167
        if ($this->option->where !== '') {
168
            return $this->option->where;
169
        }
170
171
        return false;
172
    }
173
174
    /**
175
     * Sets a WHERE condition for a specific table during the export process.
176
     */
177
    public function where(string $table, string $condition): self
178
    {
179
        $this->tableWheres[$table] = $condition;
180
181
        return $this;
182
    }
183
184
    /**
185
     * Keyed by table name, with the value as the numeric limit:
186
     * e.g. 'users' => 3000
187
     */
188
    public function setTableLimits(array $tableLimits)
189
    {
190
        $this->tableLimits = $tableLimits;
191
    }
192
193
    /**
194
     * Returns the LIMIT for the table.
195
     * Must be numeric to be returned.
196
     *
197
     * @return false|int
198
     */
199
    public function getTableLimit(string $tableName)
200
    {
201
        if (! isset($this->tableLimits[$tableName])) {
202
            return false;
203
        }
204
205
        $limit = $this->tableLimits[$tableName];
206
207
        if (! is_numeric($limit)) {
208
            return false;
209
        }
210
211
        return $limit;
212
    }
213
214
    /**
215
     * Sets a LIMIT condition for a specific table during the export process.
216
     */
217
    public function limit(string $table, int $limit): self
218
    {
219
        $this->tableLimits[$table] = $limit;
220
221
        return $this;
222
    }
223
224
    /**
225
     * Returns header for dump file.
226
     */
227
    private function getDumpFileHeader(): string
228
    {
229
        $header = '';
230
231
        if (! $this->option->skip_comments) {
232
            $divider = str_repeat('----------------------------------------------------', 2);
233
234
            // Some info about software, source and time
235
            $header = '-- ' . $divider . PHP_EOL . '-- ' . PHP_EOL .
236
                    '-- Database Backup Manager' . PHP_EOL .
237
                    '-- This backup was created automatically by the Dimtrovich Db-Dumper. A simplest PHP Database Backup Manager' . PHP_EOL .
238
                    '-- © ' . date('Y') . ' Dimitri Sitchet Tomkeu' . PHP_EOL .
239
                    '-- https://github.com/dimtrovich/php-db-dumper' . PHP_EOL .
240
                    '-- ' . PHP_EOL;
241
            if ($this->driver !== 'sqlite') {
242
                $header .= '-- Host: ' . $this->pdo->getAttribute(PDO::ATTR_CONNECTION_STATUS) . PHP_EOL;
243
            }
244
            $header .= "-- Database: {$this->database}" . PHP_EOL .
245
                        '-- Server version: ' . $this->pdo->getAttribute(PDO::ATTR_SERVER_VERSION) . ' Driver: ' . $this->driver . PHP_EOL;
246
247
            if (! $this->option->skip_dump_date) {
248
                $header .= '-- ' . PHP_EOL . '-- Generated on: ' . date('r') . PHP_EOL;
249
            }
250
            if ('' !== $this->option->message) {
251
                $header .= '-- ' . PHP_EOL . trim($this->option->message) . PHP_EOL;
252
            }
253
254
            $header .= '-- ' . PHP_EOL . $divider . PHP_EOL . PHP_EOL;
255
        }
256
257
        return $header;
258
    }
259
260
    /**
261
     * Returns footer for dump file.
262
     */
263
    private function getDumpFileFooter()
264
    {
265
        $footer = '';
266
267
        if (! $this->option->skip_comments) {
268
            $footer .= '-- Dump completed';
269
270
            if (! $this->option->skip_dump_date) {
271
                $footer .= ' on: ' . date('r');
272
            }
273
274
            $footer .= PHP_EOL;
275
        }
276
277
        return $footer;
278
    }
279
280
    /**
281
     * Reads table names from database.
282
     * Fills $this->tables array so they will be dumped later.
283
     */
284
    private function getDatabaseStructureTables()
285
    {
286
        $tables = $this->pdo->query($this->adapter->showTables($this->database));
287
288
        // Listing all tables from database
289
        if ($this->option->include_tables === []) {
290
            // include all tables for now, blacklisting happens later
291
            foreach ($tables as $row) {
292
                $this->tables[] = current($row);
293
            }
294
        } else {
295
            // include only the tables mentioned in include-tables
296
            foreach ($tables as $row) {
297
                if (in_array(current($row), $this->option->include_tables, true)) {
298
                    $this->tables[] = current($row);
299
                    $elem           = array_search(current($row), $this->option->include_tables, true);
300
                    unset($this->option->include_tables[$elem]);
301
                }
302
            }
303
        }
304
    }
305
306
    /**
307
     * Reads view names from database.
308
     * Fills $this->tables array so they will be dumped later.
309
     */
310
    private function getDatabaseStructureViews()
311
    {
312
        $views = $this->pdo->query($this->adapter->showViews($this->database));
313
314
        // Listing all views from database
315
        if ($this->option->include_views === []) {
316
            // include all views for now, blacklisting happens later
317
            foreach ($views as $row) {
318
                $this->views[] = current($row);
319
            }
320
        } else {
321
            // include only the tables mentioned in include-tables
322
            foreach ($views as $row) {
323
                if (in_array(current($row), $this->option->include_views, true)) {
324
                    $this->views[] = current($row);
325
                    $elem          = array_search(current($row), $this->option->include_views, true);
326
                    unset($this->option->include_views[$elem]);
327
                }
328
            }
329
        }
330
    }
331
332
    /**
333
     * Reads trigger names from database.
334
     * Fills $this->tables array so they will be dumped later.
335
     */
336
    private function getDatabaseStructureTriggers()
337
    {
338
        // Listing all triggers from database
339
        if ($this->option->skip_triggers) {
340
            foreach ($this->pdo->query($this->adapter->showTriggers($this->database)) as $row) {
341
                $this->triggers[] = $row['Trigger'];
342
            }
343
        }
344
    }
345
346
    /**
347
     * Reads procedure names from database.
348
     * Fills $this->tables array so they will be dumped later.
349
     */
350
    private function getDatabaseStructureProcedures(): void
351
    {
352
        // Listing all procedures from database
353
        if ($this->option->routines) {
354
            foreach ($this->pdo->query($this->adapter->showProcedures($this->database)) as $row) {
355
                $this->procedures[] = $row['procedure_name'];
356
            }
357
        }
358
    }
359
360
    /**
361
     * Reads functions names from database.
362
     * Fills $this->tables array so they will be dumped later.
363
     */
364
    private function getDatabaseStructureFunctions(): void
365
    {
366
        // Listing all functions from database
367
        if ($this->option->routines) {
368
            foreach ($this->pdo->query($this->adapter->showFunctions($this->database)) as $row) {
369
                $this->functions[] = $row['function_name'];
370
            }
371
        }
372
    }
373
374
    /**
375
     * Reads event names from database.
376
     * Fills $this->tables array so they will be dumped later.
377
     */
378
    private function getDatabaseStructureEvents(): void
379
    {
380
        // Listing all events from database
381
        if ($this->option->events) {
382
            foreach ($this->pdo->query($this->adapter->showEvents($this->database)) as $row) {
383
                $this->events[] = $row['event_name'];
384
            }
385
        }
386
    }
387
388
    /**
389
     * Compare if $table name matches with a definition inside $arr
390
     *
391
     * @param $arr array with strings or patterns
392
     */
393
    private function matches(string $table, array $arr): bool
394
    {
395
        $match = false;
396
397
        foreach ($arr as $pattern) {
398
            if ('/' !== $pattern[0]) {
399
                continue;
400
            }
401
            if (1 === preg_match($pattern, $table)) {
402
                $match = true;
403
            }
404
        }
405
406
        return in_array($table, $arr, true) || $match;
407
    }
408
409
    /**
410
     * Exports all the tables selected from database
411
     */
412
    private function exportTables()
413
    {
414
        // Exporting tables one by one
415
        foreach ($this->tables as $table) {
416
            if ($this->matches($table, $this->option->exclude_tables)) {
417
                continue;
418
            }
419
420
            $this->getTableStructure($table);
421
422
            if ([] === $this->option->no_data) { // don't break compatibility with old trigger
423
                $this->listValues($table);
424
            } elseif ([] !== $this->option->no_data || $this->matches($table, $this->option->no_data)) {
425
                continue;
426
            } else {
427
                $this->listValues($table);
428
            }
429
        }
430
    }
431
432
    /**
433
     * Exports all the views found in database
434
     */
435
    private function exportViews()
436
    {
437
        if (false === $this->option->no_create_info) {
438
            // Exporting views one by one
439
            foreach ($this->views as $view) {
440
                if ($this->matches($view, $this->option->exclude_tables)) {
441
                    continue;
442
                }
443
444
                $this->tableColumnTypes[$view] = $this->getTableColumnTypes($view);
445
                $this->getViewStructureTable($view);
446
            }
447
448
            foreach ($this->views as $view) {
449
                if ($this->matches($view, $this->option->exclude_tables)) {
450
                    continue;
451
                }
452
453
                $this->getViewStructureView($view);
454
            }
455
        }
456
    }
457
458
    /**
459
     * Exports all the triggers found in database
460
     */
461
    private function exportTriggers()
462
    {
463
        // Exporting triggers one by one
464
        foreach ($this->triggers as $trigger) {
465
            $this->getTriggerStructure($trigger);
466
        }
467
    }
468
469
    /**
470
     * Exports all the procedures found in database
471
     */
472
    private function exportProcedures()
473
    {
474
        // Exporting triggers one by one
475
        foreach ($this->procedures as $procedure) {
476
            $this->getProcedureStructure($procedure);
477
        }
478
    }
479
480
    /**
481
     * Exports all the functions found in database
482
     */
483
    private function exportFunctions()
484
    {
485
        // Exporting triggers one by one
486
        foreach ($this->functions as $function) {
487
            $this->getFunctionStructure($function);
488
        }
489
    }
490
491
    /**
492
     * Exports all the events found in database
493
     */
494
    private function exportEvents()
495
    {
496
        // Exporting triggers one by one
497
        foreach ($this->events as $event) {
498
            $this->getEventStructure($event);
499
        }
500
    }
501
502
    /**
503
     * Table structure extractor
504
     */
505
    private function getTableStructure(string $tableName)
506
    {
507
        if (! $this->option->no_create_info) {
508
            $ret = '';
509
510
            if (! $this->option->skip_comments) {
511
                $ret = '--' . PHP_EOL .
512
                    "-- Table structure for table `{$tableName}`" . PHP_EOL .
513
                    '--' . PHP_EOL . PHP_EOL;
514
            }
515
516
            $stmt = $this->adapter->showCreateTable($tableName);
517
518
            foreach ($this->pdo->query($stmt) as $r) {
519
                $this->compressor->write($ret);
520
521
                if ($this->option->add_drop_table) {
522
                    $this->compressor->write($this->adapter->dropTable($tableName));
523
                }
524
525
                $this->compressor->write($this->adapter->createTable($r));
526
527
                break;
528
            }
529
        }
530
531
        $this->tableColumnTypes[$tableName] = $this->getTableColumnTypes($tableName);
532
    }
533
534
    /**
535
     * Store column types to create data dumps and for Stand-In tables
536
     *
537
     * @return array type column types detailed
538
     */
539
    private function getTableColumnTypes(string $tableName): array
540
    {
541
        $columnTypes = [];
542
543
        $columns = $this->pdo->query(
544
            $this->adapter->showColumns($tableName)
545
        );
546
        $columns->setFetchMode(PDO::FETCH_ASSOC);
547
548
        foreach ($columns as $key => $col) {
549
            $field = $col['Field'] ?? ($col['name'] ?? '');
550
            if ($field === '') {
551
                continue; // skip if field name is empty (MySQL 8.0+ returns empty name for computed columns)
552
            }
553
554
            $types               = $this->adapter->parseColumnType($col);
555
            $columnTypes[$field] = [
556
                'is_numeric' => $types['is_numeric'],
557
                'is_blob'    => $types['is_blob'],
558
                'type'       => $types['type'],
559
                'type_sql'   => $types['type_sql'] ?? $col['Type'],
560
                'is_virtual' => $types['is_virtual'],
561
            ];
562
        }
563
564
        return $columnTypes;
565
    }
566
567
    /**
568
     * View structure extractor, create table (avoids cyclic references)
569
     */
570
    private function getViewStructureTable(string $viewName): void
571
    {
572
        if (! $this->option->skip_comments) {
573
            $ret = '--' . PHP_EOL .
574
                "-- Stand-In structure for view `{$viewName}`" . PHP_EOL .
575
                '--' . PHP_EOL . PHP_EOL;
576
577
            $this->compressor->write($ret);
578
        }
579
580
        $stmt = $this->adapter->showCreateView($viewName);
581
582
        // create views as tables, to resolve dependencies
583
        foreach ($this->pdo->query($stmt) as $r) {
584
            if ($this->option->add_drop_table) {
585
                $this->compressor->write($this->adapter->dropView($viewName));
586
            }
587
588
            $this->compressor->write($this->createStandInTable($viewName));
589
            break;
590
        }
591
    }
592
593
    /**
594
     * Write a create table statement for the table Stand-In, show create
595
     * table would return a create algorithm when used on a view
596
     */
597
    public function createStandInTable(string $viewName): string
598
    {
599
        $ret = [];
600
601
        foreach ($this->tableColumnTypes[$viewName] as $k => $v) {
602
            $ret[] = "`{$k}` {$v['type_sql']}";
603
        }
604
605
        $ret = implode(PHP_EOL . ',', $ret);
606
607
        return "CREATE TABLE IF NOT EXISTS `{$viewName}` (" .
608
            PHP_EOL . $ret . PHP_EOL . ');' . PHP_EOL;
609
    }
610
611
    /**
612
     * View structure extractor, create view
613
     */
614
    private function getViewStructureView(string $viewName): void
615
    {
616
        if (! $this->option->skip_comments) {
617
            $ret = '--' . PHP_EOL .
618
                "-- View structure for view `{$viewName}`" . PHP_EOL .
619
                '--' . PHP_EOL . PHP_EOL;
620
            $this->compressor->write($ret);
621
        }
622
623
        $stmt = $this->adapter->showCreateView($viewName);
624
625
        // create views, to resolve dependencies
626
        // replacing tables with views
627
        foreach ($this->pdo->query($stmt) as $r) {
628
            // because we must replace table with view, we should delete it
629
            $this->compressor->write($this->adapter->dropView($viewName));
630
            $this->compressor->write($this->adapter->createView($r));
631
632
            break;
633
        }
634
    }
635
636
    /**
637
     * Trigger structure extractor
638
     */
639
    private function getTriggerStructure(string $triggerName): void
640
    {
641
        $stmt = $this->adapter->showCreateTrigger($triggerName);
642
643
        foreach ($this->pdo->query($stmt) as $r) {
644
            if ($this->option->add_drop_trigger) {
645
                $this->compressor->write($this->adapter->addDropTrigger($triggerName));
646
            }
647
648
            $this->compressor->write($this->adapter->createTrigger($r));
649
650
            return;
651
        }
652
    }
653
654
    /**
655
     * Procedure structure extractor
656
     */
657
    private function getProcedureStructure(string $procedureName)
658
    {
659
        if (! $this->option->skip_comments) {
660
            $ret = '--' . PHP_EOL .
661
                "-- Dumping routines for database '" . $this->database . "'" . PHP_EOL .
662
                '--' . PHP_EOL . PHP_EOL;
663
            $this->compressor->write($ret);
664
        }
665
666
        $stmt = $this->adapter->showCreateProcedure($procedureName);
667
668
        foreach ($this->pdo->query($stmt) as $r) {
669
            $this->compressor->write($this->adapter->createProcedure($r));
670
671
            return;
672
        }
673
    }
674
675
    /**
676
     * Function structure extractor
677
     */
678
    private function getFunctionStructure(string $functionName)
679
    {
680
        if (! $this->option->skip_comments) {
681
            $ret = '--' . PHP_EOL .
682
                "-- Dumping routines for database '" . $this->database . "'" . PHP_EOL .
683
                '--' . PHP_EOL . PHP_EOL;
684
685
            $this->compressor->write($ret);
686
        }
687
688
        $stmt = $this->adapter->showCreateFunction($functionName);
689
690
        foreach ($this->pdo->query($stmt) as $r) {
691
            $this->compressor->write($this->adapter->createFunction($r));
692
693
            return;
694
        }
695
    }
696
697
    /**
698
     * Event structure extractor
699
     */
700
    private function getEventStructure(string $eventName)
701
    {
702
        if (! $this->option->skip_comments) {
703
            $ret = '--' . PHP_EOL .
704
                "-- Dumping events for database '" . $this->database . "'" . PHP_EOL .
705
                '--' . PHP_EOL . PHP_EOL;
706
707
            $this->compressor->write($ret);
708
        }
709
710
        $stmt = $this->adapter->showCreateEvent($eventName);
711
712
        foreach ($this->pdo->query($stmt) as $r) {
713
            $this->compressor->write($this->adapter->createEvent($r));
714
715
            return;
716
        }
717
    }
718
719
    /**
720
     * Prepare values for output
721
     *
722
     * @param array $row Associative array of column names and values to be quoted
723
     */
724
    private function prepareColumnValues(string $tableName, array $row): array
725
    {
726
        $ret         = [];
727
        $columnTypes = $this->tableColumnTypes[$tableName];
728
729
        if ($this->transformTableRowCallable !== null) {
730
            $row = ($this->transformTableRowCallable)($tableName, $row);
731
        }
732
733
        foreach ($row as $colName => $colValue) {
734
            $ret[] = $this->escape($colValue, $columnTypes[$colName]);
735
        }
736
737
        return $ret;
738
    }
739
740
    /**
741
     * Escape values with quotes when needed
742
     *
743
     * @param mixed $colValue
744
     * @param mixed $colType
745
     */
746
    private function escape($colValue, $colType)
747
    {
748
        if (null === $colValue) {
749
            return 'NULL';
750
        }
751
        if ($this->option->hex_blob && $colType['is_blob']) {
752
            if ($colType['type'] === 'bit' || ! empty($colValue)) {
753
                return "0x{$colValue}";
754
            }
755
756
            return "''";
757
        }
758
        if ($colType['is_numeric']) {
759
            return $colValue;
760
        }
761
762
        return $this->pdo->quote($colValue);
763
    }
764
765
    /**
766
     * Set a callable that will be used to transform table rows
767
     */
768
    public function transformTableRow(callable $callable)
769
    {
770
        $this->transformTableRowCallable = $callable;
771
    }
772
773
    /**
774
     * Table rows extractor
775
     */
776
    private function listValues(string $tableName)
777
    {
778
        $this->prepareListValues($tableName);
779
780
        $onlyOnce = true;
781
782
        // colStmt is used to form a query to obtain row values
783
        $colStmt = $this->getColumnStmt($tableName);
784
785
        // colNames is used to get the name of the columns when using complete-insert
786
        if ($this->option->complete_insert) {
787
            $colNames = $this->getColumnNames($tableName);
788
        }
789
790
        $stmt = 'SELECT ' . implode(',', $colStmt) . " FROM `{$tableName}`";
791
792
        // Table specific conditions override the default 'where'
793
        $condition = $this->getTableWhere($tableName);
794
795
        if ($condition) {
796
            $stmt .= " WHERE {$condition}";
797
        }
798
799
        $limit = $this->getTableLimit($tableName);
800
801
        if ($limit !== false) {
802
            $stmt .= " LIMIT {$limit}";
803
        }
804
805
        $resultSet = $this->pdo->query($stmt);
806
        $resultSet->setFetchMode(PDO::FETCH_ASSOC);
807
808
        $ignore = $this->option->insert_ignore ? '  IGNORE' : '';
809
810
        $count = 0;
811
        $line  = '';
812
813
        foreach ($resultSet as $row) {
814
            $count++;
815
            $vals = $this->prepareColumnValues($tableName, $row);
816
            if ($onlyOnce || ! $this->option->extended_insert) {
817
                if ($this->option->complete_insert) {
818
                    $line .= "INSERT{$ignore} INTO `{$tableName}` (" .
819
                        implode(', ', $colNames) .
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $colNames does not seem to be defined for all execution paths leading up to this point.
Loading history...
820
                        ') VALUES (' . implode(',', $vals) . ')';
821
                } else {
822
                    $line .= "INSERT{$ignore} INTO `{$tableName}` VALUES (" . implode(',', $vals) . ')';
823
                }
824
                $onlyOnce = false;
825
            } else {
826
                $line .= ',(' . implode(',', $vals) . ')';
827
            }
828
829
            if ((strlen($line) > $this->option->net_buffer_length)
830
                    || ! $this->option->extended_insert) {
831
                $onlyOnce = true;
832
                $this->compressor->write($line . ';' . PHP_EOL);
833
                $line = '';
834
            }
835
        }
836
837
        $resultSet->closeCursor();
838
839
        if ('' !== $line) {
840
            $this->compressor->write($line . ';' . PHP_EOL);
841
        }
842
843
        $this->endListValues($tableName, $count);
844
845
        $this->event->emit('table.export', $tableName, $count);
846
    }
847
848
    /**
849
     * Table rows extractor, append information prior to dump
850
     */
851
    public function prepareListValues(string $tableName)
852
    {
853
        if (! $this->option->skip_comments) {
854
            $this->compressor->write(
855
                '--' . PHP_EOL .
856
                "-- Dumping data for table `{$tableName}`" . PHP_EOL .
857
                '--' . PHP_EOL . PHP_EOL
858
            );
859
        }
860
861
        if ($this->option->lock_tables && ! $this->option->single_transaction) {
862
            $this->adapter->lockTable($tableName);
863
        }
864
865
        if ($this->option->add_locks) {
866
            $this->compressor->write(
867
                $this->adapter->startAddLockTable($tableName)
868
            );
869
        }
870
871
        if ($this->option->disable_keys) {
872
            $this->compressor->write(
873
                $this->adapter->startAddDisableKeys($tableName)
874
            );
875
        }
876
877
        // Disable autocommit for faster reload
878
        if ($this->option->no_autocommit) {
879
            $this->compressor->write(
880
                $this->adapter->startDisableAutocommit()
881
            );
882
        }
883
    }
884
885
    /**
886
     * Table rows extractor, close locks and commits after dump
887
     *
888
     * @param int $count Number of rows inserted.
889
     */
890
    public function endListValues(string $tableName, int $count = 0)
891
    {
892
        if ($this->option->disable_keys) {
893
            $this->compressor->write(
894
                $this->adapter->endAddDisableKeys($tableName)
895
            );
896
        }
897
898
        if ($this->option->add_locks) {
899
            $this->compressor->write($this->adapter->endAddLockTable($tableName));
900
        }
901
902
        if ($this->option->lock_tables && ! $this->option->single_transaction) {
903
            $this->adapter->unlockTable($tableName);
904
        }
905
906
        // Commit to enable autocommit
907
        if ($this->option->no_autocommit) {
908
            $this->compressor->write(
909
                $this->adapter->endDisableAutocommit()
910
            );
911
        }
912
913
        $this->compressor->write(PHP_EOL);
914
915
        if (! $this->option->skip_comments) {
916
            $this->compressor->write(
917
                '-- Dumped table `' . $tableName . "` with {$count} row(s)" . PHP_EOL .
918
                '--' . PHP_EOL . PHP_EOL
919
            );
920
        }
921
    }
922
923
    /**
924
     * Build SQL List of all columns on current table which will be used for selecting
925
     *
926
     * @return array SQL sentence with columns for select
927
     */
928
    public function getColumnStmt(string $tableName): array
929
    {
930
        $colStmt = [];
931
932
        foreach ($this->tableColumnTypes[$tableName] as $colName => $colType) {
933
            if ($colType['is_virtual']) {
934
                $this->option->complete_insert = true;
935
936
                continue;
937
            }
938
            if ($colType['type'] === 'bit' && $this->option->hex_blob) {
939
                $colStmt[] = "LPAD(HEX(`{$colName}`),2,'0') AS `{$colName}`";
940
            } elseif ($colType['type'] === 'double' && PHP_VERSION_ID > 80100) {
941
                $colStmt[] = sprintf('CONCAT(`%s`) AS `%s`', $colName, $colName);
942
            } elseif ($colType['is_blob'] && $this->option->hex_blob) {
943
                $colStmt[] = "HEX(`{$colName}`) AS `{$colName}`";
944
            } else {
945
                $colStmt[] = "`{$colName}`";
946
            }
947
        }
948
949
        return $colStmt;
950
    }
951
952
    /**
953
     * Build SQL List of all columns on current table which will be used for inserting
954
     *
955
     * @return array columns for sql sentence for insert
956
     */
957
    public function getColumnNames(string $tableName): array
958
    {
959
        $colNames = [];
960
961
        foreach ($this->tableColumnTypes[$tableName] as $colName => $colType) {
962
            if ($colType['is_virtual']) {
963
                $this->option->complete_insert = true;
964
965
                continue;
966
            }
967
            $colNames[] = "`{$colName}`";
968
        }
969
970
        return $colNames;
971
    }
972
}
973