Unit 5 - Notes
Unit 5: Laravel Form validation
1. CSRF Field (Cross-Site Request Forgery)
Concept
Cross-Site Request Forgery (CSRF) is a malicious exploit whereby unauthorized commands are transmitted from a user that the web application trusts. Laravel automatically protects the application from this type of attack by generating a CSRF "token" for each active user session managed by the application.
The Logic
- Token Generation: When a session starts, Laravel generates a random string token.
- Storage: This token is stored in the user's session.
- Verification: Whenever a state-changing request (POST, PUT, PATCH, DELETE) is submitted, the middleware
VerifyCsrfTokenchecks if the token submitted with the request matches the one stored in the session. - Result: If the tokens match, the request proceeds. If not, the request is rejected with HTTP 419 (Page Expired).
Implementation in Blade Views
Any HTML form pointing to a POST, PUT, PATCH, or DELETE route must include a hidden CSRF token field.
The @csrf Directive:
Laravel provides a convenient Blade directive to generate the hidden input field.
<form method="POST" action="/profile">
@csrf
<!-- Form fields... -->
</form>
HTML Equivalent:
The @csrf directive renders the following HTML:
<input type="hidden" name="_token" value="Hh7z...random...string">
X-CSRF-TOKEN (AJAX)
For JavaScript-driven applications (AJAX), the token is usually stored in a <meta> tag:
<meta name="csrf-token" content="{{ csrf_token() }}">
JavaScript libraries (like Axios or jQuery) are then configured to read this meta tag and send it as a header (
X-CSRF-TOKEN) with every request.
2. Method Field (Method Spoofing)
The Limitation
HTML forms usually only support GET and POST methods. However, RESTful routing conventions require the use of other HTTP verbs such as PUT (for updates), PATCH (for partial updates), and DELETE (for removal).
The Solution: Method Spoofing
Laravel allows you to spoof these methods by adding a hidden _method field to the form. The middleware recognizes this field and treats the request as the specified HTTP verb.
Implementation
Using the @method Directive:
<!-- Example: A Delete Form -->
<form action="/posts/1" method="POST">
@csrf
@method('DELETE')
<button type="submit">Delete Post</button>
</form>
HTML Equivalent:
The @method('DELETE') directive generates:
<input type="hidden" name="_method" value="DELETE">
Supported Methods
@method('PUT')@method('PATCH')@method('DELETE')
3. Laravel Form Validation
Overview
Laravel provides a robust system for validating incoming HTTP requests. The most common approach involves using the validate method provided by the Illuminate\Http\Request object.
The Validation Logic Flow
- Input: The user submits a form.
- Check: The controller calls
$request->validate(). - Pass: If validation rules pass, the code continues execution.
- Fail: If validation fails:
- An exception is thrown.
- The user is automatically redirected back to the previous location.
- All validation errors are flashed to the session.
- Old input data is flashed to the session (so the user doesn't have to re-type).
- If it is an AJAX request, a JSON response with 422 Unprocessable Entity is returned.
Basic Controller Example
use Illuminate\Http\Request;
public function store(Request $request)
{
// Validate the request...
$validatedData = $request->validate([
'title' => 'required|unique:posts|max:255',
'body' => 'required',
]);
// The blog post is valid, proceed to store...
}
4. Validation Rules
Laravel offers a vast library of validation rules. Rules can be defined as a pipe-delimited string or an array.
Syntax Styles
Pipe Delimited (String):
'email' => 'required|unique:users|max:255'
Array Syntax:
'email' => ['required', 'unique:users', 'max:255']
Common Validation Rules
| Rule | Description | Example |
|---|---|---|
| required | The field must be present and not empty. | 'name' => 'required' |
| Must be a valid email address format. | 'email' => 'email' |
|
| numeric | Must be a number. | 'age' => 'numeric' |
| min:value | Minimum length (string) or value (number). | 'password' => 'min:8' |
| max:value | Maximum length (string) or value (number). | 'title' => 'max:255' |
| unique:table | Must not exist in the database table. | 'email' => 'unique:users' |
| confirmed | Field must match foo_confirmation field. |
'password' => 'confirmed' |
| nullable | Field may be null (optional). | 'notes' => 'nullable' |
| date | Must be a valid date string. | 'dob' => 'date' |
| boolean | Must be true, false, 1, 0, "1", or "0". | 'terms' => 'boolean' |
| mimes:foo,bar | File validation for specific extensions. | 'photo' => 'mimes:jpg,bmp,png' |
Stopping on First Failure (bail)
By default, Laravel checks all rules for an attribute. To stop validating an attribute after the first rule failure, use bail:
'title' => 'bail|required|unique:posts|max:255',
5. Error Messages
When validation fails, Laravel flashes errors to the session. These are available in the $errors variable (an instance of Illuminate\Support\MessageBag), which is automatically shared with all views.
Checking for Errors
To check if there are any errors generally:
@if ($errors->any())
<div class="alert alert-danger">
<ul>
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
Displaying Specific Field Errors (@error)
The @error directive is used to check if a validation error exists for a specific attribute.
<label for="title">Post Title</label>
<input id="title" type="text" name="title" class="@error('title') is-invalid @enderror">
@error('title')
<div class="alert alert-danger">{{ $message }}</div>
@enderror
Note: Within the
@error block, the $message variable contains the specific error text.
Customizing Error Messages
You can customize error messages in two ways:
-
In the Controller (Inline): Pass a second array to the
validatemethod.
PHP$request->validate( [ 'title' => 'required' ], [ 'title.required' => 'We need to know what to call this post!' ] ); -
Global Language Files: Modify
lang/en/validation.php.
PHP'custom' => [ 'email' => [ 'required' => 'We need your email address!', ], ],
6. Custom Validation Rules
Sometimes built-in rules are insufficient for specific business logic (e.g., checking if a product serial number follows a specific proprietary format).
Creating a Rule Object
Use the Artisan command:
php artisan make:rule Uppercase
This creates a class in
app/Rules/Uppercase.php.
Rule Object Structure
A rule object contains two main methods:
passes(value): Returnstrueorfalse.message(): Returns the error string if validation fails.
Example: app/Rules/Uppercase.php
namespace App\Rules;
use Illuminate\Contracts\Validation\Rule;
class Uppercase implements Rule
{
public function passes($attribute, $value)
{
return strtoupper($value) === $value;
}
public function message()
{
return 'The :attribute must be completely uppercase.';
}
}
Applying the Custom Rule
To use the rule in a controller, instantiate the object in the validation array:
use App\Rules\Uppercase;
$request->validate([
'name' => ['required', 'string', new Uppercase],
]);
Using Closures (Simpler method)
If the custom rule is only needed once, you can use a closure instead of creating a file:
$request->validate([
'name' => [
'required',
function ($attribute, $value, $fail) {
if ($value === 'admin') {
$fail('The '.$attribute.' cannot be admin.');
}
},
],
]);
7. Repopulating Forms (Old Input)
Concept
When a form fails validation, it is poor User Experience (UX) to clear all the fields and force the user to start over. Laravel automatically flashes the input data to the session during a validation failure redirect.
The old() Helper
The old global helper retrieves the previously flashed input data from the session.
Syntax:
old('field_name', 'default_value')
Implementation in Blade
Use the value attribute in HTML inputs.
Standard Text Input:
<label>Username</label>
<input type="text" name="username" value="{{ old('username') }}">
If the user typed "john_doe" but forgot a password, upon redirect, "john_doe" will remain in this field.
Textarea:
<textarea name="description">{{ old('description') }}</textarea>
Select Boxes:
For select boxes, you must check if the option matches the old input to add the selected attribute.
<select name="size">
<option value="S" {{ old('size') == 'S' ? 'selected' : '' }}>Small</option>
<option value="M" {{ old('size') == 'M' ? 'selected' : '' }}>Medium</option>
</select>
Edit Forms (Database + Old Input):
When editing existing data, priority should be:
- Old input (if validation just failed).
- Database value (if the page just loaded).
- Null.
<input type="text" name="title" value="{{ old('title', $post->title) }}">