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 ofmigrate
‘s other options, which are also needed. The goal is to set context formigrate
‘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 basemigrate
:
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
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
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:
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.
Try adding this as
app/Console/Commands/MigrateCore.php
:The error you were getting:
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:The constructor for
IlluminateDatabaseConsoleMigrationsMigrateCommand
wants to be injected with an instance ofIlluminateDatabaseMigrationsMigrator
which in turn is looking for aIlluminateDatabaseMigrationsMigrationRepositoryInterface
. 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 theapp()
helper, and then pass them to the constructor.Got a bit of help from this Laracasts post.