404 Not Found

404 Not Found


nginx

Laravel Form Validation

Validation is Laravel's "security checkpoint"—all data entering the system must pass validation, and not a single piece of invalid data can slip through.

1. What You'll Learn


2. A True Story of a Security Auditor

(1) Pain Point: User input causes data discrepancies

When Alice signed up for ShopMetrics, she entered "abc" for her email address, "123" for her phone number, and left the store name field blank—the system accepted everything, resulting in a database full of invalid data. A malicious user submitted a product with a negative price via the API to Bob's store, resulting in an order amount of -999 USD and causing the financial reports to crash. During a security audit, Charlie discovered that 17 endpoints had no input validation whatsoever.

(2) Solutions for Laravel Validation

Laravel validation intercepts data before it enters the system—a single array of rules can cover all validation logic, and the FormRequest class keeps controllers clean.

PHP
// Validation rules — 30 seconds to write, protects forever
$validated = $request->validate([
    'name' => 'required|string|max:255',
    'email' => 'required|email|unique:users',
    'price' => 'required|numeric|min:0',
]);

(3) Revenue

After Alice enabled verification, invalid registrations dropped to zero; Bob's product prices will never be negative again; and Charlie passed all 17 endpoints in his security audit.


3. Quick Reference for Validation Rules

(1) Common Rules

Rule Description Example
required Required 'name' => 'required'
email Email Format 'email' => 'required|email'
unique:table,column Unique 'slug' => 'unique:shops,slug'
exists:table,column Exists 'shop_id' => 'exists:shops,id'
min:n Minimum Value/Length 'price' => 'numeric|min:0'
max:n Maximum Value/Length 'name' => 'string|max:255'
numeric Number 'quantity' => 'required|numeric'
integer Integer 'page' => 'integer|min:1'
string String 'name' => 'required|string'
boolean Boolean 'is_active' => 'boolean'
date Date 'published_at' => 'date'
file File 'logo' => 'file|max:2048'
image Image file 'photo' => 'image|mimes:jpeg,png'
confirmed Secondary Confirmation 'password' => 'confirmed'
regex:pattern Regular Expressions 'phone' => 'regex:/^[0-9]{10}$/'
in:a,b,c Enumeration Value 'status' => 'in:active,suspended'

(2) Verify the pipeline process

100%
flowchart TD
    A[Request Input] --> B[Validate Rules]
    B -->|Pass| C[Sanitized Data]
    C --> D[Controller Logic]
    B -->|Fail| E[Redirect Back with Errors]
    E --> F["Display \$errors in View"]

(1) ▶ Example: ShopMetrics Product Creation Validation

PHP
// Inline validation in controller
public function store(Request $request): RedirectResponse
{
    $validated = $request->validate([
        'name' => 'required|string|max:255',
        'sku' => 'required|string|unique:products,sku',
        'price' => 'required|numeric|min:0.01|max:999999.99',
        'stock' => 'required|integer|min:0',
        'category_id' => 'required|exists:categories,id',
        'description' => 'nullable|string|max:5000',
        'is_active' => 'boolean',
    ]);

    $product = Product::create($validated);

    return redirect()->route('products.show', $product)
        ->with('success', 'Product created.');
}

Output:

TEXT
// Execution Successful

4. Form Request Validation Class

(1) Create a Form Request

BASH
php artisan make:request StoreShopRequest
php artisan make:request UpdateShopRequest

(2) Defining Rules and Authorization

PHP
// app/Http/Requests/StoreShopRequest.php
class StoreShopRequest extends FormRequest
{
    public function authorize(): bool
    {
        return auth()->check() && auth()->user()->can('create', Shop::class);
    }

    public function rules(): array
    {
        return [
            'name' => 'required|string|max:255',
            'slug' => 'required|string|unique:shops,slug',
            'description' => 'nullable|string|max:5000',
            'domain' => 'nullable|url',
            'status' => 'in:active,suspended',
        ];
    }

