Merge Form Request Validation for store and update

3.1k views Asked by At

I am using Request validation to validate the user's input.

This is UpdateUser:

<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Gate;

class UpdateUser extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize()
    {
        return Gate::allows('update-user');
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {

        $user_id = Arr::get($this->request->get('user'), 'id');

        return [
            'user.firstname'      => 'required|string|max:255',
            'user.lastname'       => 'required|string|max:255',
            'user.email'          => "required|string|email|max:255|unique:users,email,{$user_id}",
            'user.password'       => 'sometimes|nullable|string|min:4|confirmed',
        ];
    }
}

As you can see, there is some update-specific stuff happening:

The authorize() method checks whether the user is allowed to update-user and inside the rules I am excluding the row of the current user from being unique:

'user.email'          => "required|string|email|max:255|unique:users,email,{$user_id}",

As I would like to merge UpdateUser and StoreUser, what would be the most efficient and readable way to determine, whether I am updating or saving?

This is my current approach:

<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Gate;

class UpdateUser extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize()
    {
        if($this->isUpdating())
        {
            return Gate::allows('update-user');
        }

        return Gate::allows('create-user');
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        if($this->isUpdating()){
            $user_id = Arr::get($this->request->get('user'), 'id');

            return [
               ...
            ];
        }

        return [];
    }
    
    /**
     * @return bool
     */
    protected function isUpdating(){
        return $this->isMethod('put') || $this->isMethod('patch');
    }
}

I am wondering if I may extend the FormRequest class and provide isUpdating() by default.

3

There are 3 answers

0
STA On BEST ANSWER

Your update and store method are not the same request type, you have PUT and PATCH method on your request instance, so you can check the request type as like :

switch ($request->method()) {
   case 'PATCH':
        // do anything in 'patch request';
        break;

   case 'PUT':
        // do anything in 'put request';
        break;

   default:
       // invalid request
       break;
}
0
Donkarnash On

I learnt about a new approach to validation some time ago using separate validator class and I kinda like it a lot. Let me show you

Create a directory Validators and a class inside UserValidator

class UserValidator
{
    public function rules(User $user)
    {
        return [
            'user.firstname' => [
                'required',
                'string',
                'max:255',
            ],

            'user.lastname' => [
                'required',
                'string',
                'max:255',
            ],

            'user.email' => [
                $user->exists ? 'sometimes' : null,
                'required',
                'string',
                'email',
                'max:255',
                Rule::unique('users', 'email')->ignore($user->exists ? $user->id : null)
            ],

            'user.password' => [
                $user->exists ? 'sometimes' : null,
                'required',
                'string',
                'min:8'
            ],
        ];
    }

    public function validate(array $data, User $user)
    {
        return validator($data, $this->rules($user))            
            //->after(function ($validator) use ($data, $user) {
            //    Custom validation here if need be
            //})
            ->validate();
    }
}

Then authorization can be done in Controller

class UserController
{
    use AuthorizesRequests;

    /**
     * @param Request $request
     */
    public function store(Request $request)
    {
        $this->authorize('create_user', User::class);

        $data = (new UserValidator())->validate(
            $request->all(),
            $user = new User()
        );

        $user->fill($data)->save();
    }

    /**
     * @param Request $request
     * @param \App\user $user
     */
    public function update(Request $request, User $user)
    {
        $this->authorize('update_user', $user);

        $data = (new UserValidator())->validate(
            $request->all(),
            $user
        );

        $user->fill($data)->save();
    }
}

This was proposed and explained by (twitter handle) @themsaid

0
aitbella On

because i'm using file upload in update i cant use put so both route are POST that why i use route name

$id= $this->route('product'); 
$rules= [
    'name' => 'required',
    'sku' =>'required|unique:products,sku,' .$id,
    'image' => 'nullable|image|max:1024', // Max 1MB
];
if(Route::currentRouteName() == "products.store"){
    $rules['sku'] = 'required|unique:products';
};

return $rules;

and the samething goes for authorization