diff --git a/src/Illuminate/Database/Console/Migrations/MigrateMakeCommand.php b/src/Illuminate/Database/Console/Migrations/MigrateMakeCommand.php index 8c5e21d35437..815035a3eab8 100644 --- a/src/Illuminate/Database/Console/Migrations/MigrateMakeCommand.php +++ b/src/Illuminate/Database/Console/Migrations/MigrateMakeCommand.php @@ -2,6 +2,7 @@ namespace Illuminate\Database\Console\Migrations; +use Illuminate\Support\Str; use Illuminate\Support\Composer; use Illuminate\Database\Migrations\MigrationCreator; @@ -63,7 +64,7 @@ public function handle() // It's possible for the developer to specify the tables to modify in this // schema operation. The developer may also specify if this table needs // to be freshly created so we can create the appropriate migrations. - $name = trim($this->input->getArgument('name')); + $name = Str::snake(trim($this->input->getArgument('name'))); $table = $this->input->getOption('table'); @@ -78,17 +79,6 @@ public function handle() $create = true; } - // Next, we will attempt to guess the table name if this the migration has - // "create" in the name. This will allow us to provide a convenient way - // of creating migrations that create new tables for the application. - if (! $table) { - if (preg_match('/^create_(\w+)_table$/', $name, $matches)) { - $table = $matches[1]; - - $create = true; - } - } - // Now we are ready to write the migration out to disk. Once we've written // the migration out, we will dump-autoload for the entire framework to // make sure that the migrations are registered by the class loaders. diff --git a/src/Illuminate/Database/Migrations/MigrationCreator.php b/src/Illuminate/Database/Migrations/MigrationCreator.php index 29bae7add9f4..c72a892eeff1 100755 --- a/src/Illuminate/Database/Migrations/MigrationCreator.php +++ b/src/Illuminate/Database/Migrations/MigrationCreator.php @@ -48,15 +48,13 @@ public function create($name, $path, $table = null, $create = false) { $this->ensureMigrationDoesntAlreadyExist($name); - // First we will get the stub file for the migration, which serves as a type - // of template for the migration. Once we have those we will populate the - // various place-holders, save the file, and run the post create event. - $stub = $this->getStub($table, $create); + // First we will get the file path for the migration and generate the contents + // dynamically based on the arguments provided by the developer. Once we + // have these we will save the file and fire the post create event. + $path = $this->getPath($name, $path); + $content = $this->getContent($name, $table, $create); - $this->files->put( - $path = $this->getPath($name, $path), - $this->populateStub($name, $stub, $table) - ); + $this->files->put($path, $content); // Next, we will fire any hooks that are supposed to fire after a migration is // created. Once that is done we'll be ready to return the full path to the @@ -67,7 +65,7 @@ public function create($name, $path, $table = null, $create = false) } /** - * Ensure that a migration with the given name doesn't already exist. + * Ensure that a migration with the given name does not already exist. * * @param string $name * @return void @@ -81,49 +79,89 @@ protected function ensureMigrationDoesntAlreadyExist($name) } } + /** + * Generate the content for the migration file. + * + * @param string $name + * @param string $table + * @param bool $create + * @return string + */ + protected function getContent($name, $table, $create) + { + $stub = $this->getStub($name, $table, $create); + $placeholders = $this->getPlaceholders($name, $table); + + // Here we will replace the any place-holders with the values specified by + // the developer, which is useful for quickly creating a tables creation + // or update migration from the console instead of typing it manually. + + return $this->populateStub($stub, $placeholders); + } + /** * Get the migration stub file. * + * @param string $name * @param string $table * @param bool $create * @return string */ - protected function getStub($table, $create) + protected function getStub($name, $table, $create) { - if (is_null($table)) { - return $this->files->get($this->stubPath().'/blank.stub'); + // We also have stubs for creating, dropping, renaming tables as well as + // adding, removing, or renaming columns. This saves the developer + // some typing when they making migrations. + + if ($create) { + return $this->files->get($this->stubPath().'/create.stub'); } - // We also have stubs for creating new tables and modifying existing tables - // to save the developer some typing when they are creating a new tables - // or modifying existing tables. We'll grab the appropriate stub here. - else { - $stub = $create ? 'create.stub' : 'update.stub'; + if ($this->nameFollowsConvention($name)) { + return $this->files->get($this->stubPath().'/'.$this->extractStubFromName($name)); + } - return $this->files->get($this->stubPath()."/{$stub}"); + if (! is_null($table)) { + return $this->files->get($this->stubPath().'/update.stub'); } + + return $this->files->get($this->stubPath().'/blank.stub'); } /** * Populate the place-holders in the migration stub. * - * @param string $name * @param string $stub - * @param string $table + * @param array $placeholders * @return string */ - protected function populateStub($name, $stub, $table) + protected function populateStub($stub, array $placeholders) { - $stub = str_replace('DummyClass', $this->getClassName($name), $stub); + return str_replace(array_keys($placeholders), $placeholders, $stub); + } + + /** + * Determine the place-holders and values for the migration stub. + * + * @param string $name + * @param string $table + * @return array + */ + protected function getPlaceholders($name, $table) + { + $placeholders = [ + 'DummyClass' => $this->getClassName($name), + ]; + + if ($this->nameFollowsConvention($name)) { + $placeholders += $this->extractPlaceholderValuesFromName($name); + } - // Here we will replace the table place-holders with the table specified by - // the developer, which is useful for quickly creating a tables creation - // or update migration from the console instead of typing it manually. if (! is_null($table)) { - $stub = str_replace('DummyTable', $table, $stub); + $placeholders['DummyTable'] = $table; } - return $stub; + return $placeholders; } /** @@ -149,6 +187,66 @@ protected function getPath($name, $path) return $path.'/'.$this->getDatePrefix().'_'.$name.'.php'; } + /** + * Does the migration name follow one of the convenient naming conventions. + * + * @param string $name + * @return bool + */ + protected function nameFollowsConvention($name) + { + return preg_match('/(create|drop)_\w+/', $name) + || preg_match('/(rename|add)_\w+_to_\w+/', $name) + || preg_match('/remove_\w+_from_\w+/', $name); + } + + /** + * Extract the stub file name from the migration name. + * + * @param string $name + * @return bool + */ + protected function extractStubFromName($name) + { + $stub = Str::before($name, '_'); + + if ($stub === 'rename') { + $stub = Str::contains($name, '_in_') ? 'rename-column' : 'rename-table'; + } + + return $stub.'.stub'; + } + + /** + * Extract the place-holders from the migration name. + * + * @param string $name + * @return array + */ + protected function extractPlaceholderValuesFromName($name) + { + $patterns = [ + 'create_(?P\w+)', + 'drop_(?P\w+)', + 'rename_(?P\w+)_to_(?P\w+)_in_(?P\w+)', + 'rename_(?P\w+)_to_(?P\w+)', + 'add_(?P\w+)_to_(?P\w+)', + 'remove_(?P\w+)_from_(?P\w+)', + ]; + + preg_match('/'.implode('|', $patterns).'/J', $name, $matches); + + $placeholders = array_filter($matches, 'is_string', ARRAY_FILTER_USE_KEY); + + array_walk($placeholders, function (&$value, $key) { + if (Str::startsWith($key, 'DummyTable') && Str::endsWith($value, '_table')) { + $value = Str::replaceLast('_table', '', $value); + } + }); + + return array_filter($placeholders); + } + /** * Fire the registered post create hooks. * diff --git a/src/Illuminate/Database/Migrations/stubs/add.stub b/src/Illuminate/Database/Migrations/stubs/add.stub new file mode 100644 index 000000000000..b58e94541c49 --- /dev/null +++ b/src/Illuminate/Database/Migrations/stubs/add.stub @@ -0,0 +1,32 @@ +string('DummyColumn')->nullable(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('DummyTable', function (Blueprint $table) { + $table->dropColumn('DummyColumn'); + }); + } +} diff --git a/src/Illuminate/Database/Migrations/stubs/drop.stub b/src/Illuminate/Database/Migrations/stubs/drop.stub new file mode 100644 index 000000000000..9abc131ac03c --- /dev/null +++ b/src/Illuminate/Database/Migrations/stubs/drop.stub @@ -0,0 +1,31 @@ +increments('id'); + $table->timestamps(); + }); + } +} diff --git a/src/Illuminate/Database/Migrations/stubs/remove.stub b/src/Illuminate/Database/Migrations/stubs/remove.stub new file mode 100644 index 000000000000..7ede488790a5 --- /dev/null +++ b/src/Illuminate/Database/Migrations/stubs/remove.stub @@ -0,0 +1,32 @@ +dropColumn('DummyColumn'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('DummyTable', function (Blueprint $table) { + $table->string('DummyColumn')->nullable(); + }); + } +} diff --git a/src/Illuminate/Database/Migrations/stubs/rename-column.stub b/src/Illuminate/Database/Migrations/stubs/rename-column.stub new file mode 100644 index 000000000000..d77392af9c66 --- /dev/null +++ b/src/Illuminate/Database/Migrations/stubs/rename-column.stub @@ -0,0 +1,32 @@ +renameColumn('DummyColumnFrom', 'DummyColumnTo'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('DummyTable', function (Blueprint $table) { + $table->renameColumn('DummyColumnTo', 'DummyColumnFrom'); + }); + } +} diff --git a/src/Illuminate/Database/Migrations/stubs/rename-table.stub b/src/Illuminate/Database/Migrations/stubs/rename-table.stub new file mode 100644 index 000000000000..890836c994a8 --- /dev/null +++ b/src/Illuminate/Database/Migrations/stubs/rename-table.stub @@ -0,0 +1,28 @@ +getCreator(); unset($_SERVER['__migration.creator']); $creator->afterCreate(function () { $_SERVER['__migration.creator'] = true; }); - $creator->expects($this->any())->method('getDatePrefix')->will($this->returnValue('foo')); - $creator->getFilesystem()->shouldReceive('get')->once()->with($creator->stubPath().'/blank.stub')->andReturn('DummyClass'); - $creator->getFilesystem()->shouldReceive('put')->once()->with('foo/foo_create_bar.php', 'CreateBar'); + $creator->expects($this->any())->method('getDatePrefix')->will($this->returnValue('12345')); + $creator->getFilesystem()->shouldReceive('get')->once()->with($creator->stubPath().'/blank.stub')->andReturn('blank: DummyClass'); + $creator->getFilesystem()->shouldReceive('put')->once()->with('foo/12345_migration_name.php', 'blank: MigrationName'); - $creator->create('create_bar', 'foo'); + $creator->create('migration_name', 'foo'); $this->assertTrue($_SERVER['__migration.creator']); unset($_SERVER['__migration.creator']); } - public function testTableUpdateMigrationStoresMigrationFile() + public function testBasicCreateMethodStoresUpdateMigrationFileWhenPassedTable() { $creator = $this->getCreator(); - $creator->expects($this->any())->method('getDatePrefix')->will($this->returnValue('foo')); - $creator->getFilesystem()->shouldReceive('get')->once()->with($creator->stubPath().'/update.stub')->andReturn('DummyClass DummyTable'); - $creator->getFilesystem()->shouldReceive('put')->once()->with('foo/foo_create_bar.php', 'CreateBar baz'); + $creator->expects($this->any())->method('getDatePrefix')->will($this->returnValue('12345')); + $creator->getFilesystem()->shouldReceive('get')->once()->with($creator->stubPath().'/update.stub')->andReturn('update: DummyClass DummyTable'); + $creator->getFilesystem()->shouldReceive('put')->once()->with('foo/12345_migration_name.php', 'update: MigrationName baz'); - $creator->create('create_bar', 'foo', 'baz'); + $creator->create('migration_name', 'foo', 'baz'); } - public function testTableCreationMigrationStoresMigrationFile() + public function testBasicCreateMethodStoresCreateMigrationFileWhenPassedTableAndCreate() { $creator = $this->getCreator(); - $creator->expects($this->any())->method('getDatePrefix')->will($this->returnValue('foo')); - $creator->getFilesystem()->shouldReceive('get')->once()->with($creator->stubPath().'/create.stub')->andReturn('DummyClass DummyTable'); - $creator->getFilesystem()->shouldReceive('put')->once()->with('foo/foo_create_bar.php', 'CreateBar baz'); + $creator->expects($this->any())->method('getDatePrefix')->will($this->returnValue('12345')); + $creator->getFilesystem()->shouldReceive('get')->once()->with($creator->stubPath().'/create.stub')->andReturn('create: DummyClass DummyTable'); + $creator->getFilesystem()->shouldReceive('put')->once()->with('foo/12345_migration_name.php', 'create: MigrationName baz'); - $creator->create('create_bar', 'foo', 'baz', true); + $creator->create('migration_name', 'foo', 'baz', true); + } + + public function testAdvancedCreateMethodStoresCreateMigration() + { + $creator = $this->getCreator(); + $creator->expects($this->any())->method('getDatePrefix')->will($this->returnValue('12345')); + $creator->getFilesystem()->shouldReceive('get')->once()->with($creator->stubPath().'/create.stub')->andReturn('create: DummyClass DummyTable'); + $creator->getFilesystem()->shouldReceive('put')->once()->with('foo/12345_create_users_table.php', 'create: CreateUsersTable users'); + + $creator->create('create_users_table', 'foo'); + } + + public function testAdvancedCreateMethodStoresDropMigration() + { + $creator = $this->getCreator(); + $creator->expects($this->any())->method('getDatePrefix')->will($this->returnValue('12345')); + $creator->getFilesystem()->shouldReceive('get')->once()->with($creator->stubPath().'/drop.stub')->andReturn('drop: DummyClass DummyTable'); + $creator->getFilesystem()->shouldReceive('put')->once()->with('foo/12345_drop_users_table.php', 'drop: DropUsersTable users'); + + $creator->create('drop_users_table', 'foo'); + } + + public function testAdvancedCreateMethodStoresRenameTableMigration() + { + $creator = $this->getCreator(); + $creator->expects($this->any())->method('getDatePrefix')->will($this->returnValue('12345')); + $creator->getFilesystem()->shouldReceive('get')->once()->with($creator->stubPath().'/rename-table.stub')->andReturn('rename-table: DummyClass DummyTable DummyTableFrom DummyTableTo'); + $creator->getFilesystem()->shouldReceive('put')->once()->with('foo/12345_rename_bar_table_to_baz_table.php', 'rename-table: RenameBarTableToBazTable DummyTable bar baz'); + + $creator->create('rename_bar_table_to_baz_table', 'foo'); + } + + public function testAdvancedCreateMethodStoresRenameTableMigrationWithoutTableSuffix() + { + $creator = $this->getCreator(); + $creator->expects($this->any())->method('getDatePrefix')->will($this->returnValue('12345')); + $creator->getFilesystem()->shouldReceive('get')->once()->with($creator->stubPath().'/rename-table.stub')->andReturn('rename-table: DummyClass DummyTable DummyTableFrom DummyTableTo'); + $creator->getFilesystem()->shouldReceive('put')->once()->with('foo/12345_rename_bar_to_baz.php', 'rename-table: RenameBarToBaz DummyTable bar baz'); + + $creator->create('rename_bar_to_baz', 'foo'); + } + + public function testAdvancedCreateMethodStoresAddColumnTableMigration() + { + $creator = $this->getCreator(); + $creator->expects($this->any())->method('getDatePrefix')->will($this->returnValue('12345')); + $creator->getFilesystem()->shouldReceive('get')->once()->with($creator->stubPath().'/add.stub')->andReturn('add: DummyClass DummyTable DummyColumn'); + $creator->getFilesystem()->shouldReceive('put')->once()->with('foo/12345_add_email_to_users_table.php', 'add: AddEmailToUsersTable users email'); + + $creator->create('add_email_to_users_table', 'foo'); + } + + public function testAdvancedCreateMethodStoresRemoveColumnTableMigration() + { + $creator = $this->getCreator(); + $creator->expects($this->any())->method('getDatePrefix')->will($this->returnValue('12345')); + $creator->getFilesystem()->shouldReceive('get')->once()->with($creator->stubPath().'/remove.stub')->andReturn('remove: DummyClass DummyTable DummyColumn'); + $creator->getFilesystem()->shouldReceive('put')->once()->with('foo/12345_remove_email_from_users_table.php', 'remove: RemoveEmailFromUsersTable users email'); + + $creator->create('remove_email_from_users_table', 'foo'); + } + + public function testAdvancedCreateMethodStoresRenameColumnTableMigration() + { + $creator = $this->getCreator(); + $creator->expects($this->any())->method('getDatePrefix')->will($this->returnValue('12345')); + $creator->getFilesystem()->shouldReceive('get')->once()->with($creator->stubPath().'/rename-column.stub')->andReturn('rename-column: DummyClass DummyTable DummyColumn DummyColumnFrom DummyColumnTo'); + $creator->getFilesystem()->shouldReceive('put')->once()->with('foo/12345_rename_bar_to_baz_in_users.php', 'rename-column: RenameBarToBazInUsers users DummyColumn bar baz'); + + $creator->create('rename_bar_to_baz_in_users', 'foo'); } /** diff --git a/tests/Database/DatabaseMigrationMakeCommandTest.php b/tests/Database/DatabaseMigrationMakeCommandTest.php index fa984f3fbdee..63e87af952f2 100755 --- a/tests/Database/DatabaseMigrationMakeCommandTest.php +++ b/tests/Database/DatabaseMigrationMakeCommandTest.php @@ -59,7 +59,7 @@ public function testBasicCreateGivesCreatorProperArgumentsWhenTableIsSet() $this->runCommand($command, ['name' => 'create_foo', '--create' => 'users']); } - public function testBasicCreateGivesCreatorProperArgumentsWhenCreateTablePatternIsFound() + public function testBasicCreateGivesCreatorProperArgumentsWhenNameIsStudlyCase() { $command = new MigrateMakeCommand( $creator = m::mock('Illuminate\Database\Migrations\MigrationCreator'), @@ -69,9 +69,9 @@ public function testBasicCreateGivesCreatorProperArgumentsWhenCreateTablePattern $app = new \Illuminate\Foundation\Application; $app->useDatabasePath(__DIR__); $command->setLaravel($app); - $creator->shouldReceive('create')->once()->with('create_users_table', __DIR__.DIRECTORY_SEPARATOR.'migrations', 'users', true); + $creator->shouldReceive('create')->once()->with('create_users_table', __DIR__.DIRECTORY_SEPARATOR.'migrations', null, false); - $this->runCommand($command, ['name' => 'create_users_table']); + $this->runCommand($command, ['name' => 'CreateUsersTable']); } public function testCanSpecifyPathToCreateMigrationsIn()