Skip to content

Handling Requests in Laravel

1. Introduction

In Laravel, HTTP requests are typically handled with the Illuminate\Http\Request class:

php
use Illuminate\Http\Request;

This base class provides the features you need for most cases. When validation or authorization becomes more complex, prefer dedicated Form Request classes that extend Illuminate\Foundation\Http\FormRequest.

2. Avoid ->input(), ->get(), and ->all()

  • $request->input('field') / $request->get('field'): Return mixed, which weakens type safety.
  • $request->all(): Returns an array of all raw input without casting or filtering, which increases mass assignment risk, can expose sensitive fields, and makes data-tampering attacks easier.

Prefer these typed alternatives:

MethodReturn type
$request->string('name')->value()string (trimmed and cast)
$request->integer('age', $defaultValue)int
$request->float('price', $defaultValue)float
$request->boolean('flag', $defaultValue)bool
$request->array('items')array
$request->enum(MyEnum::class, 'status')MyEnum (or null if nullable)
$request->file('avatar')UploadedFile

These methods keep types safe and make the code clearer.

3. Validation rules

  • Always write rules as an array, not a pipe-separated string (|).
php
use Illuminate\Validation\Rule;
use App\Enums\UserStatus;
use App\Rules\IsValidDomain;

public function rules(): array
{
    return [
        'name' => ['required', 'string', 'max:255'],
        'email' => [
            'required',
            'email',
            Rule::unique('users', 'email')->ignore($this->user?->id),
        ],
        'password' => ['required', 'string', 'min:8', 'confirmed'],
        'status' => ['required', Rule::enum(UserStatus::class)],
        'website' => ['nullable', 'url', new IsValidDomain()],
        'category_id' => [
            'required',
            'integer',
            Rule::exists('categories', 'id')->where(function ($query) {
                return $query->where('is_active', true);
            }),
        ],
    ];
}

4. Extract complex logic into a Custom Rule

When validation logic becomes complex, move it out of the Form Request rules() method into a Custom Validation Rule. This separates concerns, improves reuse, keeps Form Requests readable, and makes the rule easy to unit test on its own.

Creating and using a Custom Rule

Create the class with Artisan:

bash
php artisan make:rule IsValidPromotionCode

The Rule class implements Illuminate\Contracts\Validation\Rule and defines passes($attribute, $value) and message():

php
<?php

namespace App\Rules;

use App\Models\Promotion;
use Illuminate\Contracts\Validation\Rule;

final class IsValidPromotionCode implements Rule
{
    public function passes($attribute, $value): bool
    {
        $promotion = Promotion::where('code', $value)
            ->where('expires_at', '>', now())
            ->where('is_active', true)
            ->first();

        return ! is_null($promotion);
    }

    public function message(): string
    {
        return 'The promotion code is invalid or has expired.';
    }
}

Use the rule in a Form Request:

php
<?php

namespace App\Http\Requests;

use App\Rules\IsValidPromotionCode;
use Illuminate\Foundation\Http\FormRequest;

final class ApplyPromotionRequest extends FormRequest
{
    public function authorize(): bool
    {
        return true;
    }

    /** @return array<string, mixed> */
    public function rules(): array
    {
        return [
            'product_id' => ['required', 'exists:products,id'],
            'quantity' => ['required', 'integer', 'min:1'],
            'promo_code' => ['nullable', 'string', 'max:50', new IsValidPromotionCode()],
        ];
    }
}

5. Type hints and validated data

  • Inject Request or a custom FormRequest into the controller/action.
  • Use $request->validated() to read only fields defined and checked in rules(), which reduces accidental access to disallowed fields and helps prevent mass assignment.
php
public function store(MyFormRequest $request): JsonResponse
{
    $data = $request->validated();

    return $this->ok(
        app(CreateUserAction::class)->execute($data),
        'User created successfully'
    );
}

6. API response

  • Always use the App\Concerns\HasApiResponse trait in Actions for consistent JSON responses.
  • Use self::ok($data, $message) for successful responses.
  • Use self::exception($e) inside a catch block to return detailed errors when an exception occurs.

Example in IndexProductAction:

php
use App\Concerns\HasApiResponse;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Lorisleiva\Actions\Concerns\AsAction;
use Throwable;

final class IndexProductAction
{
    use HasApiResponse, AsAction;

    public function asController(Request $request): JsonResponse
    {
        try {
            $query->standardQuery($request);
            $result = $this->buildResponseData($request, $query);

            return self::ok($result);
        } catch (Throwable $e) {
            return self::exception($e);
        }
    }
}

7. Best practices summary

  • Import: use Illuminate\Http\Request;
  • Do not use: ->input(), ->get(), ->all()
  • Use: ->string(), ->integer(), ->array(), ->enum()
  • Validation: array-form rules, Custom Rule
  • Logic: extract into Service/Action; do not put it in the Request
  • Type safety: PHPStan-friendly, easier to test and maintain