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

3

7

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,`
]
Korean answered 29/8, 2017 at 12:0 Comment(0)
K
16

I got around this using:

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

but it is not necessarily the best way to do this

Korean answered 31/8, 2017 at 9:5 Comment(2)
Just spent 1 hour with this. Thanks!Informant
No worries if you find a better way please post an answer :)Korean
A
9

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'];
});
Arenicolous answered 13/8, 2018 at 20:13 Comment(4)
This makes sense except that the framework's MigrateCommand class appears to use IoC to instantiate its migrator. I can't figure out how it works there but not in a new command.Armil
@Armil The MigrateCommand accepts the migrator through the constructor. When the service provider instantiates a new MigrateCommand, it passes the migrator into the constructor.Arenicolous
Of course, you are correct. I was sure this was happening, but I stopped scanning the MigrationServiceProvider class before looking at registerCommands() because it struck me as the standard commands() method.Armil
Thank you so much for this detailed explanation plus working example.Platina
P
0

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'));
}
Patronize answered 20/11, 2019 at 11:2 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.