migrator.php 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  1. <?php namespace Laravel\CLI\Tasks\Migrate;
  2. use Laravel\Str;
  3. use Laravel\File;
  4. use Laravel\Bundle;
  5. use Laravel\CLI\Tasks\Task;
  6. use Laravel\Database\Schema;
  7. class Migrator extends Task {
  8. /**
  9. * The migration resolver instance.
  10. *
  11. * @var Resolver
  12. */
  13. protected $resolver;
  14. /**
  15. * The migration database instance.
  16. *
  17. * @var Database
  18. */
  19. protected $database;
  20. /**
  21. * Create a new instance of the Migrator CLI task.
  22. *
  23. * @param Resolver $resolver
  24. * @param Database $database
  25. * @return void
  26. */
  27. public function __construct(Resolver $resolver, Database $database)
  28. {
  29. $this->resolver = $resolver;
  30. $this->database = $database;
  31. }
  32. /**
  33. * Run a database migration command.
  34. *
  35. * @param array $arguments
  36. * @return void
  37. */
  38. public function run($arguments = array())
  39. {
  40. // If no arguments were passed to the task, we will just migrate
  41. // to the latest version across all bundles. Otherwise, we will
  42. // parse the arguments to determine the bundle for which the
  43. // database migrations should be run.
  44. if (count($arguments) == 0)
  45. {
  46. $this->migrate();
  47. }
  48. else
  49. {
  50. $this->migrate(array_get($arguments, 0));
  51. }
  52. }
  53. /**
  54. * Run the outstanding migrations for a given bundle.
  55. *
  56. * @param string $bundle
  57. * @param int $version
  58. * @return void
  59. */
  60. public function migrate($bundle = null, $version = null)
  61. {
  62. $migrations = $this->resolver->outstanding($bundle);
  63. if (count($migrations) == 0)
  64. {
  65. echo "No outstanding migrations.";
  66. return;
  67. }
  68. // We need to grab the latest batch ID and increment it by one.
  69. // This allows us to group the migrations so we can easily
  70. // determine which migrations need to roll back.
  71. $batch = $this->database->batch() + 1;
  72. foreach ($migrations as $migration)
  73. {
  74. $migration['migration']->up();
  75. echo 'Migrated: '.$this->display($migration).PHP_EOL;
  76. // After running a migration, we log its execution in the migration
  77. // table so that we can easily determine which migrations we'll
  78. // reverse in the event of a migration rollback.
  79. $this->database->log($migration['bundle'], $migration['name'], $batch);
  80. }
  81. }
  82. /**
  83. * Rollback the latest migration command.
  84. *
  85. * @param array $arguments
  86. * @return bool
  87. */
  88. public function rollback($arguments = array())
  89. {
  90. $migrations = $this->resolver->last();
  91. if (count($migrations) == 0)
  92. {
  93. echo "Nothing to rollback.";
  94. return false;
  95. }
  96. // The "last" method on the resolver returns an array of migrations,
  97. // along with their bundles and names. We will iterate through each
  98. // migration and run the "down" method.
  99. foreach (array_reverse($migrations) as $migration)
  100. {
  101. $migration['migration']->down();
  102. echo 'Rolled back: '.$this->display($migration).PHP_EOL;
  103. // By only removing the migration after it has successfully rolled back,
  104. // we can re-run the rollback command in the event of any errors with
  105. // the migration and pick up where we left off.
  106. $this->database->delete($migration['bundle'], $migration['name']);
  107. }
  108. return true;
  109. }
  110. /**
  111. * Rollback all of the executed migrations.
  112. *
  113. * @param array $arguments
  114. * @return void
  115. */
  116. public function reset($arguments = array())
  117. {
  118. while ($this->rollback()) {};
  119. }
  120. /**
  121. * Reset the database to pristine state and run all migrations
  122. *
  123. * @param array $arguments
  124. * @return void
  125. */
  126. public function rebuild()
  127. {
  128. // Clean the database
  129. $this->reset();
  130. echo PHP_EOL;
  131. // Re-run all migrations
  132. $this->migrate();
  133. echo 'The database was successfully rebuilt'.PHP_EOL;
  134. }
  135. /**
  136. * Install the database tables used by the migration system.
  137. *
  138. * @return void
  139. */
  140. public function install()
  141. {
  142. Schema::table('laravel_migrations', function($table)
  143. {
  144. $table->create();
  145. // Migrations can be run for a specific bundle, so we'll use
  146. // the bundle name and string migration name as a unique ID
  147. // for the migrations, allowing us to easily identify which
  148. // migrations have been run for each bundle.
  149. $table->string('bundle', 50);
  150. $table->string('name', 200);
  151. // When running a migration command, we will store a batch
  152. // ID with each of the rows on the table. This will allow
  153. // us to grab all of the migrations that were run for the
  154. // last command when performing rollbacks.
  155. $table->integer('batch');
  156. $table->primary(array('bundle', 'name'));
  157. });
  158. echo "Migration table created successfully.";
  159. }
  160. /**
  161. * Generate a new migration file.
  162. *
  163. * @param array $arguments
  164. * @return string
  165. */
  166. public function make($arguments = array())
  167. {
  168. if (count($arguments) == 0)
  169. {
  170. throw new \Exception("I need to know what to name the migration.");
  171. }
  172. list($bundle, $migration) = Bundle::parse($arguments[0]);
  173. // The migration path is prefixed with the date timestamp, which
  174. // is a better way of ordering migrations than a simple integer
  175. // incrementation, since developers may start working on the
  176. // next migration at the same time unknowingly.
  177. $prefix = date('Y_m_d_His');
  178. $path = Bundle::path($bundle).'migrations'.DS;
  179. // If the migration directory does not exist for the bundle,
  180. // we will create the directory so there aren't errors when
  181. // when we try to write the migration file.
  182. if ( ! is_dir($path)) mkdir($path);
  183. $file = $path.$prefix.'_'.$migration.EXT;
  184. File::put($file, $this->stub($bundle, $migration));
  185. echo "Great! New migration created!";
  186. // Once the migration has been created, we'll return the
  187. // migration file name so it can be used by the task
  188. // consumer if necessary for further work.
  189. return $file;
  190. }
  191. /**
  192. * Get the stub migration with the proper class name.
  193. *
  194. * @param string $bundle
  195. * @param string $migration
  196. * @return string
  197. */
  198. protected function stub($bundle, $migration)
  199. {
  200. $stub = File::get(path('sys').'cli/tasks/migrate/stub'.EXT);
  201. $prefix = Bundle::class_prefix($bundle);
  202. // The class name is formatted similarly to tasks and controllers,
  203. // where the bundle name is prefixed to the class if it is not in
  204. // the default "application" bundle.
  205. $class = $prefix.Str::classify($migration);
  206. return str_replace('{{class}}', $class, $stub);
  207. }
  208. /**
  209. * Get the migration bundle and name for display.
  210. *
  211. * @param array $migration
  212. * @return string
  213. */
  214. protected function display($migration)
  215. {
  216. return $migration['bundle'].'/'.$migration['name'];
  217. }
  218. }