Laravel 5 FormRequest validator with multiple scenarios

2k Views Asked by At

I would like to ask how should I handle validation on multiple scenarios using FormRequest in L5? I know and I was told that I can create saparate FormRequest files to handle different validations but it is very redundant and also noted that I would need to inject it into the controller manually using the use FormRequest; keyword. What did previously in L4.2 is that I can define a new function inside my customValidator.php which then being called during controller validation via trycatch and then the data is being validated by service using the below implementation.

class somethingFormValidator extends \Core\Validators\LaravelValidator 
{
    protected $rules = array(
        'title'             =>  'required',
        'fullname'          =>  'required',
        // and many more
        );


    public function scenario($scene)
    {
        switch ($scene) {
            case 'update':
                $this->rules = array(
                        'title'             =>  'required',
                        'fullname'          =>  'required',
                        // and other update validated inputs
                    break;
        }

        return $this;
    } 
}

Which then in my LaravelValidator.php

<?php namespace Core\Validators;

use Validator;

abstract class LaravelValidator {

        /**
         * Validator
         *
         * @var \Illuminate\Validation\Factory
         */
        protected $validator;

        /**
         * Validation data key => value array
         *
         * @var Array
         */
        protected $data = array();

        /**
         * Validation errors
         *
         * @var Array
         */
        protected $errors = array();

        /**
         * Validation rules
         *
         * @var Array
         */
        protected $rules = array();

        /**
         * Custom validation messages
         *
         * @var Array
         */
        protected $messages = array();

        public function __construct(Validator $validator)
        {
                $this->validator = $validator;
        }

        /**
         * Set data to validate
         *
         * @return \Services\Validations\AbstractLaravelValidator
         */
        public function with(array $data)
        {
                $this->data = $data;

                return $this;
        }

        /**
         * Validation passes or fails
         *
         * @return Boolean
         */
        public function passes()
        {
                $validator = Validator::make(
                        $this->data,
                        $this->rules,
                        $this->messages
                );

                if ($validator->fails())
                {
                        $this->errors = $validator->messages();

                        return false;
                }

                return true;
        }

        /**
         * Return errors, if any
         *
         * @return array
         */
        public function errors()
        {
                return $this->errors;
        }

}

and then finally this is how i call the scenarios inside services like this

public function __construct(somethingFormValidator $v)
    {
        $this->v = $v;
    }

public function updateSomething($array)
    {
         if($this->v->scenario('update')->with($array)->passes())
          {
            //do something
          else 
          {
            throw new ValidationFailedException(
                    'Validation Fail',
                    null,
                    $this->v->errors()
                    );
          }
    }

So the problem is now since i have migrated to L5 and L5 uses FormRequest, how should I use scenario validation in my codes?

<?php namespace App\Http\Requests;

use App\Http\Requests\Request;

class ResetpasswordRequest extends Request {

    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize()
    {
        return true;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        return [
        'login_email'           =>  'required',
        'g-recaptcha-response'  =>  'required|captcha',
        ];
    }


    public function messages()
    {
        return [
            'login_email.required'              =>  'Email cannot be blank',
            'g-recaptcha-response.required'     =>  'Are you a robot?',
            'g-recaptcha-response.captcha'      =>  'Captcha session timeout'
        ];
    }

    public function scenario($scene)
    {
        switch ($scene) {
            case 'scene1':
                $this->rules = array(
                        //scenario rules
                        );
                    break;
        }

        return $this;
    } 

}

also how should I call it in the controller?

public function postReset(ResetpasswordRequest $request)
    {
        $profile = ProfileService::getProfileByEmail(Request::input('login_email'));

        if($profile == null)
        {
            $e = array('login_email' => 'This email address is not registered');
            return redirect()->route('reset')->withInput()->withErrors($e);
        }
        else
        {
            //$hash = ProfileService::createResetHash($profile->profile_id);
            $time = strtotime('now');
            $ip = Determinator::getClientIP();
            MailProcessor::sendResetEmail(array('email' => $profile->email, 
                            'ip' => $ip, 'time' => $time,));
        }
    }
2

There are 2 best solutions below

0
On BEST ANSWER

I believe the real issue at hand is everything is validated through the form request object before it reaches your controller and you were unable to set the appropriate validation rules.

The best solution I can come up with for that is to set the validation rules in the form request object's constructor. Unfortunately, I am not sure how or where you are able to come up with the $scene var as it seems to be hard-coded in your example as 'update'.

I did come up with this though. Hopefully reading my comments in the constructor will help further.

namespace App\Http\Requests;

use App\Http\Requests\Request;

class TestFormRequest extends Request
{

    protected $rules = [
        'title'             =>  'required',
        'fullname'          =>  'required',
        // and many more
    ];

    public function __construct()
    {
        call_user_func_array(array($this, 'parent::__construct'), func_get_args());

        // Not sure how to come up with the scenario.  It would be easiest to add/set a hidden form field
        // and set it to 'scene1' etc...
        $this->scenario($this->get('scenario'));

        // Could also inspect the route to set the correct scenario if that would be helpful?
        // $this->route()->getUri();
    }

    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize()
    {
        return true;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        return $this->rules;
    }

    public function scenario($scene)
    {
        switch ($scene) {
            case 'scene1':
                $this->rules = [
                    //scenario rules
                ];
                break;
        }
    }
}
0
On

You can use laratalks/validator package for validation with multiple scenarios in laravel. see this repo