migrator.php 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278
  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 bundles supplied, filter migrations to rollback only bundles'
  92. // migrations.
  93. if (count($arguments) > 0)
  94. {
  95. $bundles = $arguments;
  96. if ( ! is_array($bundles)) $bundles = array($bundles);
  97. $migrations = array_filter($migrations, function($migration) use ($bundles)
  98. {
  99. return in_array($migration['bundle'], $bundles);
  100. });
  101. }
  102. if (count($migrations) == 0)
  103. {
  104. echo "Nothing to rollback.".PHP_EOL;
  105. return false;
  106. }
  107. // The "last" method on the resolver returns an array of migrations,
  108. // along with their bundles and names. We will iterate through each
  109. // migration and run the "down" method.
  110. foreach (array_reverse($migrations) as $migration)
  111. {
  112. $migration['migration']->down();
  113. echo 'Rolled back: '.$this->display($migration).PHP_EOL;
  114. // By only removing the migration after it has successfully rolled back,
  115. // we can re-run the rollback command in the event of any errors with
  116. // the migration and pick up where we left off.
  117. $this->database->delete($migration['bundle'], $migration['name']);
  118. }
  119. return true;
  120. }
  121. /**
  122. * Rollback all of the executed migrations.
  123. *
  124. * @param array $arguments
  125. * @return void
  126. */
  127. public function reset($arguments = array())
  128. {
  129. while ($this->rollback($arguments)) {};
  130. }
  131. /**
  132. * Reset the database to pristine state and run all migrations
  133. *
  134. * @param array $arguments
  135. * @return void
  136. */
  137. public function rebuild()
  138. {
  139. // Clean the database
  140. $this->reset();
  141. echo PHP_EOL;
  142. // Re-run all migrations
  143. $this->migrate();
  144. echo 'The database was successfully rebuilt'.PHP_EOL;
  145. }
  146. /**
  147. * Install the database tables used by the migration system.
  148. *
  149. * @return void
  150. */
  151. public function install()
  152. {
  153. Schema::table('laravel_migrations', function($table)
  154. {
  155. $table->create();
  156. // Migrations can be run for a specific bundle, so we'll use
  157. // the bundle name and string migration name as a unique ID
  158. // for the migrations, allowing us to easily identify which
  159. // migrations have been run for each bundle.
  160. $table->string('bundle', 50);
  161. $table->string('name', 200);
  162. // When running a migration command, we will store a batch
  163. // ID with each of the rows on the table. This will allow
  164. // us to grab all of the migrations that were run for the
  165. // last command when performing rollbacks.
  166. $table->integer('batch');
  167. $table->primary(array('bundle', 'name'));
  168. });
  169. echo "Migration table created successfully.";
  170. }
  171. /**
  172. * Generate a new migration file.
  173. *
  174. * @param array $arguments
  175. * @return string
  176. */
  177. public function make($arguments = array())
  178. {
  179. if (count($arguments) == 0)
  180. {
  181. throw new \Exception("I need to know what to name the migration.");
  182. }
  183. list($bundle, $migration) = Bundle::parse($arguments[0]);
  184. // The migration path is prefixed with the date timestamp, which
  185. // is a better way of ordering migrations than a simple integer
  186. // incrementation, since developers may start working on the
  187. // next migration at the same time unknowingly.
  188. $prefix = date('Y_m_d_His');
  189. $path = Bundle::path($bundle).'migrations'.DS;
  190. // If the migration directory does not exist for the bundle,
  191. // we will create the directory so there aren't errors when
  192. // when we try to write the migration file.
  193. if ( ! is_dir($path)) mkdir($path);
  194. $file = $path.$prefix.'_'.$migration.EXT;
  195. File::put($file, $this->stub($bundle, $migration));
  196. echo "Great! New migration created!";
  197. // Once the migration has been created, we'll return the
  198. // migration file name so it can be used by the task
  199. // consumer if necessary for further work.
  200. return $file;
  201. }
  202. /**
  203. * Get the stub migration with the proper class name.
  204. *
  205. * @param string $bundle
  206. * @param string $migration
  207. * @return string
  208. */
  209. protected function stub($bundle, $migration)
  210. {
  211. $stub = File::get(path('sys').'cli/tasks/migrate/stub'.EXT);
  212. $prefix = Bundle::class_prefix($bundle);
  213. // The class name is formatted similarly to tasks and controllers,
  214. // where the bundle name is prefixed to the class if it is not in
  215. // the default "application" bundle.
  216. $class = $prefix.Str::classify($migration);
  217. return str_replace('{{class}}', $class, $stub);
  218. }
  219. /**
  220. * Get the migration bundle and name for display.
  221. *
  222. * @param array $migration
  223. * @return string
  224. */
  225. protected function display($migration)
  226. {
  227. return $migration['bundle'].'/'.$migration['name'];
  228. }
  229. }