Laravel Controllers & Request Handling

·

·

Controllers sit between routes and your domain logic. They should stay thin: parse the request, authorize the action, delegate to services or models, and return a consistent response.

Creating and Organizing Controllers

Generate controllers with php artisan make:controller PostController. Add –resource for the seven RESTful methods or –invokable for single-action controllers.

Group controllers by domain—Admin, Api/V1, Auth—to mirror your route files and keep namespaces predictable for autoloading.

Step 1: Resource controller methods

A resource controller defines index, create, store, show, edit, update, and destroy. Each method receives typed requests and returns views or redirects following REST conventions.

Keep controllers under roughly 15 lines per method. Extract complex queries to repositories or actions classes so PHPUnit tests can target business rules without HTTP fakes.

class PostController extends Controller
{
    public function index() { ... }
    public function store(StorePostRequest $request) { ... }
}

Step 2: Single action invokable controllers

php artisan make:controller PublishPostController –invokable creates a class with only __invoke(). Routes reference the class directly: Route::post('/posts/{post}/publish', PublishPostController::class).

Invokable controllers shine for one-off actions—checkout, webhooks, exports—without stuffing unrelated methods into a god controller.

Route::post('/posts/{post}/publish', PublishPostController::class);

Step 3: Constructor dependency injection

Laravel resolves type-hinted dependencies from the service container automatically. Inject services like PostService or repositories in the constructor instead of facades when testability matters.

Register bindings in AppServiceProvider when you need interfaces mapped to implementations. Constructor injection works for controllers, listeners, and jobs alike.

public function __construct(private PostService $posts) {}

Step 4: Controller middleware

Apply middleware in the constructor with $this->middleware('auth')->only(['store', 'update']) or $this->middleware('can:update,post')->only('update').

Middleware defined on controllers runs after route middleware, giving fine-grained control per action without duplicating route definitions.

$this->middleware('auth')->except(['index', 'show']);

Request Objects and Input

The IlluminateHttpRequest instance exposes query strings, JSON bodies, files, headers, and authenticated users. Form Request classes centralize validation and authorization.

Always validate and authorize before touching the database. Laravel's pipeline makes this explicit and repeatable across endpoints.

Step 1: Accessing request data safely

Use $request->input('title'), $request->string('title'), or $request->validated() after validation. The string() helper returns Stringable instances for fluent trimming and slugging.

Avoid $request->all() for mass assignment into models. Whitelist fields with $fillable on the model and validated() from Form Requests to block unexpected columns.

$validated = $request->validate([
    'title' => 'required|string|max:255',
    'body' => 'required',
]);

Step 2: Form Request classes

php artisan make:request StorePostRequest generates authorize() and rules() methods. Type-hint StorePostRequest in store() and Laravel runs validation before the method body executes.

Failed validation automatically returns 422 JSON or redirects back with errors for web forms. Customize messages() and attributes() for user-friendly copy.

public function rules(): array
{
    return ['title' => 'required|unique:posts'];
}

Step 3: File uploads

Uploaded files live on $request->file('avatar'). Store them with $path = $request->file('avatar')->store('avatars', 'public') and persist $path in the database.

Validate files with image|max:2048 rules and scan uploads in production. Use Storage::disk('s3') for cloud deployments without changing controller logic.

$path = $request->file('doc')->store('uploads');

Step 4: JSON and API requests

API clients send Content-Type: application/json. Access nested data with $request->input('user.email') or $request->json()->all().

Return consistent JSON envelopes with data and meta keys. Use ApiResource classes to transform models and hide internal columns from public consumers.

return response()->json(['data' => new PostResource($post)], 201);

Responses, Redirects, and Views

Controllers can return strings, views, JSON, redirects, streamed downloads, or Symfony responses. Choose the return type that matches the client expecting the result.

Flash session data pairs naturally with redirects after POST requests, implementing the post/redirect/get pattern that prevents duplicate form submissions.

Step 1: Returning Blade views

return view('posts.show', compact('post')) passes data to Blade templates. Use view()->share() in service providers for global variables like navigation menus.

View composers bind data to specific templates before render, decoupling sidebars and footers from every controller method.

return view('posts.index', ['posts' => Post::latest()->paginate(15)]);

Step 2: Redirects and named routes

return redirect()->route('posts.show', $post)->with('status', 'Post created!') sends users to the show page with a flash message available in the session for one request.

Use redirect()->back()->withInput() when validation fails on custom flows outside Form Requests. The old() helper repopulates form fields.

return redirect()->route('posts.index')->with('status', 'Saved.');

Step 3: HTTP status codes and abort

Return response()->noContent(204) after API deletes. Call abort(403) or abort_if(! $user->isAdmin(), 403) to stop execution with proper error pages or JSON errors.

Customize error rendering in bootstrap/app.php or resources/views/errors. APIs should return Problem Details or consistent error JSON for machine clients.

abort_if($post->user_id !== auth()->id(), 403);

Step 4: Download and stream responses

return Storage::download($path) sends files with correct headers. Stream large CSV exports with response()->stream() to avoid memory spikes on big datasets.

Set Cache-Control headers on downloadable reports. Log export actions for audit trails in regulated industries.

return Storage::disk('local')->download($reportPath);

Aadyanex structures Laravel controllers with action classes, form requests, and API resources so your HTTP layer stays thin, testable, and ready for mobile or SPA clients.