How To Detect Missing Methods in Resource Controllers in Laravel 11

As Laravel 11 developers, we are constantly making efforts to maintain the very well clean, well-structured codebases. One aspect of this is ensuring our resource controllers are complete and contain all necessary methods and in this post I think we must get that without having the knowledge of what’s being missed out we cannot progress. In this post, we would explore an effective approach to detect missing methods in resource controllers, a technique that would definitely enhance our Laravel development process. Let us begin by refreshing our understanding of resource controllers in Laravel 11. A resource controller typically contains seven methods that handle CRUD operations for a given resource. Laravel expert Jeffrey Way has often emphasized the importance of writing custom Artisan commands to automate repetitive tasks. He states, “Artisan commands are an excellent way to encapsulate complex logic that you find yourself performing over and over.” Our DetectMissingControllerMethods command exemplifies this principle, providing a reusable tool for maintaining controller consistency.

When we generate a resource controller using Artisan, it creates a controller with these seven methods:

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class UserController extends Controller
{
    public function index()
    {
        // Display a listing of users
    }

    public function create()
    {
        // Show the form for creating a new user
    }

    public function store(Request $request)
    {
        // Store a newly created user in the database
    }

    public function show($id)
    {
        // Display the specified user
    }

    public function edit($id)
    {
        // Show the form for editing the specified user
    }

    public function update(Request $request, $id)
    {
        // Update the specified user in the database
    }

    public function destroy($id)
    {
        // Remove the specified user from the database
    }
}

As our application grows, we might inadvertently remove or forget to implement one of these methods. To address this, we will create a custom Artisan command that scans our controllers and reports any missing resource methods.
First, let us create our new Artisan command:

php artisan make:command DetectMissingControllerMethods

This command generates a new file in the app/Console/Commands directory. We will now implement our detection logic in this file. Here is the complete code for our command class:

<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use Illuminate\Support\Facades\File;
use ReflectionClass;

class DetectMissingControllerMethods extends Command
{
    protected $signature = 'detect:missing-methods {controller?}';
    protected $description = 'Detect missing resource methods in controllers';

    protected $resourceMethods = ['index', 'create', 'store', 'show', 'edit', 'update', 'destroy'];

    public function handle()
    {
        $controllerName = $this->argument('controller');

        if ($controllerName) {
            $this->checkController($controllerName);
        } else {
            $this->checkAllControllers();
        }
    }

    protected function checkAllControllers()
    {
        $controllers = File::allFiles(app_path('Http/Controllers'));

        foreach ($controllers as $controller) {
            $this->checkController($controller->getBasename('.php'));
        }
    }

    protected function checkController($controllerName)
    {
        $fullyQualifiedName = "App\\Http\\Controllers\\{$controllerName}";

        if (!class_exists($fullyQualifiedName)) {
            $this->error("Controller {$controllerName} does not exist.");
            return;
        }

        $reflection = new ReflectionClass($fullyQualifiedName);
        $methods = $reflection->getMethods();

        $implementedMethods = array_map(function ($method) {
            return $method->name;
        }, $methods);

        $missingMethods = array_diff($this->resourceMethods, $implementedMethods);

        if (empty($missingMethods)) {
            $this->info("Controller {$controllerName} has all resource methods implemented.");
        } else {
            $this->warn("Controller {$controllerName} is missing the following methods:");
            foreach ($missingMethods as $method) {
                $this->line("- {$method}");
            }
        }
    }
}

Let us break down this code to understand how it functions. Our command class extends the base Command class provided by Laravel. We have defined a command signature that allows us to optionally specify a controller name:

protected $signature = 'detect:missing-methods {controller?}';

The handle method serves as the entry point of our command:

public function handle()
{
    $controllerName = $this->argument('controller');

    if ($controllerName) {
        $this->checkController($controllerName);
    } else {
        $this->checkAllControllers();
    }
}

If a specific controller name was provided, it calls checkController for that controller. Otherwise, it calls checkAllControllers to scan all controllers in the app/Http/Controllers directory. The checkAllControllers method uses Laravel’s File facade to get all controller files:

protected function checkAllControllers()
{
    $controllers = File::allFiles(app_path('Http/Controllers'));

    foreach ($controllers as $controller) {
        $this->checkController($controller->getBasename('.php'));
    }
}

The checkController method is where the core logic resides:

protected function checkController($controllerName)
{
    $fullyQualifiedName = "App\\Http\\Controllers\\{$controllerName}";

    if (!class_exists($fullyQualifiedName)) {
        $this->error("Controller {$controllerName} does not exist.");
        return;
    }

    $reflection = new ReflectionClass($fullyQualifiedName);
    $methods = $reflection->getMethods();

    $implementedMethods = array_map(function ($method) {
        return $method->name;
    }, $methods);

    $missingMethods = array_diff($this->resourceMethods, $implementedMethods);

    if (empty($missingMethods)) {
        $this->info("Controller {$controllerName} has all resource methods implemented.");
    } else {
        $this->warn("Controller {$controllerName} is missing the following methods:");
        foreach ($missingMethods as $method) {
            $this->line("- {$method}");
        }
    }
}

This method uses PHP’s Reflection API to introspect the given controller class. It retrieves all implemented methods and compares them against our list of resource methods. Any methods in our list that are not implemented in the controller are considered missing.
To use this command, we would run:

php artisan detect:missing-methods UserController

This would check the UserController for missing resource methods. If we wanted to check all controllers, we would simply run:

php artisan detect:missing-methods

Let us consider a practical example. Suppose we have a ProductController that is missing the edit and update methods:

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class ProductController extends Controller
{
    public function index()
    {
        // Display a listing of products
    }

    public function create()
    {
        // Show the form for creating a new product
    }

    public function store(Request $request)
    {
        // Store a newly created product in the database
    }

    public function show($id)
    {
        // Display the specified product
    }

    public function destroy($id)
    {
        // Remove the specified product from the database
    }
}

When we run our command:

php artisan detect:missing-methods ProductController

We would see output similar to this:

Controller ProductController is missing the following methods:
- edit
- update

This output immediately alerts us to the missing methods, allowing us to address the issue promptly. It is worth noting that this approach assumes all controllers should implement all seven resource methods. In practice, you might have controllers that intentionally omit certain methods. You could enhance this command to account for such cases, perhaps by reading method requirements from a configuration file or by using PHP attributes to mark required methods.