    public function messages(): array
    {
        return [
            'name.required' => 'Shop name is required.',
            'slug.unique' => 'This URL slug is already taken.',
            'domain.url' => 'Please enter a valid URL.',
        ];
    }
}

(3) Using it in the controller

PHP
public function store(StoreShopRequest $request): RedirectResponse
{
    // $request->validated() only contains validated data
    $shop = Shop::create($request->validated());
    return redirect()->route('shops.show', $shop);
}
Dimension Inline Validation Form Request
Location Within the controller method Separate class file
Reusability Low ✅ Reusability across multiple controllers
Authorization Check Manual ✅ authorize()
Controller Code Longer Streamlined
Suitable for Simple verification Complex/reusable verification

(1) ▶ Example: ShopMetrics StoreShopRequest

PHP
// app/Http/Requests/StoreShopRequest.php
class StoreShopRequest extends FormRequest
{
    public function authorize(): bool
    {
        return auth()->user()->role === 'tenant_owner';
    }

    public function rules(): array
    {
        return [
            'name' => 'required|string|max:255',
            'slug' => 'required|alpha_dash|unique:shops,slug,NULL,id,tenant_id,' . tenant()->id,
            'description' => 'nullable|string|max:5000',
            'status' => 'sometimes|in:active,suspended',
        ];
    }

    public function messages(): array
    {
        return [
            'slug.unique' => 'You already have a shop with this slug.',
            'slug.alpha_dash' => 'Slug can only contain letters, numbers, dashes.',
        ];
    }

    protected function prepareForValidation(): void
    {
        $this->merge([
            'tenant_id' => tenant()->id,
            'slug' => Str::slug($this->slug ?? $this->name),
        ]);
    }
}

Output:

TEXT
// Execution Successful

5. Custom Validation Rules

(1) Closure Rules

PHP
// Inline custom rule
$validated = $request->validate([
    'discount' => [
        'required',
        'numeric',
        'min:0',
        function (string $attribute, mixed $value, Closure $fail) {
            if ($value > request('subtotal')) {
                $fail('The discount cannot exceed the subtotal.');
            }
        },
    ],
]);

(2) Rule Object

BASH
php artisan make:rule ValidCouponCode
PHP
// app/Rules/ValidCouponCode.php
class ValidCouponCode implements ValidationRule
{
    public function validate(string $attribute, mixed $value, Closure $fail): void
    {
        $coupon = Coupon::where('code', $value)
            ->where('expires_at', '>', now())
            ->where('usage_limit', '>', DB::raw('usage_count'))
            ->first();

        if (!$coupon) {
            $fail('This coupon code is invalid or expired.');
        }
    }
}

// Usage
public function rules(): array
{
    return [
        'coupon_code' => ['nullable', 'string', new ValidCouponCode()],
    ];
}
Method Suitable Scenarios Reusability
Closure Simple one-time logic Low
Rule Object Complex/Reusable Logic
Rule::class Method Database-related validation

(3) Rule Class Methods

PHP
use Illuminate\Validation\Rule;

// Unique ignoring current model
'slug' => Rule::unique('shops', 'slug')->ignore($shop->id),

// In with dynamic values
'status' => Rule::in(['active', 'suspended', 'closed']),

// Exists with additional query
'shop_id' => Rule::exists('shops', 'id')->where(function ($query) {
    $query->where('tenant_id', tenant()->id);
}),

(1) ▶ Example: ShopMetrics Order Validation Rules

PHP
// app/Http/Requests/StoreOrderRequest.php
class StoreOrderRequest extends FormRequest
{
    public function rules(): array
    {
        return [
            'items' => 'required|array|min:1',
            'items.*.product_id' => [
                'required',
                'integer',
                Rule::exists('products', 'id')->where('is_active', true),
            ],
            'items.*.quantity' => 'required|integer|min:1|max:100',
            'coupon_code' => ['nullable', 'string', new ValidCouponCode()],
            'discount' => [
                'sometimes',
                'numeric',
                'min:0',
                function ($attribute, $value, $fail) {
                    if ($value > $this->input('subtotal', 0)) {
                        $fail('Discount cannot exceed subtotal.');
                    }
                },
            ],
        ];
    }
}

