Custom laravel migration command "[Illuminate\Database\Migrations\MigrationRepositoryInterface] is not instantiable"

2.9k views Asked by At

I'm trying to create a custom laravel (5.2) migration command that basically works the same as migrate:status except it just lists the pending migrations instead of all the migrations.

To do this i've very simply copied the migrate:status into another class within my app/console directory and adjusted the code to suit my needs. However whenever I try to run it I get an error:

[Illuminate\Contracts\Container\BindingResolutionException] Target [Illuminate\Database\Migrations\MigrationRepositoryInterface] is not instantiable while building [App\Console\Commands\PendingMigrations, Illuminate\Database\Migrations\Migrator].

The contents of the class itself and the fire() method doesn't seem to matter as it doesn't get that far, it fails within the __construct() method.

<?php namespace App\Console\Commands;

use Illuminate\Console\Command;
use Illuminate\Database\Migrations\Migrator;

class PendingMigrations extends Command
{
    /**
     * The console command name.
     *
     * @var string
     */
    protected $name = 'migrate:pending';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Shows a list of pending migrations';

    /**
     * The migrator instance.
     *
     * @var \Illuminate\Database\Migrations\Migrator
     */
    protected $migrator;

    /**
     * Create a new migration rollback command instance.
     *
     * @param  \Illuminate\Database\Migrations\Migrator $migrator
     * @return \Illuminate\Database\Console\Migrations\StatusCommand
     */
    public function __construct(Migrator $migrator)
    {
        parent::__construct();

        $this->migrator = $migrator;
    }

    /**
     * Execute the console command.
     *
     * @return void
     */
    public function fire()
    {
    }
}

The reason for it is likely to be something to do with the IoC container and the order with which things are loaded, but I don't know enough about the inner workings of Laravel to figure out any more than that.

It surely must be possible?

I am currently stuck on 5.2, so i'm not sure if this problem exists in more recent versions.

The only thing i've attempted so far is added the migration service provider to the top of the list in config/app.php however it didn't seem to have an affect and it was just a random guess anyway.

providers' => [
    Illuminate\Database\MigrationServiceProvider::class,`
]
3

There are 3 answers

2
John Mellor On BEST ANSWER

I got around this using:

$this->migrator = app('migrator');

but it is not necessarily the best way to do this

4
patricus On

The Migrator instance is not bound to the class name in the IoC container, it is bound to the migrator alias.

From Illuminate\Database\MigrationServiceProvider:

/**
 * Register the migrator service.
 *
 * @return void
 */
protected function registerMigrator()
{
    // The migrator is responsible for actually running and rollback the migration
    // files in the application. We'll pass in our database connection resolver
    // so the migrator can resolve any of these connections when it needs to.
    $this->app->singleton('migrator', function ($app) {
        $repository = $app['migration.repository'];

        return new Migrator($repository, $app['db'], $app['files']);
    });
}

Since the class name is not bound in the IoC container, when Laravel resolves your command and attempts to resolve the Migrator dependency, it attempts to build a new one from scratch and fails because the Illuminate\Database\Migrations\MigrationRepositoryInterface is also not bound in the IoC container (hence the error you're receiving).

Since Laravel can't figure this out itself, you need to either register the binding for the Migrator class name, or you need to register the binding for your command. Laravel itself registers all the bindings for the commands in the Illuminate\Foundation\Providers\ArtisanServiceProvider. An example of the command.migrate binding:

/**
 * Register the command.
 *
 * @return void
 */
protected function registerMigrateCommand()
{
    $this->app->singleton('command.migrate', function ($app) {
        return new MigrateCommand($app['migrator']);
    });
}

So, in your AppServiceProvider, or another service provider you setup, you can add one of the following:

Register the command in the IoC:

$this->app->singleton(\App\Console\Commands\PendingMigrations::class, function ($app) {
    return new \App\Console\Commands\PendingMigrations($app['migrator']);
});

Or, register the Migrator class name in the IoC:

$this->app->singleton(\Illuminate\Database\Migrations\Migrator::class, function ($app) {
    return $app['migrator'];
});
0
Lloople On

As I don't want to register the migrator everywhere in the app, but I still want to extend the MigrateCommand itself, I came up with this approach to maintain my app as it is:

public function __construct()
{
    app()->singleton(\App\Console\Commands\PendingMigrations::class, function ($app) {
        return new \App\Console\Commands\PendingMigrations($app['migrator']);
    });

    parent::__construct(app('migrator'));
}