Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[10.x] Improvements for artisan migrate --pretend command πŸš€ #48768

Merged
merged 12 commits into from
Oct 26, 2023
25 changes: 25 additions & 0 deletions src/Illuminate/Database/Connection.php
Original file line number Diff line number Diff line change
Expand Up @@ -655,6 +655,27 @@ public function pretend(Closure $callback)
});
}

/**
* Execute the given callback without "pretending".
*
* @param \Closure $callback
* @return mixed
*/
public function withoutPretending(Closure $callback)
{
if (! $this->pretending) {
return $callback();
}

$this->pretending = false;

$result = $callback();

$this->pretending = true;

return $result;
}

/**
* Execute the given callback in "dry run" mode.
*
Expand Down Expand Up @@ -829,6 +850,10 @@ public function logQuery($query, $bindings, $time = null)

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

$query = $this->pretending === true
? $this->queryGrammar?->substituteBindingsIntoRawSql($query, $bindings) ?? $query
: $query;

if ($this->loggingQueries) {
$this->queryLog[] = compact('query', 'bindings', 'time');
}
Expand Down
1 change: 1 addition & 0 deletions src/Illuminate/Support/Facades/DB.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
* @method static int affectingStatement(string $query, array $bindings = [])
* @method static bool unprepared(string $query)
* @method static array pretend(\Closure $callback)
* @method static mixed withoutPretending(\Closure $callback)
* @method static void bindValues(\PDOStatement $statement, array $bindings)
* @method static array prepareBindings(array $bindings)
* @method static void logQuery(string $query, array $bindings, float|null $time = null)
Expand Down
133 changes: 133 additions & 0 deletions tests/Integration/Migration/MigratorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

namespace Illuminate\Tests\Integration\Migration;

use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
use Mockery as m;
use Orchestra\Testbench\TestCase;
use Symfony\Component\Console\Output\OutputInterface;
Expand Down Expand Up @@ -98,6 +100,137 @@ public function testPretendMigrate()
$this->assertFalse(DB::getSchemaBuilder()->hasTable('people'));
}

public function testIgnorePretendModeForCallbackData()
{
// Create two tables with different columns so that we can query it later
// with the new method DB::withoutPretending().

Schema::create('table_1', function (Blueprint $table) {
$table->increments('id');
$table->string('column_1');
});

Schema::create('table_2', function (Blueprint $table) {
$table->increments('id');
$table->string('column_2')->default('default_value');
});

// From here on we simulate to be in pretend mode. This normally is done by
// running the migration with the option --pretend.

DB::pretend(function () {
// Returns an empty array because we are in pretend mode.
$tablesEmpty = DB::select("SELECT name FROM sqlite_master WHERE type='table'");

$this->assertTrue([] === $tablesEmpty);

// Returns an array with two tables because we ignore pretend mode.
$tablesList = DB::withoutPretending(function (): array {
return DB::select("SELECT name FROM sqlite_master WHERE type='table'");
});

$this->assertTrue([] !== $tablesList);

// The following would not be possible in pretend mode, if the
// method DB::withoutPretending() would not exists,
// because nothing is executed in pretend mode.
foreach ($tablesList as $table) {
if (in_array($table->name, ['sqlite_sequence', 'migrations'])) {
continue;
}

$columnsEmpty = DB::select("PRAGMA table_info($table->name)");

$this->assertTrue([] === $columnsEmpty);

$columnsList = DB::withoutPretending(function () use ($table): array {
return DB::select("PRAGMA table_info($table->name)");
});

$this->assertTrue([] !== $columnsList);
$this->assertCount(2, $columnsList);

// Confirm that we are still in pretend mode. This column should
// not be added. We query the table columns again to ensure the
// count is still two.
DB::statement("ALTER TABLE $table->name ADD COLUMN column_3 varchar(255) DEFAULT 'default_value' NOT NULL");

$columnsList = DB::withoutPretending(function () use ($table): array {
return DB::select("PRAGMA table_info($table->name)");
});

$this->assertCount(2, $columnsList);
}
});

Schema::dropIfExists('table_1');
Schema::dropIfExists('table_2');
}

public function testIgnorePretendModeForCallbackOutputDynamicContentIsShown()
{
// Persist data to table we can work with.
$this->expectInfo('Running migrations.');
$this->expectTask('2014_10_12_000000_create_people_is_dynamic_table', 'DONE');

$this->output->shouldReceive('writeln')->once();

$this->subject->run([__DIR__.'/pretending/2014_10_12_000000_create_people_is_dynamic_table.php'], ['pretend' => false]);

$this->assertTrue(DB::getSchemaBuilder()->hasTable('people'));

// Test the actual functionality.
$this->expectInfo('Running migrations.');
$this->expectTwoColumnDetail('DynamicContentIsShown');
$this->expectBulletList([
'create table "blogs" ("id" integer primary key autoincrement not null, "url" varchar, "name" varchar)',
'insert into "blogs" ("url") values (\'www.janedoe.com\'), (\'www.johndoe.com\')',
'ALTER TABLE \'pseudo_table_name\' MODIFY \'column_name\' VARCHAR(191)',
'select * from "people"',
'insert into "blogs" ("id", "name") values (1, \'Jane Doe Blog\')',
'insert into "blogs" ("id", "name") values (2, \'John Doe Blog\')',
]);

$this->output->shouldReceive('writeln')->once();

$this->subject->run([__DIR__.'/pretending/2023_10_17_000000_dynamic_content_is_shown.php'], ['pretend' => true]);

$this->assertFalse(DB::getSchemaBuilder()->hasTable('blogs'));

Schema::dropIfExists('people');
}

public function testIgnorePretendModeForCallbackOutputDynamicContentNotShown()
{
// Persist data to table we can work with.
$this->expectInfo('Running migrations.');
$this->expectTask('2014_10_12_000000_create_people_non_dynamic_table', 'DONE');

$this->output->shouldReceive('writeln')->once();

$this->subject->run([__DIR__.'/pretending/2014_10_12_000000_create_people_non_dynamic_table.php'], ['pretend' => false]);

$this->assertTrue(DB::getSchemaBuilder()->hasTable('people'));

// Test the actual functionality.
$this->expectInfo('Running migrations.');
$this->expectTwoColumnDetail('DynamicContentNotShown');
$this->expectBulletList([
'create table "blogs" ("id" integer primary key autoincrement not null, "url" varchar, "name" varchar)',
'insert into "blogs" ("url") values (\'www.janedoe.com\'), (\'www.johndoe.com\')',
'ALTER TABLE \'pseudo_table_name\' MODIFY \'column_name\' VARCHAR(191)',
'select * from "people"',
]);

$this->output->shouldReceive('writeln')->once();

$this->subject->run([__DIR__.'/pretending/2023_10_17_000000_dynamic_content_not_shown.php'], ['pretend' => true]);

$this->assertFalse(DB::getSchemaBuilder()->hasTable('blogs'));

Schema::dropIfExists('people');
}

protected function expectInfo($message): void
{
$this->output->shouldReceive('writeln')->once()->with(m::on(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;

class CreatePeopleIsDynamicTable extends Migration
{
public function up()
{
Schema::create('people', function (Blueprint $table) {
$table->increments('id');
$table->string('blog_id')->nullable();
$table->string('name');
$table->string('email')->unique();
$table->string('password');
$table->rememberToken();
$table->timestamps();
});

DB::table('people')->insert([
['email' => 'jane@example.com', 'name' => 'Jane Doe', 'password' => 'secret'],
['email' => 'john@example.com', 'name' => 'John Doe', 'password' => 'secret'],
]);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;

class CreatePeopleNonDynamicTable extends Migration
{
public function up()
{
Schema::create('people', function (Blueprint $table) {
$table->increments('id');
$table->string('name');
$table->string('email')->unique();
$table->string('password');
$table->rememberToken();
$table->timestamps();
});

DB::table('people')->insert([
['email' => 'jane@example.com', 'name' => 'Jane Doe', 'password' => 'secret'],
['email' => 'john@example.com', 'name' => 'John Doe', 'password' => 'secret'],
]);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;

class DynamicContentIsShown extends Migration
{
public function up()
{
Schema::create('blogs', function (Blueprint $table) {
$table->increments('id');
$table->string('url')->nullable();
$table->string('name')->nullable();
});

DB::table('blogs')->insert([
['url' => 'www.janedoe.com'],
['url' => 'www.johndoe.com'],
]);

DB::statement("ALTER TABLE 'pseudo_table_name' MODIFY 'column_name' VARCHAR(191)");

/** @var \Illuminate\Support\Collection $tablesList */
$tablesList = DB::withoutPretending(function () {
return DB::table('people')->get();
});

$tablesList->each(function ($person, $key) {
DB::table('blogs')->where('blog_id', '=', $person->blog_id)->insert([
'id' => $key + 1,
'name' => "{$person->name} Blog",
]);
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;

class DynamicContentNotShown extends Migration
{
public function up()
{
Schema::create('blogs', function (Blueprint $table) {
$table->increments('id');
$table->string('url')->nullable();
$table->string('name')->nullable();
});

DB::table('blogs')->insert([
['url' => 'www.janedoe.com'],
['url' => 'www.johndoe.com'],
]);

DB::statement("ALTER TABLE 'pseudo_table_name' MODIFY 'column_name' VARCHAR(191)");

DB::table('people')->get()->each(function ($person, $key) {
DB::table('blogs')->where('blog_id', '=', $person->blog_id)->insert([
'id' => $key + 1,
'name' => "{$person->name} Blog",
]);
});
}
}