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