[ Index ]

PHP Cross Reference of phpBB-3.2.11-deutsch

title

Body

[close]

/phpbb/db/ -> migrator.php (source)

   1  <?php
   2  /**
   3  *
   4  * This file is part of the phpBB Forum Software package.
   5  *
   6  * @copyright (c) phpBB Limited <https://www.phpbb.com>
   7  * @license GNU General Public License, version 2 (GPL-2.0)
   8  *
   9  * For full copyright and license information, please see
  10  * the docs/CREDITS.txt file.
  11  *
  12  */
  13  
  14  namespace phpbb\db;
  15  
  16  use phpbb\db\output_handler\migrator_output_handler_interface;
  17  use phpbb\db\output_handler\null_migrator_output_handler;
  18  use Symfony\Component\DependencyInjection\ContainerAwareInterface;
  19  use Symfony\Component\DependencyInjection\ContainerInterface;
  20  
  21  /**
  22  * The migrator is responsible for applying new migrations in the correct order.
  23  */
  24  class migrator
  25  {
  26      /**
  27       * @var ContainerInterface
  28       */
  29      protected $container;
  30  
  31      /** @var \phpbb\config\config */
  32      protected $config;
  33  
  34      /** @var \phpbb\db\driver\driver_interface */
  35      protected $db;
  36  
  37      /** @var \phpbb\db\tools\tools_interface */
  38      protected $db_tools;
  39  
  40      /** @var \phpbb\db\migration\helper */
  41      protected $helper;
  42  
  43      /** @var string */
  44      protected $table_prefix;
  45  
  46      /** @var string */
  47      protected $phpbb_root_path;
  48  
  49      /** @var string */
  50      protected $php_ext;
  51  
  52      /** @var string */
  53      protected $migrations_table;
  54  
  55      /**
  56      * State of all migrations
  57      *
  58      * (SELECT * FROM migrations table)
  59      *
  60      * @var array
  61      */
  62      protected $migration_state = array();
  63  
  64      /**
  65      * Array of all migrations available to be run
  66      *
  67      * @var array
  68      */
  69      protected $migrations = array();
  70  
  71      /**
  72      * Array of migrations that have been determined to be fulfillable
  73      *
  74      * @var array
  75      */
  76      protected $fulfillable_migrations = array();
  77  
  78      /**
  79      * 'name,' 'class,' and 'state' of the last migration run
  80      *
  81      * 'effectively_installed' set and set to true if the migration was effectively_installed
  82      *
  83      * @var array
  84      */
  85      protected $last_run_migration = false;
  86  
  87      /**
  88       * The output handler. A null handler is configured by default.
  89       *
  90       * @var migrator_output_handler_interface
  91       */
  92      protected $output_handler;
  93  
  94      /**
  95      * Constructor of the database migrator
  96      */
  97  	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)
  98      {
  99          $this->container = $container;
 100          $this->config = $config;
 101          $this->db = $db;
 102          $this->db_tools = $db_tools;
 103          $this->helper = $helper;
 104  
 105          $this->migrations_table = $migrations_table;
 106  
 107          $this->phpbb_root_path = $phpbb_root_path;
 108          $this->php_ext = $php_ext;
 109  
 110          $this->table_prefix = $table_prefix;
 111  
 112          $this->output_handler = new null_migrator_output_handler();
 113  
 114          foreach ($tools as $tool)
 115          {
 116              $this->tools[$tool->get_name()] = $tool;
 117          }
 118  
 119          $this->tools['dbtools'] = $this->db_tools;
 120  
 121          $this->load_migration_state();
 122      }
 123  
 124      /**
 125       * Set the output handler.
 126       *
 127       * @param migrator_output_handler_interface $handler The output handler
 128       */
 129  	public function set_output_handler(migrator_output_handler_interface $handler)
 130      {
 131          $this->output_handler = $handler;
 132      }
 133  
 134      /**
 135      * Loads all migrations and their application state from the database.
 136      *
 137      * @return null
 138      */
 139  	public function load_migration_state()
 140      {
 141          $this->migration_state = array();
 142  
 143          // prevent errors in case the table does not exist yet
 144          $this->db->sql_return_on_error(true);
 145  
 146          $sql = "SELECT *
 147              FROM " . $this->migrations_table;
 148          $result = $this->db->sql_query($sql);
 149  
 150          if (!$this->db->get_sql_error_triggered())
 151          {
 152              while ($migration = $this->db->sql_fetchrow($result))
 153              {
 154                  $this->migration_state[$migration['migration_name']] = $migration;
 155  
 156                  $this->migration_state[$migration['migration_name']]['migration_depends_on'] = unserialize($migration['migration_depends_on']);
 157                  $this->migration_state[$migration['migration_name']]['migration_data_state'] = !empty($migration['migration_data_state']) ? unserialize($migration['migration_data_state']) : '';
 158              }
 159          }
 160  
 161          $this->db->sql_freeresult($result);
 162  
 163          $this->db->sql_return_on_error(false);
 164      }
 165  
 166      /**
 167       * Get an array with information about the last migration run.
 168       *
 169       * The array contains 'name', 'class' and 'state'. 'effectively_installed' is set
 170       * and set to true if the last migration was effectively_installed.
 171       *
 172       * @return array
 173       */
 174  	public function get_last_run_migration()
 175      {
 176          return $this->last_run_migration;
 177      }
 178  
 179      /**
 180      * Sets the list of available migration class names to the given array.
 181      *
 182      * @param array $class_names An array of migration class names
 183      * @return null
 184      */
 185  	public function set_migrations($class_names)
 186      {
 187          foreach ($class_names as $key => $class)
 188          {
 189              if (!self::is_migration($class))
 190              {
 191                  unset($class_names[$key]);
 192              }
 193          }
 194  
 195          $this->migrations = $class_names;
 196      }
 197  
 198      /**
 199       * Get the list of available migration class names
 200       *
 201       * @return array Array of all migrations available to be run
 202       */
 203  	public function get_migrations()
 204      {
 205          return $this->migrations;
 206      }
 207  
 208      /**
 209       * Get the list of available and not installed migration class names
 210       *
 211       * @return array
 212       */
 213  	public function get_installable_migrations()
 214      {
 215          $unfinished_migrations = array();
 216  
 217          foreach ($this->migrations as $name)
 218          {
 219              if (!isset($this->migration_state[$name]) ||
 220                  !$this->migration_state[$name]['migration_schema_done'] ||
 221                  !$this->migration_state[$name]['migration_data_done'])
 222              {
 223                  $unfinished_migrations[] = $name;
 224              }
 225          }
 226  
 227          return $unfinished_migrations;
 228      }
 229  
 230      /**
 231      * Runs a single update step from the next migration to be applied.
 232      *
 233      * The update step can either be a schema or a (partial) data update. To
 234      * check if update() needs to be called again use the finished() method.
 235      *
 236      * @return null
 237      */
 238  	public function update()
 239      {
 240          $this->container->get('dispatcher')->disable();
 241          $this->update_do();
 242          $this->container->get('dispatcher')->enable();
 243      }
 244  
 245      /**
 246       * Get a valid migration name from the migration state array in case the
 247       * supplied name is not in the migration state list.
 248       *
 249       * @param string $name Migration name
 250       * @return string Migration name
 251       */
 252  	protected function get_valid_name($name)
 253      {
 254          // Try falling back to a valid migration name with or without leading backslash
 255          if (!isset($this->migration_state[$name]))
 256          {
 257              $prepended_name = ($name[0] == '\\' ? '' : '\\') . $name;
 258              $prefixless_name = $name[0] == '\\' ? substr($name, 1) : $name;
 259  
 260              if (isset($this->migration_state[$prepended_name]))
 261              {
 262                  $name = $prepended_name;
 263              }
 264              else if (isset($this->migration_state[$prefixless_name]))
 265              {
 266                  $name = $prefixless_name;
 267              }
 268          }
 269  
 270          return $name;
 271      }
 272  
 273      /**
 274       * Effectively runs a single update step from the next migration to be applied.
 275       *
 276       * @return null
 277       */
 278  	protected function update_do()
 279      {
 280          foreach ($this->migrations as $name)
 281          {
 282              $name = $this->get_valid_name($name);
 283  
 284              if (!isset($this->migration_state[$name]) ||
 285                  !$this->migration_state[$name]['migration_schema_done'] ||
 286                  !$this->migration_state[$name]['migration_data_done'])
 287              {
 288                  if (!$this->try_apply($name))
 289                  {
 290                      continue;
 291                  }
 292                  else
 293                  {
 294                      return;
 295                  }
 296              }
 297              else
 298              {
 299                  $this->output_handler->write(array('MIGRATION_EFFECTIVELY_INSTALLED', $name), migrator_output_handler_interface::VERBOSITY_DEBUG);
 300              }
 301          }
 302      }
 303  
 304      /**
 305      * Attempts to apply a step of the given migration or one of its dependencies
 306      *
 307      * @param    string    $name The class name of the migration
 308      * @return    bool    Whether any update step was successfully run
 309      * @throws \phpbb\db\migration\exception
 310      */
 311  	protected function try_apply($name)
 312      {
 313          if (!class_exists($name))
 314          {
 315              $this->output_handler->write(array('MIGRATION_NOT_VALID', $name), migrator_output_handler_interface::VERBOSITY_DEBUG);
 316              return false;
 317          }
 318  
 319          $migration = $this->get_migration($name);
 320  
 321          $state = (isset($this->migration_state[$name])) ?
 322              $this->migration_state[$name] :
 323              array(
 324                  'migration_depends_on'    => $migration->depends_on(),
 325                  'migration_schema_done' => false,
 326                  'migration_data_done'    => false,
 327                  'migration_data_state'    => '',
 328                  'migration_start_time'    => 0,
 329                  'migration_end_time'    => 0,
 330              );
 331  
 332          if (!empty($state['migration_depends_on']))
 333          {
 334              $this->output_handler->write(array('MIGRATION_APPLY_DEPENDENCIES', $name), migrator_output_handler_interface::VERBOSITY_DEBUG);
 335          }
 336  
 337          foreach ($state['migration_depends_on'] as $depend)
 338          {
 339              $depend = $this->get_valid_name($depend);
 340  
 341              // Test all possible namings before throwing exception
 342              if ($this->unfulfillable($depend) !== false)
 343              {
 344                  throw new \phpbb\db\migration\exception('MIGRATION_NOT_FULFILLABLE', $name, $depend);
 345              }
 346  
 347              if (!isset($this->migration_state[$depend]) ||
 348                  !$this->migration_state[$depend]['migration_schema_done'] ||
 349                  !$this->migration_state[$depend]['migration_data_done'])
 350              {
 351                  return $this->try_apply($depend);
 352              }
 353          }
 354  
 355          $this->last_run_migration = array(
 356              'name'    => $name,
 357              'class'    => $migration,
 358              'state'    => $state,
 359              'task'    => '',
 360          );
 361  
 362          if (!isset($this->migration_state[$name]))
 363          {
 364              if ($state['migration_start_time'] == 0 && $migration->effectively_installed())
 365              {
 366                  $state = array(
 367                      'migration_depends_on'    => $migration->depends_on(),
 368                      'migration_schema_done' => true,
 369                      'migration_data_done'    => true,
 370                      'migration_data_state'    => '',
 371                      'migration_start_time'    => 0,
 372                      'migration_end_time'    => 0,
 373                  );
 374  
 375                  $this->last_run_migration['effectively_installed'] = true;
 376  
 377                  $this->output_handler->write(array('MIGRATION_EFFECTIVELY_INSTALLED', $name), migrator_output_handler_interface::VERBOSITY_VERBOSE);
 378              }
 379              else
 380              {
 381                  $state['migration_start_time'] = time();
 382              }
 383          }
 384  
 385          $this->set_migration_state($name, $state);
 386  
 387          if (!$state['migration_schema_done'])
 388          {
 389              $verbosity = empty($state['migration_data_state']) ?
 390                  migrator_output_handler_interface::VERBOSITY_VERBOSE : migrator_output_handler_interface::VERBOSITY_DEBUG;
 391              $this->output_handler->write(array('MIGRATION_SCHEMA_RUNNING', $name), $verbosity);
 392  
 393              $this->last_run_migration['task'] = 'process_schema_step';
 394  
 395              $total_time = (is_array($state['migration_data_state']) && isset($state['migration_data_state']['_total_time'])) ?
 396                  $state['migration_data_state']['_total_time'] : 0.0;
 397              $elapsed_time = microtime(true);
 398  
 399              $steps = $this->helper->get_schema_steps($migration->update_schema());
 400              $result = $this->process_data_step($steps, $state['migration_data_state']);
 401  
 402              $elapsed_time = microtime(true) - $elapsed_time;
 403              $total_time += $elapsed_time;
 404  
 405              if (is_array($result))
 406              {
 407                  $result['_total_time'] = $total_time;
 408              }
 409  
 410              $state['migration_data_state'] = ($result === true) ? '' : $result;
 411              $state['migration_schema_done'] = ($result === true);
 412  
 413              if ($state['migration_schema_done'])
 414              {
 415                  $this->output_handler->write(array('MIGRATION_SCHEMA_DONE', $name, $total_time), migrator_output_handler_interface::VERBOSITY_NORMAL);
 416              }
 417              else
 418              {
 419                  $this->output_handler->write(array('MIGRATION_SCHEMA_IN_PROGRESS', $name, $elapsed_time), migrator_output_handler_interface::VERBOSITY_VERY_VERBOSE);
 420              }
 421          }
 422          else if (!$state['migration_data_done'])
 423          {
 424              try
 425              {
 426                  $verbosity = empty($state['migration_data_state']) ?
 427                      migrator_output_handler_interface::VERBOSITY_VERBOSE : migrator_output_handler_interface::VERBOSITY_DEBUG;
 428                  $this->output_handler->write(array('MIGRATION_DATA_RUNNING', $name), $verbosity);
 429  
 430                  $this->last_run_migration['task'] = 'process_data_step';
 431  
 432                  $total_time = (is_array($state['migration_data_state']) && isset($state['migration_data_state']['_total_time'])) ?
 433                      $state['migration_data_state']['_total_time'] : 0.0;
 434                  $elapsed_time = microtime(true);
 435  
 436                  $result = $this->process_data_step($migration->update_data(), $state['migration_data_state']);
 437  
 438                  $elapsed_time = microtime(true) - $elapsed_time;
 439                  $total_time += $elapsed_time;
 440  
 441                  if (is_array($result))
 442                  {
 443                      $result['_total_time'] = $total_time;
 444                  }
 445  
 446                  $state['migration_data_state'] = ($result === true) ? '' : $result;
 447                  $state['migration_data_done'] = ($result === true);
 448                  $state['migration_end_time'] = ($result === true) ? time() : 0;
 449  
 450                  if ($state['migration_data_done'])
 451                  {
 452                      $this->output_handler->write(array('MIGRATION_DATA_DONE', $name, $total_time), migrator_output_handler_interface::VERBOSITY_NORMAL);
 453                  }
 454                  else
 455                  {
 456                      $this->output_handler->write(array('MIGRATION_DATA_IN_PROGRESS', $name, $elapsed_time), migrator_output_handler_interface::VERBOSITY_VERY_VERBOSE);
 457                  }
 458              }
 459              catch (\phpbb\db\migration\exception $e)
 460              {
 461                  // Reset data state and revert the schema changes
 462                  $state['migration_data_state'] = '';
 463                  $this->set_migration_state($name, $state);
 464  
 465                  $this->revert_do($name);
 466  
 467                  throw $e;
 468              }
 469          }
 470  
 471          $this->set_migration_state($name, $state);
 472  
 473          return true;
 474      }
 475  
 476      /**
 477      * Runs a single revert step from the last migration installed
 478      *
 479      * YOU MUST ADD/SET ALL MIGRATIONS THAT COULD BE DEPENDENT ON THE MIGRATION TO REVERT TO BEFORE CALLING THIS METHOD!
 480      * The revert step can either be a schema or a (partial) data revert. To
 481      * check if revert() needs to be called again use the migration_state() method.
 482      *
 483      * @param string $migration String migration name to revert (including any that depend on this migration)
 484      */
 485  	public function revert($migration)
 486      {
 487          $this->container->get('dispatcher')->disable();
 488          $this->revert_do($migration);
 489          $this->container->get('dispatcher')->enable();
 490      }
 491  
 492      /**
 493       * Effectively runs a single revert step from the last migration installed
 494       *
 495       * @param string $migration String migration name to revert (including any that depend on this migration)
 496       * @return null
 497       */
 498  	protected function revert_do($migration)
 499      {
 500          if (!isset($this->migration_state[$migration]))
 501          {
 502              // Not installed
 503              return;
 504          }
 505  
 506          foreach ($this->migrations as $name)
 507          {
 508              $state = $this->migration_state($name);
 509  
 510              if ($state && in_array($migration, $state['migration_depends_on']) && ($state['migration_schema_done'] || $state['migration_data_done']))
 511              {
 512                  $this->revert_do($name);
 513                  return;
 514              }
 515          }
 516  
 517          $this->try_revert($migration);
 518      }
 519  
 520      /**
 521      * Attempts to revert a step of the given migration or one of its dependencies
 522      *
 523      * @param    string    $name The class name of the migration
 524      * @return    bool    Whether any update step was successfully run
 525      */
 526  	protected function try_revert($name)
 527      {
 528          if (!class_exists($name))
 529          {
 530              return false;
 531          }
 532  
 533          $migration = $this->get_migration($name);
 534  
 535          $state = $this->migration_state[$name];
 536  
 537          $this->last_run_migration = array(
 538              'name'    => $name,
 539              'class'    => $migration,
 540              'task'    => '',
 541          );
 542  
 543          if ($state['migration_data_done'])
 544          {
 545              $verbosity = empty($state['migration_data_state']) ?
 546                  migrator_output_handler_interface::VERBOSITY_VERBOSE : migrator_output_handler_interface::VERBOSITY_DEBUG;
 547              $this->output_handler->write(array('MIGRATION_REVERT_DATA_RUNNING', $name), $verbosity);
 548  
 549              $total_time = (is_array($state['migration_data_state']) && isset($state['migration_data_state']['_total_time'])) ?
 550                  $state['migration_data_state']['_total_time'] : 0.0;
 551              $elapsed_time = microtime(true);
 552  
 553              $steps = array_merge($this->helper->reverse_update_data($migration->update_data()), $migration->revert_data());
 554              $result = $this->process_data_step($steps, $state['migration_data_state']);
 555  
 556              $elapsed_time = microtime(true) - $elapsed_time;
 557              $total_time += $elapsed_time;
 558  
 559              if (is_array($result))
 560              {
 561                  $result['_total_time'] = $total_time;
 562              }
 563  
 564              $state['migration_data_state'] = ($result === true) ? '' : $result;
 565              $state['migration_data_done'] = ($result === true) ? false : true;
 566  
 567              $this->set_migration_state($name, $state);
 568  
 569              if (!$state['migration_data_done'])
 570              {
 571                  $this->output_handler->write(array('MIGRATION_REVERT_DATA_DONE', $name, $total_time), migrator_output_handler_interface::VERBOSITY_NORMAL);
 572              }
 573              else
 574              {
 575                  $this->output_handler->write(array('MIGRATION_REVERT_DATA_IN_PROGRESS', $name, $elapsed_time), migrator_output_handler_interface::VERBOSITY_VERY_VERBOSE);
 576              }
 577          }
 578          else if ($state['migration_schema_done'])
 579          {
 580              $verbosity = empty($state['migration_data_state']) ?
 581                  migrator_output_handler_interface::VERBOSITY_VERBOSE : migrator_output_handler_interface::VERBOSITY_DEBUG;
 582              $this->output_handler->write(array('MIGRATION_REVERT_SCHEMA_RUNNING', $name), $verbosity);
 583  
 584              $total_time = (is_array($state['migration_data_state']) && isset($state['migration_data_state']['_total_time'])) ?
 585                  $state['migration_data_state']['_total_time'] : 0.0;
 586              $elapsed_time = microtime(true);
 587  
 588              $steps = $this->helper->get_schema_steps($migration->revert_schema());
 589              $result = $this->process_data_step($steps, $state['migration_data_state']);
 590  
 591              $elapsed_time = microtime(true) - $elapsed_time;
 592              $total_time += $elapsed_time;
 593  
 594              if (is_array($result))
 595              {
 596                  $result['_total_time'] = $total_time;
 597              }
 598  
 599              $state['migration_data_state'] = ($result === true) ? '' : $result;
 600              $state['migration_schema_done'] = ($result === true) ? false : true;
 601  
 602              if (!$state['migration_schema_done'])
 603              {
 604                  $sql = 'DELETE FROM ' . $this->migrations_table . "
 605                      WHERE migration_name = '" . $this->db->sql_escape($name) . "'";
 606                  $this->db->sql_query($sql);
 607  
 608                  $this->last_run_migration = false;
 609                  unset($this->migration_state[$name]);
 610  
 611                  $this->output_handler->write(array('MIGRATION_REVERT_SCHEMA_DONE', $name, $total_time), migrator_output_handler_interface::VERBOSITY_NORMAL);
 612              }
 613              else
 614              {
 615                  $this->set_migration_state($name, $state);
 616  
 617                  $this->output_handler->write(array('MIGRATION_REVERT_SCHEMA_IN_PROGRESS', $name, $elapsed_time), migrator_output_handler_interface::VERBOSITY_VERY_VERBOSE);
 618              }
 619          }
 620  
 621          return true;
 622      }
 623  
 624      /**
 625      * Process the data step of the migration
 626      *
 627      * @param array $steps The steps to run
 628      * @param bool|string $state Current state of the migration
 629      * @param bool $revert true to revert a data step
 630      * @return bool|string migration state. True if completed, serialized array if not finished
 631      * @throws \phpbb\db\migration\exception
 632      */
 633  	protected function process_data_step($steps, $state, $revert = false)
 634      {
 635          if (count($steps) === 0)
 636          {
 637              return true;
 638          }
 639  
 640          $state = is_array($state) ? $state : false;
 641  
 642          // reverse order of steps if reverting
 643          if ($revert === true)
 644          {
 645              $steps = array_reverse($steps);
 646          }
 647  
 648          $step = $last_result = 0;
 649          if ($state)
 650          {
 651              $step = $state['step'];
 652  
 653              // We send the result from last time to the callable function
 654              $last_result = $state['result'];
 655          }
 656  
 657          try
 658          {
 659              // Result will be null or true if everything completed correctly
 660              // Stop after each update step, to let the updater control the script runtime
 661              $result = $this->run_step($steps[$step], $last_result, $revert);
 662              if (($result !== null && $result !== true) || $step + 1 < count($steps))
 663              {
 664                  return array(
 665                      'result'    => $result,
 666                      // Move on if the last call finished
 667                      'step'        => ($result !== null && $result !== true) ? $step : $step + 1,
 668                  );
 669              }
 670          }
 671          catch (\phpbb\db\migration\exception $e)
 672          {
 673              // We should try rolling back here
 674              foreach ($steps as $reverse_step_identifier => $reverse_step)
 675              {
 676                  // If we've reached the current step we can break because we reversed everything that was run
 677                  if ($reverse_step_identifier == $step)
 678                  {
 679                      break;
 680                  }
 681  
 682                  // Reverse the step that was run
 683                  $result = $this->run_step($reverse_step, false, !$revert);
 684              }
 685  
 686              throw $e;
 687          }
 688  
 689          return true;
 690      }
 691  
 692      /**
 693      * Run a single step
 694      *
 695      * An exception should be thrown if an error occurs
 696      *
 697      * @param mixed $step Data step from migration
 698      * @param mixed $last_result Result to pass to the callable (only for 'custom' method)
 699      * @param bool $reverse False to install, True to attempt uninstallation by reversing the call
 700      * @return null
 701      */
 702  	protected function run_step($step, $last_result = 0, $reverse = false)
 703      {
 704          $callable_and_parameters = $this->get_callable_from_step($step, $last_result, $reverse);
 705  
 706          if ($callable_and_parameters === false)
 707          {
 708              return;
 709          }
 710  
 711          $callable = $callable_and_parameters[0];
 712          $parameters = $callable_and_parameters[1];
 713  
 714          return call_user_func_array($callable, $parameters);
 715      }
 716  
 717      /**
 718      * Get a callable statement from a data step
 719      *
 720      * @param array $step Data step from migration
 721      * @param mixed $last_result Result to pass to the callable (only for 'custom' method)
 722      * @param bool $reverse False to install, True to attempt uninstallation by reversing the call
 723      * @return array Array with parameters for call_user_func_array(), 0 is the callable, 1 is parameters
 724      * @throws \phpbb\db\migration\exception
 725      */
 726  	protected function get_callable_from_step(array $step, $last_result = 0, $reverse = false)
 727      {
 728          $type = $step[0];
 729          $parameters = $step[1];
 730  
 731          $parts = explode('.', $type);
 732  
 733          $class = $parts[0];
 734          $method = false;
 735  
 736          if (isset($parts[1]))
 737          {
 738              $method = $parts[1];
 739          }
 740  
 741          switch ($class)
 742          {
 743              case 'if':
 744                  if (!isset($parameters[0]))
 745                  {
 746                      throw new \phpbb\db\migration\exception('MIGRATION_INVALID_DATA_MISSING_CONDITION', $step);
 747                  }
 748  
 749                  if (!isset($parameters[1]))
 750                  {
 751                      throw new \phpbb\db\migration\exception('MIGRATION_INVALID_DATA_MISSING_STEP', $step);
 752                  }
 753  
 754                  if ($reverse)
 755                  {
 756                      // We might get unexpected results when trying
 757                      // to revert this, so just avoid it
 758                      return false;
 759                  }
 760  
 761                  $condition = $parameters[0];
 762  
 763                  if (!$condition || (is_array($condition) && !$this->run_step($condition, $last_result, $reverse)))
 764                  {
 765                      return false;
 766                  }
 767  
 768                  $step = $parameters[1];
 769  
 770                  return $this->get_callable_from_step($step);
 771              break;
 772  
 773              case 'custom':
 774                  if (!is_callable($parameters[0]))
 775                  {
 776                      throw new \phpbb\db\migration\exception('MIGRATION_INVALID_DATA_CUSTOM_NOT_CALLABLE', $step);
 777                  }
 778  
 779                  if ($reverse)
 780                  {
 781                      return false;
 782                  }
 783                  else
 784                  {
 785                      return array(
 786                          $parameters[0],
 787                          array($last_result),
 788                      );
 789                  }
 790              break;
 791  
 792              default:
 793                  if (!$method)
 794                  {
 795                      throw new \phpbb\db\migration\exception('MIGRATION_INVALID_DATA_UNKNOWN_TYPE', $step);
 796                  }
 797  
 798                  if (!isset($this->tools[$class]))
 799                  {
 800                      throw new \phpbb\db\migration\exception('MIGRATION_INVALID_DATA_UNDEFINED_TOOL', $step);
 801                  }
 802  
 803                  if (!method_exists(get_class($this->tools[$class]), $method))
 804                  {
 805                      throw new \phpbb\db\migration\exception('MIGRATION_INVALID_DATA_UNDEFINED_METHOD', $step);
 806                  }
 807  
 808                  // Attempt to reverse operations
 809                  if ($reverse)
 810                  {
 811                      array_unshift($parameters, $method);
 812  
 813                      return array(
 814                          array($this->tools[$class], 'reverse'),
 815                          $parameters,
 816                      );
 817                  }
 818  
 819                  return array(
 820                      array($this->tools[$class], $method),
 821                      $parameters,
 822                  );
 823              break;
 824          }
 825      }
 826  
 827      /**
 828      * Insert/Update migration row into the database
 829      *
 830      * @param string $name Name of the migration
 831      * @param array $state
 832      * @return null
 833      */
 834  	protected function set_migration_state($name, $state)
 835      {
 836          $migration_row = $state;
 837          $migration_row['migration_depends_on'] = serialize($state['migration_depends_on']);
 838          $migration_row['migration_data_state'] = !empty($state['migration_data_state']) ? serialize($state['migration_data_state']) : '';
 839  
 840          if (isset($this->migration_state[$name]))
 841          {
 842              $sql = 'UPDATE ' . $this->migrations_table . '
 843                  SET ' . $this->db->sql_build_array('UPDATE', $migration_row) . "
 844                  WHERE migration_name = '" . $this->db->sql_escape($name) . "'";
 845              $this->db->sql_query($sql);
 846          }
 847          else
 848          {
 849              $migration_row['migration_name'] = $name;
 850              $sql = 'INSERT INTO ' . $this->migrations_table . '
 851                  ' . $this->db->sql_build_array('INSERT', $migration_row);
 852              $this->db->sql_query($sql);
 853          }
 854  
 855          $this->migration_state[$name] = $state;
 856  
 857          $this->last_run_migration['state'] = $state;
 858      }
 859  
 860      /**
 861      * Checks if a migration's dependencies can even theoretically be satisfied.
 862      *
 863      * @param string    $name The class name of the migration
 864      * @return bool|string False if fulfillable, string of missing migration name if unfulfillable
 865      */
 866  	public function unfulfillable($name)
 867      {
 868          $name = $this->get_valid_name($name);
 869  
 870          if (isset($this->migration_state[$name]) || isset($this->fulfillable_migrations[$name]))
 871          {
 872              return false;
 873          }
 874  
 875          if (!class_exists($name))
 876          {
 877              return $name;
 878          }
 879  
 880          $migration = $this->get_migration($name);
 881          $depends = $migration->depends_on();
 882  
 883          foreach ($depends as $depend)
 884          {
 885              $depend = $this->get_valid_name($depend);
 886              $unfulfillable = $this->unfulfillable($depend);
 887              if ($unfulfillable !== false)
 888              {
 889                  return $unfulfillable;
 890              }
 891          }
 892          $this->fulfillable_migrations[$name] = true;
 893  
 894          return false;
 895      }
 896  
 897      /**
 898      * Checks whether all available, fulfillable migrations have been applied.
 899      *
 900      * @return bool Whether the migrations have been applied
 901      */
 902  	public function finished()
 903      {
 904          foreach ($this->migrations as $name)
 905          {
 906              if (!isset($this->migration_state[$name]))
 907              {
 908                  // skip unfulfillable migrations, but fulfillables mean we
 909                  // are not finished yet
 910                  if ($this->unfulfillable($name) !== false)
 911                  {
 912                      continue;
 913                  }
 914  
 915                  return false;
 916              }
 917  
 918              $migration = $this->migration_state[$name];
 919  
 920              if (!$migration['migration_schema_done'] || !$migration['migration_data_done'])
 921              {
 922                  return false;
 923              }
 924          }
 925  
 926          return true;
 927      }
 928  
 929      /**
 930      * Gets a migration state (whether it is installed and to what extent)
 931      *
 932      * @param string $migration String migration name to check if it is installed
 933      * @return bool|array False if the migration has not at all been installed, array
 934      */
 935  	public function migration_state($migration)
 936      {
 937          if (!isset($this->migration_state[$migration]))
 938          {
 939              return false;
 940          }
 941  
 942          return $this->migration_state[$migration];
 943      }
 944  
 945      /**
 946      * Helper to get a migration
 947      *
 948      * @param string $name Name of the migration
 949      * @return \phpbb\db\migration\migration
 950      */
 951  	public function get_migration($name)
 952      {
 953          $migration = new $name($this->config, $this->db, $this->db_tools, $this->phpbb_root_path, $this->php_ext, $this->table_prefix);
 954  
 955          if ($migration instanceof ContainerAwareInterface)
 956          {
 957              $migration->setContainer($this->container);
 958          }
 959  
 960          return $migration;
 961      }
 962  
 963      /**
 964      * This function adds all migrations sent to it to the migrations table
 965      *
 966      * THIS SHOULD NOT GENERALLY BE USED! THIS IS FOR THE PHPBB INSTALLER.
 967      * THIS WILL THROW ERRORS IF MIGRATIONS ALREADY EXIST IN THE TABLE, DO NOT CALL MORE THAN ONCE!
 968      *
 969      * @param array $migrations Array of migrations (names) to add to the migrations table
 970      * @return null
 971      */
 972  	public function populate_migrations($migrations)
 973      {
 974          foreach ($migrations as $name)
 975          {
 976              if ($this->migration_state($name) === false)
 977              {
 978                  $state = array(
 979                      'migration_depends_on'    => $name::depends_on(),
 980                      'migration_schema_done' => true,
 981                      'migration_data_done'    => true,
 982                      'migration_data_state'    => '',
 983                      'migration_start_time'    => time(),
 984                      'migration_end_time'    => time(),
 985                  );
 986                  $this->set_migration_state($name, $state);
 987              }
 988          }
 989      }
 990  
 991      /**
 992      * Creates the migrations table if it does not exist.
 993      * @return null
 994      */
 995  	public function create_migrations_table()
 996      {
 997          // Make sure migrations have been installed.
 998          if (!$this->db_tools->sql_table_exists($this->table_prefix . 'migrations'))
 999          {
1000              $this->db_tools->sql_create_table($this->table_prefix . 'migrations', array(
1001                  'COLUMNS'        => array(
1002                      'migration_name'            => array('VCHAR', ''),
1003                      'migration_depends_on'        => array('TEXT', ''),
1004                      'migration_schema_done'        => array('BOOL', 0),
1005                      'migration_data_done'        => array('BOOL', 0),
1006                      'migration_data_state'        => array('TEXT', ''),
1007                      'migration_start_time'        => array('TIMESTAMP', 0),
1008                      'migration_end_time'        => array('TIMESTAMP', 0),
1009                  ),
1010                  'PRIMARY_KEY'    => 'migration_name',
1011              ));
1012          }
1013      }
1014  
1015      /**
1016       * Check if a class is a migration.
1017       *
1018       * @param string $migration A migration class name
1019       * @return bool Return true if class is a migration, false otherwise
1020       */
1021  	static public function is_migration($migration)
1022      {
1023          if (class_exists($migration))
1024          {
1025              // Migration classes should extend the abstract class
1026              // phpbb\db\migration\migration (which implements the
1027              // migration_interface) and be instantiable.
1028              $reflector = new \ReflectionClass($migration);
1029              if ($reflector->implementsInterface('\phpbb\db\migration\migration_interface') && $reflector->isInstantiable())
1030              {
1031                  return true;
1032              }
1033          }
1034  
1035          return false;
1036      }
1037  }


Generated: Wed Nov 11 20:33:01 2020 Cross-referenced by PHPXref 0.7.1