Verzeichnisstruktur phpBB-3.3.15


Veröffentlicht
28.08.2024

So funktioniert es


Auf das letzte Element klicken. Dies geht jeweils ein Schritt zurück

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