[ Index ] |
PHP Cross Reference of phpBB-3.2.11-deutsch |
[Summary view] [Print] [Text view]
1 <?php 2 /** 3 * 4 * This file is part of the phpBB Forum Software package. 5 * 6 * @copyright (c) phpBB Limited <https://www.phpbb.com> 7 * @license GNU General Public License, version 2 (GPL-2.0) 8 * 9 * For full copyright and license information, please see 10 * the docs/CREDITS.txt file. 11 * 12 */ 13 14 namespace phpbb\db; 15 16 use phpbb\db\output_handler\migrator_output_handler_interface; 17 use phpbb\db\output_handler\null_migrator_output_handler; 18 use Symfony\Component\DependencyInjection\ContainerAwareInterface; 19 use Symfony\Component\DependencyInjection\ContainerInterface; 20 21 /** 22 * The migrator is responsible for applying new migrations in the correct order. 23 */ 24 class migrator 25 { 26 /** 27 * @var ContainerInterface 28 */ 29 protected $container; 30 31 /** @var \phpbb\config\config */ 32 protected $config; 33 34 /** @var \phpbb\db\driver\driver_interface */ 35 protected $db; 36 37 /** @var \phpbb\db\tools\tools_interface */ 38 protected $db_tools; 39 40 /** @var \phpbb\db\migration\helper */ 41 protected $helper; 42 43 /** @var string */ 44 protected $table_prefix; 45 46 /** @var string */ 47 protected $phpbb_root_path; 48 49 /** @var string */ 50 protected $php_ext; 51 52 /** @var string */ 53 protected $migrations_table; 54 55 /** 56 * State of all migrations 57 * 58 * (SELECT * FROM migrations table) 59 * 60 * @var array 61 */ 62 protected $migration_state = array(); 63 64 /** 65 * Array of all migrations available to be run 66 * 67 * @var array 68 */ 69 protected $migrations = array(); 70 71 /** 72 * Array of migrations that have been determined to be fulfillable 73 * 74 * @var array 75 */ 76 protected $fulfillable_migrations = array(); 77 78 /** 79 * 'name,' 'class,' and 'state' of the last migration run 80 * 81 * 'effectively_installed' set and set to true if the migration was effectively_installed 82 * 83 * @var array 84 */ 85 protected $last_run_migration = false; 86 87 /** 88 * The output handler. A null handler is configured by default. 89 * 90 * @var migrator_output_handler_interface 91 */ 92 protected $output_handler; 93 94 /** 95 * Constructor of the database migrator 96 */ 97 public function __construct(ContainerInterface $container, \phpbb\config\config $config, \phpbb\db\driver\driver_interface $db, \phpbb\db\tools\tools_interface $db_tools, $migrations_table, $phpbb_root_path, $php_ext, $table_prefix, $tools, \phpbb\db\migration\helper $helper) 98 { 99 $this->container = $container; 100 $this->config = $config; 101 $this->db = $db; 102 $this->db_tools = $db_tools; 103 $this->helper = $helper; 104 105 $this->migrations_table = $migrations_table; 106 107 $this->phpbb_root_path = $phpbb_root_path; 108 $this->php_ext = $php_ext; 109 110 $this->table_prefix = $table_prefix; 111 112 $this->output_handler = new null_migrator_output_handler(); 113 114 foreach ($tools as $tool) 115 { 116 $this->tools[$tool->get_name()] = $tool; 117 } 118 119 $this->tools['dbtools'] = $this->db_tools; 120 121 $this->load_migration_state(); 122 } 123 124 /** 125 * Set the output handler. 126 * 127 * @param migrator_output_handler_interface $handler The output handler 128 */ 129 public function set_output_handler(migrator_output_handler_interface $handler) 130 { 131 $this->output_handler = $handler; 132 } 133 134 /** 135 * Loads all migrations and their application state from the database. 136 * 137 * @return null 138 */ 139 public function load_migration_state() 140 { 141 $this->migration_state = array(); 142 143 // prevent errors in case the table does not exist yet 144 $this->db->sql_return_on_error(true); 145 146 $sql = "SELECT * 147 FROM " . $this->migrations_table; 148 $result = $this->db->sql_query($sql); 149 150 if (!$this->db->get_sql_error_triggered()) 151 { 152 while ($migration = $this->db->sql_fetchrow($result)) 153 { 154 $this->migration_state[$migration['migration_name']] = $migration; 155 156 $this->migration_state[$migration['migration_name']]['migration_depends_on'] = unserialize($migration['migration_depends_on']); 157 $this->migration_state[$migration['migration_name']]['migration_data_state'] = !empty($migration['migration_data_state']) ? unserialize($migration['migration_data_state']) : ''; 158 } 159 } 160 161 $this->db->sql_freeresult($result); 162 163 $this->db->sql_return_on_error(false); 164 } 165 166 /** 167 * Get an array with information about the last migration run. 168 * 169 * The array contains 'name', 'class' and 'state'. 'effectively_installed' is set 170 * and set to true if the last migration was effectively_installed. 171 * 172 * @return array 173 */ 174 public function get_last_run_migration() 175 { 176 return $this->last_run_migration; 177 } 178 179 /** 180 * Sets the list of available migration class names to the given array. 181 * 182 * @param array $class_names An array of migration class names 183 * @return null 184 */ 185 public function set_migrations($class_names) 186 { 187 foreach ($class_names as $key => $class) 188 { 189 if (!self::is_migration($class)) 190 { 191 unset($class_names[$key]); 192 } 193 } 194 195 $this->migrations = $class_names; 196 } 197 198 /** 199 * Get the list of available migration class names 200 * 201 * @return array Array of all migrations available to be run 202 */ 203 public function get_migrations() 204 { 205 return $this->migrations; 206 } 207 208 /** 209 * Get the list of available and not installed migration class names 210 * 211 * @return array 212 */ 213 public function get_installable_migrations() 214 { 215 $unfinished_migrations = array(); 216 217 foreach ($this->migrations as $name) 218 { 219 if (!isset($this->migration_state[$name]) || 220 !$this->migration_state[$name]['migration_schema_done'] || 221 !$this->migration_state[$name]['migration_data_done']) 222 { 223 $unfinished_migrations[] = $name; 224 } 225 } 226 227 return $unfinished_migrations; 228 } 229 230 /** 231 * Runs a single update step from the next migration to be applied. 232 * 233 * The update step can either be a schema or a (partial) data update. To 234 * check if update() needs to be called again use the finished() method. 235 * 236 * @return null 237 */ 238 public function update() 239 { 240 $this->container->get('dispatcher')->disable(); 241 $this->update_do(); 242 $this->container->get('dispatcher')->enable(); 243 } 244 245 /** 246 * Get a valid migration name from the migration state array in case the 247 * supplied name is not in the migration state list. 248 * 249 * @param string $name Migration name 250 * @return string Migration name 251 */ 252 protected function get_valid_name($name) 253 { 254 // Try falling back to a valid migration name with or without leading backslash 255 if (!isset($this->migration_state[$name])) 256 { 257 $prepended_name = ($name[0] == '\\' ? '' : '\\') . $name; 258 $prefixless_name = $name[0] == '\\' ? substr($name, 1) : $name; 259 260 if (isset($this->migration_state[$prepended_name])) 261 { 262 $name = $prepended_name; 263 } 264 else if (isset($this->migration_state[$prefixless_name])) 265 { 266 $name = $prefixless_name; 267 } 268 } 269 270 return $name; 271 } 272 273 /** 274 * Effectively runs a single update step from the next migration to be applied. 275 * 276 * @return null 277 */ 278 protected function update_do() 279 { 280 foreach ($this->migrations as $name) 281 { 282 $name = $this->get_valid_name($name); 283 284 if (!isset($this->migration_state[$name]) || 285 !$this->migration_state[$name]['migration_schema_done'] || 286 !$this->migration_state[$name]['migration_data_done']) 287 { 288 if (!$this->try_apply($name)) 289 { 290 continue; 291 } 292 else 293 { 294 return; 295 } 296 } 297 else 298 { 299 $this->output_handler->write(array('MIGRATION_EFFECTIVELY_INSTALLED', $name), migrator_output_handler_interface::VERBOSITY_DEBUG); 300 } 301 } 302 } 303 304 /** 305 * Attempts to apply a step of the given migration or one of its dependencies 306 * 307 * @param string $name The class name of the migration 308 * @return bool Whether any update step was successfully run 309 * @throws \phpbb\db\migration\exception 310 */ 311 protected function try_apply($name) 312 { 313 if (!class_exists($name)) 314 { 315 $this->output_handler->write(array('MIGRATION_NOT_VALID', $name), migrator_output_handler_interface::VERBOSITY_DEBUG); 316 return false; 317 } 318 319 $migration = $this->get_migration($name); 320 321 $state = (isset($this->migration_state[$name])) ? 322 $this->migration_state[$name] : 323 array( 324 'migration_depends_on' => $migration->depends_on(), 325 'migration_schema_done' => false, 326 'migration_data_done' => false, 327 'migration_data_state' => '', 328 'migration_start_time' => 0, 329 'migration_end_time' => 0, 330 ); 331 332 if (!empty($state['migration_depends_on'])) 333 { 334 $this->output_handler->write(array('MIGRATION_APPLY_DEPENDENCIES', $name), migrator_output_handler_interface::VERBOSITY_DEBUG); 335 } 336 337 foreach ($state['migration_depends_on'] as $depend) 338 { 339 $depend = $this->get_valid_name($depend); 340 341 // Test all possible namings before throwing exception 342 if ($this->unfulfillable($depend) !== false) 343 { 344 throw new \phpbb\db\migration\exception('MIGRATION_NOT_FULFILLABLE', $name, $depend); 345 } 346 347 if (!isset($this->migration_state[$depend]) || 348 !$this->migration_state[$depend]['migration_schema_done'] || 349 !$this->migration_state[$depend]['migration_data_done']) 350 { 351 return $this->try_apply($depend); 352 } 353 } 354 355 $this->last_run_migration = array( 356 'name' => $name, 357 'class' => $migration, 358 'state' => $state, 359 'task' => '', 360 ); 361 362 if (!isset($this->migration_state[$name])) 363 { 364 if ($state['migration_start_time'] == 0 && $migration->effectively_installed()) 365 { 366 $state = array( 367 'migration_depends_on' => $migration->depends_on(), 368 'migration_schema_done' => true, 369 'migration_data_done' => true, 370 'migration_data_state' => '', 371 'migration_start_time' => 0, 372 'migration_end_time' => 0, 373 ); 374 375 $this->last_run_migration['effectively_installed'] = true; 376 377 $this->output_handler->write(array('MIGRATION_EFFECTIVELY_INSTALLED', $name), migrator_output_handler_interface::VERBOSITY_VERBOSE); 378 } 379 else 380 { 381 $state['migration_start_time'] = time(); 382 } 383 } 384 385 $this->set_migration_state($name, $state); 386 387 if (!$state['migration_schema_done']) 388 { 389 $verbosity = empty($state['migration_data_state']) ? 390 migrator_output_handler_interface::VERBOSITY_VERBOSE : migrator_output_handler_interface::VERBOSITY_DEBUG; 391 $this->output_handler->write(array('MIGRATION_SCHEMA_RUNNING', $name), $verbosity); 392 393 $this->last_run_migration['task'] = 'process_schema_step'; 394 395 $total_time = (is_array($state['migration_data_state']) && isset($state['migration_data_state']['_total_time'])) ? 396 $state['migration_data_state']['_total_time'] : 0.0; 397 $elapsed_time = microtime(true); 398 399 $steps = $this->helper->get_schema_steps($migration->update_schema()); 400 $result = $this->process_data_step($steps, $state['migration_data_state']); 401 402 $elapsed_time = microtime(true) - $elapsed_time; 403 $total_time += $elapsed_time; 404 405 if (is_array($result)) 406 { 407 $result['_total_time'] = $total_time; 408 } 409 410 $state['migration_data_state'] = ($result === true) ? '' : $result; 411 $state['migration_schema_done'] = ($result === true); 412 413 if ($state['migration_schema_done']) 414 { 415 $this->output_handler->write(array('MIGRATION_SCHEMA_DONE', $name, $total_time), migrator_output_handler_interface::VERBOSITY_NORMAL); 416 } 417 else 418 { 419 $this->output_handler->write(array('MIGRATION_SCHEMA_IN_PROGRESS', $name, $elapsed_time), migrator_output_handler_interface::VERBOSITY_VERY_VERBOSE); 420 } 421 } 422 else if (!$state['migration_data_done']) 423 { 424 try 425 { 426 $verbosity = empty($state['migration_data_state']) ? 427 migrator_output_handler_interface::VERBOSITY_VERBOSE : migrator_output_handler_interface::VERBOSITY_DEBUG; 428 $this->output_handler->write(array('MIGRATION_DATA_RUNNING', $name), $verbosity); 429 430 $this->last_run_migration['task'] = 'process_data_step'; 431 432 $total_time = (is_array($state['migration_data_state']) && isset($state['migration_data_state']['_total_time'])) ? 433 $state['migration_data_state']['_total_time'] : 0.0; 434 $elapsed_time = microtime(true); 435 436 $result = $this->process_data_step($migration->update_data(), $state['migration_data_state']); 437 438 $elapsed_time = microtime(true) - $elapsed_time; 439 $total_time += $elapsed_time; 440 441 if (is_array($result)) 442 { 443 $result['_total_time'] = $total_time; 444 } 445 446 $state['migration_data_state'] = ($result === true) ? '' : $result; 447 $state['migration_data_done'] = ($result === true); 448 $state['migration_end_time'] = ($result === true) ? time() : 0; 449 450 if ($state['migration_data_done']) 451 { 452 $this->output_handler->write(array('MIGRATION_DATA_DONE', $name, $total_time), migrator_output_handler_interface::VERBOSITY_NORMAL); 453 } 454 else 455 { 456 $this->output_handler->write(array('MIGRATION_DATA_IN_PROGRESS', $name, $elapsed_time), migrator_output_handler_interface::VERBOSITY_VERY_VERBOSE); 457 } 458 } 459 catch (\phpbb\db\migration\exception $e) 460 { 461 // Reset data state and revert the schema changes 462 $state['migration_data_state'] = ''; 463 $this->set_migration_state($name, $state); 464 465 $this->revert_do($name); 466 467 throw $e; 468 } 469 } 470 471 $this->set_migration_state($name, $state); 472 473 return true; 474 } 475 476 /** 477 * Runs a single revert step from the last migration installed 478 * 479 * YOU MUST ADD/SET ALL MIGRATIONS THAT COULD BE DEPENDENT ON THE MIGRATION TO REVERT TO BEFORE CALLING THIS METHOD! 480 * The revert step can either be a schema or a (partial) data revert. To 481 * check if revert() needs to be called again use the migration_state() method. 482 * 483 * @param string $migration String migration name to revert (including any that depend on this migration) 484 */ 485 public function revert($migration) 486 { 487 $this->container->get('dispatcher')->disable(); 488 $this->revert_do($migration); 489 $this->container->get('dispatcher')->enable(); 490 } 491 492 /** 493 * Effectively runs a single revert step from the last migration installed 494 * 495 * @param string $migration String migration name to revert (including any that depend on this migration) 496 * @return null 497 */ 498 protected function revert_do($migration) 499 { 500 if (!isset($this->migration_state[$migration])) 501 { 502 // Not installed 503 return; 504 } 505 506 foreach ($this->migrations as $name) 507 { 508 $state = $this->migration_state($name); 509 510 if ($state && in_array($migration, $state['migration_depends_on']) && ($state['migration_schema_done'] || $state['migration_data_done'])) 511 { 512 $this->revert_do($name); 513 return; 514 } 515 } 516 517 $this->try_revert($migration); 518 } 519 520 /** 521 * Attempts to revert a step of the given migration or one of its dependencies 522 * 523 * @param string $name The class name of the migration 524 * @return bool Whether any update step was successfully run 525 */ 526 protected function try_revert($name) 527 { 528 if (!class_exists($name)) 529 { 530 return false; 531 } 532 533 $migration = $this->get_migration($name); 534 535 $state = $this->migration_state[$name]; 536 537 $this->last_run_migration = array( 538 'name' => $name, 539 'class' => $migration, 540 'task' => '', 541 ); 542 543 if ($state['migration_data_done']) 544 { 545 $verbosity = empty($state['migration_data_state']) ? 546 migrator_output_handler_interface::VERBOSITY_VERBOSE : migrator_output_handler_interface::VERBOSITY_DEBUG; 547 $this->output_handler->write(array('MIGRATION_REVERT_DATA_RUNNING', $name), $verbosity); 548 549 $total_time = (is_array($state['migration_data_state']) && isset($state['migration_data_state']['_total_time'])) ? 550 $state['migration_data_state']['_total_time'] : 0.0; 551 $elapsed_time = microtime(true); 552 553 $steps = array_merge($this->helper->reverse_update_data($migration->update_data()), $migration->revert_data()); 554 $result = $this->process_data_step($steps, $state['migration_data_state']); 555 556 $elapsed_time = microtime(true) - $elapsed_time; 557 $total_time += $elapsed_time; 558 559 if (is_array($result)) 560 { 561 $result['_total_time'] = $total_time; 562 } 563 564 $state['migration_data_state'] = ($result === true) ? '' : $result; 565 $state['migration_data_done'] = ($result === true) ? false : true; 566 567 $this->set_migration_state($name, $state); 568 569 if (!$state['migration_data_done']) 570 { 571 $this->output_handler->write(array('MIGRATION_REVERT_DATA_DONE', $name, $total_time), migrator_output_handler_interface::VERBOSITY_NORMAL); 572 } 573 else 574 { 575 $this->output_handler->write(array('MIGRATION_REVERT_DATA_IN_PROGRESS', $name, $elapsed_time), migrator_output_handler_interface::VERBOSITY_VERY_VERBOSE); 576 } 577 } 578 else if ($state['migration_schema_done']) 579 { 580 $verbosity = empty($state['migration_data_state']) ? 581 migrator_output_handler_interface::VERBOSITY_VERBOSE : migrator_output_handler_interface::VERBOSITY_DEBUG; 582 $this->output_handler->write(array('MIGRATION_REVERT_SCHEMA_RUNNING', $name), $verbosity); 583 584 $total_time = (is_array($state['migration_data_state']) && isset($state['migration_data_state']['_total_time'])) ? 585 $state['migration_data_state']['_total_time'] : 0.0; 586 $elapsed_time = microtime(true); 587 588 $steps = $this->helper->get_schema_steps($migration->revert_schema()); 589 $result = $this->process_data_step($steps, $state['migration_data_state']); 590 591 $elapsed_time = microtime(true) - $elapsed_time; 592 $total_time += $elapsed_time; 593 594 if (is_array($result)) 595 { 596 $result['_total_time'] = $total_time; 597 } 598 599 $state['migration_data_state'] = ($result === true) ? '' : $result; 600 $state['migration_schema_done'] = ($result === true) ? false : true; 601 602 if (!$state['migration_schema_done']) 603 { 604 $sql = 'DELETE FROM ' . $this->migrations_table . " 605 WHERE migration_name = '" . $this->db->sql_escape($name) . "'"; 606 $this->db->sql_query($sql); 607 608 $this->last_run_migration = false; 609 unset($this->migration_state[$name]); 610 611 $this->output_handler->write(array('MIGRATION_REVERT_SCHEMA_DONE', $name, $total_time), migrator_output_handler_interface::VERBOSITY_NORMAL); 612 } 613 else 614 { 615 $this->set_migration_state($name, $state); 616 617 $this->output_handler->write(array('MIGRATION_REVERT_SCHEMA_IN_PROGRESS', $name, $elapsed_time), migrator_output_handler_interface::VERBOSITY_VERY_VERBOSE); 618 } 619 } 620 621 return true; 622 } 623 624 /** 625 * Process the data step of the migration 626 * 627 * @param array $steps The steps to run 628 * @param bool|string $state Current state of the migration 629 * @param bool $revert true to revert a data step 630 * @return bool|string migration state. True if completed, serialized array if not finished 631 * @throws \phpbb\db\migration\exception 632 */ 633 protected function process_data_step($steps, $state, $revert = false) 634 { 635 if (count($steps) === 0) 636 { 637 return true; 638 } 639 640 $state = is_array($state) ? $state : false; 641 642 // reverse order of steps if reverting 643 if ($revert === true) 644 { 645 $steps = array_reverse($steps); 646 } 647 648 $step = $last_result = 0; 649 if ($state) 650 { 651 $step = $state['step']; 652 653 // We send the result from last time to the callable function 654 $last_result = $state['result']; 655 } 656 657 try 658 { 659 // Result will be null or true if everything completed correctly 660 // Stop after each update step, to let the updater control the script runtime 661 $result = $this->run_step($steps[$step], $last_result, $revert); 662 if (($result !== null && $result !== true) || $step + 1 < count($steps)) 663 { 664 return array( 665 'result' => $result, 666 // Move on if the last call finished 667 'step' => ($result !== null && $result !== true) ? $step : $step + 1, 668 ); 669 } 670 } 671 catch (\phpbb\db\migration\exception $e) 672 { 673 // We should try rolling back here 674 foreach ($steps as $reverse_step_identifier => $reverse_step) 675 { 676 // If we've reached the current step we can break because we reversed everything that was run 677 if ($reverse_step_identifier == $step) 678 { 679 break; 680 } 681 682 // Reverse the step that was run 683 $result = $this->run_step($reverse_step, false, !$revert); 684 } 685 686 throw $e; 687 } 688 689 return true; 690 } 691 692 /** 693 * Run a single step 694 * 695 * An exception should be thrown if an error occurs 696 * 697 * @param mixed $step Data step from migration 698 * @param mixed $last_result Result to pass to the callable (only for 'custom' method) 699 * @param bool $reverse False to install, True to attempt uninstallation by reversing the call 700 * @return null 701 */ 702 protected function run_step($step, $last_result = 0, $reverse = false) 703 { 704 $callable_and_parameters = $this->get_callable_from_step($step, $last_result, $reverse); 705 706 if ($callable_and_parameters === false) 707 { 708 return; 709 } 710 711 $callable = $callable_and_parameters[0]; 712 $parameters = $callable_and_parameters[1]; 713 714 return call_user_func_array($callable, $parameters); 715 } 716 717 /** 718 * Get a callable statement from a data step 719 * 720 * @param array $step Data step from migration 721 * @param mixed $last_result Result to pass to the callable (only for 'custom' method) 722 * @param bool $reverse False to install, True to attempt uninstallation by reversing the call 723 * @return array Array with parameters for call_user_func_array(), 0 is the callable, 1 is parameters 724 * @throws \phpbb\db\migration\exception 725 */ 726 protected function get_callable_from_step(array $step, $last_result = 0, $reverse = false) 727 { 728 $type = $step[0]; 729 $parameters = $step[1]; 730 731 $parts = explode('.', $type); 732 733 $class = $parts[0]; 734 $method = false; 735 736 if (isset($parts[1])) 737 { 738 $method = $parts[1]; 739 } 740 741 switch ($class) 742 { 743 case 'if': 744 if (!isset($parameters[0])) 745 { 746 throw new \phpbb\db\migration\exception('MIGRATION_INVALID_DATA_MISSING_CONDITION', $step); 747 } 748 749 if (!isset($parameters[1])) 750 { 751 throw new \phpbb\db\migration\exception('MIGRATION_INVALID_DATA_MISSING_STEP', $step); 752 } 753 754 if ($reverse) 755 { 756 // We might get unexpected results when trying 757 // to revert this, so just avoid it 758 return false; 759 } 760 761 $condition = $parameters[0]; 762 763 if (!$condition || (is_array($condition) && !$this->run_step($condition, $last_result, $reverse))) 764 { 765 return false; 766 } 767 768 $step = $parameters[1]; 769 770 return $this->get_callable_from_step($step); 771 break; 772 773 case 'custom': 774 if (!is_callable($parameters[0])) 775 { 776 throw new \phpbb\db\migration\exception('MIGRATION_INVALID_DATA_CUSTOM_NOT_CALLABLE', $step); 777 } 778 779 if ($reverse) 780 { 781 return false; 782 } 783 else 784 { 785 return array( 786 $parameters[0], 787 array($last_result), 788 ); 789 } 790 break; 791 792 default: 793 if (!$method) 794 { 795 throw new \phpbb\db\migration\exception('MIGRATION_INVALID_DATA_UNKNOWN_TYPE', $step); 796 } 797 798 if (!isset($this->tools[$class])) 799 { 800 throw new \phpbb\db\migration\exception('MIGRATION_INVALID_DATA_UNDEFINED_TOOL', $step); 801 } 802 803 if (!method_exists(get_class($this->tools[$class]), $method)) 804 { 805 throw new \phpbb\db\migration\exception('MIGRATION_INVALID_DATA_UNDEFINED_METHOD', $step); 806 } 807 808 // Attempt to reverse operations 809 if ($reverse) 810 { 811 array_unshift($parameters, $method); 812 813 return array( 814 array($this->tools[$class], 'reverse'), 815 $parameters, 816 ); 817 } 818 819 return array( 820 array($this->tools[$class], $method), 821 $parameters, 822 ); 823 break; 824 } 825 } 826 827 /** 828 * Insert/Update migration row into the database 829 * 830 * @param string $name Name of the migration 831 * @param array $state 832 * @return null 833 */ 834 protected function set_migration_state($name, $state) 835 { 836 $migration_row = $state; 837 $migration_row['migration_depends_on'] = serialize($state['migration_depends_on']); 838 $migration_row['migration_data_state'] = !empty($state['migration_data_state']) ? serialize($state['migration_data_state']) : ''; 839 840 if (isset($this->migration_state[$name])) 841 { 842 $sql = 'UPDATE ' . $this->migrations_table . ' 843 SET ' . $this->db->sql_build_array('UPDATE', $migration_row) . " 844 WHERE migration_name = '" . $this->db->sql_escape($name) . "'"; 845 $this->db->sql_query($sql); 846 } 847 else 848 { 849 $migration_row['migration_name'] = $name; 850 $sql = 'INSERT INTO ' . $this->migrations_table . ' 851 ' . $this->db->sql_build_array('INSERT', $migration_row); 852 $this->db->sql_query($sql); 853 } 854 855 $this->migration_state[$name] = $state; 856 857 $this->last_run_migration['state'] = $state; 858 } 859 860 /** 861 * Checks if a migration's dependencies can even theoretically be satisfied. 862 * 863 * @param string $name The class name of the migration 864 * @return bool|string False if fulfillable, string of missing migration name if unfulfillable 865 */ 866 public function unfulfillable($name) 867 { 868 $name = $this->get_valid_name($name); 869 870 if (isset($this->migration_state[$name]) || isset($this->fulfillable_migrations[$name])) 871 { 872 return false; 873 } 874 875 if (!class_exists($name)) 876 { 877 return $name; 878 } 879 880 $migration = $this->get_migration($name); 881 $depends = $migration->depends_on(); 882 883 foreach ($depends as $depend) 884 { 885 $depend = $this->get_valid_name($depend); 886 $unfulfillable = $this->unfulfillable($depend); 887 if ($unfulfillable !== false) 888 { 889 return $unfulfillable; 890 } 891 } 892 $this->fulfillable_migrations[$name] = true; 893 894 return false; 895 } 896 897 /** 898 * Checks whether all available, fulfillable migrations have been applied. 899 * 900 * @return bool Whether the migrations have been applied 901 */ 902 public function finished() 903 { 904 foreach ($this->migrations as $name) 905 { 906 if (!isset($this->migration_state[$name])) 907 { 908 // skip unfulfillable migrations, but fulfillables mean we 909 // are not finished yet 910 if ($this->unfulfillable($name) !== false) 911 { 912 continue; 913 } 914 915 return false; 916 } 917 918 $migration = $this->migration_state[$name]; 919 920 if (!$migration['migration_schema_done'] || !$migration['migration_data_done']) 921 { 922 return false; 923 } 924 } 925 926 return true; 927 } 928 929 /** 930 * Gets a migration state (whether it is installed and to what extent) 931 * 932 * @param string $migration String migration name to check if it is installed 933 * @return bool|array False if the migration has not at all been installed, array 934 */ 935 public function migration_state($migration) 936 { 937 if (!isset($this->migration_state[$migration])) 938 { 939 return false; 940 } 941 942 return $this->migration_state[$migration]; 943 } 944 945 /** 946 * Helper to get a migration 947 * 948 * @param string $name Name of the migration 949 * @return \phpbb\db\migration\migration 950 */ 951 public function get_migration($name) 952 { 953 $migration = new $name($this->config, $this->db, $this->db_tools, $this->phpbb_root_path, $this->php_ext, $this->table_prefix); 954 955 if ($migration instanceof ContainerAwareInterface) 956 { 957 $migration->setContainer($this->container); 958 } 959 960 return $migration; 961 } 962 963 /** 964 * This function adds all migrations sent to it to the migrations table 965 * 966 * THIS SHOULD NOT GENERALLY BE USED! THIS IS FOR THE PHPBB INSTALLER. 967 * THIS WILL THROW ERRORS IF MIGRATIONS ALREADY EXIST IN THE TABLE, DO NOT CALL MORE THAN ONCE! 968 * 969 * @param array $migrations Array of migrations (names) to add to the migrations table 970 * @return null 971 */ 972 public function populate_migrations($migrations) 973 { 974 foreach ($migrations as $name) 975 { 976 if ($this->migration_state($name) === false) 977 { 978 $state = array( 979 'migration_depends_on' => $name::depends_on(), 980 'migration_schema_done' => true, 981 'migration_data_done' => true, 982 'migration_data_state' => '', 983 'migration_start_time' => time(), 984 'migration_end_time' => time(), 985 ); 986 $this->set_migration_state($name, $state); 987 } 988 } 989 } 990 991 /** 992 * Creates the migrations table if it does not exist. 993 * @return null 994 */ 995 public function create_migrations_table() 996 { 997 // Make sure migrations have been installed. 998 if (!$this->db_tools->sql_table_exists($this->table_prefix . 'migrations')) 999 { 1000 $this->db_tools->sql_create_table($this->table_prefix . 'migrations', array( 1001 'COLUMNS' => array( 1002 'migration_name' => array('VCHAR', ''), 1003 'migration_depends_on' => array('TEXT', ''), 1004 'migration_schema_done' => array('BOOL', 0), 1005 'migration_data_done' => array('BOOL', 0), 1006 'migration_data_state' => array('TEXT', ''), 1007 'migration_start_time' => array('TIMESTAMP', 0), 1008 'migration_end_time' => array('TIMESTAMP', 0), 1009 ), 1010 'PRIMARY_KEY' => 'migration_name', 1011 )); 1012 } 1013 } 1014 1015 /** 1016 * Check if a class is a migration. 1017 * 1018 * @param string $migration A migration class name 1019 * @return bool Return true if class is a migration, false otherwise 1020 */ 1021 static public function is_migration($migration) 1022 { 1023 if (class_exists($migration)) 1024 { 1025 // Migration classes should extend the abstract class 1026 // phpbb\db\migration\migration (which implements the 1027 // migration_interface) and be instantiable. 1028 $reflector = new \ReflectionClass($migration); 1029 if ($reflector->implementsInterface('\phpbb\db\migration\migration_interface') && $reflector->isInstantiable()) 1030 { 1031 return true; 1032 } 1033 } 1034 1035 return false; 1036 } 1037 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Wed Nov 11 20:33:01 2020 | Cross-referenced by PHPXref 0.7.1 |