diff --git a/src/Illuminate/Database/Migrations/Migrator.php b/src/Illuminate/Database/Migrations/Migrator.php index 093e41e78a43..b51db2a142c5 100755 --- a/src/Illuminate/Database/Migrations/Migrator.php +++ b/src/Illuminate/Database/Migrations/Migrator.php @@ -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 @@ -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'); @@ -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("Rolling back: {$name}"); @@ -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("{$name}: {$query['query']}"); } } @@ -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. * diff --git a/tests/Integration/Migration/MigratorTest.php b/tests/Integration/Migration/MigratorTest.php index 50ee6f3cbd6b..08dd9c97862f 100644 --- a/tests/Integration/Migration/MigratorTest.php +++ b/tests/Integration/Migration/MigratorTest.php @@ -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'); @@ -19,25 +37,55 @@ protected function getEnvironmentSetUp($app) ]); } - public function testDontDisplayOutputWhenOutputObjectIsNotAvailable() + public function testMigrate() + { + $this->expectOutput('Migrating: 2014_10_12_000000_create_people_table'); + $this->expectOutput(Mockery::pattern('#Migrated: 2014_10_12_000000_create_people_table (.*)#')); + $this->expectOutput('Migrating: 2015_10_04_000000_modify_people_table'); + $this->expectOutput(Mockery::pattern('#Migrated: 2015_10_04_000000_modify_people_table (.*)#')); + $this->expectOutput('Migrating: 2016_10_04_000000_modify_people_table'); + $this->expectOutput(Mockery::pattern('#Migrated: 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('Rolling back: 2016_10_04_000000_modify_people_table'); + $this->expectOutput(Mockery::pattern('#Rolled back: 2016_10_04_000000_modify_people_table (.*)#')); + $this->expectOutput('Rolling back: 2015_10_04_000000_modify_people_table'); + $this->expectOutput(Mockery::pattern('#Rolled back: 2015_10_04_000000_modify_people_table (.*)#')); + $this->expectOutput('Rolling back: 2014_10_12_000000_create_people_table'); + $this->expectOutput(Mockery::pattern('#Rolled back: 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('CreatePeopleTable: 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('CreatePeopleTable: create unique index "people_email_unique" on "people" ("email")'); + $this->expectOutput('2015_10_04_000000_modify_people_table: alter table "people" add column "first_name" varchar'); + $this->expectOutput('2016_10_04_000000_modify_people_table: 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); } } diff --git a/tests/Integration/Migration/fixtures/2015_10_04_000000_modify_people_table.php b/tests/Integration/Migration/fixtures/2015_10_04_000000_modify_people_table.php new file mode 100644 index 000000000000..88ac706cd12a --- /dev/null +++ b/tests/Integration/Migration/fixtures/2015_10_04_000000_modify_people_table.php @@ -0,0 +1,31 @@ +string('first_name')->nullable(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('people', function (Blueprint $table) { + $table->dropColumn('first_name'); + }); + } +}; diff --git a/tests/Integration/Migration/fixtures/2016_10_04_000000_modify_people_table.php b/tests/Integration/Migration/fixtures/2016_10_04_000000_modify_people_table.php new file mode 100644 index 000000000000..6492b6d7f55a --- /dev/null +++ b/tests/Integration/Migration/fixtures/2016_10_04_000000_modify_people_table.php @@ -0,0 +1,31 @@ +string('last_name')->nullable(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('people', function (Blueprint $table) { + $table->dropColumn('last_name'); + }); + } +};