PageRenderTime 44ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/ruckusing/lib/tasks/class.Ruckusing_DB_Migrate.php

https://github.com/radicaldesigns/jaguar
PHP | 236 lines | 196 code | 21 blank | 19 comment | 38 complexity | 487c048f09c35c3be15cb139fcc93344 MD5 | raw file
Possible License(s): MIT, LGPL-2.1
  1. <?php
  2. /*
  3. This is the primary work-horse method, it runs all migrations available,
  4. up to the current version.
  5. */
  6. require_once RUCKUSING_BASE . '/lib/classes/task/class.Ruckusing_Task.php';
  7. require_once RUCKUSING_BASE . '/lib/classes/task/class.Ruckusing_iTask.php';
  8. require_once RUCKUSING_BASE . '/config/config.inc.php';
  9. require_once RUCKUSING_BASE . '/lib/classes/Ruckusing_exceptions.php';
  10. require_once RUCKUSING_BASE . '/lib/classes/util/class.Ruckusing_MigratorUtil.php';
  11. require_once RUCKUSING_BASE . '/lib/classes/class.Ruckusing_BaseMigration.php';
  12. define('STYLE_REGULAR', 1);
  13. define('STYLE_OFFSET', 2);
  14. class Ruckusing_DB_Migrate extends Ruckusing_Task implements Ruckusing_iTask {
  15. private $migrator_util = null;
  16. private $task_args = array();
  17. private $regexp = '/^(\d+)\_/';
  18. private $debug = false;
  19. private $migrations_directory;
  20. private $framework;
  21. function __construct($adapter) {
  22. parent::__construct($adapter);
  23. $this->migrator_util = new Ruckusing_MigratorUtil($adapter);
  24. }
  25. /* Primary task entry point */
  26. public function execute($args) {
  27. $output = "";
  28. if(!$this->get_adapter()->supports_migrations()) {
  29. die("This database does not support migrations.");
  30. }
  31. $this->task_args = $args;
  32. echo "Started: " . date('Y-m-d g:ia T') . "\n\n";
  33. echo "[db:migrate]: \n";
  34. try {
  35. // Check that the schema_version table exists, and if not, automatically create it
  36. $this->verify_environment();
  37. $target_version = null;
  38. $style = STYLE_REGULAR;
  39. //did the user specify an explicit version?
  40. if(array_key_exists('VERSION', $this->task_args)) {
  41. $target_version = trim($this->task_args['VERSION']);
  42. }
  43. // did the user specify a relative offset, e.g. "-2" or "+3" ?
  44. if($target_version !== null) {
  45. if(preg_match('/^([\-\+])(\d+)$/', $target_version, $matches)) {
  46. if(count($matches) == 3) {
  47. $direction = $matches[1] == '-' ? 'down' : 'up';
  48. $offset = intval($matches[2]);
  49. $style = STYLE_OFFSET;
  50. }
  51. }
  52. }
  53. //determine our direction and target version
  54. $current_version = $this->migrator_util->get_max_version();
  55. if($style == STYLE_REGULAR) {
  56. if(is_null($target_version)) {
  57. $this->prepare_to_migrate($target_version, 'up');
  58. }elseif($current_version > $target_version) {
  59. $this->prepare_to_migrate($target_version, 'down');
  60. } else {
  61. $this->prepare_to_migrate($target_version, 'up');
  62. }
  63. }
  64. if($style == STYLE_OFFSET) {
  65. $this->migrate_from_offset($offset, $current_version, $direction);
  66. }
  67. // Completed - display accumulated output
  68. if(!empty($output)) {
  69. echo $output . "\n\n";
  70. }
  71. }catch(Ruckusing_MissingSchemaInfoTableException $ex) {
  72. echo "\tSchema info table does not exist. I tried creating it but failed. Check permissions.";
  73. }catch(Ruckusing_Exception $ex) {
  74. die("\n\n" . $ex->getMessage() . "\n\n");
  75. }
  76. echo "\n\nFinished: " . date('Y-m-d g:ia T') . "\n\n";
  77. }
  78. private function migrate_from_offset($offset, $current_version, $direction) {
  79. $migrations = $this->migrator_util->get_migration_files($this->get_framework()->migrations_directory(), $direction);
  80. $versions = array();
  81. $current_index = -1;
  82. for($i = 0; $i < count($migrations); $i++) {
  83. $migration = $migrations[$i];
  84. $versions[] = $migration['version'];
  85. if($migration['version'] === $current_version) {
  86. $current_index = $i;
  87. }
  88. }
  89. if($this->debug == true) {
  90. print_r($migrations);
  91. echo "\ncurrent_index: " . $current_index . "\n";
  92. echo "\ncurrent_version: " . $current_version . "\n";
  93. echo "\noffset: " . $offset . "\n";
  94. }
  95. // If we are not at the bottom then adjust our index (to satisfy array_slice)
  96. if($current_index == -1) {
  97. $current_index = 0;
  98. } else {
  99. $current_index += 1;
  100. }
  101. // check to see if we have enough migrations to run - the user
  102. // might have asked to run more than we have available
  103. $available = array_slice($migrations, $current_index, $offset);
  104. if(count($available) != $offset) {
  105. $names = array();
  106. foreach($available as $a) { $names[] = $a['file']; }
  107. $num_available = count($names);
  108. $prefix = $direction == 'down' ? '-' : '+';
  109. echo "\n\nCannot migrate " . strtoupper($direction) . " via offset \"{$prefix}{$offset}\": not enough migrations exist to execute.\n";
  110. echo "You asked for ({$offset}) but only available are ({$num_available}): " . implode(", ", $names) . "\n\n";
  111. } else {
  112. // run em
  113. $target = end($available);
  114. if($this->debug == true) {
  115. echo "\n------------- TARGET ------------------\n";
  116. print_r($target);
  117. }
  118. $this->prepare_to_migrate($target['version'], $direction);
  119. }
  120. }
  121. private function prepare_to_migrate($destination, $direction) {
  122. try {
  123. echo "\tMigrating " . strtoupper($direction);
  124. if(!is_null($destination)) {
  125. echo " to: {$destination}\n";
  126. } else {
  127. echo ":\n";
  128. }
  129. $migrations = $this->migrator_util->get_runnable_migrations($this->get_framework()->migrations_directory(), $direction, $destination);
  130. if(count($migrations) == 0) {
  131. return "\nNo relevant migrations to run. Exiting...\n";
  132. }
  133. $result = $this->run_migrations($migrations, $direction, $destination);
  134. }catch(Exception $ex) {
  135. throw $ex;
  136. }
  137. }
  138. private function run_migrations($migrations, $target_method, $destination) {
  139. $last_version = -1;
  140. foreach($migrations as $file) {
  141. $full_path = $this->get_framework()->migrations_directory() . '/' . $file['file'];
  142. if(is_file($full_path) && is_readable($full_path) ) {
  143. require_once $full_path;
  144. $klass = Ruckusing_NamingUtil::class_from_migration_file($file['file']);
  145. $obj = new $klass();
  146. $refl = new ReflectionObject($obj);
  147. if($refl->hasMethod($target_method)) {
  148. $obj->set_adapter($this->get_adapter());
  149. $start = $this->start_timer();
  150. try {
  151. //start transaction
  152. $this->get_adapter()->start_transaction();
  153. $result = $obj->$target_method();
  154. //successfully ran migration, update our version and commit
  155. $this->migrator_util->resolve_current_version($file['version'], $target_method);
  156. $this->get_adapter()->commit_transaction();
  157. }catch(Exception $e) {
  158. $this->get_adapter()->rollback_transaction();
  159. //wrap the caught exception in our own
  160. $ex = new Exception(sprintf("%s - %s", $file['class'], $e->getMessage()));
  161. throw $ex;
  162. }
  163. $end = $this->end_timer();
  164. $diff = $this->diff_timer($start, $end);
  165. printf("========= %s ======== (%.2f)\n", $file['class'], $diff);
  166. $last_version = $file['version'];
  167. $exec = true;
  168. } else {
  169. trigger_error("ERROR: {$klass} does not have a '{$target_method}' method defined!");
  170. }
  171. }//is_file
  172. }//foreach
  173. //update the schema info
  174. $result = array('last_version' => $last_version);
  175. return $result;
  176. }//run_migrations
  177. private function start_timer() {
  178. return microtime(true);
  179. }
  180. private function end_timer() {
  181. return microtime(true);
  182. }
  183. private function diff_timer($s, $e) {
  184. return $e - $s;
  185. }
  186. private function verify_environment() {
  187. if(!$this->get_adapter()->table_exists(RUCKUSING_TS_SCHEMA_TBL_NAME) ) {
  188. echo "\n\tSchema version table does not exist. Auto-creating.";
  189. $this->auto_create_schema_info_table();
  190. }
  191. // create the migrations directory if it doesnt exist
  192. $migrations_directory = $this->get_framework()->migrations_directory();
  193. if(!is_dir($migrations_directory)) {
  194. printf("\n\tMigrations directory (%s doesn't exist, attempting to create.", $migrations_directory);
  195. if(mkdir($migrations_directory) === FALSE) {
  196. printf("\n\tUnable to create migrations directory at %s, check permissions?", $migrations_directory);
  197. } else {
  198. printf("\n\tCreated OK");
  199. }
  200. }
  201. }
  202. private function auto_create_schema_info_table() {
  203. try {
  204. echo sprintf("\n\tCreating schema version table: %s", RUCKUSING_TS_SCHEMA_TBL_NAME . "\n\n");
  205. $this->get_adapter()->create_schema_version_table();
  206. return true;
  207. }catch(Exception $e) {
  208. die("\nError auto-creating 'schema_info' table: " . $e->getMessage() . "\n\n");
  209. }
  210. }
  211. }//class
  212. ?>