Skip to content

Commit 393be5a

Browse files
committed
Add migrate:redo command (alias of migrate:refresh --step=1)
1 parent 37a74cd commit 393be5a

File tree

3 files changed

+160
-0
lines changed

3 files changed

+160
-0
lines changed
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?php
2+
3+
namespace Illuminate\Database\Console\Migrations;
4+
5+
class RedoCommand extends RefreshCommand
6+
{
7+
/**
8+
* The console command name & signature.
9+
*
10+
* The redo command rolls back the last migration batch and re-runs it.
11+
*
12+
* We expose the same options as Refresh, but default --step to 1.
13+
*
14+
* @see RefreshCommand
15+
*/
16+
protected $signature = 'migrate:redo
17+
{--database= : The database connection to use}
18+
{--force : Run without confirmation}
19+
{--path=* : Paths to migration files to be executed}
20+
{--realpath : Indicate any provided migration file paths are pre-resolved absolute paths}
21+
{--seed : Re-run the database seeds}
22+
{--seeder= : The class name of the root seeder}
23+
{--step=1 : Number of migrations to be rolled back and re-run (default 1)}
24+
{--pretend : Dump the SQL instead of running the command}';
25+
26+
protected $description = 'Rollback the last migration batch and immediately re-run it';
27+
28+
public function handle(): int
29+
{
30+
// Ensure the default is always 1 when no --step supplied
31+
if (! $this->input->getOption('step')) {
32+
$this->input->setOption('step', 1);
33+
}
34+
35+
return parent::handle(); // RefreshCommand already does the heavy lifting
36+
}
37+
}

src/Illuminate/Database/MigrationServiceProvider.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
use Illuminate\Database\Console\Migrations\InstallCommand;
99
use Illuminate\Database\Console\Migrations\MigrateCommand;
1010
use Illuminate\Database\Console\Migrations\MigrateMakeCommand;
11+
use Illuminate\Database\Console\Migrations\RedoCommand;
1112
use Illuminate\Database\Console\Migrations\RefreshCommand;
1213
use Illuminate\Database\Console\Migrations\ResetCommand;
1314
use Illuminate\Database\Console\Migrations\RollbackCommand;
@@ -28,6 +29,7 @@ class MigrationServiceProvider extends ServiceProvider implements DeferrableProv
2829
'Migrate' => MigrateCommand::class,
2930
'MigrateFresh' => FreshCommand::class,
3031
'MigrateInstall' => InstallCommand::class,
32+
'MigrateRedo' => RedoCommand::class,
3133
'MigrateRefresh' => RefreshCommand::class,
3234
'MigrateReset' => ResetCommand::class,
3335
'MigrateRollback' => RollbackCommand::class,
@@ -168,6 +170,16 @@ protected function registerMigrateMakeCommand()
168170
});
169171
}
170172

