Skip to content

Commit 075c69f

Browse files
[11.x] Prevent destructive commands from running (#51376)
* Prevent destructive commands from running * Test cleanup * formatting * add consolidated method * Update DB.php * pass through * rename param * Update Prohibitable.php --------- Co-authored-by: Taylor Otwell <[email protected]>
1 parent 12ba043 commit 075c69f

File tree

8 files changed

+120
-8
lines changed

8 files changed

+120
-8
lines changed
+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<?php
2+
3+
namespace Illuminate\Console;
4+
5+
trait Prohibitable
6+
{
7+
/**
8+
* Indicates if the command should be prohibited from running.
9+
*
10+
* @var bool
11+
*/
12+
protected static $prohibitedFromRunning = false;
13+
14+
/**
15+
* Indicate whether the command should be prohibited from running.
16+
*
17+
* @param bool $prohibit
18+
* @return bool
19+
*/
20+
public static function prohibit($prohibit = true)
21+
{
22+
static::$prohibitedFromRunning = $prohibit;
23+
}
24+
25+
/**
26+
* Determine if the command is prohibited from running and display a warning if so.
27+
*
28+
* @param bool $quiet
29+
* @return bool
30+
*/
31+
protected function isProhibited(bool $quiet = false)
32+
{
33+
if (! static::$prohibitedFromRunning) {
34+
return false;
35+
}
36+
37+
if (! $quiet) {
38+
$this->components->warn('This command is prohibited from running in this environment.');
39+
}
40+
41+
return true;
42+
}
43+
}

src/Illuminate/Database/Console/Migrations/FreshCommand.php

+4-2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use Illuminate\Console\Command;
66
use Illuminate\Console\ConfirmableTrait;
7+
use Illuminate\Console\Prohibitable;
78
use Illuminate\Contracts\Events\Dispatcher;
89
use Illuminate\Database\Events\DatabaseRefreshed;
910
use Illuminate\Database\Migrations\Migrator;
@@ -13,7 +14,7 @@
1314
#[AsCommand(name: 'migrate:fresh')]
1415
class FreshCommand extends Command
1516
{
16-
use ConfirmableTrait;
17+
use ConfirmableTrait, Prohibitable;
1718

1819
/**
1920
* The console command name.
@@ -56,7 +57,8 @@ public function __construct(Migrator $migrator)
5657
*/
5758
public function handle()
5859
{
59-
if (! $this->confirmToProceed()) {
60+
if ($this->isProhibited() ||
61+
! $this->confirmToProceed()) {
6062
return 1;
6163
}
6264

src/Illuminate/Database/Console/Migrations/RefreshCommand.php

+4-2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use Illuminate\Console\Command;
66
use Illuminate\Console\ConfirmableTrait;
7+
use Illuminate\Console\Prohibitable;
78
use Illuminate\Contracts\Events\Dispatcher;
89
use Illuminate\Database\Events\DatabaseRefreshed;
910
use Symfony\Component\Console\Attribute\AsCommand;
@@ -12,7 +13,7 @@
1213
#[AsCommand(name: 'migrate:refresh')]
1314
class RefreshCommand extends Command
1415
{
15-
use ConfirmableTrait;
16+
use ConfirmableTrait, Prohibitable;
1617

1718
/**
1819
* The console command name.
@@ -35,7 +36,8 @@ class RefreshCommand extends Command
3536
*/
3637
public function handle()
3738
{
38-
if (! $this->confirmToProceed()) {
39+
if ($this->isProhibited() ||
40+
! $this->confirmToProceed()) {
3941
return 1;
4042
}
4143

src/Illuminate/Database/Console/Migrations/ResetCommand.php

+4-2
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,15 @@
33
namespace Illuminate\Database\Console\Migrations;
44

55
use Illuminate\Console\ConfirmableTrait;
6+
use Illuminate\Console\Prohibitable;
67
use Illuminate\Database\Migrations\Migrator;
78
use Symfony\Component\Console\Attribute\AsCommand;
89
use Symfony\Component\Console\Input\InputOption;
910

1011
#[AsCommand(name: 'migrate:reset')]
1112
class ResetCommand extends BaseCommand
1213
{
13-
use ConfirmableTrait;
14+
use ConfirmableTrait, Prohibitable;
1415

1516
/**
1617
* The console command name.
@@ -53,7 +54,8 @@ public function __construct(Migrator $migrator)
5354
*/
5455
public function handle()
5556
{
56-
if (! $this->confirmToProceed()) {
57+
if ($this->isProhibited() ||
58+
! $this->confirmToProceed()) {
5759
return 1;
5860
}
5961

src/Illuminate/Database/Console/WipeCommand.php

+4-2
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,14 @@
44

55
use Illuminate\Console\Command;
66
use Illuminate\Console\ConfirmableTrait;
7+
use Illuminate\Console\Prohibitable;
78
use Symfony\Component\Console\Attribute\AsCommand;
89
use Symfony\Component\Console\Input\InputOption;
910

1011
#[AsCommand(name: 'db:wipe')]
1112
class WipeCommand extends Command
1213
{
13-
use ConfirmableTrait;
14+
use ConfirmableTrait, Prohibitable;
1415

1516
/**
1617
* The console command name.
@@ -33,7 +34,8 @@ class WipeCommand extends Command
3334
*/
3435
public function handle()
3536
{
36-
if (! $this->confirmToProceed()) {
37+
if ($this->isProhibited() ||
38+
! $this->confirmToProceed()) {
3739
return 1;
3840
}
3941

src/Illuminate/Support/Facades/DB.php

+21
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22

33
namespace Illuminate\Support\Facades;
44

5+
use Illuminate\Database\Console\Migrations\FreshCommand;
6+
use Illuminate\Database\Console\Migrations\RefreshCommand;
7+
use Illuminate\Database\Console\Migrations\ResetCommand;
8+
use Illuminate\Database\Console\WipeCommand;
9+
510
/**
611
* @method static \Illuminate\Database\Connection connection(string|null $name = null)
712
* @method static \Illuminate\Database\ConnectionInterface connectUsing(string $name, array $config, bool $force = false)
@@ -109,6 +114,22 @@
109114
*/
110115
class DB extends Facade
111116
{
117+
/**
118+
* Indicate if destructive Artisan commands should be prohibited.
119+
*
120+
* Prohibits: db:wipe, migrate:fresh, migrate:refresh, and migrate:reset
121+
*
122+
* @param bool $prohibit
123+
* @return void
124+
*/
125+
public static function prohibitDestructiveCommands(bool $prohibit = true)
126+
{
127+
FreshCommand::prohibit($prohibit);
128+
RefreshCommand::prohibit($prohibit);
129+
ResetCommand::prohibit($prohibit);
130+
WipeCommand::prohibit($prohibit);
131+
}
132+
112133
/**
113134
* Get the registered name of the component.
114135
*

tests/Database/DatabaseMigrationRefreshCommandTest.php

+22
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ class DatabaseMigrationRefreshCommandTest extends TestCase
1919
{
2020
protected function tearDown(): void
2121
{
22+
RefreshCommand::prohibit(false);
2223
m::close();
2324
}
2425

@@ -72,6 +73,27 @@ public function testRefreshCommandCallsCommandsWithStep()
7273
$this->runCommand($command, ['--step' => 2]);
7374
}
7475

76+
public function testRefreshCommandExitsWhenPrevented()
77+
{
78+
$command = new RefreshCommand;
79+
80+
$app = new ApplicationDatabaseRefreshStub(['path.database' => __DIR__]);
81+
$dispatcher = $app->instance(Dispatcher::class, $events = m::mock());
82+
$console = m::mock(ConsoleApplication::class)->makePartial();
83+
$console->__construct();
84+
$command->setLaravel($app);
85+
$command->setApplication($console);
86+
87+
RefreshCommand::prohibit();
88+
89+
$code = $this->runCommand($command);
90+
91+
$this->assertSame(1, $code);
92+
93+
$console->shouldNotHaveBeenCalled();
94+
$dispatcher->shouldNotReceive('dispatch');
95+
}
96+
7597
protected function runCommand($command, $input = [])
7698
{
7799
return $command->run(new ArrayInput($input), new NullOutput);

tests/Database/DatabaseMigrationResetCommandTest.php

+18
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ class DatabaseMigrationResetCommandTest extends TestCase
1515
{
1616
protected function tearDown(): void
1717
{
18+
ResetCommand::prohibit(false);
1819
m::close();
1920
}
2021

@@ -52,6 +53,23 @@ public function testResetCommandCanBePretended()
5253
$this->runCommand($command, ['--pretend' => true, '--database' => 'foo']);
5354
}
5455

56+
public function testRefreshCommandExitsWhenPrevented()
57+
{
58+
$command = new ResetCommand($migrator = m::mock(Migrator::class));
59+
60+
$app = new ApplicationDatabaseResetStub(['path.database' => __DIR__]);
61+
$app->useDatabasePath(__DIR__);
62+
$command->setLaravel($app);
63+
64+
ResetCommand::prohibit();
65+
66+
$code = $this->runCommand($command);
67+
68+
$this->assertSame(1, $code);
69+
70+
$migrator->shouldNotHaveBeenCalled();
71+
}
72+
5573
protected function runCommand($command, $input = [])
5674
{
5775
return $command->run(new ArrayInput($input), new NullOutput);

0 commit comments

Comments
 (0)