Verzeichnisstruktur phpBB-3.3.15
- Veröffentlicht
- 28.08.2024
So funktioniert es
|
|
Auf das letzte Element klicken. Dies geht jeweils ein Schritt zurück |
Auf das Icon klicken, dies öffnet das Verzeichnis. Nochmal klicken schließt das Verzeichnis. |
|
|
(Beispiel Datei-Icons)
|
Auf das Icon klicken um den Quellcode anzuzeigen |
migrator.php
0001 <?php
0002 /**
0003 *
0004 * This file is part of the phpBB Forum Software package.
0005 *
0006 * @copyright (c) phpBB Limited <https://www.phpbb.com>
0007 * @license GNU General Public License, version 2 (GPL-2.0)
0008 *
0009 * For full copyright and license information, please see
0010 * the docs/CREDITS.txt file.
0011 *
0012 */
0013
0014 namespace phpbb\db;
0015
0016 use phpbb\db\output_handler\migrator_output_handler_interface;
0017 use phpbb\db\output_handler\null_migrator_output_handler;
0018 use Symfony\Component\DependencyInjection\ContainerAwareInterface;
0019 use Symfony\Component\DependencyInjection\ContainerInterface;
0020
0021 /**
0022 * The migrator is responsible for applying new migrations in the correct order.
0023 */
0024 class migrator
0025 {
0026 /**
0027 * @var ContainerInterface
0028 */
0029 protected $container;
0030
0031 /** @var \phpbb\config\config */
0032 protected $config;
0033
0034 /** @var \phpbb\db\driver\driver_interface */
0035 protected $db;
0036
0037 /** @var \phpbb\db\tools\tools_interface */
0038 protected $db_tools;
0039
0040 /** @var \phpbb\db\migration\helper */
0041 protected $helper;
0042
0043 /** @var string */
0044 protected $table_prefix;
0045
0046 /** @var string */
0047 protected $phpbb_root_path;
0048
0049 /** @var string */
0050 protected $php_ext;
0051
0052 /** @var string */
0053 protected $migrations_table;
0054
0055 /**
0056 * State of all migrations
0057 *
0058 * (SELECT * FROM migrations table)
0059 *
0060 * @var array
0061 */
0062 protected $migration_state = array();
0063
0064 /**
0065 * Array of all migrations available to be run
0066 *
0067 * @var array
0068 */
0069 protected $migrations = array();
0070
0071 /**
0072 * Array of migrations that have been determined to be fulfillable
0073 *
0074 * @var array
0075 */
0076 protected $fulfillable_migrations = array();
0077
0078 /**
0079 * 'name,' 'class,' and 'state' of the last migration run
0080 *
0081 * 'effectively_installed' set and set to true if the migration was effectively_installed
0082 *
0083 * @var array
0084 */
0085 protected $last_run_migration = false;
0086
0087 /**
0088 * The output handler. A null handler is configured by default.
0089 *
0090 * @var migrator_output_handler_interface
0091 */
0092 protected $output_handler;
0093
0094 /**
0095 * Constructor of the database migrator
0096 */
0097 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)
0098 {
0099 $this->container = $container;
0100 $this->config = $config;
0101 $this->db = $db;
0102 $this->db_tools = $db_tools;
0103 $this->helper = $helper;
0104
0105 $this->migrations_table = $migrations_table;
0106
0107 $this->phpbb_root_path = $phpbb_root_path;
0108 $this->php_ext = $php_ext;
0109
0110 $this->table_prefix = $table_prefix;
0111
0112 $this->output_handler = new null_migrator_output_handler();
0113
0114 foreach ($tools as $tool)
0115 {
0116 $this->tools[$tool->get_name()] = $tool;
0117 }
0118
0119 $this->tools['dbtools'] = $this->db_tools;
0120
0121 $this->load_migration_state();
0122 }
0123
0124 /**
0125 * Set the output handler.
0126 *
0127 * @param migrator_output_handler_interface $handler The output handler
0128 */
0129 public function set_output_handler(migrator_output_handler_interface $handler)
0130 {
0131 $this->output_handler = $handler;
0132 }
0133
0134 /**
0135 * Loads all migrations and their application state from the database.
0136 *
0137 * @return null
0138 */
0139 public function load_migration_state()
0140 {
0141 $this->migration_state = array();
0142
0143 // prevent errors in case the table does not exist yet
0144 $this->db->sql_return_on_error(true);
0145
0146 $sql = "SELECT *
0147 FROM " . $this->migrations_table;
0148 $result = $this->db->sql_query($sql);
0149
0150 if (!$this->db->get_sql_error_triggered())
0151 {
0152 while ($migration = $this->db->sql_fetchrow($result))
0153 {
0154 $this->migration_state[$migration['migration_name']] = $migration;
0155
0156 $this->migration_state[$migration['migration_name']]['migration_depends_on'] = unserialize($migration['migration_depends_on']);
0157 $this->migration_state[$migration['migration_name']]['migration_data_state'] = !empty($migration['migration_data_state']) ? unserialize($migration['migration_data_state']) : '';
0158 }
0159 }
0160
0161 $this->db->sql_freeresult($result);
0162
0163 $this->db->sql_return_on_error(false);
0164 }
0165
0166 /**
0167 * Get an array with information about the last migration run.
0168 *
0169 * The array contains 'name', 'class' and 'state'. 'effectively_installed' is set
0170 * and set to true if the last migration was effectively_installed.
0171 *
0172 * @return array
0173 */
0174 public function get_last_run_migration()
0175 {
0176 return $this->last_run_migration;
0177 }
0178
0179 /**
0180 * Sets the list of available migration class names to the given array.
0181 *
0182 * @param array $class_names An array of migration class names
0183 * @return null
0184 */
0185 public function set_migrations($class_names)
0186 {
0187 foreach ($class_names as $key => $class)
0188 {
0189 if (!self::is_migration($class))
0190 {
0191 unset($class_names[$key]);
0192 }
0193 }
0194
0195 $this->migrations = $class_names;
0196 }
0197
0198 /**
0199 * Get the list of available migration class names
0200 *
0201 * @return array Array of all migrations available to be run
0202 */
0203 public function get_migrations()
0204 {
0205 return $this->migrations;
0206 }
0207
0208 /**
0209 * Get the list of available and not installed migration class names
0210 *
0211 * @return array
0212 */
0213 public function get_installable_migrations()
0214 {
0215 $unfinished_migrations = array();
0216
0217 foreach ($this->migrations as $name)
0218 {
0219 if (!isset($this->migration_state[$name]) ||
0220 !$this->migration_state[$name]['migration_schema_done'] ||
0221 !$this->migration_state[$name]['migration_data_done'])
0222 {
0223 $unfinished_migrations[] = $name;
0224 }
0225 }
0226
0227 return $unfinished_migrations;
0228 }
0229
0230 /**
0231 * Runs a single update step from the next migration to be applied.
0232 *
0233 * The update step can either be a schema or a (partial) data update. To
0234 * check if update() needs to be called again use the finished() method.
0235 *
0236 * @return null
0237 */
0238 public function update()
0239 {
0240 $this->container->get('dispatcher')->disable();
0241 $this->update_do();
0242 $this->container->get('dispatcher')->enable();
0243 }
0244
0245 /**
0246 * Get a valid migration name from the migration state array in case the
0247 * supplied name is not in the migration state list.
0248 *
0249 * @param string $name Migration name
0250 * @return string Migration name
0251 */
0252 protected function get_valid_name($name)
0253 {
0254 // Try falling back to a valid migration name with or without leading backslash
0255 if (!isset($this->migration_state[$name]))
0256 {
0257 $prepended_name = ($name[0] == '\\' ? '' : '\\') . $name;
0258 $prefixless_name = $name[0] == '\\' ? substr($name, 1) : $name;
0259
0260 if (isset($this->migration_state[$prepended_name]))
0261 {
0262 $name = $prepended_name;
0263 }
0264 else if (isset($this->migration_state[$prefixless_name]))
0265 {
0266 $name = $prefixless_name;
0267 }
0268 }
0269
0270 return $name;
0271 }
0272
0273 /**
0274 * Effectively runs a single update step from the next migration to be applied.
0275 *
0276 * @return null
0277 */
0278 protected function update_do()
0279 {
0280 foreach ($this->migrations as $name)
0281 {
0282 $name = $this->get_valid_name($name);
0283
0284 if (!isset($this->migration_state[$name]) ||
0285 !$this->migration_state[$name]['migration_schema_done'] ||
0286 !$this->migration_state[$name]['migration_data_done'])
0287 {
0288 if (!$this->try_apply($name))
0289 {
0290 continue;
0291 }
0292 else
0293 {
0294 return;
0295 }
0296 }
0297 else
0298 {
0299 $this->output_handler->write(array('MIGRATION_EFFECTIVELY_INSTALLED', $name), migrator_output_handler_interface::VERBOSITY_DEBUG);
0300 }
0301 }
0302 }
0303
0304 /**
0305 * Attempts to apply a step of the given migration or one of its dependencies
0306 *
0307 * @param string $name The class name of the migration
0308 * @return bool Whether any update step was successfully run
0309 * @throws \phpbb\db\migration\exception
0310 */
0311 protected function try_apply($name)
0312 {
0313 if (!class_exists($name))
0314 {
0315 $this->output_handler->write(array('MIGRATION_NOT_VALID', $name), migrator_output_handler_interface::VERBOSITY_DEBUG);
0316 return false;
0317 }
0318
0319 $migration = $this->get_migration($name);
0320
0321 $state = (isset($this->migration_state[$name])) ?
0322 $this->migration_state[$name] :
0323 array(
0324 'migration_depends_on' => $migration->depends_on(),
0325 'migration_schema_done' => false,
0326 'migration_data_done' => false,
0327 'migration_data_state' => '',
0328 'migration_start_time' => 0,
0329 'migration_end_time' => 0,
0330 );
0331
0332 if (!empty($state['migration_depends_on']))
0333 {
0334 $this->output_handler->write(array('MIGRATION_APPLY_DEPENDENCIES', $name), migrator_output_handler_interface::VERBOSITY_DEBUG);
0335 }
0336
0337 foreach ($state['migration_depends_on'] as $depend)
0338 {
0339 $depend = $this->get_valid_name($depend);
0340
0341 // Test all possible namings before throwing exception
0342 $missing = $this->unfulfillable($depend);
0343 if ($missing !== false)
0344 {
0345 throw new \phpbb\db\migration\exception('MIGRATION_NOT_FULFILLABLE', $name, $missing);
0346 }
0347
0348 if (!isset($this->migration_state[$depend]) ||
0349 !$this->migration_state[$depend]['migration_schema_done'] ||
0350 !$this->migration_state[$depend]['migration_data_done'])
0351 {
0352 return $this->try_apply($depend);
0353 }
0354 }
0355
0356 $this->last_run_migration = array(
0357 'name' => $name,
0358 'class' => $migration,
0359 'state' => $state,
0360 'task' => '',
0361 );
0362
0363 if (!isset($this->migration_state[$name]))
0364 {
0365 if ($state['migration_start_time'] == 0 && $migration->effectively_installed())
0366 {
0367 $state = array(
0368 'migration_depends_on' => $migration->depends_on(),
0369 'migration_schema_done' => true,
0370 'migration_data_done' => true,
0371 'migration_data_state' => '',
0372 'migration_start_time' => 0,
0373 'migration_end_time' => 0,
0374 );
0375
0376 $this->last_run_migration['effectively_installed'] = true;
0377
0378 $this->output_handler->write(array('MIGRATION_EFFECTIVELY_INSTALLED', $name), migrator_output_handler_interface::VERBOSITY_VERBOSE);
0379 }
0380 else
0381 {
0382 $state['migration_start_time'] = time();
0383 }
0384 }
0385
0386 $this->set_migration_state($name, $state);
0387
0388 if (!$state['migration_schema_done'])
0389 {
0390 $verbosity = empty($state['migration_data_state']) ?
0391 migrator_output_handler_interface::VERBOSITY_VERBOSE : migrator_output_handler_interface::VERBOSITY_DEBUG;
0392 $this->output_handler->write(array('MIGRATION_SCHEMA_RUNNING', $name), $verbosity);
0393
0394 $this->last_run_migration['task'] = 'process_schema_step';
0395
0396 $total_time = (is_array($state['migration_data_state']) && isset($state['migration_data_state']['_total_time'])) ?
0397 $state['migration_data_state']['_total_time'] : 0.0;
0398 $elapsed_time = microtime(true);
0399
0400 $steps = $this->helper->get_schema_steps($migration->update_schema());
0401 $result = $this->process_data_step($steps, $state['migration_data_state']);
0402
0403 $elapsed_time = microtime(true) - $elapsed_time;
0404 $total_time += $elapsed_time;
0405
0406 if (is_array($result))
0407 {
0408 $result['_total_time'] = $total_time;
0409 }
0410
0411 $state['migration_data_state'] = ($result === true) ? '' : $result;
0412 $state['migration_schema_done'] = ($result === true);
0413
0414 if ($state['migration_schema_done'])
0415 {
0416 $this->output_handler->write(array('MIGRATION_SCHEMA_DONE', $name, $total_time), migrator_output_handler_interface::VERBOSITY_NORMAL);
0417 }
0418 else
0419 {
0420 $this->output_handler->write(array('MIGRATION_SCHEMA_IN_PROGRESS', $name, $elapsed_time), migrator_output_handler_interface::VERBOSITY_VERY_VERBOSE);
0421 }
0422 }
0423 else if (!$state['migration_data_done'])
0424 {
0425 try
0426 {
0427 $verbosity = empty($state['migration_data_state']) ?
0428 migrator_output_handler_interface::VERBOSITY_VERBOSE : migrator_output_handler_interface::VERBOSITY_DEBUG;
0429 $this->output_handler->write(array('MIGRATION_DATA_RUNNING', $name), $verbosity);
0430
0431 $this->last_run_migration['task'] = 'process_data_step';
0432
0433 $total_time = (is_array($state['migration_data_state']) && isset($state['migration_data_state']['_total_time'])) ?
0434 $state['migration_data_state']['_total_time'] : 0.0;
0435 $elapsed_time = microtime(true);
0436
0437 $result = $this->process_data_step($migration->update_data(), $state['migration_data_state']);
0438
0439 $elapsed_time = microtime(true) - $elapsed_time;
0440 $total_time += $elapsed_time;
0441
0442 if (is_array($result))
0443 {
0444 $result['_total_time'] = $total_time;
0445 }
0446
0447 $state['migration_data_state'] = ($result === true) ? '' : $result;
0448 $state['migration_data_done'] = ($result === true);
0449 $state['migration_end_time'] = ($result === true) ? time() : 0;
0450
0451 if ($state['migration_data_done'])
0452 {
0453 $this->output_handler->write(array('MIGRATION_DATA_DONE', $name, $total_time), migrator_output_handler_interface::VERBOSITY_NORMAL);
0454 }
0455 else
0456 {
0457 $this->output_handler->write(array('MIGRATION_DATA_IN_PROGRESS', $name, $elapsed_time), migrator_output_handler_interface::VERBOSITY_VERY_VERBOSE);
0458 }
0459 }
0460 catch (\phpbb\db\migration\exception $e)
0461 {
0462 // Reset data state and revert the schema changes
0463 $state['migration_data_state'] = '';
0464 $this->set_migration_state($name, $state);
0465
0466 $this->revert_do($name);
0467
0468 throw $e;
0469 }
0470 }
0471
0472 $this->set_migration_state($name, $state);
0473
0474 return true;
0475 }
0476
0477 /**
0478 * Runs a single revert step from the last migration installed
0479 *
0480 * YOU MUST ADD/SET ALL MIGRATIONS THAT COULD BE DEPENDENT ON THE MIGRATION TO REVERT TO BEFORE CALLING THIS METHOD!
0481 * The revert step can either be a schema or a (partial) data revert. To
0482 * check if revert() needs to be called again use the migration_state() method.
0483 *
0484 * @param string $migration String migration name to revert (including any that depend on this migration)
0485 */
0486 public function revert($migration)
0487 {
0488 $this->container->get('dispatcher')->disable();
0489 $this->revert_do($migration);
0490 $this->container->get('dispatcher')->enable();
0491 }
0492
0493 /**
0494 * Effectively runs a single revert step from the last migration installed
0495 *
0496 * @param string $migration String migration name to revert (including any that depend on this migration)
0497 * @return null
0498 */
0499 protected function revert_do($migration)
0500 {
0501 if (!isset($this->migration_state[$migration]))
0502 {
0503 // Not installed
0504 return;
0505 }
0506
0507 foreach ($this->migrations as $name)
0508 {
0509 $state = $this->migration_state($name);
0510
0511 if ($state && in_array($migration, $state['migration_depends_on']) && ($state['migration_schema_done'] || $state['migration_data_done']))
0512 {
0513 $this->revert_do($name);
0514 return;
0515 }
0516 }
0517
0518 $this->try_revert($migration);
0519 }
0520
0521 /**
0522 * Attempts to revert a step of the given migration or one of its dependencies
0523 *
0524 * @param string $name The class name of the migration
0525 * @return bool Whether any update step was successfully run
0526 */
0527 protected function try_revert($name)
0528 {
0529 if (!class_exists($name))
0530 {
0531 return false;
0532 }
0533
0534 $migration = $this->get_migration($name);
0535
0536 $state = $this->migration_state[$name];
0537
0538 $this->last_run_migration = array(
0539 'name' => $name,
0540 'class' => $migration,
0541 'task' => '',
0542 );
0543
0544 if ($state['migration_data_done'])
0545 {
0546 $verbosity = empty($state['migration_data_state']) ?
0547 migrator_output_handler_interface::VERBOSITY_VERBOSE : migrator_output_handler_interface::VERBOSITY_DEBUG;
0548 $this->output_handler->write(array('MIGRATION_REVERT_DATA_RUNNING', $name), $verbosity);
0549
0550 $total_time = (is_array($state['migration_data_state']) && isset($state['migration_data_state']['_total_time'])) ?
0551 $state['migration_data_state']['_total_time'] : 0.0;
0552 $elapsed_time = microtime(true);
0553
0554 $steps = array_merge($this->helper->reverse_update_data($migration->update_data()), $migration->revert_data());
0555 $result = $this->process_data_step($steps, $state['migration_data_state']);
0556
0557 $elapsed_time = microtime(true) - $elapsed_time;
0558 $total_time += $elapsed_time;
0559
0560 if (is_array($result))
0561 {
0562 $result['_total_time'] = $total_time;
0563 }
0564
0565 $state['migration_data_state'] = ($result === true) ? '' : $result;
0566 $state['migration_data_done'] = ($result === true) ? false : true;
0567
0568 $this->set_migration_state($name, $state);
0569
0570 if (!$state['migration_data_done'])
0571 {
0572 $this->output_handler->write(array('MIGRATION_REVERT_DATA_DONE', $name, $total_time), migrator_output_handler_interface::VERBOSITY_NORMAL);
0573 }
0574 else
0575 {
0576 $this->output_handler->write(array('MIGRATION_REVERT_DATA_IN_PROGRESS', $name, $elapsed_time), migrator_output_handler_interface::VERBOSITY_VERY_VERBOSE);
0577 }
0578 }
0579 else if ($state['migration_schema_done'])
0580 {
0581 $verbosity = empty($state['migration_data_state']) ?
0582 migrator_output_handler_interface::VERBOSITY_VERBOSE : migrator_output_handler_interface::VERBOSITY_DEBUG;
0583 $this->output_handler->write(array('MIGRATION_REVERT_SCHEMA_RUNNING', $name), $verbosity);
0584
0585 $total_time = (is_array($state['migration_data_state']) && isset($state['migration_data_state']['_total_time'])) ?
0586 $state['migration_data_state']['_total_time'] : 0.0;
0587 $elapsed_time = microtime(true);
0588
0589 $steps = $this->helper->get_schema_steps($migration->revert_schema());
0590 $result = $this->process_data_step($steps, $state['migration_data_state']);
0591
0592 $elapsed_time = microtime(true) - $elapsed_time;
0593 $total_time += $elapsed_time;
0594
0595 if (is_array($result))
0596 {
0597 $result['_total_time'] = $total_time;
0598 }
0599
0600 $state['migration_data_state'] = ($result === true) ? '' : $result;
0601 $state['migration_schema_done'] = ($result === true) ? false : true;
0602
0603 if (!$state['migration_schema_done'])
0604 {
0605 $sql = 'DELETE FROM ' . $this->migrations_table . "
0606 WHERE migration_name = '" . $this->db->sql_escape($name) . "'";
0607 $this->db->sql_query($sql);
0608
0609 $this->last_run_migration = false;
0610 unset($this->migration_state[$name]);
0611
0612 $this->output_handler->write(array('MIGRATION_REVERT_SCHEMA_DONE', $name, $total_time), migrator_output_handler_interface::VERBOSITY_NORMAL);
0613 }
0614 else
0615 {
0616 $this->set_migration_state($name, $state);
0617
0618 $this->output_handler->write(array('MIGRATION_REVERT_SCHEMA_IN_PROGRESS', $name, $elapsed_time), migrator_output_handler_interface::VERBOSITY_VERY_VERBOSE);
0619 }
0620 }
0621
0622 return true;
0623 }
0624
0625 /**
0626 * Process the data step of the migration
0627 *
0628 * @param array $steps The steps to run
0629 * @param bool|string $state Current state of the migration
0630 * @param bool $revert true to revert a data step
0631 * @return bool|string migration state. True if completed, serialized array if not finished
0632 * @throws \phpbb\db\migration\exception
0633 */
0634 protected function process_data_step($steps, $state, $revert = false)
0635 {
0636 if (count($steps) === 0)
0637 {
0638 return true;
0639 }
0640
0641 $state = is_array($state) ? $state : false;
0642
0643 // reverse order of steps if reverting
0644 if ($revert === true)
0645 {
0646 $steps = array_reverse($steps);
0647 }
0648
0649 $step = $last_result = 0;
0650 if ($state)
0651 {
0652 $step = $state['step'];
0653
0654 // We send the result from last time to the callable function
0655 $last_result = $state['result'];
0656 }
0657
0658 try
0659 {
0660 // Result will be null or true if everything completed correctly
0661 // Stop after each update step, to let the updater control the script runtime
0662 $result = $this->run_step($steps[$step], $last_result, $revert);
0663 if (($result !== null && $result !== true) || $step + 1 < count($steps))
0664 {
0665 return array(
0666 'result' => $result,
0667 // Move on if the last call finished
0668 'step' => ($result !== null && $result !== true) ? $step : $step + 1,
0669 );
0670 }
0671 }
0672 catch (\phpbb\db\migration\exception $e)
0673 {
0674 // We should try rolling back here
0675 foreach ($steps as $reverse_step_identifier => $reverse_step)
0676 {
0677 // If we've reached the current step we can break because we reversed everything that was run
0678 if ($reverse_step_identifier == $step)
0679 {
0680 break;
0681 }
0682
0683 // Reverse the step that was run
0684 $result = $this->run_step($reverse_step, false, !$revert);
0685 }
0686
0687 throw $e;
0688 }
0689
0690 return true;
0691 }
0692
0693 /**
0694 * Run a single step
0695 *
0696 * An exception should be thrown if an error occurs
0697 *
0698 * @param mixed $step Data step from migration
0699 * @param mixed $last_result Result to pass to the callable (only for 'custom' method)
0700 * @param bool $reverse False to install, True to attempt uninstallation by reversing the call
0701 * @return null
0702 */
0703 protected function run_step($step, $last_result = 0, $reverse = false)
0704 {
0705 $callable_and_parameters = $this->get_callable_from_step($step, $last_result, $reverse);
0706
0707 if ($callable_and_parameters === false)
0708 {
0709 return;
0710 }
0711
0712 $callable = $callable_and_parameters[0];
0713 $parameters = $callable_and_parameters[1];
0714
0715 return call_user_func_array($callable, $parameters);
0716 }
0717
0718 /**
0719 * Get a callable statement from a data step
0720 *
0721 * @param array $step Data step from migration
0722 * @param mixed $last_result Result to pass to the callable (only for 'custom' method)
0723 * @param bool $reverse False to install, True to attempt uninstallation by reversing the call
0724 * @return array Array with parameters for call_user_func_array(), 0 is the callable, 1 is parameters
0725 * @throws \phpbb\db\migration\exception
0726 */
0727 protected function get_callable_from_step(array $step, $last_result = 0, $reverse = false)
0728 {
0729 $type = $step[0];
0730 $parameters = $step[1];
0731
0732 $parts = explode('.', $type);
0733
0734 $class = $parts[0];
0735 $method = false;
0736
0737 if (isset($parts[1]))
0738 {
0739 $method = $parts[1];
0740 }
0741
0742 switch ($class)
0743 {
0744 case 'if':
0745 if (!isset($parameters[0]))
0746 {
0747 throw new \phpbb\db\migration\exception('MIGRATION_INVALID_DATA_MISSING_CONDITION', $step);
0748 }
0749
0750 if (!isset($parameters[1]))
0751 {
0752 throw new \phpbb\db\migration\exception('MIGRATION_INVALID_DATA_MISSING_STEP', $step);
0753 }
0754
0755 if ($reverse)
0756 {
0757 // We might get unexpected results when trying
0758 // to revert this, so just avoid it
0759 return false;
0760 }
0761
0762 $condition = $parameters[0];
0763
0764 if (!$condition || (is_array($condition) && !$this->run_step($condition, $last_result, $reverse)))
0765 {
0766 return false;
0767 }
0768
0769 $step = $parameters[1];
0770
0771 return $this->get_callable_from_step($step);
0772 break;
0773
0774 case 'custom':
0775 if (!is_callable($parameters[0]))
0776 {
0777 throw new \phpbb\db\migration\exception('MIGRATION_INVALID_DATA_CUSTOM_NOT_CALLABLE', $step);
0778 }
0779
0780 if ($reverse)
0781 {
0782 return false;
0783 }
0784 else
0785 {
0786 return array(
0787 $parameters[0],
0788 isset($parameters[1]) ? array_merge($parameters[1], array($last_result)) : array($last_result),
0789 );
0790 }
0791 break;
0792
0793 default:
0794 if (!$method)
0795 {
0796 throw new \phpbb\db\migration\exception('MIGRATION_INVALID_DATA_UNKNOWN_TYPE', $step);
0797 }
0798
0799 if (!isset($this->tools[$class]))
0800 {
0801 throw new \phpbb\db\migration\exception('MIGRATION_INVALID_DATA_UNDEFINED_TOOL', $step);
0802 }
0803
0804 if (!method_exists(get_class($this->tools[$class]), $method))
0805 {
0806 throw new \phpbb\db\migration\exception('MIGRATION_INVALID_DATA_UNDEFINED_METHOD', $step);
0807 }
0808
0809 // Attempt to reverse operations
0810 if ($reverse)
0811 {
0812 array_unshift($parameters, $method);
0813
0814 return array(
0815 array($this->tools[$class], 'reverse'),
0816 $parameters,
0817 );
0818 }
0819
0820 return array(
0821 array($this->tools[$class], $method),
0822 $parameters,
0823 );
0824 break;
0825 }
0826 }
0827
0828 /**
0829 * Insert/Update migration row into the database
0830 *
0831 * @param string $name Name of the migration
0832 * @param array $state
0833 * @return null
0834 */
0835 protected function set_migration_state($name, $state)
0836 {
0837 $migration_row = $state;
0838 $migration_row['migration_depends_on'] = serialize($state['migration_depends_on']);
0839 $migration_row['migration_data_state'] = !empty($state['migration_data_state']) ? serialize($state['migration_data_state']) : '';
0840
0841 if (isset($this->migration_state[$name]))
0842 {
0843 $sql = 'UPDATE ' . $this->migrations_table . '
0844 SET ' . $this->db->sql_build_array('UPDATE', $migration_row) . "
0845 WHERE migration_name = '" . $this->db->sql_escape($name) . "'";
0846 $this->db->sql_query($sql);
0847 }
0848 else
0849 {
0850 $migration_row['migration_name'] = $name;
0851 $sql = 'INSERT INTO ' . $this->migrations_table . '
0852 ' . $this->db->sql_build_array('INSERT', $migration_row);
0853 $this->db->sql_query($sql);
0854 }
0855
0856 $this->migration_state[$name] = $state;
0857
0858 $this->last_run_migration['state'] = $state;
0859 }
0860
0861 /**
0862 * Checks if a migration's dependencies can even theoretically be satisfied.
0863 *
0864 * @param string $name The class name of the migration
0865 * @return bool|string False if fulfillable, string of missing migration name if unfulfillable
0866 */
0867 public function unfulfillable($name)
0868 {
0869 $name = $this->get_valid_name($name);
0870
0871 if (isset($this->migration_state[$name]) || isset($this->fulfillable_migrations[$name]))
0872 {
0873 return false;
0874 }
0875
0876 if (!class_exists($name))
0877 {
0878 return $name;
0879 }
0880
0881 $migration = $this->get_migration($name);
0882 $depends = $migration->depends_on();
0883
0884 foreach ($depends as $depend)
0885 {
0886 $depend = $this->get_valid_name($depend);
0887 $unfulfillable = $this->unfulfillable($depend);
0888 if ($unfulfillable !== false)
0889 {
0890 return $unfulfillable;
0891 }
0892 }
0893 $this->fulfillable_migrations[$name] = true;
0894
0895 return false;
0896 }
0897
0898 /**
0899 * Checks whether all available, fulfillable migrations have been applied.
0900 *
0901 * @return bool Whether the migrations have been applied
0902 */
0903 public function finished()
0904 {
0905 foreach ($this->migrations as $name)
0906 {
0907 if (!isset($this->migration_state[$name]))
0908 {
0909 // skip unfulfillable migrations, but fulfillables mean we
0910 // are not finished yet
0911 if ($this->unfulfillable($name) !== false)
0912 {
0913 continue;
0914 }
0915
0916 return false;
0917 }
0918
0919 $migration = $this->migration_state[$name];
0920
0921 if (!$migration['migration_schema_done'] || !$migration['migration_data_done'])
0922 {
0923 return false;
0924 }
0925 }
0926
0927 return true;
0928 }
0929
0930 /**
0931 * Gets a migration state (whether it is installed and to what extent)
0932 *
0933 * @param string $migration String migration name to check if it is installed
0934 * @return bool|array False if the migration has not at all been installed, array
0935 */
0936 public function migration_state($migration)
0937 {
0938 if (!isset($this->migration_state[$migration]))
0939 {
0940 return false;
0941 }
0942
0943 return $this->migration_state[$migration];
0944 }
0945
0946 /**
0947 * Helper to get a migration
0948 *
0949 * @param string $name Name of the migration
0950 * @return \phpbb\db\migration\migration
0951 */
0952 public function get_migration($name)
0953 {
0954 $migration = new $name($this->config, $this->db, $this->db_tools, $this->phpbb_root_path, $this->php_ext, $this->table_prefix);
0955
0956 if ($migration instanceof ContainerAwareInterface)
0957 {
0958 $migration->setContainer($this->container);
0959 }
0960
0961 return $migration;
0962 }
0963
0964 /**
0965 * This function adds all migrations sent to it to the migrations table
0966 *
0967 * THIS SHOULD NOT GENERALLY BE USED! THIS IS FOR THE PHPBB INSTALLER.
0968 * THIS WILL THROW ERRORS IF MIGRATIONS ALREADY EXIST IN THE TABLE, DO NOT CALL MORE THAN ONCE!
0969 *
0970 * @param array $migrations Array of migrations (names) to add to the migrations table
0971 * @return null
0972 */
0973 public function populate_migrations($migrations)
0974 {
0975 foreach ($migrations as $name)
0976 {
0977 if ($this->migration_state($name) === false)
0978 {
0979 $state = array(
0980 'migration_depends_on' => $name::depends_on(),
0981 'migration_schema_done' => true,
0982 'migration_data_done' => true,
0983 'migration_data_state' => '',
0984 'migration_start_time' => time(),
0985 'migration_end_time' => time(),
0986 );
0987 $this->set_migration_state($name, $state);
0988 }
0989 }
0990 }
0991
0992 /**
0993 * Creates the migrations table if it does not exist.
0994 * @return null
0995 */
0996 public function create_migrations_table()
0997 {
0998 // Make sure migrations have been installed.
0999 if (!$this->db_tools->sql_table_exists($this->table_prefix . 'migrations'))
1000 {
1001 $this->db_tools->sql_create_table($this->table_prefix . 'migrations', array(
1002 'COLUMNS' => array(
1003 'migration_name' => array('VCHAR', ''),
1004 'migration_depends_on' => array('TEXT', ''),
1005 'migration_schema_done' => array('BOOL', 0),
1006 'migration_data_done' => array('BOOL', 0),
1007 'migration_data_state' => array('TEXT', ''),
1008 'migration_start_time' => array('TIMESTAMP', 0),
1009 'migration_end_time' => array('TIMESTAMP', 0),
1010 ),
1011 'PRIMARY_KEY' => 'migration_name',
1012 ));
1013 }
1014 }
1015
1016 /**
1017 * Check if a class is a migration.
1018 *
1019 * @param string $migration A migration class name
1020 * @return bool Return true if class is a migration, false otherwise
1021 */
1022 static public function is_migration($migration)
1023 {
1024 if (class_exists($migration))
1025 {
1026 // Migration classes should extend the abstract class
1027 // phpbb\db\migration\migration (which implements the
1028 // migration_interface) and be instantiable.
1029 $reflector = new \ReflectionClass($migration);
1030 if ($reflector->implementsInterface('\phpbb\db\migration\migration_interface') && $reflector->isInstantiable())
1031 {
1032 return true;
1033 }
1034 }
1035
1036 return false;
1037 }
1038 }
1039