skip to Main Content

Let’s say, for example, I want to add a couple of options to php artisan migrate:

php artisan migrate --core
php artisan migrate --project=0f9ebA2

Is it possible to extend an existing artisan command? I don’t want to change any default behavior, just add options. It would be sufficient to have a wrapper class that pre-sets certain options, like changing a config variable or adding a path argument, then passes through everything else to the artisan function as normal.

Stubbed out, php artisan migrate --core would look something like:

public function handle() {
    $this->call('migrate', ['--path' => '/database/migrations/core', ...$this->arguments()]);
}

Stubbed out, php artisan migrate --project=0f9ebA2 would look something like:

public function handle() {
    config(['tenant.key' => $this->argument('--project')]);
    $this->call('migrate', ['--path' => '/database/migrations/project', ...$this->arguments()]);
}

Laravel’s documentation is focused solely on authoring custom commands and occasionally invoking other commands with $this->call() – it doesn’t seem to cover extending existing functionality, or passing arguments through.


Already Tried / Doesn’t Work:

  • Creating a separate custom command like php artisan migrate:core. This will work exactly for the functionality I wish to add, and nothing else – it won’t preserve any of migrate‘s other options, which are also needed. The goal is to set context for migrate‘s core functionality.
  • Passing arguments through a custom command manually, like the examples above. I can get existing arguments with $this->arguments() and reattach them to the inner artisan call, but each argument also needs to be in the definition of the custom command – trying to pass an argument that isn’t in the commands defined arguments causes the command to be rejected. There doesn’t seem to be a way to just wildcard passthrough. Sure I can copy-paste migrate’s existing argument definitions, but it won’t be flexible with any future core changes or 3rd party packages.
  • Creating a new custom command that extends migrate directly. Trying a proof-of-concept alias of the base migrate:
namespace AppConsoleCommands;

use IlluminateDatabaseConsoleMigrationsMigrateCommand;

class MigrateCore extends MigrateCommand {
    protected $signature = 'migrate:core';

    public function handle() {
        parent::handle();
    }
}

Results in a hairy BindingResolutionException from higher up Laravel’s core hierarchy:

Target [IlluminateDatabaseMigrationsMigrationRepositoryInterface] is not instantiable while building [AppConsoleCommandsMigrateCore, IlluminateDatabaseMigrationsMigrator]

which, to me, feels like embarking on a path not intended.

2

Answers


  1. Extending an existing Artisan command in Laravel without altering its default behavior can be achieved by creating a custom command that wraps around the existing command. You can define your custom logic while still passing through any additional options or arguments to the underlying command.

    Here’s how you can create a wrapper command for php artisan migrate –core and php artisan migrate –project=0f9ebA2:

    php

    namespace AppConsoleCommands;
    
    use IlluminateConsoleCommand;
    
    class CustomMigrateCommand extends Command
    {
        protected $signature = 'migrate:custom {--core : Migrate core} {--project= : Migrate project}';
    
        protected $description = 'Custom migrate command with additional options';
    
        public function handle()
        {
            $arguments = [];
    
            if ($this->option('core')) {
                $arguments['--path'] = '/database/migrations/core';
            } elseif ($this->option('project')) {
                config(['tenant.key' => $this->option('project')]);
                $arguments['--path'] = '/database/migrations/project';
            }
    
            // Add any additional arguments passed to the command
            $arguments = array_merge($arguments, $this->option());
    
            // Call the original migrate command with the modified arguments
            $this->call('migrate', $arguments);
        }
    }
    

    With this approach, you define a new command migrate:custom which accepts the additional options –core and –project. Inside the handle method, you check for these options and modify the arguments accordingly. Then, you call the original migrate command passing the modified arguments.

    You can use this custom command like so:

    php artisan migrate:custom --core
    php artisan migrate:custom --project=0f9ebA2
    

    This way, you don’t need to directly extend Laravel’s built-in migrate command, and your custom command can be flexible to accommodate future changes or additional options.

    Login or Signup to reply.
  2. Try adding this as app/Console/Commands/MigrateCore.php:

    <?php
    
    namespace AppConsoleCommands;
    
    use IlluminateDatabaseConsoleMigrationsMigrateCommand;
    
    class MigrateCore extends MigrateCommand {
        public function __construct()
        {
            $migrator = app("migrator");
            $dispatcher = app("events");
            $this->signature .= "{--core : Run core migrations}";
            parent::__construct($migrator, $dispatcher);
        }
    
        public function handle(): void
        {
            if ($this->option("core")) {
                $this->input->setOption("path", "database/migrations/core");
            }
            parent::handle();
        }
    }
    

    The error you were getting:

    "Target [IlluminateDatabaseMigrationsMigrationRepositoryInterface] is not instantiable while building [AppConsoleCommandsMigrateCore, IlluminateDatabaseMigrationsMigrator]"

    is fairly cryptic, but if you look into the comments in IlluminateContainerContainer where the error is thrown, it starts to make a bit of sense:

    // If the type is not instantiable, the developer is attempting to resolve
    // an abstract type such as an Interface or Abstract Class and there is
    // no binding registered for the abstractions so we need to bail out.
    

    The constructor for IlluminateDatabaseConsoleMigrationsMigrateCommand wants to be injected with an instance of IlluminateDatabaseMigrationsMigrator which in turn is looking for a IlluminateDatabaseMigrationsMigrationRepositoryInterface. But no concrete classes have been bound to that interface yet.

    So, instead of just inheriting the constructor from IlluminateDatabaseConsoleMigrationsMigrateCommand we initialize those bindings with the app() helper, and then pass them to the constructor.

    Got a bit of help from this Laracasts post.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search