Skip to content

Commit 4e43ca0

Browse files
[10.x] Improvements for artisan migrate --pretend command 🚀 (#48768)
* Allow to exclude DB::select*() statements to be excluded from 'pretend' mode * Ensured that we are null safe * Reconsidered naming * Nah, let's keep things simple and people self-responsible * Cleanup * Naming * Changed return type to mixed * Added tests * Cleanup * Added bindings to output and improved tests * Style fixes * formatting --------- Co-authored-by: Taylor Otwell <[email protected]>
1 parent a02ef13 commit 4e43ca0

File tree

7 files changed

+281
-0
lines changed

7 files changed

+281
-0
lines changed

‎src/Illuminate/Database/Connection.php

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -655,6 +655,27 @@ public function pretend(Closure $callback)
655655
});
656656
}
657657

658+
/**
659+
* Execute the given callback without "pretending".
660+
*
661+
* @param \Closure $callback
662+
* @return mixed
663+
*/
664+
public function withoutPretending(Closure $callback)
665+
{
666+
if (! $this->pretending) {
667+
return $callback();
668+
}
669+
670+
$this->pretending = false;
671+
672+
$result = $callback();
673+
674+
$this->pretending = true;
675+
676+
return $result;
677+
}
678+
658679
/**
659680
* Execute the given callback in "dry run" mode.
660681
*
@@ -829,6 +850,10 @@ public function logQuery($query, $bindings, $time = null)
829850

830851
$this->event(new QueryExecuted($query, $bindings, $time, $this));
831852

853+
$query = $this->pretending === true
854+
? $this->queryGrammar?->substituteBindingsIntoRawSql($query, $bindings) ?? $query
855+
: $query;
856+
832857
if ($this->loggingQueries) {
833858
$this->queryLog[] = compact('query', 'bindings', 'time');
834859
}

‎src/Illuminate/Support/Facades/DB.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
* @method static int affectingStatement(string $query, array $bindings = [])
4343
* @method static bool unprepared(string $query)
4444
* @method static array pretend(\Closure $callback)
45+
* @method static mixed withoutPretending(\Closure $callback)
4546
* @method static void bindValues(\PDOStatement $statement, array $bindings)
4647
* @method static array prepareBindings(array $bindings)
4748
* @method static void logQuery(string $query, array $bindings, float|null $time = null)

‎tests/Integration/Migration/MigratorTest.php

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22

33
namespace Illuminate\Tests\Integration\Migration;
44

5+
use Illuminate\Database\Schema\Blueprint;
56
use Illuminate\Support\Facades\DB;
7+
use Illuminate\Support\Facades\Schema;
68
use Mockery as m;
79
use Orchestra\Testbench\TestCase;
810
use Symfony\Component\Console\Output\OutputInterface;
@@ -98,6 +100,137 @@ public function testPretendMigrate()
98100
$this->assertFalse(DB::getSchemaBuilder()->hasTable('people'));
99101
}
100102

103+
public function testIgnorePretendModeForCallbackData()
104+
{
105+
// Create two tables with different columns so that we can query it later
106+
// with the new method DB::withoutPretending().
107+
108+
Schema::create('table_1', function (Blueprint $table) {
109+
$table->increments('id');
110+
$table->string('column_1');
111+
});
112+
113+
Schema::create('table_2', function (Blueprint $table) {
114+
$table->increments('id');
115+
$table->string('column_2')->default('default_value');
116+
});
117+
118+
// From here on we simulate to be in pretend mode. This normally is done by
119+
// running the migration with the option --pretend.
120+
121+
DB::pretend(function () {
122+
// Returns an empty array because we are in pretend mode.
123+
$tablesEmpty = DB::select("SELECT name FROM sqlite_master WHERE type='table'");
124+
125+
$this->assertTrue([] === $tablesEmpty);
126+
127+
// Returns an array with two tables because we ignore pretend mode.
128+
$tablesList = DB::withoutPretending(function (): array {
129+
return DB::select("SELECT name FROM sqlite_master WHERE type='table'");
130+
});
131+
132+
$this->assertTrue([] !== $tablesList);
133+
134+
// The following would not be possible in pretend mode, if the
135+
// method DB::withoutPretending() would not exists,
136+
// because nothing is executed in pretend mode.
137+
foreach ($tablesList as $table) {
138+
if (in_array($table->name, ['sqlite_sequence', 'migrations'])) {
139+
continue;
140+
}
141+
142+
$columnsEmpty = DB::select("PRAGMA table_info($table->name)");
143+
144+
$this->assertTrue([] === $columnsEmpty);
145+
146+
$columnsList = DB::withoutPretending(function () use ($table): array {
147+
return DB::select("PRAGMA table_info($table->name)");
148+
});
149+
150+
$this->assertTrue([] !== $columnsList);
151+
$this->assertCount(2, $columnsList);
152+
153+
// Confirm that we are still in pretend mode. This column should
154+
// not be added. We query the table columns again to ensure the
155+
// count is still two.
156+
DB::statement("ALTER TABLE $table->name ADD COLUMN column_3 varchar(255) DEFAULT 'default_value' NOT NULL");
157+
158+
$columnsList = DB::withoutPretending(function () use ($table): array {
159+
return DB::select("PRAGMA table_info($table->name)");
160+
});
161+
162+
$this->assertCount(2, $columnsList);
163+
}
164+
});
165+
166+
Schema::dropIfExists('table_1');
167+
Schema::dropIfExists('table_2');
168+
}
169+
170+
public function testIgnorePretendModeForCallbackOutputDynamicContentIsShown()
171+
{
172+
// Persist data to table we can work with.
173+
$this->expectInfo('Running migrations.');
174+
$this->expectTask('2014_10_12_000000_create_people_is_dynamic_table', 'DONE');
175+
176+
$this->output->shouldReceive('writeln')->once();
177+
178+
$this->subject->run([__DIR__.'/pretending/2014_10_12_000000_create_people_is_dynamic_table.php'], ['pretend' => false]);
179+
180+
$this->assertTrue(DB::getSchemaBuilder()->hasTable('people'));
181+
182+
// Test the actual functionality.
183+
$this->expectInfo('Running migrations.');
184+
$this->expectTwoColumnDetail('DynamicContentIsShown');
185+
$this->expectBulletList([
186+
'create table "blogs" ("id" integer primary key autoincrement not null, "url" varchar, "name" varchar)',
187+
'insert into "blogs" ("url") values (\'www.janedoe.com\'), (\'www.johndoe.com\')',
188+
'ALTER TABLE \'pseudo_table_name\' MODIFY \'column_name\' VARCHAR(191)',
189+
'select * from "people"',
190+
'insert into "blogs" ("id", "name") values (1, \'Jane Doe Blog\')',
191+
'insert into "blogs" ("id", "name") values (2, \'John Doe Blog\')',
192+
]);
193+
194+
$this->output->shouldReceive('writeln')->once();
195+
196+
$this->subject->run([__DIR__.'/pretending/2023_10_17_000000_dynamic_content_is_shown.php'], ['pretend' => true]);
197+
198+
$this->assertFalse(DB::getSchemaBuilder()->hasTable('blogs'));
199+
200+
Schema::dropIfExists('people');
201+
}
202+
203+
public function testIgnorePretendModeForCallbackOutputDynamicContentNotShown()
204+
{
205+
// Persist data to table we can work with.
206+
$this->expectInfo('Running migrations.');
207+
$this->expectTask('2014_10_12_000000_create_people_non_dynamic_table', 'DONE');
208+
209+
$this->output->shouldReceive('writeln')->once();
210+
211+
$this->subject->run([__DIR__.'/pretending/2014_10_12_000000_create_people_non_dynamic_table.php'], ['pretend' => false]);
212+
213+
$this->assertTrue(DB::getSchemaBuilder()->hasTable('people'));
214+
215+
// Test the actual functionality.
216+
$this->expectInfo('Running migrations.');
217+
$this->expectTwoColumnDetail('DynamicContentNotShown');
218+
$this->expectBulletList([
219+
'create table "blogs" ("id" integer primary key autoincrement not null, "url" varchar, "name" varchar)',
220+
'insert into "blogs" ("url") values (\'www.janedoe.com\'), (\'www.johndoe.com\')',
221+
'ALTER TABLE \'pseudo_table_name\' MODIFY \'column_name\' VARCHAR(191)',
222+
'select * from "people"',
223+
]);
224+
225+
$this->output->shouldReceive('writeln')->once();
226+
227+
$this->subject->run([__DIR__.'/pretending/2023_10_17_000000_dynamic_content_not_shown.php'], ['pretend' => true]);
228+
229+
$this->assertFalse(DB::getSchemaBuilder()->hasTable('blogs'));
230+
231+
Schema::dropIfExists('people');
232+
}
233+
101234
protected function expectInfo($message): void
102235
{
103236
$this->output->shouldReceive('writeln')->once()->with(m::on(
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
3+
use Illuminate\Database\Migrations\Migration;
4+
use Illuminate\Database\Schema\Blueprint;
5+
use Illuminate\Support\Facades\DB;
6+
use Illuminate\Support\Facades\Schema;
7+
8+
class CreatePeopleIsDynamicTable extends Migration
9+
{
10+
public function up()
11+
{
12+
Schema::create('people', function (Blueprint $table) {
13+
$table->increments('id');
14+
$table->string('blog_id')->nullable();
15+
$table->string('name');
16+
$table->string('email')->unique();
17+
$table->string('password');
18+
$table->rememberToken();
19+
$table->timestamps();
20+
});
21+
22+
DB::table('people')->insert([
23+
['email' => '[email protected]', 'name' => 'Jane Doe', 'password' => 'secret'],
24+
['email' => '[email protected]', 'name' => 'John Doe', 'password' => 'secret'],
25+
]);
26+
}
27+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php
2+
3+
use Illuminate\Database\Migrations\Migration;
4+
use Illuminate\Database\Schema\Blueprint;
5+
use Illuminate\Support\Facades\DB;
6+
use Illuminate\Support\Facades\Schema;
7+
8+
class CreatePeopleNonDynamicTable extends Migration
9+
{
10+
public function up()
11+
{
12+
Schema::create('people', function (Blueprint $table) {
13+
$table->increments('id');
14+
$table->string('name');
15+
$table->string('email')->unique();
16+
$table->string('password');
17+
$table->rememberToken();
18+
$table->timestamps();
19+
});
20+
21+
DB::table('people')->insert([
22+
['email' => '[email protected]', 'name' => 'Jane Doe', 'password' => 'secret'],
23+
['email' => '[email protected]', 'name' => 'John Doe', 'password' => 'secret'],
24+
]);
25+
}
26+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?php
2+
3+
use Illuminate\Database\Migrations\Migration;
4+
use Illuminate\Database\Schema\Blueprint;
5+
use Illuminate\Support\Facades\DB;
6+
use Illuminate\Support\Facades\Schema;
7+
8+
class DynamicContentIsShown extends Migration
9+
{
10+
public function up()
11+
{
12+
Schema::create('blogs', function (Blueprint $table) {
13+
$table->increments('id');
14+
$table->string('url')->nullable();
15+
$table->string('name')->nullable();
16+
});
17+
18+
DB::table('blogs')->insert([
19+
['url' => 'www.janedoe.com'],
20+
['url' => 'www.johndoe.com'],
21+
]);
22+
23+
DB::statement("ALTER TABLE 'pseudo_table_name' MODIFY 'column_name' VARCHAR(191)");
24+
25+
/** @var \Illuminate\Support\Collection $tablesList */
26+
$tablesList = DB::withoutPretending(function () {
27+
return DB::table('people')->get();
28+
});
29+
30+
$tablesList->each(function ($person, $key) {
31+
DB::table('blogs')->where('blog_id', '=', $person->blog_id)->insert([
32+
'id' => $key + 1,
33+
'name' => "{$person->name} Blog",
34+
]);
35+
});
36+
}
37+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
3+
use Illuminate\Database\Migrations\Migration;
4+
use Illuminate\Database\Schema\Blueprint;
5+
use Illuminate\Support\Facades\DB;
6+
use Illuminate\Support\Facades\Schema;
7+
8+
class DynamicContentNotShown extends Migration
9+
{
10+
public function up()
11+
{
12+
Schema::create('blogs', function (Blueprint $table) {
13+
$table->increments('id');
14+
$table->string('url')->nullable();
15+
$table->string('name')->nullable();
16+
});
17+
18+
DB::table('blogs')->insert([
19+
['url' => 'www.janedoe.com'],
20+
['url' => 'www.johndoe.com'],
21+
]);
22+
23+
DB::statement("ALTER TABLE 'pseudo_table_name' MODIFY 'column_name' VARCHAR(191)");
24+
25+
DB::table('people')->get()->each(function ($person, $key) {
26+
DB::table('blogs')->where('blog_id', '=', $person->blog_id)->insert([
27+
'id' => $key + 1,
28+
'name' => "{$person->name} Blog",
29+
]);
30+
});
31+
}
32+
}

0 commit comments

Comments
 (0)