Total Complexity | 152 |
Total Lines | 951 |
Duplicated Lines | 0 % |
Changes | 4 | ||
Bugs | 1 | Features | 0 |
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 |
||
20 | class Exporter |
||
21 | { |
||
22 | use Dumper; |
||
|
|||
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) . |
||
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 |
||
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 |