Unit 5 - Notes

INT221 6 min read

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

  1. Token Generation: When a session starts, Laravel generates a random string token.
  2. Storage: This token is stored in the user's session.
  3. Verification: Whenever a state-changing request (POST, PUT, PATCH, DELETE) is submitted, the middleware VerifyCsrfToken checks if the token submitted with the request matches the one stored in the session.
  4. 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.

HTML
<form method="POST" action="/profile">
    @csrf
    <!-- Form fields... -->
</form>

HTML Equivalent:
The @csrf directive renders the following HTML:

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:

HTML
<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:

HTML
<!-- 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:

HTML
<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

  1. Input: The user submits a form.
  2. Check: The controller calls $request->validate().
  3. Pass: If validation rules pass, the code continues execution.
  4. 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

PHP
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):

PHP
'email' => 'required|unique:users|max:255'

Array Syntax:

PHP
'email' => ['required', 'unique:users', 'max:255']

Common Validation Rules

Rule Description Example
required The field must be present and not empty. 'name' => 'required'
email 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:

PHP
'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:

HTML
@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.

HTML
<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:

  1. In the Controller (Inline): Pass a second array to the validate method.

    PHP
        $request->validate(
            [ 'title' => 'required' ],
            [ 'title.required' => 'We need to know what to call this post!' ]
        );
        

  2. 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:

BASH
php artisan make:rule Uppercase

This creates a class in app/Rules/Uppercase.php.

Rule Object Structure

A rule object contains two main methods:

  1. passes(value): Returns true or false.
  2. message(): Returns the error string if validation fails.

Example: app/Rules/Uppercase.php

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:

PHP
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:

PHP
$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:

HTML
<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:

HTML
<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.

HTML
<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:

  1. Old input (if validation just failed).
  2. Database value (if the page just loaded).
  3. Null.

HTML
<input type="text" name="title" value="{{ old('title', $post->title) }}">