Customizing Stubs in Laravel

Tutorials

April 8th, 2021

laravel-stubs-featured.png

This post will show you how to customize stubs used to generate various classes in your application. While a minor inconvenience, manually adjusting every generated class can be tedious, and Laravel provides a way for developers to publish and version stubs in an application if you want to suit generated classes to your specific taste.

If you want to follow along, you can create a new Laravel project with the Laravel installer, using Sail, or any other way you prefer to create a new application:

1laravel new stub-demo --git

You might have noticed that the Laravel installer now supports Git and GitHub integration assuming you have the minimum git version required, you should have a new repository and a first commit.

Versioning our demo project is an excellent way to visualize the stub changes we make along the way and see what kind of files Laravel publishes to the app.

Publishing Stubs

The first step in customizing stubs could be to add stubs you'd like to customize individually to the /stubs folder at the root of a Laravel project, or you can publish all of them with Artisan:

1$ php artisan stub:publish
2$ git add stubs
3$ git status
4Changes to be committed:
5 (use "git reset HEAD <file>..." to unstage)
6
7 new file: stubs/cast.stub
8 new file: stubs/console.stub
9 new file: stubs/controller.api.stub
10 new file: stubs/controller.invokable.stub
11 new file: stubs/controller.model.api.stub
12 new file: stubs/controller.model.stub
13 new file: stubs/controller.nested.api.stub
14 new file: stubs/controller.nested.stub
15 new file: stubs/controller.plain.stub
16 new file: stubs/controller.stub
17 new file: stubs/factory.stub
18 new file: stubs/job.queued.stub
19 new file: stubs/job.stub
20 new file: stubs/middleware.stub
21 new file: stubs/migration.create.stub
22 new file: stubs/migration.stub
23 new file: stubs/migration.update.stub
24 new file: stubs/model.pivot.stub
25 new file: stubs/model.stub
26 new file: stubs/observer.plain.stub
27 new file: stubs/observer.stub
28 new file: stubs/policy.plain.stub
29 new file: stubs/policy.stub
30 new file: stubs/request.stub
31 new file: stubs/resource-collection.stub
32 new file: stubs/resource.stub
33 new file: stubs/rule.stub
34 new file: stubs/seeder.stub
35 new file: stubs/test.stub
36 new file: stubs/test.unit.stub

As you can see, we have quite a few stubs published in the app folder! I'll leave it up to you if you want to version all of them, but you could either keep a copy of them or only keep the specific stubs you want to customize.

Custom Controller Stubs

Laravel 8.36 introduced the idea of a --type flag when making a controller, allowing you to write custom stub files for generating a controller:

1<?php
2// stubs/controller.custom.stub
3namespace {{ namespace }};
4
5use {{ rootNamespace }}Http\Controllers\Controller;
6use Illuminate\Http\Request;
7
8/**
9 * Hello from the custom controller stub
10 */
11class {{ class }}
12{
13 //
14}

After adding the custom stub class, you can generate a controller using this template:

1php artisan make:controller --type=custom MyController

Which would generate the following controller file:

1<?php
2
3namespace App\Http\Controllers;
4
5use Illuminate\Http\Request;
6
7/**
8 * Hello from the custom controller stub
9 */
10class MyController
11{
12 //
13}

While this flexibility level is neat, I believe most developers can fit within the bounds of the stubs provided by the framework. Using the new --type flag is a manual way of picking which controller template you want to generate:

1php artisan make:controller --type=plain PlainController

Which would generate a file based on the stubs/controller.plain.stub file published by Laravel:

1<?php
2
3namespace App\Http\Controllers;
4
5use Illuminate\Http\Request;
6
7class PlainController extends Controller
8{
9 //
10}

A Sane Approach

Suppose you wanted to prepend a copyright comment to every file generated in your Laravel application. In that case, you could consider versioning all available stubs, and as you upgrade, run the stub:publish command to get newly added stubs.

For typical use-cases, though, perhaps you might only version the stubs you need to customize. For example, let's say that you don't want any controllers to extend the base Controller class; you could version all the controller.* stubs with your customizations but remove all the other stubs.

What If Stubs Change Upstream?

Let's say you version all the stubs from stub:publish, but you are concerned that as the Laravel framework receives updates to core stub files, your app will be out-of-date. If you version all stubs, you can always force an update to get the latest versions.

Take this for example, let's modify a stub and commit it to Git:

1echo "/* test */" >> stubs/test.stub
2git commit -am"Testing stub update"

You've updated the test stub and committed the update to Git. Let's say later Laravel publishes some updates to stubs and you want to verify if any have changed:

1$ php artisan stub:publish --force
2$ git diff
3diff --git a/stubs/test.stub b/stubs/test.stub
4index 834a53d..84c75cb 100644
5--- a/stubs/test.stub
6+++ b/stubs/test.stub
7@@ -20,4 +20,3 @@ class {{ class }} extends TestCase
8 $response->assertStatus(200);
9 }
10 }
11-/* test */

You have an easy way to see how your stubs have diverged from the Laravel codebase over time! Since the stubs are versioned you can simply undo changes the --force flag causes if you need to merge your changes with the latest stub changes.

Filed in:

Paul Redmond

Full stack web developer. Author of Lumen Programming Guide and Docker for PHP Developers.