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

[8.x] Add anonymous migrations #36906

Merged
merged 3 commits into from Apr 13, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
45 changes: 38 additions & 7 deletions src/Illuminate/Database/Migrations/Migrator.php
Expand Up @@ -13,6 +13,7 @@
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;
use ReflectionClass;
use Symfony\Component\Console\Output\OutputInterface;

class Migrator
Expand Down Expand Up @@ -185,9 +186,8 @@ protected function runUp($file, $batch, $pretend)
// First we will resolve a "real" instance of the migration class from this
// migration file name. Once we have the instances we can run the actual
// command such as "up" or "down", or we can just simulate the action.
$migration = $this->resolve(
$name = $this->getMigrationName($file)
);
$migration = $this->resolvePath($file);
$name = $this->getMigrationName($file);

if ($pretend) {
return $this->pretendToRun($migration, 'up');
Expand Down Expand Up @@ -348,9 +348,8 @@ protected function runDown($file, $migration, $pretend)
// First we will get the file name of the migration so we can resolve out an
// instance of the migration. Once we get an instance we can either run a
// pretend execution of the migration or we can run the real migration.
$instance = $this->resolve(
$name = $this->getMigrationName($file)
);
$instance = $this->resolvePath($file);
$name = $this->getMigrationName($file);

$this->note("<comment>Rolling back:</comment> {$name}");

Expand Down Expand Up @@ -413,6 +412,11 @@ protected function pretendToRun($migration, $method)
foreach ($this->getQueries($migration, $method) as $query) {
$name = get_class($migration);

$reflectionClass = new ReflectionClass($migration);
if ($reflectionClass->isAnonymous()) {
$name = $this->getMigrationName($reflectionClass->getFileName());
}

$this->note("<info>{$name}:</info> {$query['query']}");
}
}
Expand Down Expand Up @@ -448,11 +452,38 @@ protected function getQueries($migration, $method)
*/
public function resolve($file)
{
$class = Str::studly(implode('_', array_slice(explode('_', $file), 4)));
$class = $this->getMigrationClass($file);

return new $class;
}

/**
* Resolve a migration instance from migration path.
*
* @param string $path
* @return object
*/
protected function resolvePath(string $path)
{
$class = $this->getMigrationClass($this->getMigrationName($path));
if (class_exists($class)) {
return new $class;
}

return $this->files->getRequire($path);
}

/**
* Generate migration class name based on migration name.
*
* @param string $migrationName
* @return string
*/
protected function getMigrationClass(string $migrationName): string
{
return Str::studly(implode('_', array_slice(explode('_', $migrationName), 4)));
}

/**
* Get all of the migration files in a given path.
*
Expand Down
74 changes: 61 additions & 13 deletions tests/Integration/Migration/MigratorTest.php
Expand Up @@ -2,11 +2,29 @@

namespace Illuminate\Tests\Integration\Migration;

use Illuminate\Support\Facades\DB;
use Mockery;
use Mockery\Mock;
use Orchestra\Testbench\TestCase;
use PDOException;
use Symfony\Component\Console\Output\OutputInterface;

class MigratorTest extends TestCase
{
/**
* @var Mock
*/
private $output;

protected function setUp(): void
{
parent::setUp();

$this->output = Mockery::mock(OutputInterface::class);
$this->subject = $this->app->make('migrator');
$this->subject->setOutput($this->output);
$this->subject->getRepository()->createRepository();
}

protected function getEnvironmentSetUp($app)
{
$app['config']->set('app.debug', 'true');
Expand All @@ -19,25 +37,55 @@ protected function getEnvironmentSetUp($app)
]);
}

