migrator.php 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246
  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
  69. // by one. This allows us to group the migrations such
  70. // that we can easily determine which migrations need
  71. // to roll back for the command.
  72. $batch = $this->database->batch() + 1;
  73. foreach ($migrations as $migration)
  74. {
  75. $migration['migration']->up();
  76. echo 'Migrated: '.$this->display($migration).PHP_EOL;
  77. // After running a migration, we log its execution in the
  78. // migration table so that we can easily determine which
  79. // migrations we'll reverse on a rollback.
  80. $this->database->log($migration['bundle'], $migration['name'], $batch);
  81. }
  82. }
  83. /**
  84. * Rollback the latest migration command.
  85. *
  86. * @param array $arguments
  87. * @return bool
  88. */
  89. public function rollback($arguments = array())
  90. {
  91. $migrations = $this->resolver->last();
  92. if (count($migrations) == 0)
  93. {
  94. echo "Nothing to rollback.";
  95. return false;
  96. }
  97. // The "last" method on the resolver returns an array of migrations,
  98. // along with their bundles and names. We will iterate through each
  99. // migration and run the "down" method, removing them from the
  100. // database as we go.
  101. foreach ($migrations as $migration)
  102. {
  103. $migration['migration']->down();
  104. echo 'Rolled back: '.$this->display($migration).PHP_EOL;
  105. // By only removing the migration after it has successfully rolled back,
  106. // we can re-run the rollback command in the event of any errors with
  107. // the migration. When we re-run, only the migrations that have not
  108. // been rolled back will still be in the database.
  109. $this->database->delete($migration['bundle'], $migration['name']);
  110. }
  111. return true;
  112. }
  113. /**
  114. * Rollback all of the executed migrations.
  115. *
  116. * @param array $arguments
  117. * @return void
  118. */
  119. public function reset($arguments = array())
  120. {
  121. while ($this->rollback()) {};
  122. }
  123. /**
  124. * Install the database tables used by the migration system.
  125. *
  126. * @return void
  127. */
  128. public function install()
  129. {
  130. Schema::table('laravel_migrations', function($table)
  131. {
  132. $table->create();
  133. // Migrations can be run for a specific bundle, so we'll use
  134. // the bundle name and string migration name as an unique ID
  135. // for the migrations, allowing us to easily identify which
  136. // migrations have been run for each bundle.
  137. $table->string('bundle');
  138. $table->string('name');
  139. // When running a migration command, we will store a batch
  140. // ID with each of the rows on the table. This will allow
  141. // us to grab all of the migrations that were run for the
  142. // last command when performing rollbacks.
  143. $table->integer('batch');
  144. $table->primary(array('bundle', 'name'));
  145. });
  146. echo "Migration table created successfully.";
  147. }
  148. /**
  149. * Generate a new migration file.
  150. *
  151. * @param array $arguments
  152. * @return string
  153. */
  154. public function make($arguments = array())
  155. {
  156. if (count($arguments) == 0)
  157. {
  158. throw new \Exception("I need to know what to name the migration.");
  159. }
  160. list($bundle, $migration) = Bundle::parse($arguments[0]);
  161. // The migration path is prefixed with the date timestamp, which
  162. // is a better way of ordering migrations than a simple integer
  163. // incrementation, since developers may start working on the
  164. // next migration at the same time unknowingly.
  165. $prefix = date('Y_m_d');
  166. $path = Bundle::path($bundle).'migrations'.DS;
  167. // If the migration directory does not exist for the bundle,
  168. // we will create the directory so there aren't errors when
  169. // when we try to write the migration file.
  170. if ( ! is_dir($path)) mkdir($path);
  171. $file = $path.$prefix.'_'.$migration.EXT;
  172. File::put($file, $this->stub($bundle, $migration));
  173. echo "Great! New migration created!";
  174. // Once the migration has been created, we'll return the
  175. // migration file name so it can be used by the task
  176. // consumer if necessary.
  177. return $file;
  178. }
  179. /**
  180. * Get the stub migration with the proper class name.
  181. *
  182. * @param string $bundle
  183. * @param string $migration
  184. * @return string
  185. */
  186. protected function stub($bundle, $migration)
  187. {
  188. $stub = File::get($GLOBALS['SYS_PATH'].'cli/tasks/migrate/stub'.EXT);
  189. // The class name is formatted simialrly to tasks and controllers,
  190. // where the bundle name is prefixed to the class if it is not in
  191. // the default bundle.
  192. $class = Bundle::class_prefix($bundle).Str::classify($migration);
  193. return str_replace('{{class}}', $class, $stub);
  194. }
  195. /**
  196. * Get the migration bundle and name for display.
  197. *
  198. * @param array $migration
  199. * @return string
  200. */
  201. protected function display($migration)
  202. {
  203. return $migration['bundle'].'/'.$migration['name'];
  204. }
  205. }