Create a generator "make" command in Laravel

In this post, I wanna show you how to create a class generator command in a very clean, Laravel-y way, if that didn't ring a bell, it's like the artisan make:* commands, let's dive in.

For our example we're gonna implement a make command to generate an "Action" class, an action class is a simple class that has a single handle() method and usually does one thing only.

Laravel has an abstract class that all make commands extend, and then override what's needed for the custom behavior, for example generating a Seeder is different than generating a Controller.

Extend

First, let's generate a command: php artisan make:command ActionMakeCommand but as I said we want it to extend Illuminate\Console\GeneratorCommand instead of the default Illuminate\Console\Command. Let's change the command name to protected $name = 'make:action';, and override protected $type to set it to 'Action'; this will be used to output the message "Action already exists" if you tried generating the same action twice.

Define the abstract

There's a single abstract method on the parent class that we'll have to define, getStub() and will return a path to the "stub" file, let's implement it to be

protected function getStub()
{
return base_path('stubs/action.stub');
}

a stub file is a file that has the general shape of the final file we want to generate but with dummy placeholders so we can replace them with our desired values, for example, if we're generating a CreateUserAction class, the stub would have DummyClass as a class name and we'll replace it with CreateUserAction when generating the final file.

Name your space

If you want to generate your file in the default namespace (usually "App"), you can safely ignore this. If however, you want it under a custom one, "App\Actions" for example, we need to tell Laravel this by overriding protected getDefaultNamespace(), so it would be

protected function getDefaultNamespace($rootNamespace)
{
return $rootNamespace.'\Actions';
}

Laravel will pass you the root namespace for convenience, again it's usually "App" unless you changed it (now deprecated).

Build

Finally, we're ready to build the final class (pun intended), if all you need is to change in the stub is the class name and the namespace, ignore this section. For more customizations, Laravel will call buildClass() on the parent class, which will read the stub, change the class name, and the namespace. So you can override this as your case requires, what's important is that you need to return the final processed class as a string to be written to the destination file.

Give me an option, not an argument

The class we extended defines a getArguments() method to return a list of available arguments, by default Laravel defines only a "name" argument, which would be the name of the class we're generating.

You can also define a list of options by overriding getOptions(), options are those switches on the cli (ex. --force). On that note, it's good practice to add the force option to enable the developer to choose if he wants to overwrite an already existing file or not. I'll be good to you and give you this to copy-paste it ;)

protected function getOptions()
{
return [
['force', null, InputOption::VALUE_NONE, 'Create the class even if it already exists']
];
}

Gotcha.

If your file is not in a psr-4 autoloaded directory (think Seeders) you'll need to re-generate Composer's autoloader, so in our handle(), one options is to call parent::handle() to let Laravel do its work, then call app()->make(\Illuminate\Support\Composer::class)->dumpAutoloads() to re-generate the autoloader. Another is ti type hint Composer in the constructor (like the gist below).

I hope you learned something new from this post, also, for your copy-paste convenience I created a gist of the finished above class here.

I'd love to hear your thoughts on the matter, the comments section is below if you're lost.