Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

fogley's avatar

Dynamic routing and middleware from within controller

So, I have a problem. It seems that using middleware from within a controller becomes a problem when paired with my current routing setup...

Currently, I am handling a bunch of external endpoints with the following setup:

Route::match(['get', 'post'], '/api/{api_key}', function ($api_key) {
    if (file_exists(app_path("Http/Controllers/ApiEndpoints/{$api_key}.php"))) {
        return app("\\App\\Http\\Controllers\\ApiEndpoints\\{$api_key}")->receive();
    } else {
        return response('Please provide a valid API key', 403);
    }
});

A third party provides a "key" which is simply translated to an actual controller name, then the request is routed to the controller (more precisely, the function receive()) - if it exists.

The problem is that every api key should access unique logic and have the ability to use different middleware. However, that does not work.

This is an excerpt from one such controller (these are located in app/Http/Controllers/ApiEndpoints):

namespace App\Http\Controllers\ApiEndpoints;

use App\Http\Middleware\CustomMiddlewareDummyName;
use Illuminate\Routing\Controllers\HasMiddleware;
use Illuminate\Routing\Controllers\Middleware;

class test implements HasMiddleware
{
    public static function middleware(): array
    {
        return [
            new Middleware(CustomMiddlewareDummyName::class),
        ];
    }

    public function receive()
    {
		//do stuff
    }
}

The middleware is never loaded, the request is simply sent to receive() with no apparent consequences. I can make it work on a controller from the default controller folder.

I am hopeless about the specifics on namespaces which I think is where the problem stems from. Any and all help is greatly appreciated. So is alternative solutions.

0 likes
12 replies
Snapey's avatar

you are not using your controller AS A CONTROLLER. Furthermore, you dont extend the base controller, so how do you expect the framework to know it should call the middleware function?

Convert your route back to a standard route with apikey as a route parameter, then decide in your controller if the api key is valid and if it corresponds to a function in your controller.

Snapey's avatar

@ghabe

The stub controller that comes in http/controllers folder

as seen here https://laravel.com/docs/12.x/controllers#controller-middleware

<?php
 
namespace App\Http\Controllers;
 
use Illuminate\Routing\Controllers\HasMiddleware;
use Illuminate\Routing\Controllers\Middleware;
 
class UserController extends Controller implements HasMiddleware
{
    /**
     * Get the middleware that should be assigned to the controller.
     */
    public static function middleware(): array
    {

    // etc

But thats not really the issue

fogley's avatar

@Snapey This doesn't work in my setup, returning the following error on a completely fresh controller (placed in app/Http/Controllers):

Cannot make non static method Illuminate\Routing\Controller::middleware() static in class App\Http\Controllers\AnotherController 

Apparently my controller extends Illuminate\Routing\Controller which then means I have to follow the warning stated in the docs, and sure enough it does work when I implement HasMiddleware and remove extends Controller, thus explaining why I don't.

Is this a quirk from earlier versions? I have dutifully followed the upgrade guides from Laravel 8 and I have not come across any notes on this.

Any suggestions?

Snapey's avatar

@fogley yes, use controllers how they are meant to be used. Not by just calling them as a regular class

fogley's avatar

@Snapey I assume you mean I should set up a single route with the $api_key as a parameter. Then pair that key with a function in the controller. I already did this which resulted in a gigantic controller with matching code smell. Another solution is to pair the individual controllers with a specific route, this results in a gigantic route file. - This however seems to be the least polluting solution.

fogley's avatar

@Snapey This works (using the constructor solution), but would it be wrong to assume that this a deprecated solution?

In regards to solving this in a more traditional way, the following solution definitely works and it makes it easy to apply proper (varying) middleware directly from the route file, but it also leaves a tremendous amount of replicated code for each endpoint:

Route::match(['get', 'post'], '/api/abc', ["\App\Http\Controllers\ApiEndpoints\abc::class", 'receive']);

abc is hardcoded twice, the namespace is always the same, so is the called function (receive()). Am I blind to something obvious if I want to mitigate so much duplication?

Snapey's avatar

@fogley is your middleware different on every controller? Why is it middleware?

fogley's avatar

@Snapey Yes, middleware can differ from endpoint to endpoint. That's why I figured it would work best if I could define it from within the controller itself.

It's middleware because it needs to add additional, implied data (country data) to the request before the request is validated (by another middleware) and ultimately passed to the controller. - This data is different from controller to controller. An alternative is to dump this responsibilty to the end user, but that felt unnecessary and superflous as all their payloads then contains hardcoded, duplicate values.

Snapey's avatar

@fogley No the alternative is to put the business logic eg validation, into the controller itself, or a request class.

Snapey's avatar

would it be wrong to assume that this a deprecated solution?

Its not deprecated. Taylor committed to supporting previous versions architecture unless the upgrade docs tell you that something has been removed and you need to refactor.

The only solution to remove this worry is to start with L12 framework and move your application over.

fogley's avatar

This thread has made me realize I need to rethink my application.

I've decided to rewrite my routes file and hardcode all the endpoints, with their respective middleware as a proper, albeit somewhat clunky and temporary solution. This also makes sure that artisan:optimize plays nice. I've also decided to begin implementing proper validation over the current middleware-centric implementation.

Custom request classes is something I didn't even realize existed, gonna take a look at that as well...

Thanks for your time.

Please or to participate in this conversation.