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

HungryBus's avatar

Laravel: dynamic routes with FormRequest

I have a system that might have a lot of controllers in the future. What I wanted to achieve (and achieved) is to make some form of dynamic routes, in order not to flood routes/web.php file with tons of route groups (there might be more than 300 routes).

So I did this: routes/web.php

Route::get('/report/{report_name}', [ReportController::class, 'index'])->name('report');
Route::post('/report/{report}/{method_name}', [ReportController::class, 'getRpcCall'])->name('report'); // This one used for RPC call (like Vue.js/AJAX calls)

ReportController.php:

public function index(string $reportName = null)
{
  if (!class_exists(self::REPORTS_NAMESPACE . $reportName . 'Controller')) {
    throw new ReportClassNotFoundException($reportName);
  }

  $report = app(self::REPORTS_NAMESPACE . $reportName . 'Controller');

  return $report->index();
}

public function getRpcCall(Request $request, string $report = null, string $methodName = null): mixed
{
  if (!class_exists(self::REPORTS_RPC_NAMESPACE . $report . 'RpcController')) {
    throw new ReportClassNotFoundException($report);
  }

  $report = app(self::REPORTS_RPC_NAMESPACE . $report . 'RpcController');
  $method = $methodName;

  if (!method_exists($report, $method)) {
    throw new MethodNotFoundException($report, $methodName);
  }

  return $report->$method($request);
}

But since I am a big fan of using FormRequests (in order not to bloat Controller with stuff that shouldn't be there, according to the SRP), I created a controller for reporting, that is being called dynamically, using above methods, like this: {{ route('rpc.report', ['FuelReporting', 'updateFuelCardData']) }}. Here it is:

class SomeReportingRpcController extends Controller
{
  public function updateFuelCardData(SomeModelUpdateRequest $request): Response
  {
    // Some stuff going on here
  }
}

As you can see, I use SomeModelUpdateRequest as a form request (created via php artisan make:request SomeModelUpdateRequest) for validation purposes. But the problem is that Laravel throws an error that

Argument #1 ($request) must be of type App\Http\Requests\SomeModelUpdateRequest, Illuminate\Http\Request given, called in ReportController.php

As I thought, that even if SomeModelUpdateRequest extends FormRequest (which extends Illuminate\Http\Request class), it should be fine, since method param type hinting is not conflicting with parent and child classes, but I was wrong.

Any ideas on how to keep the "dynamic route" logic and be able to use Form Requests?

I already have a working solution to ditch the Form request and use $request->validate() in some service class, but it is not so clean and I won't be able to use $request->validated() after that, that is kind of crucial for me.

Kind regards,

0 likes
10 replies
undeportedmexican's avatar

Well, the thing is you're passing a Illuminate\Http\Request object, instead of an App\Http\Requests\SomeModelUpdateRequest, which you already deducted.

And yes, they're completely different, albeit one parent to the other.

I can think of 2 things you can do. You could create an abstract class or interface, and typehint for that instead of the request. But I think that would be messy and complicated.

Other thing you can do, is you can instantiate a new App\Http\Requests\SomeModelUpdateRequest and pass that to the method instead of the Illuminate\Http\Request object. Of course you would have to use some convention to be able to new up the correct FormRequest object (Depending on the route you're going to be using).

1 like
HungryBus's avatar

@undeportedmexican The second version got me, but what exactly do you mean by "Instantiate a new App\Http\Requests\SomeModelUpdateRequest and pass that to the method instead of the Illuminate\Http\Request object"?

I might have misunderstood you

undeportedmexican's avatar

@HungryBus

I mean something like:

public function getRpcCall(Request $request, string $report = null, string $methodName = null): mixed
{
  if (!class_exists(self::REPORTS_RPC_NAMESPACE . $report . 'RpcController')) {
    throw new ReportClassNotFoundException($report);
  }

// This is what I'm adding .... instantiating the Form Request dynamically like the Controller.
  if (!class_exists(self::REPORTS_RPC_NAMESPACE . $report . 'Request')) {
    throw new ReportClassNotFoundException($report);
  }

  $formRequest = app(self::REPORTS_RPC_NAMESPACE . $report . 'Request');
  $formRequest->replace($request->all());

  $report = app(self::REPORTS_RPC_NAMESPACE . $report . 'RpcController');
  $method = $methodName;

  if (!method_exists($report, $method)) {
    throw new MethodNotFoundException($report, $methodName);
  }

  return $report->$method($formRequest); // Use the new request in the method, instead of the request received.
}
Tray2's avatar

Just a suggestion, stick with regular routes instead, and if you want to clean them up a bit, you can put them in separate files that you pull into your web routes.

Just the same way as the auth routes when you use breeze.

require __DIR__.'/auth.php';
1 like
HungryBus's avatar

@Tray2 Nope, not the best solution - I already thought about this. It'd be a multi-tenant app with multiple (7) parts, each part could have about 300 routes. Imagine trying to debug at least one included route file with >300 routes, all grouped in one or another way :/

Sinnbeck's avatar

@HungryBus if you want route groups and actual routes, you could register the files in the RouteServiceProvider instead. That way each file has a specific prefix etc. You can add as many as you want

1 like
HungryBus's avatar

@Sinnbeck I know, but I care more about readability and debugging. In that case, there would be 300 route groups for each domain (let's say tenant), each group would contain ~3-5 routes and that's max of 1500 routes, not including RPC routes, that would be even more

achatzi's avatar

@hungrybus Found this here https://laracasts.com/discuss/channels/code-review/is-there-a-way-to-make-a-form-request-from-another-request?reply=473335

So maybe you can try something like

return $report->$method(SomeModelUpdateRequest::createFrom($request));
class SomeReportingRpcController extends Controller
{
  public function updateFuelCardData(SomeModelUpdateRequest $request): Response
  {
    $this->validate($form, $form->rules()); 
  }
}

It is from 3 years ago so things could have changed

1 like

Please or to participate in this conversation.