Output:

TEXT
// Execution Successful

6. Handling Error Messages

(1) Web Page Display Errors

HTML
<!-- Display all errors -->
@if ($errors->any())
    <div class="alert alert-error">
        <ul>
            @foreach ($errors->all() as $error)
                <li>{{ $error }}</li>
            @endforeach
        </ul>
    </div>
@endif

<!-- Display error for specific field -->
<input type="text" name="name" value="{{ old('name') }}"
       class="{{ $errors->has('name') ? 'border-red-500' : '' }}">
@error('name')
    <p class="text-red-500 text-sm">{{ $message }}</p>
@enderror

(2) API JSON Error Response

JSON
{
    "message": "The given data was invalid.",
    "errors": {
        "name": ["The name field is required."],
        "email": ["The email must be a valid email address."]
    }
}
Request Type Validation Failure Behavior Invalid Format
Web Forms Redirect Back to Form + Display Errors $errors View Variables
API JSON Returns 422 + JSON {"errors": {...}}
AJAX Returns 422 + JSON Same as API

(1) ▶ Example: Handling Validation Errors in the ShopMetrics API

PHP
// app/Http/Controllers/Api/ShopController.php
public function store(StoreShopRequest $request): JsonResponse
{
    $shop = Shop::create($request->validated());

    return response()->json([
        'message' => 'Shop created successfully.',
        'data' => new ShopResource($shop),
    ], 201);
}

// Client receives 422 on validation failure:
// {
//   "message": "The slug has already been taken.",
//   "errors": {
//     "slug": ["The slug has already been taken."]
//   }
// }

Output:

TEXT
// Execution Successful

7. Condition Validation

(1) Sometimes Rule

PHP
// Only validate if field is present
$validated = $request->validate([
    'name' => 'required|string',
    'notes' => 'sometimes|nullable|string|max:5000',
]);

// Conditional rules based on another field
Validator::make($data, [
    'payment_method' => 'required|in:credit_card,bank_transfer',
    'card_number' => 'required_if:payment_method,credit_card|numeric',
    'bank_account' => 'required_if:payment_method,bank_transfer|numeric',
]);

(2) Bail Rules

PHP
// Stop validating after first failure on a field
$validated = $request->validate([
    'email' => 'bail|required|email|unique:users',
    // If required fails, email and unique won't run
]);
Rule Function Use Case
sometimes Validate only if the field exists Optional field
bail Stop after first failure Expensive verification
required_if Required field Payment method → Card number
required_unless Field is optional
required_with Required Confirm Password
prohibited_if Condition Prohibited
exclude_if Exclude conditions Do not write to validated
nullable Allows null Optional field

(1) ▶ Example: ShopMetrics Conditional Validation Scenario

PHP
// app/Http/Requests/UpdateSubscriptionRequest.php
class UpdateSubscriptionRequest extends FormRequest
{
    public function rules(): array
    {
        return [
            'plan_id' => 'required|exists:plans,id',
            'payment_method' => 'required|in:credit_card,paypal,bank_transfer',
            'card_number' => 'required_if:payment_method,credit_card|string|size:16',
            'card_cvv' => 'required_if:payment_method,credit_card|string|size:3',
            'paypal_email' => 'required_if:payment_method,paypal|email',
            'bank_account' => 'required_if:payment_method,bank_transfer|string',
            'coupon_code' => 'sometimes|nullable|string|max:50',
        ];
    }
}

Output:

TEXT
// Execution Successful

8. Comprehensive Example: The Complete ShopMetrics Verification Process

PHP
// ============================================
// Comprehensive: ShopMetrics Order Validation
// Covers: Form Request, custom rules, conditional, errors
// ============================================

