How to run laravel migration and DB seeder except one

Niklesh Raut picture Niklesh Raut · Feb 4, 2016 · Viewed 8.7k times · Source

I am having many migration and seeder files to run, Although I will need to run all files but currently I need to skip one migration and seeder.

How could I skip one file from laravel migration and db seeder command.

I do not want to delete files from the migrations or seeds folder to skip the file.

Answer

LombaX picture LombaX · Jul 27, 2016

Laravel doesn't give you a default method to do it. However, you can create your own console commands and seeder to achieve it.
Let's say you have this default DatabaseSeeder class:

class DatabaseSeeder extends Seeder
{
    public function run()
    {
        $this->call(ExampleTableSeeder::class);
        $this->call(UserSamplesTableSeeder::class);
    }
}

the goal is to create a new command overriding "db:seed" and pass a new parameter, an "except" parameter, to the DatabaseSeeder class.

This is the final code, I created on my Laravel 5.2 instance and tried:

Command, put in app/Console/Commands, don't forget to update your Kernel.php:

namespace App\Console\Commands;
use Illuminate\Console\Command;
class SeedExcept extends Command
{
    protected $signature = 'db:seed-except {--except=class name to jump}';
    protected $description = 'Seed all except one';
    public function handle()
    {
        $except = $this->option('except');
        $seeder = new \DatabaseSeeder($except);
        $seeder->run();
    }
}

DatabaseSeeder

use Illuminate\Database\Seeder;
class DatabaseSeeder extends Seeder
{
    protected $except;

    public function __construct($except = null) {
        $this->except = $except;
    }

    public function call($class)
    {
        if ($class != $this->except)
        {
            echo "calling $class \n";
            //parent::call($class);  // uncomment this to execute after tests
        }
    }

    public function run()
    {
        $this->call(ExampleTableSeeder::class);
        $this->call(UserSamplesTableSeeder::class);
    }
}

It the code, you'll find that I commented the line that calls the seed and added an echo for testing purposes.

Executing this command:

php artisan db:seed-except

will give you:

calling ExampleTableSeeder
calling UserSamplesTableSeeder

However, adding "except":

php artisan db:seed-except --except=ExampleTableSeeder

will give you

calling UserSamplesTableSeeder

This works overriding the default call method of your DatabaseSeeder class and calling the parent only if the name of the class is not in the $except variable. The variable is populated by the SeedExcept custom command.

Regarding migrations, the thing is similar but a little bit more difficult.

I can't give you tested code for this by now, but the thing is:

  • you create a migrate-except command that overrides the MigrateCommand class (namespace Illuminate\Database\Console\Migrations, located in vendor/laravel/framework/src/Illuminate/Database/Console/Migrations/MigrateCommand.php).
  • the MigrateCommand takes a Migrator object (namespace Illuminate\Database\Migrations, path vendor/laravel/framework/src/Illuminate/Database/Migrations/Migrator.php) in the constructor (injected via IoC). The Migrator class owns the logic that reads all the migrations inside the folder and execute it. This logic is inside the run() method
  • create a subclass of Migrator, for example MyMigrator, and override the run() method to skip the files passed with the special option
  • override the __construct() method of your MigrateExceptCommand and pass your MyMigrator: public function __construct(MyMigrator $migrator)

If I have time I'll add the code for an example before the bounty ends

EDIT as promised, here's an example for migrations:

MyMigrator class, extends Migrator and contains the logic to skip files:

namespace App\Helpers;
use Illuminate\Database\Migrations\Migrator;
class MyMigrator extends Migrator
{
    public $except = null;

    // run() method copied from it's superclass adding the skip logic
    public function run($path, array $options = [])
    {
        $this->notes = [];

        $files = $this->getMigrationFiles($path);

        // skip logic
        // remove file from array
        if (isset($this->except))
        {
            $index = array_search($this->except,$files);
            if($index !== FALSE){
                unset($files[$index]);
            }
        }
        var_dump($files); // debug

        $ran = $this->repository->getRan();
        $migrations = array_diff($files, $ran);
        $this->requireFiles($path, $migrations);

        //$this->runMigrationList($migrations, $options);  // commented for debugging purposes
    }
}

The MigrateExcept custom command

namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Database\Console\Migrations\MigrateCommand;
use App\Helpers\MyMigrator;
use Illuminate\Database\Migrations\Migrator;
use Symfony\Component\Console\Input\InputOption;

class MigrateExcept extends MigrateCommand
{
    protected $name = 'migrate-except'; 

    public function __construct(MyMigrator $migrator)
    {   
        parent::__construct($migrator);
    }

    public function fire()
    {
        // set the "except" param, containing the name of the file to skip, on our custom migrator
        $this->migrator->except = $this->option('except');
        parent::fire();
    }

    // add the 'except' option to the command
    protected function getOptions()
    {
        return [
            ['database', null, InputOption::VALUE_OPTIONAL, 'The database connection to use.'],

            ['force', null, InputOption::VALUE_NONE, 'Force the operation to run when in production.'],

            ['path', null, InputOption::VALUE_OPTIONAL, 'The path of migrations files to be executed.'],

            ['pretend', null, InputOption::VALUE_NONE, 'Dump the SQL queries that would be run.'],

            ['seed', null, InputOption::VALUE_NONE, 'Indicates if the seed task should be re-run.'],

            ['step', null, InputOption::VALUE_NONE, 'Force the migrations to be run so they can be rolled back individually.'],

            ['except', null, InputOption::VALUE_OPTIONAL, 'Files to jump'],
        ];
    }
}

Last, you need to add this to a service provider to permit the Laravel IoC resolve the dependencies

namespace App\Providers;
use App\Helpers\MyMigrator;
use App\Console\Commands\MigrateExcept;


class CustomServiceProvider extends ServiceProvider
{
    public function boot()
    {
        parent::boot($events);

        $this->app->bind('Illuminate\Database\Migrations\MigrationRepositoryInterface', 'migration.repository');
        $this->app->bind('Illuminate\Database\ConnectionResolverInterface', 'Illuminate\Database\DatabaseManager');

        $this->app->singleton('MyMigrator', function ($app) {
            $repository = $app['migration.repository'];
            return new MyMigrator($repository, $app['db'], $app['files']);
        });
    }
}

Don't forget to add Commands\MigrateExcept::class in the Kernel.php

Now, if you execute

php artisan migrate-except

you have:

array(70) {
  [0] =>
  string(43) "2014_04_24_110151_create_oauth_scopes_table"
  [1] =>
  string(43) "2014_04_24_110304_create_oauth_grants_table"
  [2] =>
  string(49) "2014_04_24_110403_create_oauth_grant_scopes_table"
  ...

but adding the except param:

php artisan migrate-except --except=2014_04_24_110151_create_oauth_scopes_table

array(69) {
  [1] =>
  string(43) "2014_04_24_110304_create_oauth_grants_table"
  [2] =>
  string(49) "2014_04_24_110403_create_oauth_grant_scopes_table"

So, recap:

  • we create a custom migrate-except command, MigrateExcept class, extending MigrateCommand
  • we create a custom migrator class, MyMigrator, extending the behavior of the standard Migrator
  • when MigrateExcept is fire(), pass the name of the file to skip to our MyMigrator class
  • MyMigrator overrides the run() method of Migrator and skip the passed migration
  • More: since we need to instruct Laravel IoC about the new created classes, so it can inject them correctly, we create a Service Provider

The code is tested so it should work correctly on Laravel 5.2 (hoping that cut&paste worked correctly :-) ...if anyone has any doubt leave a comment