MigrateCommand.php 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315
  1. <?php
  2. namespace Illuminate\Database\Console\Migrations;
  3. use Illuminate\Console\ConfirmableTrait;
  4. use Illuminate\Contracts\Console\Isolatable;
  5. use Illuminate\Contracts\Events\Dispatcher;
  6. use Illuminate\Database\Events\SchemaLoaded;
  7. use Illuminate\Database\Migrations\Migrator;
  8. use Illuminate\Database\SQLiteDatabaseDoesNotExistException;
  9. use Illuminate\Database\SqlServerConnection;
  10. use PDOException;
  11. use RuntimeException;
  12. use Symfony\Component\Console\Attribute\AsCommand;
  13. use Throwable;
  14. use function Laravel\Prompts\confirm;
  15. #[AsCommand(name: 'migrate')]
  16. class MigrateCommand extends BaseCommand implements Isolatable
  17. {
  18. use ConfirmableTrait;
  19. /**
  20. * The name and signature of the console command.
  21. *
  22. * @var string
  23. */
  24. protected $signature = 'migrate {--database= : The database connection to use}
  25. {--force : Force the operation to run when in production}
  26. {--path=* : The path(s) to the migrations files to be executed}
  27. {--realpath : Indicate any provided migration file paths are pre-resolved absolute paths}
  28. {--schema-path= : The path to a schema dump file}
  29. {--pretend : Dump the SQL queries that would be run}
  30. {--seed : Indicates if the seed task should be re-run}
  31. {--seeder= : The class name of the root seeder}
  32. {--step : Force the migrations to be run so they can be rolled back individually}
  33. {--graceful : Return a successful exit code even if an error occurs}';
  34. /**
  35. * The console command description.
  36. *
  37. * @var string
  38. */
  39. protected $description = 'Run the database migrations';
  40. /**
  41. * The migrator instance.
  42. *
  43. * @var \Illuminate\Database\Migrations\Migrator
  44. */
  45. protected $migrator;
  46. /**
  47. * The event dispatcher instance.
  48. *
  49. * @var \Illuminate\Contracts\Events\Dispatcher
  50. */
  51. protected $dispatcher;
  52. /**
  53. * Create a new migration command instance.
  54. *
  55. * @param \Illuminate\Database\Migrations\Migrator $migrator
  56. * @param \Illuminate\Contracts\Events\Dispatcher $dispatcher
  57. * @return void
  58. */
  59. public function __construct(Migrator $migrator, Dispatcher $dispatcher)
  60. {
  61. parent::__construct();
  62. $this->migrator = $migrator;
  63. $this->dispatcher = $dispatcher;
  64. }
  65. /**
  66. * Execute the console command.
  67. *
  68. * @return int
  69. */
  70. public function handle()
  71. {
  72. if (! $this->confirmToProceed()) {
  73. return 1;
  74. }
  75. try {
  76. $this->runMigrations();
  77. } catch (Throwable $e) {
  78. if ($this->option('graceful')) {
  79. $this->components->warn($e->getMessage());
  80. return 0;
  81. }
  82. throw $e;
  83. }
  84. return 0;
  85. }
  86. /**
  87. * Run the pending migrations.
  88. *
  89. * @return void
  90. */
  91. protected function runMigrations()
  92. {
  93. $this->migrator->usingConnection($this->option('database'), function () {
  94. $this->prepareDatabase();
  95. // Next, we will check to see if a path option has been defined. If it has
  96. // we will use the path relative to the root of this installation folder
  97. // so that migrations may be run for any path within the applications.
  98. $this->migrator->setOutput($this->output)
  99. ->run($this->getMigrationPaths(), [
  100. 'pretend' => $this->option('pretend'),
  101. 'step' => $this->option('step'),
  102. ]);
  103. // Finally, if the "seed" option has been given, we will re-run the database
  104. // seed task to re-populate the database, which is convenient when adding
  105. // a migration and a seed at the same time, as it is only this command.
  106. if ($this->option('seed') && ! $this->option('pretend')) {
  107. $this->call('db:seed', [
  108. '--class' => $this->option('seeder') ?: 'Database\\Seeders\\DatabaseSeeder',
  109. '--force' => true,
  110. ]);
  111. }
  112. });
  113. }
  114. /**
  115. * Prepare the migration database for running.
  116. *
  117. * @return void
  118. */
  119. protected function prepareDatabase()
  120. {
  121. if (! $this->repositoryExists()) {
  122. $this->components->info('Preparing database.');
  123. $this->components->task('Creating migration table', function () {
  124. return $this->callSilent('migrate:install', array_filter([
  125. '--database' => $this->option('database'),
  126. ])) == 0;
  127. });
  128. $this->newLine();
  129. }
  130. if (! $this->migrator->hasRunAnyMigrations() && ! $this->option('pretend')) {
  131. $this->loadSchemaState();
  132. }
  133. }
  134. /**
  135. * Determine if the migrator repository exists.
  136. *
  137. * @return bool
  138. */
  139. protected function repositoryExists()
  140. {
  141. return retry(2, fn () => $this->migrator->repositoryExists(), 0, function ($e) {
  142. try {
  143. if ($e->getPrevious() instanceof SQLiteDatabaseDoesNotExistException) {
  144. return $this->createMissingSqliteDatabase($e->getPrevious()->path);
  145. }
  146. $connection = $this->migrator->resolveConnection($this->option('database'));
  147. if (
  148. $e->getPrevious() instanceof PDOException &&
  149. $e->getPrevious()->getCode() === 1049 &&
  150. in_array($connection->getDriverName(), ['mysql', 'mariadb'])) {
  151. return $this->createMissingMysqlDatabase($connection);
  152. }
  153. return false;
  154. } catch (Throwable) {
  155. return false;
  156. }
  157. });
  158. }
  159. /**
  160. * Create a missing SQLite database.
  161. *
  162. * @param string $path
  163. * @return bool
  164. *
  165. * @throws \RuntimeException
  166. */
  167. protected function createMissingSqliteDatabase($path)
  168. {
  169. if ($this->option('force')) {
  170. return touch($path);
  171. }
  172. if ($this->option('no-interaction')) {
  173. return false;
  174. }
  175. $this->components->warn('The SQLite database configured for this application does not exist: '.$path);
  176. if (! confirm('Would you like to create it?', default: true)) {
  177. $this->components->info('Operation cancelled. No database was created.');
  178. throw new RuntimeException('Database was not created. Aborting migration.');
  179. }
  180. return touch($path);
  181. }
  182. /**
  183. * Create a missing MySQL database.
  184. *
  185. * @return bool
  186. *
  187. * @throws \RuntimeException
  188. */
  189. protected function createMissingMysqlDatabase($connection)
  190. {
  191. if ($this->laravel['config']->get("database.connections.{$connection->getName()}.database") !== $connection->getDatabaseName()) {
  192. return false;
  193. }
  194. if (! $this->option('force') && $this->option('no-interaction')) {
  195. return false;
  196. }
  197. if (! $this->option('force') && ! $this->option('no-interaction')) {
  198. $this->components->warn("The database '{$connection->getDatabaseName()}' does not exist on the '{$connection->getName()}' connection.");
  199. if (! confirm('Would you like to create it?', default: true)) {
  200. $this->components->info('Operation cancelled. No database was created.');
  201. throw new RuntimeException('Database was not created. Aborting migration.');
  202. }
  203. }
  204. try {
  205. $this->laravel['config']->set("database.connections.{$connection->getName()}.database", null);
  206. $this->laravel['db']->purge();
  207. $freshConnection = $this->migrator->resolveConnection($this->option('database'));
  208. return tap($freshConnection->unprepared("CREATE DATABASE IF NOT EXISTS `{$connection->getDatabaseName()}`"), function () {
  209. $this->laravel['db']->purge();
  210. });
  211. } finally {
  212. $this->laravel['config']->set("database.connections.{$connection->getName()}.database", $connection->getDatabaseName());
  213. }
  214. }
  215. /**
  216. * Load the schema state to seed the initial database schema structure.
  217. *
  218. * @return void
  219. */
  220. protected function loadSchemaState()
  221. {
  222. $connection = $this->migrator->resolveConnection($this->option('database'));
  223. // First, we will make sure that the connection supports schema loading and that
  224. // the schema file exists before we proceed any further. If not, we will just
  225. // continue with the standard migration operation as normal without errors.
  226. if ($connection instanceof SqlServerConnection ||
  227. ! is_file($path = $this->schemaPath($connection))) {
  228. return;
  229. }
  230. $this->components->info('Loading stored database schemas.');
  231. $this->components->task($path, function () use ($connection, $path) {
  232. // Since the schema file will create the "migrations" table and reload it to its
  233. // proper state, we need to delete it here so we don't get an error that this
  234. // table already exists when the stored database schema file gets executed.
  235. $this->migrator->deleteRepository();
  236. $connection->getSchemaState()->handleOutputUsing(function ($type, $buffer) {
  237. $this->output->write($buffer);
  238. })->load($path);
  239. });
  240. $this->newLine();
  241. // Finally, we will fire an event that this schema has been loaded so developers
  242. // can perform any post schema load tasks that are necessary in listeners for
  243. // this event, which may seed the database tables with some necessary data.
  244. $this->dispatcher->dispatch(
  245. new SchemaLoaded($connection, $path)
  246. );
  247. }
  248. /**
  249. * Get the path to the stored schema for the given connection.
  250. *
  251. * @param \Illuminate\Database\Connection $connection
  252. * @return string
  253. */
  254. protected function schemaPath($connection)
  255. {
  256. if ($this->option('schema-path')) {
  257. return $this->option('schema-path');
  258. }
  259. if (file_exists($path = database_path('schema/'.$connection->getName().'-schema.dump'))) {
  260. return $path;
  261. }
  262. return database_path('schema/'.$connection->getName().'-schema.sql');
  263. }
  264. }