Verzeichnisstruktur phpBB-3.2.0
- Veröffentlicht
- 06.01.2017
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 if ($this->unfulfillable($depend) !== false)
0343 {
0344 throw new \phpbb\db\migration\exception('MIGRATION_NOT_FULFILLABLE', $name, $depend);
0345 }
0346
0347 if (!isset($this->migration_state[$depend]) ||
0348 !$this->migration_state[$depend]['migration_schema_done'] ||
0349 !$this->migration_state[$depend]['migration_data_done'])
0350 {
0351 return $this->try_apply($depend);
0352 }
0353 }
0354
0355 $this->last_run_migration = array(
0356 'name' => $name,
0357 'class' => $migration,
0358 'state' => $state,
0359 'task' => '',
0360 );
0361
0362 if (!isset($this->migration_state[$name]))
0363 {
0364 if ($state['migration_start_time'] == 0 && $migration->effectively_installed())
0365 {
0366 $state = array(
0367 'migration_depends_on' => $migration->depends_on(),
0368 'migration_schema_done' => true,
0369 'migration_data_done' => true,
0370 'migration_data_state' => '',
0371 'migration_start_time' => 0,
0372 'migration_end_time' => 0,
0373 );
0374
0375 $this->last_run_migration['effectively_installed'] = true;
0376
0377 $this->output_handler->write(array('MIGRATION_EFFECTIVELY_INSTALLED', $name), migrator_output_handler_interface::VERBOSITY_VERBOSE);
0378 }
0379 else
0380 {
0381 $state['migration_start_time'] = time();
0382 }
0383 }
0384
0385 $this->set_migration_state($name, $state);
0386
0387 if (!$state['migration_schema_done'])
0388 {
0389 $verbosity = empty($state['migration_data_state']) ?
0390 migrator_output_handler_interface::VERBOSITY_VERBOSE : migrator_output_handler_interface::VERBOSITY_DEBUG;
0391 $this->output_handler->write(array('MIGRATION_SCHEMA_RUNNING', $name), $verbosity);
0392
0393 $this->last_run_migration['task'] = 'process_schema_step';
0394
0395 $total_time = (is_array($state['migration_data_state']) && isset($state['migration_data_state']['_total_time'])) ?
0396 $state['migration_data_state']['_total_time'] : 0.0;
0397 $elapsed_time = microtime(true);
0398
0399 $steps = $this->helper->get_schema_steps($migration->update_schema());
0400 $result = $this->process_data_step($steps, $state['migration_data_state']);
0401
0402 $elapsed_time = microtime(true) - $elapsed_time;
0403 $total_time += $elapsed_time;
0404
0405 if (is_array($result))
0406 {
0407 $result['_total_time'] = $total_time;
0408 }
0409
0410 $state['migration_data_state'] = ($result === true) ? '' : $result;
0411 $state['migration_schema_done'] = ($result === true);
0412
0413 if ($state['migration_schema_done'])
0414 {
0415 $this->output_handler->write(array('MIGRATION_SCHEMA_DONE', $name, $total_time), migrator_output_handler_interface::VERBOSITY_NORMAL);
0416 }
0417 else
0418 {
0419 $this->output_handler->write(array('MIGRATION_SCHEMA_IN_PROGRESS', $name, $elapsed_time), migrator_output_handler_interface::VERBOSITY_VERY_VERBOSE);
0420 }
0421 }
0422 else if (!$state['migration_data_done'])
0423 {
0424 try
0425 {
0426 $verbosity = empty($state['migration_data_state']) ?
0427 migrator_output_handler_interface::VERBOSITY_VERBOSE : migrator_output_handler_interface::VERBOSITY_DEBUG;
0428 $this->output_handler->write(array('MIGRATION_DATA_RUNNING', $name), $verbosity);
0429
0430 $this->last_run_migration['task'] = 'process_data_step';
0431
0432 $total_time = (is_array($state['migration_data_state']) && isset($state['migration_data_state']['_total_time'])) ?
0433 $state['migration_data_state']['_total_time'] : 0.0;
0434 $elapsed_time = microtime(true);
0435
0436 $result = $this->process_data_step($migration->update_data(), $state['migration_data_state']);
0437
0438 $elapsed_time = microtime(true) - $elapsed_time;
0439 $total_time += $elapsed_time;
0440
0441 if (is_array($result))
0442 {
0443 $result['_total_time'] = $total_time;
0444 }
0445
0446 $state['migration_data_state'] = ($result === true) ? '' : $result;
0447 $state['migration_data_done'] = ($result === true);
0448 $state['migration_end_time'] = ($result === true) ? time() : 0;
0449
0450 if ($state['migration_data_done'])
0451 {
0452 $this->output_handler->write(array('MIGRATION_DATA_DONE', $name, $total_time), migrator_output_handler_interface::VERBOSITY_NORMAL);
0453 }
0454 else
0455 {
0456 $this->output_handler->write(array('MIGRATION_DATA_IN_PROGRESS', $name, $elapsed_time), migrator_output_handler_interface::VERBOSITY_VERY_VERBOSE);
0457 }
0458 }
0459 catch (\phpbb\db\migration\exception $e)
0460 {
0461 // Reset data state and revert the schema changes
0462 $state['migration_data_state'] = '';
0463 $this->set_migration_state($name, $state);
0464
0465 $this->revert_do($name);
0466
0467 throw $e;
0468 }
0469 }
0470
0471 $this->set_migration_state($name, $state);
0472
0473 return true;
0474 }
0475
0476 /**
0477 * Runs a single revert step from the last migration installed
0478 *
0479 * YOU MUST ADD/SET ALL MIGRATIONS THAT COULD BE DEPENDENT ON THE MIGRATION TO REVERT TO BEFORE CALLING THIS METHOD!
0480 * The revert step can either be a schema or a (partial) data revert. To
0481 * check if revert() needs to be called again use the migration_state() method.
0482 *
0483 * @param string $migration String migration name to revert (including any that depend on this migration)
0484 */
0485 public function revert($migration)
0486 {
0487 $this->container->get('dispatcher')->disable();
0488 $this->revert_do($migration);
0489 $this->container->get('dispatcher')->enable();
0490 }
0491
0492 /**
0493 * Effectively runs a single revert step from the last migration installed
0494 *
0495 * @param string $migration String migration name to revert (including any that depend on this migration)
0496 * @return null
0497 */
0498 protected function revert_do($migration)
0499 {
0500 if (!isset($this->migration_state[$migration]))
0501 {
0502 // Not installed
0503 return;
0504 }
0505
0506 foreach ($this->migration_state as $name => $state)
0507 {
0508 if (!empty($state['migration_depends_on']) && in_array($migration, $state['migration_depends_on']))
0509 {
0510 $this->revert_do($name);
0511 }
0512 }
0513
0514 $this->try_revert($migration);
0515 }
0516
0517 /**
0518 * Attempts to revert a step of the given migration or one of its dependencies
0519 *
0520 * @param string $name The class name of the migration
0521 * @return bool Whether any update step was successfully run
0522 */
0523 protected function try_revert($name)
0524 {
0525 if (!class_exists($name))
0526 {
0527 return false;
0528 }
0529
0530 $migration = $this->get_migration($name);
0531
0532 $state = $this->migration_state[$name];
0533
0534 $this->last_run_migration = array(
0535 'name' => $name,
0536 'class' => $migration,
0537 'task' => '',
0538 );
0539
0540 if ($state['migration_data_done'])
0541 {
0542 $verbosity = empty($state['migration_data_state']) ?
0543 migrator_output_handler_interface::VERBOSITY_VERBOSE : migrator_output_handler_interface::VERBOSITY_DEBUG;
0544 $this->output_handler->write(array('MIGRATION_REVERT_DATA_RUNNING', $name), $verbosity);
0545
0546 $total_time = (is_array($state['migration_data_state']) && isset($state['migration_data_state']['_total_time'])) ?
0547 $state['migration_data_state']['_total_time'] : 0.0;
0548 $elapsed_time = microtime(true);
0549
0550 $steps = array_merge($this->helper->reverse_update_data($migration->update_data()), $migration->revert_data());
0551 $result = $this->process_data_step($steps, $state['migration_data_state']);
0552
0553 $elapsed_time = microtime(true) - $elapsed_time;
0554 $total_time += $elapsed_time;
0555
0556 if (is_array($result))
0557 {
0558 $result['_total_time'] = $total_time;
0559 }
0560
0561 $state['migration_data_state'] = ($result === true) ? '' : $result;
0562 $state['migration_data_done'] = ($result === true) ? false : true;
0563
0564 $this->set_migration_state($name, $state);
0565
0566 if (!$state['migration_data_done'])
0567 {
0568 $this->output_handler->write(array('MIGRATION_REVERT_DATA_DONE', $name, $total_time), migrator_output_handler_interface::VERBOSITY_NORMAL);
0569 }
0570 else
0571 {
0572 $this->output_handler->write(array('MIGRATION_REVERT_DATA_IN_PROGRESS', $name, $elapsed_time), migrator_output_handler_interface::VERBOSITY_VERY_VERBOSE);
0573 }
0574 }
0575 else if ($state['migration_schema_done'])
0576 {
0577 $verbosity = empty($state['migration_data_state']) ?
0578 migrator_output_handler_interface::VERBOSITY_VERBOSE : migrator_output_handler_interface::VERBOSITY_DEBUG;
0579 $this->output_handler->write(array('MIGRATION_REVERT_SCHEMA_RUNNING', $name), $verbosity);
0580
0581 $total_time = (is_array($state['migration_data_state']) && isset($state['migration_data_state']['_total_time'])) ?
0582 $state['migration_data_state']['_total_time'] : 0.0;
0583 $elapsed_time = microtime(true);
0584
0585 $steps = $this->helper->get_schema_steps($migration->revert_schema());
0586 $result = $this->process_data_step($steps, $state['migration_data_state']);
0587
0588 $elapsed_time = microtime(true) - $elapsed_time;
0589 $total_time += $elapsed_time;
0590
0591 if (is_array($result))
0592 {
0593 $result['_total_time'] = $total_time;
0594 }
0595
0596 $state['migration_data_state'] = ($result === true) ? '' : $result;
0597 $state['migration_schema_done'] = ($result === true) ? false : true;
0598
0599 if (!$state['migration_schema_done'])
0600 {
0601 $sql = 'DELETE FROM ' . $this->migrations_table . "
0602 WHERE migration_name = '" . $this->db->sql_escape($name) . "'";
0603 $this->db->sql_query($sql);
0604
0605 $this->last_run_migration = false;
0606 unset($this->migration_state[$name]);
0607
0608 $this->output_handler->write(array('MIGRATION_REVERT_SCHEMA_DONE', $name, $total_time), migrator_output_handler_interface::VERBOSITY_NORMAL);
0609 }
0610 else
0611 {
0612 $this->set_migration_state($name, $state);
0613
0614 $this->output_handler->write(array('MIGRATION_REVERT_SCHEMA_IN_PROGRESS', $name, $elapsed_time), migrator_output_handler_interface::VERBOSITY_VERY_VERBOSE);
0615 }
0616 }
0617
0618 return true;
0619 }
0620
0621 /**
0622 * Process the data step of the migration
0623 *
0624 * @param array $steps The steps to run
0625 * @param bool|string $state Current state of the migration
0626 * @param bool $revert true to revert a data step
0627 * @return bool|string migration state. True if completed, serialized array if not finished
0628 * @throws \phpbb\db\migration\exception
0629 */
0630 protected function process_data_step($steps, $state, $revert = false)
0631 {
0632 if (sizeof($steps) === 0)
0633 {
0634 return true;
0635 }
0636
0637 $state = is_array($state) ? $state : false;
0638
0639 // reverse order of steps if reverting
0640 if ($revert === true)
0641 {
0642 $steps = array_reverse($steps);
0643 }
0644
0645 $step = $last_result = 0;
0646 if ($state)
0647 {
0648 $step = $state['step'];
0649
0650 // We send the result from last time to the callable function
0651 $last_result = $state['result'];
0652 }
0653
0654 try
0655 {
0656 // Result will be null or true if everything completed correctly
0657 // Stop after each update step, to let the updater control the script runtime
0658 $result = $this->run_step($steps[$step], $last_result, $revert);
0659 if (($result !== null && $result !== true) || $step + 1 < sizeof($steps))
0660 {
0661 return array(
0662 'result' => $result,
0663 // Move on if the last call finished
0664 'step' => ($result !== null && $result !== true) ? $step : $step + 1,
0665 );
0666 }
0667 }
0668 catch (\phpbb\db\migration\exception $e)
0669 {
0670 // We should try rolling back here
0671 foreach ($steps as $reverse_step_identifier => $reverse_step)
0672 {
0673 // If we've reached the current step we can break because we reversed everything that was run
0674 if ($reverse_step_identifier == $step)
0675 {
0676 break;
0677 }
0678
0679 // Reverse the step that was run
0680 $result = $this->run_step($reverse_step, false, !$revert);
0681 }
0682
0683 throw $e;
0684 }
0685
0686 return true;
0687 }
0688
0689 /**
0690 * Run a single step
0691 *
0692 * An exception should be thrown if an error occurs
0693 *
0694 * @param mixed $step Data step from migration
0695 * @param mixed $last_result Result to pass to the callable (only for 'custom' method)
0696 * @param bool $reverse False to install, True to attempt uninstallation by reversing the call
0697 * @return null
0698 */
0699 protected function run_step($step, $last_result = 0, $reverse = false)
0700 {
0701 $callable_and_parameters = $this->get_callable_from_step($step, $last_result, $reverse);
0702
0703 if ($callable_and_parameters === false)
0704 {
0705 return;
0706 }
0707
0708 $callable = $callable_and_parameters[0];
0709 $parameters = $callable_and_parameters[1];
0710
0711 return call_user_func_array($callable, $parameters);
0712 }
0713
0714 /**
0715 * Get a callable statement from a data step
0716 *
0717 * @param array $step Data step from migration
0718 * @param mixed $last_result Result to pass to the callable (only for 'custom' method)
0719 * @param bool $reverse False to install, True to attempt uninstallation by reversing the call
0720 * @return array Array with parameters for call_user_func_array(), 0 is the callable, 1 is parameters
0721 * @throws \phpbb\db\migration\exception
0722 */
0723 protected function get_callable_from_step(array $step, $last_result = 0, $reverse = false)
0724 {
0725 $type = $step[0];
0726 $parameters = $step[1];
0727
0728 $parts = explode('.', $type);
0729
0730 $class = $parts[0];
0731 $method = false;
0732
0733 if (isset($parts[1]))
0734 {
0735 $method = $parts[1];
0736 }
0737
0738 switch ($class)
0739 {
0740 case 'if':
0741 if (!isset($parameters[0]))
0742 {
0743 throw new \phpbb\db\migration\exception('MIGRATION_INVALID_DATA_MISSING_CONDITION', $step);
0744 }
0745
0746 if (!isset($parameters[1]))
0747 {
0748 throw new \phpbb\db\migration\exception('MIGRATION_INVALID_DATA_MISSING_STEP', $step);
0749 }
0750
0751 if ($reverse)
0752 {
0753 // We might get unexpected results when trying
0754 // to revert this, so just avoid it
0755 return false;
0756 }
0757
0758 $condition = $parameters[0];
0759
0760 if (!$condition)
0761 {
0762 return false;
0763 }
0764
0765 $step = $parameters[1];
0766
0767 return $this->get_callable_from_step($step);
0768 break;
0769
0770 case 'custom':
0771 if (!is_callable($parameters[0]))
0772 {
0773 throw new \phpbb\db\migration\exception('MIGRATION_INVALID_DATA_CUSTOM_NOT_CALLABLE', $step);
0774 }
0775
0776 if ($reverse)
0777 {
0778 return false;
0779 }
0780 else
0781 {
0782 return array(
0783 $parameters[0],
0784 array($last_result),
0785 );
0786 }
0787 break;
0788
0789 default:
0790 if (!$method)
0791 {
0792 throw new \phpbb\db\migration\exception('MIGRATION_INVALID_DATA_UNKNOWN_TYPE', $step);
0793 }
0794
0795 if (!isset($this->tools[$class]))
0796 {
0797 throw new \phpbb\db\migration\exception('MIGRATION_INVALID_DATA_UNDEFINED_TOOL', $step);
0798 }
0799
0800 if (!method_exists(get_class($this->tools[$class]), $method))
0801 {
0802 throw new \phpbb\db\migration\exception('MIGRATION_INVALID_DATA_UNDEFINED_METHOD', $step);
0803 }
0804
0805 // Attempt to reverse operations
0806 if ($reverse)
0807 {
0808 array_unshift($parameters, $method);
0809
0810 return array(
0811 array($this->tools[$class], 'reverse'),
0812 $parameters,
0813 );
0814 }
0815
0816 return array(
0817 array($this->tools[$class], $method),
0818 $parameters,
0819 );
0820 break;
0821 }
0822 }
0823
0824 /**
0825 * Insert/Update migration row into the database
0826 *
0827 * @param string $name Name of the migration
0828 * @param array $state
0829 * @return null
0830 */
0831 protected function set_migration_state($name, $state)
0832 {
0833 $migration_row = $state;
0834 $migration_row['migration_depends_on'] = serialize($state['migration_depends_on']);
0835 $migration_row['migration_data_state'] = !empty($state['migration_data_state']) ? serialize($state['migration_data_state']) : '';
0836
0837 if (isset($this->migration_state[$name]))
0838 {
0839 $sql = 'UPDATE ' . $this->migrations_table . '
0840 SET ' . $this->db->sql_build_array('UPDATE', $migration_row) . "
0841 WHERE migration_name = '" . $this->db->sql_escape($name) . "'";
0842 $this->db->sql_query($sql);
0843 }
0844 else
0845 {
0846 $migration_row['migration_name'] = $name;
0847 $sql = 'INSERT INTO ' . $this->migrations_table . '
0848 ' . $this->db->sql_build_array('INSERT', $migration_row);
0849 $this->db->sql_query($sql);
0850 }
0851
0852 $this->migration_state[$name] = $state;
0853
0854 $this->last_run_migration['state'] = $state;
0855 }
0856
0857 /**
0858 * Checks if a migration's dependencies can even theoretically be satisfied.
0859 *
0860 * @param string $name The class name of the migration
0861 * @return bool|string False if fulfillable, string of missing migration name if unfulfillable
0862 */
0863 public function unfulfillable($name)
0864 {
0865 $name = $this->get_valid_name($name);
0866
0867 if (isset($this->migration_state[$name]) || isset($this->fulfillable_migrations[$name]))
0868 {
0869 return false;
0870 }
0871
0872 if (!class_exists($name))
0873 {
0874 return $name;
0875 }
0876
0877 $migration = $this->get_migration($name);
0878 $depends = $migration->depends_on();
0879
0880 foreach ($depends as $depend)
0881 {
0882 $depend = $this->get_valid_name($depend);
0883 $unfulfillable = $this->unfulfillable($depend);
0884 if ($unfulfillable !== false)
0885 {
0886 return $unfulfillable;
0887 }
0888 }
0889 $this->fulfillable_migrations[$name] = true;
0890
0891 return false;
0892 }
0893
0894 /**
0895 * Checks whether all available, fulfillable migrations have been applied.
0896 *
0897 * @return bool Whether the migrations have been applied
0898 */
0899 public function finished()
0900 {
0901 foreach ($this->migrations as $name)
0902 {
0903 if (!isset($this->migration_state[$name]))
0904 {
0905 // skip unfulfillable migrations, but fulfillables mean we
0906 // are not finished yet
0907 if ($this->unfulfillable($name) !== false)
0908 {
0909 continue;
0910 }
0911
0912 return false;
0913 }
0914
0915 $migration = $this->migration_state[$name];
0916
0917 if (!$migration['migration_schema_done'] || !$migration['migration_data_done'])
0918 {
0919 return false;
0920 }
0921 }
0922
0923 return true;
0924 }
0925
0926 /**
0927 * Gets a migration state (whether it is installed and to what extent)
0928 *
0929 * @param string $migration String migration name to check if it is installed
0930 * @return bool|array False if the migration has not at all been installed, array
0931 */
0932 public function migration_state($migration)
0933 {
0934 if (!isset($this->migration_state[$migration]))
0935 {
0936 return false;
0937 }
0938
0939 return $this->migration_state[$migration];
0940 }
0941
0942 /**
0943 * Helper to get a migration
0944 *
0945 * @param string $name Name of the migration
0946 * @return \phpbb\db\migration\migration
0947 */
0948 protected function get_migration($name)
0949 {
0950 $migration = new $name($this->config, $this->db, $this->db_tools, $this->phpbb_root_path, $this->php_ext, $this->table_prefix);
0951
0952 if ($migration instanceof ContainerAwareInterface)
0953 {
0954 $migration->setContainer($this->container);
0955 }
0956
0957 return $migration;
0958 }
0959
0960 /**
0961 * This function adds all migrations sent to it to the migrations table
0962 *
0963 * THIS SHOULD NOT GENERALLY BE USED! THIS IS FOR THE PHPBB INSTALLER.
0964 * THIS WILL THROW ERRORS IF MIGRATIONS ALREADY EXIST IN THE TABLE, DO NOT CALL MORE THAN ONCE!
0965 *
0966 * @param array $migrations Array of migrations (names) to add to the migrations table
0967 * @return null
0968 */
0969 public function populate_migrations($migrations)
0970 {
0971 foreach ($migrations as $name)
0972 {
0973 if ($this->migration_state($name) === false)
0974 {
0975 $state = array(
0976 'migration_depends_on' => $name::depends_on(),
0977 'migration_schema_done' => true,
0978 'migration_data_done' => true,
0979 'migration_data_state' => '',
0980 'migration_start_time' => time(),
0981 'migration_end_time' => time(),
0982 );
0983 $this->set_migration_state($name, $state);
0984 }
0985 }
0986 }
0987
0988 /**
0989 * Creates the migrations table if it does not exist.
0990 * @return null
0991 */
0992 public function create_migrations_table()
0993 {
0994 // Make sure migrations have been installed.
0995 if (!$this->db_tools->sql_table_exists($this->table_prefix . 'migrations'))
0996 {
0997 $this->db_tools->sql_create_table($this->table_prefix . 'migrations', array(
0998 'COLUMNS' => array(
0999 'migration_name' => array('VCHAR', ''),
1000 'migration_depends_on' => array('TEXT', ''),
1001 'migration_schema_done' => array('BOOL', 0),
1002 'migration_data_done' => array('BOOL', 0),
1003 'migration_data_state' => array('TEXT', ''),
1004 'migration_start_time' => array('TIMESTAMP', 0),
1005 'migration_end_time' => array('TIMESTAMP', 0),
1006 ),
1007 'PRIMARY_KEY' => 'migration_name',
1008 ));
1009 }
1010 }
1011
1012 /**
1013 * Check if a class is a migration.
1014 *
1015 * @param string $migration A migration class name
1016 * @return bool Return true if class is a migration, false otherwise
1017 */
1018 static public function is_migration($migration)
1019 {
1020 if (class_exists($migration))
1021 {
1022 // Migration classes should extend the abstract class
1023 // phpbb\db\migration\migration (which implements the
1024 // migration_interface) and be instantiable.
1025 $reflector = new \ReflectionClass($migration);
1026 if ($reflector->implementsInterface('\phpbb\db\migration\migration_interface') && $reflector->isInstantiable())
1027 {
1028 return true;
1029 }
1030 }
1031
1032 return false;
1033 }
1034 }
1035