public function testDontDisplayOutputWhenOutputObjectIsNotAvailable()
public function testMigrate()
{
$this->expectOutput('<comment>Migrating:</comment> 2014_10_12_000000_create_people_table');
$this->expectOutput(Mockery::pattern('#<info>Migrated:</info> 2014_10_12_000000_create_people_table (.*)#'));
$this->expectOutput('<comment>Migrating:</comment> 2015_10_04_000000_modify_people_table');
$this->expectOutput(Mockery::pattern('#<info>Migrated:</info> 2015_10_04_000000_modify_people_table (.*)#'));
$this->expectOutput('<comment>Migrating:</comment> 2016_10_04_000000_modify_people_table');
$this->expectOutput(Mockery::pattern('#<info>Migrated:</info> 2016_10_04_000000_modify_people_table (.*)#'));

$this->subject->run([__DIR__.'/fixtures']);

self::assertTrue(DB::getSchemaBuilder()->hasTable('people'));
self::assertTrue(DB::getSchemaBuilder()->hasColumn('people', 'first_name'));
self::assertTrue(DB::getSchemaBuilder()->hasColumn('people', 'last_name'));
}

public function testRollback()
{
$migrator = $this->app->make('migrator');
$this->getConnection()->statement('CREATE TABLE people(id INT, first_name VARCHAR, last_name VARCHAR);');
$this->subject->getRepository()->log('2014_10_12_000000_create_people_table', 1);
$this->subject->getRepository()->log('2015_10_04_000000_modify_people_table', 1);
$this->subject->getRepository()->log('2016_10_04_000000_modify_people_table', 1);

$migrator->getRepository()->createRepository();
$this->expectOutput('<comment>Rolling back:</comment> 2016_10_04_000000_modify_people_table');
$this->expectOutput(Mockery::pattern('#<info>Rolled back:</info> 2016_10_04_000000_modify_people_table (.*)#'));
$this->expectOutput('<comment>Rolling back:</comment> 2015_10_04_000000_modify_people_table');
$this->expectOutput(Mockery::pattern('#<info>Rolled back:</info> 2015_10_04_000000_modify_people_table (.*)#'));
$this->expectOutput('<comment>Rolling back:</comment> 2014_10_12_000000_create_people_table');
$this->expectOutput(Mockery::pattern('#<info>Rolled back:</info> 2014_10_12_000000_create_people_table (.*)#'));

$migrator->run([__DIR__.'/fixtures']);
$this->subject->rollback([__DIR__.'/fixtures']);

$this->assertTrue($this->tableExists('people'));
self::assertFalse(DB::getSchemaBuilder()->hasTable('people'));
}

private function tableExists($table): bool
public function testPretendMigrate()
{
try {
$this->app->make('db')->select("SELECT COUNT(*) FROM $table");
} catch (PDOException $e) {
return false;
}
$this->expectOutput('<info>CreatePeopleTable:</info> create table "people" ("id" integer not null primary key autoincrement, "name" varchar not null, "email" varchar not null, "password" varchar not null, "remember_token" varchar, "created_at" datetime, "updated_at" datetime)');
$this->expectOutput('<info>CreatePeopleTable:</info> create unique index "people_email_unique" on "people" ("email")');
$this->expectOutput('<info>2015_10_04_000000_modify_people_table:</info> alter table "people" add column "first_name" varchar');
$this->expectOutput('<info>2016_10_04_000000_modify_people_table:</info> alter table "people" add column "last_name" varchar');

$this->subject->run([__DIR__.'/fixtures'], ['pretend' => true]);

return true;
self::assertFalse(DB::getSchemaBuilder()->hasTable('people'));
}

private function expectOutput($argument): void
{
$this->output->shouldReceive('writeln')->once()->with($argument);
}
}
@@ -0,0 +1,31 @@
<?php

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

return new class extends Migration {
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('people', function (Blueprint $table) {
$table->string('first_name')->nullable();
});
}

/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('people', function (Blueprint $table) {
$table->dropColumn('first_name');
});
}
};
@@ -0,0 +1,31 @@
<?php

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

return new class extends Migration {
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('people', function (Blueprint $table) {
$table->string('last_name')->nullable();
});
}

/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('people', function (Blueprint $table) {
$table->dropColumn('last_name');
});
}
};