[ Index ]

PHP Cross Reference of phpBB-3.3.14-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              $missing = $this->unfulfillable($depend);
 343              if ($missing !== false)
 344              {
 345                  throw new \phpbb\db\migration\exception('MIGRATION_NOT_FULFILLABLE', $name, $missing);
 346              }
 347  
 348              if (!isset($this->migration_state[$depend]) ||
 349                  !$this->migration_state[$depend]['migration_schema_done'] ||
 350                  !$this->migration_state[$depend]['migration_data_done'])
 351              {
 352                  return $this->try_apply($depend);
 353              }
 354          }
 355  
 356          $this->last_run_migration = array(
 357              'name'    => $name,
 358              'class'    => $migration,
 359              'state'    => $state,
 360              'task'    => '',
 361          );
 362  
 363          if (!isset($this->migration_state[$name]))
 364          {
 365              if ($state['migration_start_time'] == 0 && $migration->effectively_installed())
 366              {
 367                  $state = array(
 368                      'migration_depends_on'    => $migration->depends_on(),
 369                      'migration_schema_done' => true,
 370                      'migration_data_done'    => true,
 371                      'migration_data_state'    => '',
 372                      'migration_start_time'    => 0,
 373                      'migration_end_time'    => 0,
 374                  );
 375  
 376                  $this->last_run_migration['effectively_installed'] = true;
 377  
 378                  $this->output_handler->write(array('MIGRATION_EFFECTIVELY_INSTALLED', $name), migrator_output_handler_interface::VERBOSITY_VERBOSE);
 379              }
 380              else
 381              {
 382                  $state['migration_start_time'] = time();
 383              }
 384          }
 385  
 386          $this->set_migration_state($name, $state);
 387  
 388          if (!$state['migration_schema_done'])
 389          {
 390              $verbosity = empty($state['migration_data_state']) ?
 391                  migrator_output_handler_interface::VERBOSITY_VERBOSE : migrator_output_handler_interface::VERBOSITY_DEBUG;
 392              $this->output_handler->write(array('MIGRATION_SCHEMA_RUNNING', $name), $verbosity);
 393  
 394              $this->last_run_migration['task'] = 'process_schema_step';
 395  
 396              $total_time = (is_array($state['migration_data_state']) && isset($state['migration_data_state']['_total_time'])) ?
 397                  $state['migration_data_state']['_total_time'] : 0.0;
 398              $elapsed_time = microtime(true);
 399  
 400              $steps = $this->helper->get_schema_steps($migration->update_schema());
 401              $result = $this->process_data_step($steps, $state['migration_data_state']);
 402  
 403              $elapsed_time = microtime(true) - $elapsed_time;
 404              $total_time += $elapsed_time;
 405  
 406              if (is_array($result))
 407              {
 408                  $result['_total_time'] = $total_time;
 409              }
 410  
 411              $state['migration_data_state'] = ($result === true) ? '' : $result;
 412              $state['migration_schema_done'] = ($result === true);
 413  
 414              if ($state['migration_schema_done'])
 415              {
 416                  $this->output_handler->write(array('MIGRATION_SCHEMA_DONE', $name, $total_time), migrator_output_handler_interface::VERBOSITY_NORMAL);
 417              }
 418              else
 419              {
 420                  $this->output_handler->write(array('MIGRATION_SCHEMA_IN_PROGRESS', $name, $elapsed_time), migrator_output_handler_interface::VERBOSITY_VERY_VERBOSE);
 421              }
 422          }
 423          else if (!$state['migration_data_done'])
 424          {
 425              try
 426              {
 427                  $verbosity = empty($state['migration_data_state']) ?
 428                      migrator_output_handler_interface::VERBOSITY_VERBOSE : migrator_output_handler_interface::VERBOSITY_DEBUG;
 429                  $this->output_handler->write(array('MIGRATION_DATA_RUNNING', $name), $verbosity);
 430  
 431                  $this->last_run_migration['task'] = 'process_data_step';
 432  
 433                  $total_time = (is_array($state['migration_data_state']) && isset($state['migration_data_state']['_total_time'])) ?
 434                      $state['migration_data_state']['_total_time'] : 0.0;
 435                  $elapsed_time = microtime(true);
 436  
 437                  $result = $this->process_data_step($migration->update_data(), $state['migration_data_state']);
 438  
 439                  $elapsed_time = microtime(true) - $elapsed_time;
 440                  $total_time += $elapsed_time;
 441  
 442                  if (is_array($result))
 443                  {
 444                      $result['_total_time'] = $total_time;
 445                  }
 446  
 447                  $state['migration_data_state'] = ($result === true) ? '' : $result;
 448                  $state['migration_data_done'] = ($result === true);
 449                  $state['migration_end_time'] = ($result === true) ? time() : 0;
 450  
 451                  if ($state['migration_data_done'])
 452                  {
 453                      $this->output_handler->write(array('MIGRATION_DATA_DONE', $name, $total_time), migrator_output_handler_interface::VERBOSITY_NORMAL);
 454                  }
 455                  else
 456                  {
 457                      $this->output_handler->write(array('MIGRATION_DATA_IN_PROGRESS', $name, $elapsed_time), migrator_output_handler_interface::VERBOSITY_VERY_VERBOSE);
 458                  }
 459              }
 460              catch (\phpbb\db\migration\exception $e)
 461              {
 462                  // Reset data state and revert the schema changes
 463                  $state['migration_data_state'] = '';
 464                  $this->set_migration_state($name, $state);
 465  
 466                  $this->revert_do($name);
 467  
 468                  throw $e;
 469              }
 470          }
 471  
 472          $this->set_migration_state($name, $state);
 473  
 474          return true;
 475      }
 476  
 477      /**
 478      * Runs a single revert step from the last migration installed
 479      *
 480      * YOU MUST ADD/SET ALL MIGRATIONS THAT COULD BE DEPENDENT ON THE MIGRATION TO REVERT TO BEFORE CALLING THIS METHOD!
 481      * The revert step can either be a schema or a (partial) data revert. To
 482      * check if revert() needs to be called again use the migration_state() method.
 483      *
 484      * @param string $migration String migration name to revert (including any that depend on this migration)
 485      */
 486  	public function revert($migration)
 487      {
 488          $this->container->get('dispatcher')->disable();
 489          $this->revert_do($migration);
 490          $this->container->get('dispatcher')->enable();
 491      }
 492  
 493      /**
 494       * Effectively runs a single revert step from the last migration installed
 495       *
 496       * @param string $migration String migration name to revert (including any that depend on this migration)
 497       * @return null
 498       */
 499  	protected function revert_do($migration)
 500      {
 501          if (!isset($this->migration_state[$migration]))
 502          {
 503              // Not installed
 504              return;
 505          }
 506  
 507          foreach ($this->migrations as $name)
 508          {
 509              $state = $this->migration_state($name);
 510  
 511              if ($state && in_array($migration, $state['migration_depends_on']) && ($state['migration_schema_done'] || $state['migration_data_done']))
 512              {
 513                  $this->revert_do($name);
 514                  return;
 515              }
 516          }
 517  
 518          $this->try_revert($migration);
 519      }
 520  
 521      /**
 522      * Attempts to revert a step of the given migration or one of its dependencies
 523      *
 524      * @param    string    $name The class name of the migration
 525      * @return    bool    Whether any update step was successfully run
 526      */
 527  	protected function try_revert($name)
 528      {
 529          if (!class_exists($name))
 530          {
 531              return false;
 532          }
 533  
 534          $migration = $this->get_migration($name);
 535  
 536          $state = $this->migration_state[$name];
 537  
 538          $this->last_run_migration = array(
 539              'name'    => $name,
 540              'class'    => $migration,
 541              'task'    => '',
 542          );
 543  
 544          if ($state['migration_data_done'])
 545          {
 546              $verbosity = empty($state['migration_data_state']) ?
 547                  migrator_output_handler_interface::VERBOSITY_VERBOSE : migrator_output_handler_interface::VERBOSITY_DEBUG;
 548              $this->output_handler->write(array('MIGRATION_REVERT_DATA_RUNNING', $name), $verbosity);
 549  
 550              $total_time = (is_array($state['migration_data_state']) && isset($state['migration_data_state']['_total_time'])) ?
 551                  $state['migration_data_state']['_total_time'] : 0.0;
 552              $elapsed_time = microtime(true);
 553  
 554              $steps = array_merge($this->helper->reverse_update_data($migration->update_data()), $migration->revert_data());
 555              $result = $this->process_data_step($steps, $state['migration_data_state']);
 556  
 557              $elapsed_time = microtime(true) - $elapsed_time;
 558              $total_time += $elapsed_time;
 559  
 560              if (is_array($result))
 561              {
 562                  $result['_total_time'] = $total_time;
 563              }
 564  
 565              $state['migration_data_state'] = ($result === true) ? '' : $result;
 566              $state['migration_data_done'] = ($result === true) ? false : true;
 567  
 568              $this->set_migration_state($name, $state);
 569  
 570              if (!$state['migration_data_done'])
 571              {
 572                  $this->output_handler->write(array('MIGRATION_REVERT_DATA_DONE', $name, $total_time), migrator_output_handler_interface::VERBOSITY_NORMAL);
 573              }
 574              else
 575              {
 576                  $this->output_handler->write(array('MIGRATION_REVERT_DATA_IN_PROGRESS', $name, $elapsed_time), migrator_output_handler_interface::VERBOSITY_VERY_VERBOSE);
 577              }
 578          }
 579          else if ($state['migration_schema_done'])
 580          {
 581              $verbosity = empty($state['migration_data_state']) ?
 582                  migrator_output_handler_interface::VERBOSITY_VERBOSE : migrator_output_handler_interface::VERBOSITY_DEBUG;
 583              $this->output_handler->write(array('MIGRATION_REVERT_SCHEMA_RUNNING', $name), $verbosity);
 584  
 585              $total_time = (is_array($state['migration_data_state']) && isset($state['migration_data_state']['_total_time'])) ?
 586                  $state['migration_data_state']['_total_time'] : 0.0;
 587              $elapsed_time = microtime(true);
 588  
 589              $steps = $this->helper->get_schema_steps($migration->revert_schema());
 590              $result = $this->process_data_step($steps, $state['migration_data_state']);
 591  
 592              $elapsed_time = microtime(true) - $elapsed_time;
 593              $total_time += $elapsed_time;
 594  
 595              if (is_array($result))
 596              {
 597                  $result['_total_time'] = $total_time;
 598              }
 599  
 600              $state['migration_data_state'] = ($result === true) ? '' : $result;
 601              $state['migration_schema_done'] = ($result === true) ? false : true;
 602  
 603              if (!$state['migration_schema_done'])
 604              {
 605                  $sql = 'DELETE FROM ' . $this->migrations_table . "
 606                      WHERE migration_name = '" . $this->db->sql_escape($name) . "'";
 607                  $this->db->sql_query($sql);
 608  
 609                  $this->last_run_migration = false;
 610                  unset($this->migration_state[$name]);
 611  
 612                  $this->output_handler->write(array('MIGRATION_REVERT_SCHEMA_DONE', $name, $total_time), migrator_output_handler_interface::VERBOSITY_NORMAL);
 613              }
 614              else
 615              {
 616                  $this->set_migration_state($name, $state);
 617  
 618                  $this->output_handler->write(array('MIGRATION_REVERT_SCHEMA_IN_PROGRESS', $name, $elapsed_time), migrator_output_handler_interface::VERBOSITY_VERY_VERBOSE);
 619              }
 620          }
 621  
 622          return true;
 623      }
 624  
 625      /**
 626      * Process the data step of the migration
 627      *
 628      * @param array $steps The steps to run
 629      * @param bool|string $state Current state of the migration
 630      * @param bool $revert true to revert a data step
 631      * @return bool|string migration state. True if completed, serialized array if not finished
 632      * @throws \phpbb\db\migration\exception
 633      */
 634  	protected function process_data_step($steps, $state, $revert = false)
 635      {
 636          if (count($steps) === 0)
 637          {
 638              return true;
 639          }
 640  
 641          $state = is_array($state) ? $state : false;
 642  
 643          // reverse order of steps if reverting
 644          if ($revert === true)
 645          {
 646              $steps = array_reverse($steps);
 647          }
 648  
 649          $step = $last_result = 0;
 650          if ($state)
 651          {
 652              $step = $state['step'];
 653  
 654              // We send the result from last time to the callable function
 655              $last_result = $state['result'];
 656          }
 657  
 658          try
 659          {
 660              // Result will be null or true if everything completed correctly
 661              // Stop after each update step, to let the updater control the script runtime
 662              $result = $this->run_step($steps[$step], $last_result, $revert);
 663              if (($result !== null && $result !== true) || $step + 1 < count($steps))
 664              {
 665                  return array(
 666                      'result'    => $result,
 667                      // Move on if the last call finished
 668                      'step'        => ($result !== null && $result !== true) ? $step : $step + 1,
 669                  );
 670              }
 671          }
 672          catch (\phpbb\db\migration\exception $e)
 673          {
 674              // We should try rolling back here
 675              foreach ($steps as $reverse_step_identifier => $reverse_step)
 676              {
 677                  // If we've reached the current step we can break because we reversed everything that was run
 678                  if ($reverse_step_identifier == $step)
 679                  {
 680                      break;
 681                  }
 682  
 683                  // Reverse the step that was run
 684                  $result = $this->run_step($reverse_step, false, !$revert);
 685              }
 686  
 687              throw $e;
 688          }
 689  
 690          return true;
 691      }
 692  
 693      /**
 694      * Run a single step
 695      *
 696      * An exception should be thrown if an error occurs
 697      *
 698      * @param mixed $step Data step from migration
 699      * @param mixed $last_result Result to pass to the callable (only for 'custom' method)
 700      * @param bool $reverse False to install, True to attempt uninstallation by reversing the call
 701      * @return null
 702      */
 703  	protected function run_step($step, $last_result = 0, $reverse = false)
 704      {
 705          $callable_and_parameters = $this->get_callable_from_step($step, $last_result, $reverse);
 706  
 707          if ($callable_and_parameters === false)
 708          {
 709              return;
 710          }
 711  
 712          $callable = $callable_and_parameters[0];
 713          $parameters = $callable_and_parameters[1];
 714  
 715          return call_user_func_array($callable, $parameters);
 716      }
 717  
 718      /**
 719      * Get a callable statement from a data step
 720      *
 721      * @param array $step Data step from migration
 722      * @param mixed $last_result Result to pass to the callable (only for 'custom' method)
 723      * @param bool $reverse False to install, True to attempt uninstallation by reversing the call
 724      * @return array Array with parameters for call_user_func_array(), 0 is the callable, 1 is parameters
 725      * @throws \phpbb\db\migration\exception
 726      */
 727  	protected function get_callable_from_step(array $step, $last_result = 0, $reverse = false)
 728      {
 729          $type = $step[0];
 730          $parameters = $step[1];
 731  
 732          $parts = explode('.', $type);
 733  
 734          $class = $parts[0];
 735          $method = false;
 736  
 737          if (isset($parts[1]))
 738          {
 739              $method = $parts[1];
 740          }
 741  
 742          switch ($class)
 743          {
 744              case 'if':
 745                  if (!isset($parameters[0]))
 746                  {
 747                      throw new \phpbb\db\migration\exception('MIGRATION_INVALID_DATA_MISSING_CONDITION', $step);
 748                  }
 749  
 750                  if (!isset($parameters[1]))
 751                  {
 752                      throw new \phpbb\db\migration\exception('MIGRATION_INVALID_DATA_MISSING_STEP', $step);
 753                  }
 754  
 755                  if ($reverse)
 756                  {
 757                      // We might get unexpected results when trying
 758                      // to revert this, so just avoid it
 759                      return false;
 760                  }
 761  
 762                  $condition = $parameters[0];
 763  
 764                  if (!$condition || (is_array($condition) && !$this->run_step($condition, $last_result, $reverse)))
 765                  {
 766                      return false;
 767                  }
 768  
 769                  $step = $parameters[1];
 770  
 771                  return $this->get_callable_from_step($step);
 772              break;
 773  
 774              case 'custom':
 775                  if (!is_callable($parameters[0]))
 776                  {
 777                      throw new \phpbb\db\migration\exception('MIGRATION_INVALID_DATA_CUSTOM_NOT_CALLABLE', $step);
 778                  }
 779  
 780                  if ($reverse)
 781                  {
 782                      return false;
 783                  }
 784                  else
 785                  {
 786                      return array(
 787                          $parameters[0],
 788                          isset($parameters[1]) ? array_merge($parameters[1], array($last_result)) : array($last_result),
 789                      );
 790                  }
 791              break;
 792  
 793              default:
 794                  if (!$method)
 795                  {
 796                      throw new \phpbb\db\migration\exception('MIGRATION_INVALID_DATA_UNKNOWN_TYPE', $step);
 797                  }
 798  
 799                  if (!isset($this->tools[$class]))
 800                  {
 801                      throw new \phpbb\db\migration\exception('MIGRATION_INVALID_DATA_UNDEFINED_TOOL', $step);
 802                  }
 803  
 804                  if (!method_exists(get_class($this->tools[$class]), $method))
 805                  {
 806                      throw new \phpbb\db\migration\exception('MIGRATION_INVALID_DATA_UNDEFINED_METHOD', $step);
 807                  }
 808  
 809                  // Attempt to reverse operations
 810                  if ($reverse)
 811                  {
 812                      array_unshift($parameters, $method);
 813  
 814                      return array(
 815                          array($this->tools[$class], 'reverse'),
 816                          $parameters,
 817                      );
 818                  }
 819  
 820                  return array(
 821                      array($this->tools[$class], $method),
 822                      $parameters,
 823                  );
 824              break;
 825          }
 826      }
 827  
 828      /**
 829      * Insert/Update migration row into the database
 830      *
 831      * @param string $name Name of the migration
 832      * @param array $state
 833      * @return null
 834      */
 835  	protected function set_migration_state($name, $state)
 836      {
 837          $migration_row = $state;
 838          $migration_row['migration_depends_on'] = serialize($state['migration_depends_on']);
 839          $migration_row['migration_data_state'] = !empty($state['migration_data_state']) ? serialize($state['migration_data_state']) : '';
 840  
 841          if (isset($this->migration_state[$name]))
 842          {
 843              $sql = 'UPDATE ' . $this->migrations_table . '
 844                  SET ' . $this->db->sql_build_array('UPDATE', $migration_row) . "
 845                  WHERE migration_name = '" . $this->db->sql_escape($name) . "'";
 846              $this->db->sql_query($sql);
 847          }
 848          else
 849          {
 850              $migration_row['migration_name'] = $name;
 851              $sql = 'INSERT INTO ' . $this->migrations_table . '
 852                  ' . $this->db->sql_build_array('INSERT', $migration_row);
 853              $this->db->sql_query($sql);
 854          }
 855  
 856          $this->migration_state[$name] = $state;
 857  
 858          $this->last_run_migration['state'] = $state;
 859      }
 860  
 861      /**
 862      * Checks if a migration's dependencies can even theoretically be satisfied.
 863      *
 864      * @param string    $name The class name of the migration
 865      * @return bool|string False if fulfillable, string of missing migration name if unfulfillable
 866      */
 867  	public function unfulfillable($name)
 868      {
 869          $name = $this->get_valid_name($name);
 870  
 871          if (isset($this->migration_state[$name]) || isset($this->fulfillable_migrations[$name]))
 872          {
 873              return false;
 874          }
 875  
 876          if (!class_exists($name))
 877          {
 878              return $name;
 879          }
 880  
 881          $migration = $this->get_migration($name);
 882          $depends = $migration->depends_on();
 883  
 884          foreach ($depends as $depend)
 885          {
 886              $depend = $this->get_valid_name($depend);
 887              $unfulfillable = $this->unfulfillable($depend);
 888              if ($unfulfillable !== false)
 889              {
 890                  return $unfulfillable;
 891              }
 892          }
 893          $this->fulfillable_migrations[$name] = true;
 894  
 895          return false;
 896      }
 897  
 898      /**
 899      * Checks whether all available, fulfillable migrations have been applied.
 900      *
 901      * @return bool Whether the migrations have been applied
 902      */
 903  	public function finished()
 904      {
 905          foreach ($this->migrations as $name)
 906          {
 907              if (!isset($this->migration_state[$name]))
 908              {
 909                  // skip unfulfillable migrations, but fulfillables mean we
 910                  // are not finished yet
 911                  if ($this->unfulfillable($name) !== false)
 912                  {
 913                      continue;
 914                  }
 915  
 916                  return false;
 917              }
 918  
 919              $migration = $this->migration_state[$name];
 920  
 921              if (!$migration['migration_schema_done'] || !$migration['migration_data_done'])
 922              {
 923                  return false;
 924              }
 925          }
 926  
 927          return true;
 928      }
 929  
 930      /**
 931      * Gets a migration state (whether it is installed and to what extent)
 932      *
 933      * @param string $migration String migration name to check if it is installed
 934      * @return bool|array False if the migration has not at all been installed, array
 935      */
 936  	public function migration_state($migration)
 937      {
 938          if (!isset($this->migration_state[$migration]))
 939          {
 940              return false;
 941          }
 942  
 943          return $this->migration_state[$migration];
 944      }
 945  
 946      /**
 947      * Helper to get a migration
 948      *
 949      * @param string $name Name of the migration
 950      * @return \phpbb\db\migration\migration
 951      */
 952  	public function get_migration($name)
 953      {
 954          $migration = new $name($this->config, $this->db, $this->db_tools, $this->phpbb_root_path, $this->php_ext, $this->table_prefix);
 955  
 956          if ($migration instanceof ContainerAwareInterface)
 957          {
 958              $migration->setContainer($this->container);
 959          }
 960  
 961          return $migration;
 962      }
 963  
 964      /**
 965      * This function adds all migrations sent to it to the migrations table
 966      *
 967      * THIS SHOULD NOT GENERALLY BE USED! THIS IS FOR THE PHPBB INSTALLER.
 968      * THIS WILL THROW ERRORS IF MIGRATIONS ALREADY EXIST IN THE TABLE, DO NOT CALL MORE THAN ONCE!
 969      *
 970      * @param array $migrations Array of migrations (names) to add to the migrations table
 971      * @return null
 972      */
 973  	public function populate_migrations($migrations)
 974      {
 975          foreach ($migrations as $name)
 976          {
 977              if ($this->migration_state($name) === false)
 978              {
 979                  $state = array(
 980                      'migration_depends_on'    => $name::depends_on(),
 981                      'migration_schema_done' => true,
 982                      'migration_data_done'    => true,
 983                      'migration_data_state'    => '',
 984                      'migration_start_time'    => time(),
 985                      'migration_end_time'    => time(),
 986                  );
 987                  $this->set_migration_state($name, $state);
 988              }
 989          }
 990      }
 991  
 992      /**
 993      * Creates the migrations table if it does not exist.
 994      * @return null
 995      */
 996  	public function create_migrations_table()
 997      {
 998          // Make sure migrations have been installed.
 999          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  }


Generated: Mon Nov 25 19:05:08 2024 Cross-referenced by PHPXref 0.7.1