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:
- Generate a Task model and migration:
php artisan make:model Task -m
- Create a resource controller:
php artisan make:controller TaskController --resource
- Implement all 7 CRUD methods (index, create, store, show, edit, update, destroy)
- Register the resource route in web.php
- Create basic Blade views for each method
Practice Exercise 2: Form Validation
Implement validation for creating a task:
- 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)
- Create custom error messages for each rule
- Display validation errors in the create view
- Ensure old input is preserved when validation fails
Practice Exercise 3: Form Request
Refactor your validation into a Form Request:
- Create a StoreTaskRequest:
php artisan make:request StoreTaskRequest
- Move validation rules from controller to the Form Request
- Add authorization logic (only authenticated users can create tasks)
- Update your controller to use the Form Request
- 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!