Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Request Controller Laravel Package

lorisleiva/request-controller

A Laravel package that lets Form Requests act as invokable controllers, combining validation, authorization, and request handling in a single class for cleaner single-action endpoints.

View on GitHub
Deep Wiki
Context7

Getting Started

lorisleiva/request-controller merges two classes you normally write for every write endpoint — a FormRequest for authorization/validation and an invokable controller for the action — into a single class. The class extends the package's request-controller base, uses Laravel's normal FormRequest validation lifecycle, and exposes an action method (conventionally handle()) that is invoked when the route dispatches.

Install it:

composer require lorisleiva/request-controller

Look first at the package's base class in vendor/lorisleiva/request-controller/src and at the README example. The base class is small; most behavior you rely on is inherited from Laravel's FormRequest (authorize(), rules(), prepareForValidation(), validated(), automatic redirect/422 responses).

A first use case — a POST /posts endpoint:

namespace App\Http\Controllers\Posts;

use App\Models\Post;
use Lorisleiva\RequestController\RequestController;

class StorePost extends RequestController
{
    public function rules(): array
    {
        return [
            'title' => ['required', 'string', 'max:255'],
            'body'  => ['required', 'string'],
        ];
    }

    public function handle()
    {
        $post = Post::create($this->validated());

        return redirect()->route('posts.show', $post);
    }
}
Route::post('/posts', StorePost::class)->name('posts.store');

That is the whole day-one workflow: define rules, put the action body in handle(), register the class as the route action. If you need authorization, add authorize(); if you need input normalization, add prepareForValidation().

Implementation Patterns

One class per endpoint. The package fits command-style endpoints best: store, update, destroy, login, logout, webhooks, single-purpose form submissions. It is a poor fit for large resource controllers with many methods, because each method becomes its own class.

Co-locate authorization, normalization, validation, and the action.

class UpdatePost extends RequestController
{
    public function authorize(): bool
    {
        return $this->user()->can('update', $this->route('post'));
    }

    public function prepareForValidation(): void
    {
        $this->merge(['slug' => Str::slug($this->input('title'))]);
    }

    public function rules(): array
    {
        return [
            'title' => ['required', 'string', 'max:255'],
            'slug'  => ['required', 'string', Rule::unique('posts')->ignore($this->route('post'))],
        ];
    }

    public function handle()
    {
        $post = $this->route('post');
        $post->update($this->validated());

        return back()->with('status', 'Post updated.');
    }
}

Use route model binding through the request. Because the class is the request, route parameters are available with $this->route('post'). Prefer that over trying to type-hint route-bound models into the action method; behavior there depends on how the package invokes the action and is less portable across versions.

Share behavior with an intermediate base class. If you have many request controllers, create App\Http\Controllers\RequestController extending the package class and put cross-cutting helpers there (e.g. a respond() helper, common authorization checks). This is also where you would add a failedValidation() override if you want a consistent error envelope.

Reuse validation rules outside the endpoint. Since the class is a FormRequest, its rules() are only usable through HTTP dispatch. If the same rules are needed by a console command or another endpoint, extract them to a dedicated rules class or trait and call it from rules().

Test through HTTP feature tests, not by instantiating the class. The validation lifecycle fires when the container resolves the request during route dispatch. Test the endpoint as a client:

public function test_title_is_required(): void
{
    $this->post(route('posts.store'), ['body' => 'Hello'])
        ->assertSessionHasErrors('title');
}

Keep domain logic out of the request class. The request controller is a delivery mechanism. Delegate to an action/service/job for anything reusable, transactional, or complex; let handle() validate, authorize, call the domain code, and return a response.

Apply middleware at the route level. Use route groups and ->middleware(...) rather than relying on a controller middleware() method; the class does not extend Laravel's base Controller, so controller middleware conventions do not apply.

Naming convention. These classes are controllers, not plain requests, so put them under App\Http\Controllers (possibly in an Posts subnamespace) and name them after the action (StorePost, UpdatePost). Avoid the ...Request suffix or you will confuse readers and IDE navigation.

Gotchas and Tips

  • Staleness and compatibility are the biggest risk. Last release was November 2020 and the package has very low adoption. Check its composer.json constraints against your Laravel version and run a smoke test on your target version before committing. If it is incompatible, the pattern is easy to reproduce with a small base class of your own.
  • Do not add constructor dependencies. As a FormRequest/Symfony Request subclass, the constructor signature is owned by the framework. Resolve services inside handle() via app() or method injection, not via __construct.
  • Validation only runs on route dispatch. If you instantiate the class manually in a test or call handle() directly, authorize()/rules() will not run. Feature-test through the router.
  • Authorization runs before validation. A failed authorize() produces a 403 before validation errors are considered. This is the normal FormRequest order, but it surprises people who expect validation-first feedback.
  • Use validated(), not all(). all() includes unvalidated keys, which is a mass-assignment footgun. validated() returns only keys covered by rules; on newer Laravel versions safe() is also available, but validated() is the compatible choice.
  • Middleware defined on the class is not automatically applied. The class is a request, not a Controller subclass. Put auth/throttle/can middleware on the route or route group.
  • Route caching works, but verify it. Invokable class actions are cacheable; run php artisan route:cache and php artisan route:list as part of your checks so a broken action reference is caught early.
  • Class explosion is real. A resource with seven endpoints becomes seven classes. For small APIs this is a readability win; for larger CRUD surfaces it becomes navigation overhead. Adopt selectively rather than as a blanket policy.
  • Debugging checklist. If validation seems skipped, confirm the route points at the class (not a closure or a different controller), confirm the class extends the package base, and inspect route:list. If you get a 419, it is CSRF/middleware, not the package. If you get a 500 on resolution, check the package's Laravel version constraints.
  • Extension points to remember. authorize(), rules(), messages(), attributes(), prepareForValidation(), passedValidation(), and failedValidation() all behave as they do on a normal FormRequest. For custom JSON error shapes, override failedValidation() in an intermediate base class rather than in every endpoint.
  • Treat the package as an architectural choice, not a utility. The main benefit is locality — rules and action in one file for small endpoints. The main cost is mixing HTTP validation concerns with controller responsibilities and adding a third-party dependency for something a plain invokable controller plus a FormRequest already does with one extra file.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console