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.
Auf den Verzeichnisnamen klicken, dies zeigt nur das Verzeichnis mit Inhalt an

      (Beispiel Datei-Icons)

Auf das Icon klicken um den Quellcode anzuzeigen

migrator.php

Zuletzt modifiziert: 09.10.2024, 12:52 - Dateigröße: 27.38 KiB


 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