// app/Rules/SufficientStock.php
class SufficientStock implements ValidationRule
{
    public function validate(string $attribute, mixed $value, Closure $fail): void
    {
        $productId = request()->input(str_replace('.quantity', '.product_id', $attribute));
        $product = Product::find($productId);
        if ($product && $value > $product->stock) {
            $fail("Only {$product->stock} units available for {$product->name}.");
        }
    }
}

// app/Http/Requests/StoreOrderRequest.php
class StoreOrderRequest extends FormRequest
{
    public function authorize(): bool
    {
        return auth()->check();
    }

    public function rules(): array
    {
        return [
            'items' => 'required|array|min:1|max:50',
            'items.*.product_id' => [
                'required',
                'integer',
                Rule::exists('products', 'id')->where('is_active', true),
            ],
            'items.*.quantity' => [
                'required',
                'integer',
                'min:1',
                'max:100',
                new SufficientStock(),
            ],
            'coupon_code' => 'sometimes|nullable|string|max:50',
            'notes' => 'sometimes|nullable|string|max:1000',
            'shipping_address.line1' => 'required|string|max:255',
            'shipping_address.city' => 'required|string|max:100',
            'shipping_address.zip' => 'required|string|max:20',
            'shipping_address.country' => 'required|string|size:2',
        ];
    }

    public function messages(): array
    {
        return [
            'items.required' => 'Your cart is empty.',
            'items.min' => 'Add at least one item to place an order.',
            'items.*.product_id.exists' => 'One of the selected products is unavailable.',
            'shipping_address.line1.required' => 'Street address is required.',
        ];
    }
}

// Controller stays clean
public function store(StoreOrderRequest $request): RedirectResponse
{
    $order = $this->orderService->createFromRequest($request);
    return redirect()->route('orders.show', $order)
        ->with('success', 'Order placed successfully!');
}

❓ FAQ

Q How do I choose between validate() and Form Request?
A For simple validation (3-5 rules), use validate() directly; for complex validation (10+ rules, reusability, or authorization checks), use Form Request. Form Request is the best practice.
Q How do I exclude the current record when updating a "unique" rule?
A Use the Rule::unique('shops', 'slug')->ignore($shop->id) or unique:shops,slug,{shop} format. The update validation must exclude the current record; otherwise, validation will always fail.
Q What format is returned when API authentication fails?
A Laravel automatically returns a 422 status code + JSON: {"message":"...","errors":{"field":["error message"]}}. The front end should parse the errors object based on the 422 status code to display field-level errors.
Q What is the purpose of prepareForValidation?
A It modifies the input data before validation—for example, by automatically generating a slug, formatting a phone number, or adding default values. The modified data is then used in the validation process and in the output of validated().
Q How do I test validation rules during testing?
A Use $this->postJson() to submit invalid data; verify that a 422 error and the error message $response->assertJsonValidationErrors('name') are returned. You can also use Validator::make() to test the rules directly.
Q Do too many validation rules affect performance?
A unique and exists rules query the database, which incurs a performance overhead. For high-frequency APIs, consider using bail to terminate the operation early or caching validation results. In most scenarios, performance is not an issue.

📖 Summary


📝 Exercises

  1. Basic Exercise (⭐): Create a StoreProductRequest for a ShopMetrics product, including validation rules for name, sku, price, stock, and category_id, to ensure that the SKU is unique and the price is a positive number.

  2. Advanced Exercise (⭐⭐): Create a ValidCouponCode rule object to verify that a coupon has not expired and still has remaining uses. Apply this rule to a StoreOrderRequest and test the responses for valid and invalid coupons.

  3. Challenge (⭐⭐⭐): Implement the SufficientStock Rule (to verify that the order quantity does not exceed inventory) on the items.*.quantity fields of StoreOrderRequest to handle inventory contention issues when multiple items are ordered simultaneously.

Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