kmcvey's avatar

How to register Artisan commands outside default folder.

Creating a new command file in the default /App/Console/Command folder works every-time. When I try to move the command file to a different folder and update the name space, the Artisan command doesn't load. I updated the kernel.php file to point to the other folder and i can resolve the path manually, but the Artisan command still doesn't show up. What am i doing wrong? Laravel Framework 8.83.27

/**
     * Register the commands for the application.
     *
     * @return void
     */
    protected function commands()
    {

        $this->load(__DIR__.'/Commands'); // Artisan command visible
        $this->load(__DIR__.'/../../modules/ServiceNow/Console'); // Artisan command not visible

        require base_path('routes/console.php');
    }
0 likes
10 replies
LaryAI's avatar
Level 58

To register Artisan commands outside the default folder in Laravel, you need to follow these steps:

  1. Move your command file to the desired folder. In this case, it should be moved to the /modules/ServiceNow/Console folder.

  2. Update the namespace of your command file to reflect the new folder structure. For example, if your command file is named MyCommand.php and it was originally in the App\Console\Commands namespace, you should update it to Modules\ServiceNow\Console\Commands.

  3. Open the app\Console\Kernel.php file and update the commands() method to load the commands from the new folder. Replace the existing code with the following:

/**
 * Register the commands for the application.
 *
 * @return void
 */
protected function commands()
{
    $this->load(__DIR__.'/Commands'); // Load commands from the default folder
    $this->load(__DIR__.'/../../modules/ServiceNow/Console/Commands'); // Load commands from the new folder

    require base_path('routes/console.php');
}

Make sure to update the path to the new folder accordingly.

  1. Save the changes and run the php artisan command to see if your Artisan command is now visible. You should be able to see your command listed among the available commands.

By following these steps, you should be able to register Artisan commands outside the default folder in Laravel.

kmcvey's avatar

@LaryAI Thank you Mr. AI but neither path works.

$this->load(__DIR__.'/../../modules/ServiceNow/Console/Commands');
$this->load(__DIR__.'/../../modules/ServiceNow/Console');
Snapey's avatar

whats the path to your Console folder? Get it from your ide and paste it here rather than what you think it is

kmcvey's avatar

My original post was incorrect, I'm working out of the Commands folder

// To command folder modules\ServiceNow\Console\Commands

// Full path "core is the root folder for my application" C:\develop\core\modules\ServiceNow\Console\Commands

Namespace in my Artisan command file Sync.php

namespace Modules\ServiceNow\Console\Commands;
Snapey's avatar

do you have something in composer.json to tell it that the modules folder is used for Modules ?

kmcvey's avatar
"autoload": {
        "psr-4": {
            "App\": "app/",
            "Modules\": "modules/",
            "Database\Factories\": "database/factories/",
            "Database\Seeders\": "database/seeders/"
        }
Snapey's avatar

I've done some debugging on this, and it seems that this auto-discovery of the classes only works for commands in the App namespace.

This works but its not as convenient because you have to list the commands;

        // Module Commands
        $commands = collect([
            Test1::class,
            Test2::class,
        ])->each(function($command){
            Artisan::starting(function ($artisan) use ($command) {
                $artisan->resolve($command);
            });
        });
Snapey's avatar

A possible workaround as below.

Place a copy of the following Register.php in each commands folder. In each copy, adjust only the namespace.

<?php

namespace Modules\A\Commands;

use Symfony\Component\Finder\Finder;
use Illuminate\Console\Application;

class Register
{
    public function __invoke()
    {

        $class = new \ReflectionClass($this);

        $namespace = $class->getNamespaceName();

        foreach ((new Finder)->in(__DIR__)->files() as $file) {

            if($class->getFileName() == $file->getPathName()) {
                continue;
            }

            $command = $class->getNamespaceName() . '\' . $file->getFilename();
            $command = str_replace('.php','',$command);

            Application::starting(function ($artisan) use ($command) {
                $artisan->resolve($command);
            });
        };
    }
}

Then in the app\Console\Kernel.php;

    protected function commands()
    {
        $this->load(__DIR__.'/Commands');

        (new \Modules\A\Commands\Register)();
        (new \Modules\B\Commands\Register)();
        (new \Modules\C\Commands\Register)();

        require base_path('routes/console.php');
    }

It could perhaps be simplified further by having a single register that navigates the folder tree and adjusts the namespace for each commands folder.

The main obstacle to a simple solution is that I could not find a simple way to discover the namespace and class name given file contents.

kmcvey's avatar

Thank you, I'll give this a try.

JeffH's avatar

I ran into the same thing here. I had my commands in /laravel/app/Commands/ and I had temporary commands in (like fixup scripts) /laravel/app/Commands/Temp/

Github said it cannot be done by default https://github.com/laravel/framework/issues/46656#issuecomment-1493835888

I basically did this

/**
 * Register the commands for the application.
 *
 * @return void
 */
protected function commands()
{
    // we need to load our command from /laravel/app/Commands/
    $this->loadCommands(
        __DIR__ . "/Commands",
    );

    require base_path('routes/console.php');
}

private function loadCommands(string $path): void {
    $class = new \ReflectionClass($this);
    $namespace = $class->getNamespaceName();

    // go through each file so we can load the command
    foreach ((new Finder)->in($path)->files() as $file) {
        // remove .php, remove the path so we only keep the namespace
        $command = $namespace.str_replace(
            ['/', '.php'],
            ['\', ''],
            Str::after($file->getRealPath(), realpath(__DIR__))
        );

        // and load the commands
        Application::starting(function ($artisan) use ($command) {
            $artisan->resolve($command);
        });
    }
}
1 like

Please or to participate in this conversation.