Here are the steps to create an artisan command that will generate custom classes and interfaces that follow a pre-determined template.
For Laravel projects I write utility classes and interfaces to abstract away the logic; these files go into subdirectories within app/Utilities/
(of course it can be anything). Let’s say you want to write FooUtility
for something. My usual workflow is this:
-
Create a directory
Foo
insideapp/Utilities
-
Create two files
FooUtility.php
andFooUtilityInterface.php
insideapp/Utilities/Foo/
. -
Add the few lines of boilerplane needed for classes and interfaces to work. For instance, something like:
<?php namespace app/Utilities/Foo; class FooUtility implements FooUtilityInterface { // }
A similar boilerplate is needed for the interface also.
This repetitive manual work should be avoided, especially when you need dozens of utility classes and interfaces. I have run into troubles before with namespace declarations, or the odd hard-to-spot mismatch between the filename, directory name, interface name, class name etc that makes PSR-4 yell. To avoid such mistakes I created an artisan command to create such utility classes and interfaces, and load them with the initial boilerplate. Steps to create an artisan command that would generate files wasn’t immediately obvious from the excellent Laravel documentation and hence this article. These are the general steps:
- Create stub files (templates) for the newly generated class
- Create an artisan command.
- By default, the artisan command class extends
Command
; replace that withGeneratorCommand
which can do all the heavy lifting of generating the files and populating the initial contents.
For my use-case I had to create both an interface as well as a regular class, I created two artisan commands – one to create the interface and one to create the class. From within the command to create the class, I programmatically call the command to create the interface, so that in practice I only have to call a single artisan command to create both the class as well as the interface.
Create stubs for the files
Stub file is the template based on which our artisan command generates whatever it is that need to be generated. I created two stubs – one for the interface and one for the class, and put them in app/CustomStubs
.
foo/app/CustomStubs/utilityInterface.stub
<?php
// custom stub
namespace DummyNamespace;
interface DummyClass {
//
}
foo/app/CustomStubs/utility.stub
<?php
// custom stub
namespace DummyNamespace;
class DummyClass implements DummyClassInterface {
//
}
DummyClass
, DummyNamespace
, and DummyClassInterface
might seem random; later you’ll see how the GeneratorCommand
replaces those specific words with actual class names.
There’s a way to publish built-in stubs (the ones for models, controllers etc) into stubs/
directory in your project, and use that same directory to hold custom stubs. I chose to maintain a separate directory (CustomStubs/
in this case) because it’s unclear to me whether putting our own stubs inside the published directory is idiomatic usage.
Create Artisan Command
This part is easy:
$ php artisan make:command MakeUtilityInterface
Console command created successfully.
This creates a file in app/Console/Commands/MakeUtilityInterface.php
with the following contents:
|
|
Here are the modifications to be done to this file:
- Extend
GeneratorCommand
instead ofCommand
. - Assign proper values to
$signature
and$description
. The former specifies the syntax of the command, and the latter is a description that shows up when you, for example, runphp artisan list
. - Declare a protected variable
$type
– the given value will show up in the console after running the artisan command. - Implement
getStub()
method – this should return the path to the stub file. - Implement
getDefaultNamespace()
method – this should return the namespace for the file being generated. The generated file will be placed here. - Remove the
handle()
function so that the parent class’shandle()
function would do all the work. - Remove the constructor because it’s not needed.
After making these changes you should have something like:
|
|
If you run php artisan list
, you should see an entry like below:
make:utilityinterface Create interface for custom utilities
Let’s take it for a spin and create our utility interface:
$ php artisan make:utilityinterface Foo/BazUtilityInterface
utility interface created successfully.
After this command runs, this is what we expect:
- A new file
BazUtilityInterface.php
would be created atapp/Utilities/Foo/BazUtilityInterface.php
- The newly created interface would have the name
BazUtilityInterface
- Declared namespace of the
BazUtilityInterface
would beApp\Utilities\Foo
.
A new interface would actually have been created at app/Utilities/Foo/BazUtilityInterface.php
:
|
|
Artisan command to create a utility classe can be written similarly. To avoid having to manually run two artisan commands to separately create the class and interface (which, aside from being an inconvenience, can lead to mistakes), I’m calling the command to make interface from within the handle()
method (which we omitted for the previous command).
Assuming you named your command MakeUtility
, here’s how MakeUtility.php
should look like:
|
|
Let’s take it for a spin:
$ php artisan make:utility Foo/BazUtility
utility interface created successfully.
utility class created successfully
This would:
- Create an interface
BazUtilityInterface
atapp/Utilities/Foo/BazUtilityInterface.php
- Create a class
BazUtility
atapp/Utilities/Foo/BazUtility.php
The newly created BazUtility
class would have the following content:
|
|
How did I know the keywords to use in stub files?
If you examine the source code of GeneratorCommand
class (located at vendor/laravel/framework/src/Illuminate/Console/GeneratorCommand.php
), you’ll find:
/**
* Replace the namespace for the given stub.
*
* @param string $stub
* @param string $name
* @return $this
*/
protected function replaceNamespace(&$stub, $name)
{
$searches = [
['DummyNamespace', 'DummyRootNamespace', 'NamespacedDummyUserModel'],
['{{ namespace }}', '{{ rootNamespace }}', '{{ namespacedUserModel }}'],
['{{namespace}}', '{{rootNamespace}}', '{{namespacedUserModel}}'],
];
foreach ($searches as $search) {
$stub = str_replace(
$search,
[$this->getNamespace($name), $this->rootNamespace(), $this->userProviderModel()],
$stub
);
}
return $this;
}
/**
* Replace the class name for the given stub.
*
* @param string $stub
* @param string $name
* @return string
*/
protected function replaceClass($stub, $name)
{
$class = str_replace($this->getNamespace($name).'\\', '', $name);
return str_replace(['DummyClass', '{{ class }}', '{{class}}'], $class, $stub);
}
As you see GeneratorCommand
looks for those keywords and replaces them; the replacement works even if DummyClass
is within another word like DummyClassInterface
.
There exists further possibilities. For instance, I could have set an optional flag that would tell the make:utility
command whether or not the interface should be created.