Laravel Framework

Controllers & Request Handling

18 min Lesson 4 of 45

Introduction to Controllers

Controllers are responsible for handling incoming HTTP requests and returning responses. Instead of defining all request handling logic in route closures, controllers organize related logic into dedicated classes.

Why Use Controllers? Controllers help you organize code logically, making your application more maintainable and testable. They follow the Single Responsibility Principle by grouping related HTTP request handling logic together.

Creating Controllers

Basic Controller Creation

Use the Artisan command to generate a new controller:

# Create a basic controller php artisan make:controller PostController # Creates: app/Http/Controllers/PostController.php

This generates a basic controller class:

<?php namespace App\Http\Controllers; use Illuminate\Http\Request; class PostController extends Controller { // Your methods go here }

Controller with Methods

Add methods to handle different actions:

<?php namespace App\Http\Controllers; use Illuminate\Http\Request; class PostController extends Controller { public function index() { return view('posts.index'); } public function show($id) { return view('posts.show', ['postId' => $id]); } public function create() { return view('posts.create'); } public function store(Request $request) { // Handle form submission return redirect()->route('posts.index'); } }

Defining Routes to Controllers

use App\Http\Controllers\PostController; // Single action routes Route::get('/posts', [PostController::class, 'index'])->name('posts.index'); Route::get('/posts/{id}', [PostController::class, 'show'])->name('posts.show'); Route::get('/posts/create', [PostController::class, 'create'])->name('posts.create'); Route::post('/posts', [PostController::class, 'store'])->name('posts.store');

Resource Controllers

Resource controllers make it painless to build RESTful controllers around a resource. They include methods for typical CRUD operations.

Creating Resource Controllers

# Create a resource controller with standard CRUD methods php artisan make:controller PostController --resource # Create with model binding php artisan make:controller PostController --model=Post # Create API resource controller (without create/edit) php artisan make:controller PostController --api

Resource Controller Structure

<?php namespace App\Http\Controllers; use App\Models\Post; use Illuminate\Http\Request; class PostController extends Controller { /** * Display a listing of the resource. */ public function index() { $posts = Post::all(); return view('posts.index', compact('posts')); } /** * Show the form for creating a new resource. */ public function create() { return view('posts.create'); } /** * Store a newly created resource in storage. */ public function store(Request $request) { $validated = $request->validate([ 'title' => 'required|max:255', 'content' => 'required', ]); $post = Post::create($validated); return redirect() ->route('posts.show', $post) ->with('success', 'Post created successfully!'); } /** * Display the specified resource. */ public function show(Post $post) { return view('posts.show', compact('post')); } /** * Show the form for editing the specified resource. */ public function edit(Post $post) { return view('posts.edit', compact('post')); } /** * Update the specified resource in storage. */ public function update(Request $request, Post $post) { $validated = $request->validate([ 'title' => 'required|max:255', 'content' => 'required', ]); $post->update($validated); return redirect() ->route('posts.show', $post) ->with('success', 'Post updated successfully!'); } /** * Remove the specified resource from storage. */ public function destroy(Post $post) { $post->delete(); return redirect() ->route('posts.index') ->with('success', 'Post deleted successfully!'); } }

Registering Resource Routes

use App\Http\Controllers\PostController; Route::resource('posts', PostController::class); // This single line creates all 7 RESTful routes
Convention Over Configuration: Resource controllers follow RESTful naming conventions. By following these conventions, you can register all CRUD routes with a single line of code!

Single Action Controllers

Sometimes a controller handles only one action. Use the __invoke magic method:

# Create invokable controller php artisan make:controller ShowProfileController --invokable
<?php namespace App\Http\Controllers; use App\Models\User; class ShowProfileController extends Controller { /** * Handle the incoming request. */ public function __invoke($id) { $user = User::findOrFail($id); return view('profile.show', compact('user')); } }

Routing to Invokable Controllers

use App\Http\Controllers\ShowProfileController; // No method name needed Route::get('/profile/{id}', ShowProfileController::class);

The Request Object

The Request object contains information about the HTTP request, including input, cookies, files, and more.

Accessing Request Data

use Illuminate\Http\Request; public function store(Request $request) { // Get all input data $data = $request->all(); // Get specific input $name = $request->input('name'); $email = $request->input('email'); // Get with default value $role = $request->input('role', 'user'); // Short-hand property access $name = $request->name; $email = $request->email; // Only get specified fields $data = $request->only(['name', 'email']); // Get all except specified fields $data = $request->except(['_token', '_method']); // Check if input exists if ($request->has('name')) { // Input exists } // Check if input has a value (not empty) if ($request->filled('name')) { // Input has a value } // Check if input is missing if ($request->missing('name')) { // Input is missing } }

Retrieving Query String Data

// URL: /posts?page=2&sort=latest public function index(Request $request) { $page = $request->query('page'); // 2 $sort = $request->query('sort'); // latest // Get all query parameters $query = $request->query(); // With default value $perPage = $request->query('per_page', 15); }

Retrieving File Uploads

public function upload(Request $request) { // Check if file was uploaded if ($request->hasFile('avatar')) { $file = $request->file('avatar'); // Get file info $name = $file->getClientOriginalName(); $extension = $file->getClientOriginalExtension(); $size = $file->getSize(); // Store the file $path = $file->store('avatars'); // Stores in storage/app/avatars/ // Store with custom name $path = $file->storeAs('avatars', 'user-avatar.jpg'); // Store on specific disk $path = $file->store('avatars', 's3'); } }

Request Methods & URIs

public function handle(Request $request) { // Get request method $method = $request->method(); // GET, POST, etc. // Check request method if ($request->isMethod('post')) { // Handle POST request } // Get request path $path = $request->path(); // returns: posts/create // Get full URL $url = $request->url(); // returns: http://example.com/posts/create // Get full URL with query string $urlWithQuery = $request->fullUrl(); // returns: http://example.com/posts/create?sort=latest // Check if request expects JSON if ($request->expectsJson()) { return response()->json($data); } // Check if request is AJAX if ($request->ajax()) { // Handle AJAX request } }

Input Validation

Laravel provides powerful validation features to ensure incoming data meets your requirements.

Basic Validation

public function store(Request $request) { $validated = $request->validate([ 'title' => 'required|max:255', 'content' => 'required', 'category' => 'required|in:tech,lifestyle,business', 'email' => 'required|email|unique:users', 'age' => 'required|integer|min:18|max:100', 'website' => 'nullable|url', 'published_at' => 'nullable|date', ]); // If validation passes, this code executes $post = Post::create($validated); return redirect()->route('posts.show', $post); }

Common Validation Rules

// Required field 'name' => 'required' // String with min and max length 'username' => 'required|string|min:3|max:20' // Email validation 'email' => 'required|email' // Unique in database table 'email' => 'required|email|unique:users' // Numeric and within range 'age' => 'required|numeric|min:18|max:120' // Integer 'quantity' => 'required|integer' // Boolean 'active' => 'required|boolean' // Date format 'birth_date' => 'required|date|date_format:Y-m-d' // Must match another field 'password_confirmation' => 'required|same:password' // Array validation 'tags' => 'required|array|min:1' 'tags.*' => 'string|max:50' // File validation 'avatar' => 'required|file|image|max:2048' // max 2MB 'document' => 'required|file|mimes:pdf,doc,docx|max:5120' // Nullable (optional) 'bio' => 'nullable|string|max:500' // Multiple rules as array 'email' => ['required', 'email', 'unique:users']

Custom Error Messages

public function store(Request $request) { $validated = $request->validate([ 'title' => 'required|max:255', 'email' => 'required|email|unique:users', ], [ 'title.required' => 'Please provide a title for your post.', 'title.max' => 'The title cannot exceed 255 characters.', 'email.required' => 'An email address is required.', 'email.unique' => 'This email is already registered.', ]); }

Displaying Validation Errors

<!-- In Blade template --> @if ($errors->any()) <div class="alert alert-danger"> <ul> @foreach ($errors->all() as $error) <li>{{ $error }}</li> @endforeach </ul> </div> @endif <!-- Display error for specific field --> <input type="text" name="title" value="{{ old('title') }}"> @error('title') <div class="error">{{ $message }}</div> @enderror

Form Request Validation

For complex validation logic, create dedicated Form Request classes:

Creating Form Requests

# Create a form request php artisan make:request StorePostRequest # Creates: app/Http/Requests/StorePostRequest.php

Form Request Class

<?php namespace App\Http\Requests; use Illuminate\Foundation\Http\FormRequest; class StorePostRequest extends FormRequest { /** * Determine if the user is authorized to make this request. */ public function authorize(): bool { // Check if user can create posts return auth()->check(); } /** * Get the validation rules that apply to the request. */ public function rules(): array { return [ 'title' => 'required|max:255', 'content' => 'required|min:100', 'category_id' => 'required|exists:categories,id', 'tags' => 'array|max:5', 'tags.*' => 'string|max:50', 'featured_image' => 'nullable|image|max:2048', ]; } /** * Get custom error messages. */ public function messages(): array { return [ 'title.required' => 'Every post needs a catchy title!', 'content.min' => 'Please write at least 100 characters.', ]; } /** * Get custom attribute names. */ public function attributes(): array { return [ 'category_id' => 'category', ]; } }

Using Form Requests in Controllers

use App\Http\Requests\StorePostRequest; public function store(StorePostRequest $request) { // Request is automatically validated // If validation fails, user is redirected with errors // Get validated data only $validated = $request->validated(); $post = Post::create($validated); return redirect()->route('posts.show', $post); }
Form Request Benefits: Form Requests keep your controllers clean, centralize validation logic, provide authorization checks, and are easier to test and reuse across multiple controllers.

Responses

Returning Views

public function index() { return view('posts.index'); } // With data public function show($id) { $post = Post::findOrFail($id); return view('posts.show', compact('post')); }

JSON Responses

public function apiIndex() { $posts = Post::all(); return response()->json($posts); } // With status code public function apiStore(Request $request) { $post = Post::create($request->validated()); return response()->json($post, 201); // 201 Created } // Error response public function apiError() { return response()->json([ 'error' => 'Resource not found' ], 404); }

Redirects

// Redirect to URI return redirect('/dashboard'); // Redirect to named route return redirect()->route('posts.index'); // Redirect to named route with parameters return redirect()->route('posts.show', ['id' => 5]); // Redirect back to previous page return redirect()->back(); // Redirect with flash data return redirect() ->route('posts.index') ->with('success', 'Post created successfully!'); // Redirect with input return redirect() ->back() ->withInput() ->withErrors(['email' => 'Invalid email address']);

Download Responses

public function download() { $file = storage_path('app/documents/report.pdf'); return response()->download($file); } // With custom name return response()->download($file, 'monthly-report.pdf'); // Stream file (display in browser) return response()->file($file);

Practice Exercise 1: Resource Controller

Create a complete resource controller for managing tasks:

  1. Generate a Task model and migration: php artisan make:model Task -m
  2. Create a resource controller: php artisan make:controller TaskController --resource
  3. Implement all 7 CRUD methods (index, create, store, show, edit, update, destroy)
  4. Register the resource route in web.php
  5. Create basic Blade views for each method

Practice Exercise 2: Form Validation

Implement validation for creating a task:

  1. Add validation rules to the store method: title (required, max 255), description (nullable, max 1000), due_date (required, date, after today), priority (required, in: low,medium,high)
  2. Create custom error messages for each rule
  3. Display validation errors in the create view
  4. Ensure old input is preserved when validation fails

Practice Exercise 3: Form Request

Refactor your validation into a Form Request:

  1. Create a StoreTaskRequest: php artisan make:request StoreTaskRequest
  2. Move validation rules from controller to the Form Request
  3. Add authorization logic (only authenticated users can create tasks)
  4. Update your controller to use the Form Request
  5. Test that validation still works correctly

Summary

In this lesson, you've learned:

  • How to create and organize controllers for handling HTTP requests
  • Using resource controllers for RESTful CRUD operations
  • Creating single-action invokable controllers
  • Working with the Request object to access input data, files, and request information
  • Implementing input validation with rules and custom messages
  • Creating Form Request classes for complex validation logic
  • Returning different types of responses (views, JSON, redirects, downloads)

Controllers are the heart of your application's request handling. In the next lesson, we'll explore Blade templating to create beautiful, dynamic views!