Creating a Custom Laravel Model Generator

Tag: Laravel v5.0+

WARNING! This is more of an "exploratorial" where we look under the hood at the classes involved and what makes them work, in order to think about designing our own custom classes. There is no cut & paste code here that will be directly usable at the end. We aren't going to write "clean code" or even worry about hard-coding things in places to make it work. That's for next time.

I'll start with a little back story. I'm playing with an idea of mine to to build a yaml file driven scaffolding system for Laravel. In order to really make it work, I need to be able to make use of not just the out-of-the-box Laravel Generators, but also extensions such as Jeffrey Way's or custom ones that future devs will build.

I decided to pull up the code behind `make:model` and see if I could add a few things, since it is a very simple template and one I already have some ideas about. Two specific goals I have are to extend my new models from `BaseModel` instead of `Model`, and to pass a string of fields into the `fillable` array from the commandline.

Step one is find the actual code. This is actually a little trickier at first than you might think, but perfectly logical once you remember that the Laravel Framework itself is modular, and so the console commands and stubs for each will found in the appropriate module directory. Most, however, are in Foundation/Console directory, where you'll find the `ModelMakeCommand` we're going to use as well as the `GeneratorCommand` it and most of the other generators inherit from. You'll also find a `stubs` directory in here where all the templates are located. We are initially working with `model.stub`, though we'll soon see that we can't use this actual file.

My first idea as I started exploring this was to extend `ModelMakeCommand`, creating my own command with the additional changes. Running the commandline:

php artisan make:console JeffConsole --command=test:model

We get a new `app/Console/Commands` class called `JeffConsole` (because my name's Jeff, and this is my blog) that can be run as `php artisan test:model` (once you register it in app/Console/Kernel.php). This won't output or do anything yet, but run it just to confirm things are set up correctly.

Of course, right now this is just a vanilla command class. We'd like to inherit from the `ModelMakeCoomand`, so we need two changes:

//use Illuminate\Console\Command; use Illuminate\Foundation\Console\ModelMakeCommand; class JeffConsole extends ModelMakeCommand

Since we are inheriting from `ModelMakeCommand`, you would expect to actually run the command `php artisan test:model JeffModel` to create a new model. When we do, it throws an error at us:

[ErrorException] Argument 1 passed to Illuminate\Console\GeneratorCommand::__construct() mus t be an instance of Illuminate\Filesystem\Filesystem, none given, called in /home/vagrant/Code/laravel51/app/Console/Commands/JeffConsole.php on line 31 and defined

Okay, time to take our first look at the `GeneratorCommand` class. This error is easy - the default console class our generator makes includes an empty `__construct()`, but the `GeneratorCommand` wants a Filesystem passed in. Since we don't anticipate needing any changes in our class setup, we can just delete the new class' offending `__construct()` and move on. Run `php artisan test:model JeffModel` again and see:

[RuntimeException] Too many arguments.

Too many? A model generator can accept the name as a required argument, plus an option `-m` flag to create a migration. How can that be too many? This is where it gets a little bit confusing and I confess that I have not fully explored the "why" behind this. Look closely at `ModelMakeCommand` and `JeffConsole`, particularly the function names that drive the main code. You see that in the model generator it is `fire()`, whereas our new class uses the Laravel 5 syntax, `handle()`. It looks like when these functions were ported over from version 4, instead of being rewritten something was added to allow them to run as is, but our new commands need to work in the new syntax. Read up on the new syntax if you're not already familiar, but for the moment let's change our signature to read:

protected $signature = 'test:model {name} ';

Ok. What do we have so far? A class that no longer breaks, but doesn't do any of the things we set out to do and doesn't even create a basic model! I told you this was an "exploratorial".

Let's fix the second problem first, since we already know what is causing that. We have an empty `handle()` method, and we know the parent uses `fire()`. So let's set that up to work correctly:

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

Arghhh!

[InvalidArgumentException] The "migration" option does not exist.

Try this...

protected $signature = 'test:model {name} {--migration}';

Now running `php artisan test:model JeffModel` will give us a `JeffModel` class, albeit just a default one. Our next task is to replace the `extends Model` with `extends BaseModel`, which means creating a new `model.stub` file. Here's where it gets a little bit twisted again.

Out parent class has a function called `getStub()` which looks into a directory at `__DIR__.'/stubs/model.stub'`, that that is part of our package library so we don't want to overwrite that one. Our new class is in a different directory - `app/Console`, so if we copy that exact function the directory reference will actually point to where we are, and we can create our own `stubs` directory and fill it with .stub files. If we do that - make a copy of `stubs/model.stub` in `app/Console/`, we can then edit the new file as so:

class DummyClass extends BaseModel

When we re-run `php artisan test:model JeffModel` it will give us a class built on the new stub. The last part is to add a string of fields to a `$fillable` variable. We can look at how similar things are done by studying the `GeneratorCommand`. The `fire()` methods runs it all; the `buildClass()` does most of the heavy lifting. So we'll want to modify the new `model.stub` with:

protected $fillable = [DummyFillable];

add a new argument to take the fields string, and overwrite the `buildClass()` function locally to do an additional string replace. As I hinted at the beginning, there will be more on this in later articles as I work on the scaffolding side project. Do we even need to extend the `ModelMakeCommand` or just use the `GeneratorCommand` directly? Or do we even want to rewrite that class to make every thing more generally extendable?

Lots of questions, and lots of exciting experimentation to do!