Laravel, Beyond Validation

Published on Dec-09-2019 – 8 minute read

Validation is an important aspect of any application. It streamlines the user based on some certain rules or logic. Out of the box, Laravel provides an easy way to implement validation in our applications. Laravel provides a validate method by the Illuminate\Http\Request object. If the validation rules pass, your code will keep executing normally; however, if validation fails, an exception will be thrown and the proper error response will automatically be sent back to the user.


public function store(Request $request)
{
    $request->validate([
        'title' => 'required|unique:posts|max:255',
        'body' => 'required',
    ]);

}

Let's Talk About Custom Validation Rule Strategies

Closure Based Validation Strategy

Now let write a rule that confirm if the password matches the current user's password


use Illuminate\Support\Facades\Hash;


public function __invoke(Request $request){

     $user  = $request->user();
     $this->validate($request,[
        'password'=>[
          'required',
          function($attribute,$value,$fail) use ($user){
              if(!Hash::check($value, $user->password)){
                 $fail('Current password is incorrect');
               }
           }
         ]
     ]);

   return back()->withStatus('Passed');
}

In the closure, we get three things,

  • Attribute: This is used to build your validation role or validation message

  • Value: This is your password, the value of your input

  • Fail: This returns fail if it actually fails otherwise we assume that it passes.

Globally Registering Rule Strategy

In the Providers/AppServiceProvider.php, we have the boot() method which is instantiated before our application runs. Under the boot method we will go ahead and extend the Validator method.


public function boot(){
	Validator::extend('current_password',function($attribute,$value,$parameter,$validator){
		return Hash::check($value, optional(auth()->user())->password),'Current					 	 
   		password is wrong');
	});
}

Then we can have this in our controller


public function __invoke(Request $request){

     $user  = $request->user();
     $this->validate($request,[
        'password'=>[
          'required',
	  'current_password',	
         ]
     ]);

   return back()->withStatus('Passed');
}

Converting to a Class

Extending a validator is fine if you have one or two rules to add. If you have a lot of custom rules or the custom rule do have a lot of things to do in the closure, you'd probably want to create a class for it. Let's create a new folder in our app directory App/Validators, then in the folder lets create a file called PasswordValidator.php


namespace App\Validators;
use Illuminate\Support\Facades\Hash;

class PasswordValidator
{
	public function validate($attribute, $value, $parameter, $validator){ 
	return Hash::check($value,optional(auth()->user())->password);	
	}
	
}


Then updating our boot method, we have


use App\Validators\PasswordValidator;

public function boot(){
	Validator::extend('current_password',PasswordValidator::class),'Current					 	 
   		password is wrong');
	});
}

Rule Object Strategy

In my case i prefer this because it comes with everything you need. Lets generate a new rule object with php artisan make:rule CurrentPassword. This will be created in your App/Rules folder which comes with a boilerplate. We can have our User injected as a dependency so that we can use it in our rule object.

public function __invoke(Request $request){

     $user  = $request->user();
     $this->validate($request,[
        'password'=>[
          'required',
	  new CurrentPassword($user);	
         ]
     ]);

   return back()->withStatus('Passed');
}

namespace App\Rules;

use Illuminate\Contracts\Validation\Rule;
use Illuminate\Support\Facades\Hash;
use App\User;

class CurrentPassword implements Rule
{

  protected $user;

    /**
     * Create a new rule instance.
     *
     * @return void
     */
    public function __construct(User $user)
    {
        $this->user = $user;
    }

    /**
     * Determine if the validation rule passes.
     *
     * @param  string  $attribute
     * @param  mixed  $value
     * @return bool
     */
    public function passes($attribute, $value)
    {
        return Hash::check($value, $this->user->password);
    }

    /**
     * Get the validation error message.
     *
     * @return string
     */
    public function message()
    {
        return 'Current password is wrong.';
    }
}

Conclusion

Customizing Laravel validation can be a little tricky, you just have to choose based on your need and how flexible you are with different strategies. If you actually want a clean nice code, i think Rule Object Strategy is fine.