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