173+
/**
174+
* Register the command.
175+
*
176+
* @return void
177+
*/
178+
protected function registerMigrateRedoCommand()
179+
{
180+
$this->app->singleton(RedoCommand::class);
181+
}
182+
171183
/**
172184
* Register the command.
173185
*
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
<?php
2+
3+
namespace Illuminate\Tests\Database;
4+
5+
use Illuminate\Contracts\Events\Dispatcher;
6+
use Illuminate\Database\Console\Migrations\MigrateCommand;
7+
use Illuminate\Database\Console\Migrations\RedoCommand;
8+
use Illuminate\Database\Console\Migrations\RollbackCommand;
9+
use Illuminate\Database\Events\DatabaseRefreshed;
10+
use Mockery as m;
11+
use PHPUnit\Framework\TestCase;
12+
use Symfony\Component\Console\Application as ConsoleApplication;
13+
use Symfony\Component\Console\Input\ArrayInput;
14+
use Symfony\Component\Console\Output\NullOutput;
15+
16+
class DatabaseMigrationRedoCommandTest extends TestCase
17+
{
18+
protected function tearDown(): void
19+
{
20+
m::close();
21+
}
22+
23+
public function testRedoCommandRollsBackOneStepAndRunsMigrations()
24+
{
25+
$command = new RedoCommand;
26+
27+
$app = new ApplicationDatabaseRefreshStub(['path.database' => __DIR__]);
28+
$dispatcher = $app->instance(Dispatcher::class, $events = m::mock());
29+
$console = m::mock(ConsoleApplication::class)->makePartial();
30+
$console->__construct();
31+
$command->setLaravel($app);
32+
$command->setApplication($console);
33+
34+
$rollbackCommand = m::mock(RollbackCommand::class);
35+
$migrateCommand = m::mock(MigrateCommand::class);
36+
37+
$console->shouldReceive('find')->with('migrate:rollback')->andReturn($rollbackCommand);
38+
$console->shouldReceive('find')->with('migrate')->andReturn($migrateCommand);
39+
$dispatcher->shouldReceive('dispatch')->once()->with(m::type(DatabaseRefreshed::class));
40+
41+
$quote = DIRECTORY_SEPARATOR === '\\' ? '"' : "'";
42+
43+
// Expect: migrate:rollback --step=1 --force then migrate --force
44+
$rollbackCommand->shouldReceive('run')
45+
->with(new InputMatcher("--step=1 --force=1 {$quote}migrate:rollback{$quote}"), m::any());
46+
$migrateCommand->shouldReceive('run')
47+
->with(new InputMatcher('--force=1 migrate'), m::any());
48+
49+
$this->runCommand($command);
50+
}
51+
52+
public function testRedoCommandHonoursCustomStepOption()
53+
{
54+
$command = new RedoCommand;
55+
56+
$app = new ApplicationDatabaseRefreshStub(['path.database' => __DIR__]);
57+
$dispatcher = $app->instance(Dispatcher::class, $events = m::mock());
58+
$console = m::mock(ConsoleApplication::class)->makePartial();
59+
$console->__construct();
60+
$command->setLaravel($app);
61+
$command->setApplication($console);
62+
63+
$rollbackCommand = m::mock(RollbackCommand::class);
64+
$migrateCommand = m::mock(MigrateCommand::class);
65+
66+
$console->shouldReceive('find')->with('migrate:rollback')->andReturn($rollbackCommand);
67+
$console->shouldReceive('find')->with('migrate')->andReturn($migrateCommand);
68+
$dispatcher->shouldReceive('dispatch')->once()->with(m::type(DatabaseRefreshed::class));
69+
70+
$quote = DIRECTORY_SEPARATOR === '\\' ? '"' : "'";
71+
72+
// Expect passed step value (e.g. 3)
73+
$rollbackCommand->shouldReceive('run')
74+
->with(new InputMatcher("--step=3 --force=1 {$quote}migrate:rollback{$quote}"), m::any());
75+
$migrateCommand->shouldReceive('run')
76+
->with(new InputMatcher('--force=1 migrate'), m::any());
77+
78+
$this->runCommand($command, ['--step' => 3]);
79+
}
80+
81+
public function testRedoCommandExitsWhenProhibited()
82+
{
83+
$command = new RedoCommand;
84+
85+
$app = new ApplicationDatabaseRefreshStub(['path.database' => __DIR__]);
86+
$dispatcher = $app->instance(Dispatcher::class, $events = m::mock());
87+
$console = m::mock(ConsoleApplication::class)->makePartial();
88+
$console->__construct();
89+
$command->setLaravel($app);
90+
$command->setApplication($console);
91+
92+
// Re-use the global prohibition flag defined in RefreshCommand
93+
\Illuminate\Database\Console\Migrations\RefreshCommand::prohibit();
94+
95+
$code = $this->runCommand($command);
96+
97+
$this->assertSame(1, $code);
98+
99+
$console->shouldNotHaveBeenCalled();
100+
$dispatcher->shouldNotReceive('dispatch');
101+
}
102+
103+
// -----------------------------------------------------------------
104+
// Helpers
105+
// -----------------------------------------------------------------
106+
107+
protected function runCommand($command, $input = [])
108+
{
109+
return $command->run(new ArrayInput($input), new NullOutput);
110+
}
111+
}

0 commit comments

Comments
 (0)