DEV Community

Ghulam Mujtaba
Ghulam Mujtaba

Posted on

Automatically Redirect Back Upon Failed Validation

Form Validation Refactoring

Once again, in the same controller that handles logging in users, I will point your attention to all aspects of form validation. In the future, when you want to create multiple forms in your application, you may need to initiate the form, validation, event, and duplicate part of the code to get back the last submitted form and redirect it to the desired page in every single controller if you work using this file. According to 'store.php', first, we are creating a new form, then validating it. We can refactor this to make it look better and easier to understand, and add a single place to put errors and the last submitted form, and redirect the user after this. To refactor this, we have to remove the first grab out parameters of the first if conditional and remove the conditional. Then, we have to inline the new form and validate statement as:

LoginForm::validate($email, $password);
Enter fullscreen mode Exit fullscreen mode

LoginForm Class

Let's see if it can work. Go to LoginForm.php and change the public function validate() to public static function validate(). Then, make all the data inside it static by using a static constructor. Next, create a __construct() method that can accept email and password in a single array $attribute. Also, the validate method can accept an array of attributes, then move up to the static construct method.

public static function validate($attributes)
    {
        $instance = new static($attributes);

        if($instance->failed()){
            throw new ValidationException();
        }
        
       return $instance;
Enter fullscreen mode Exit fullscreen mode

Try-catch Block

So that when you call a static constructor, you firstly have to instantiate a class with a dedicated instance and use the count of errors to check for errors and return a value in Boolean (true or false). If any validation fails, then throw a new exception in LoginForm.php as 'throw new /Exception' (since we are using ValidationException right now in project).

try {

    $form = LoginForm::validate($attributes = [
'email' => $_POST['email'],
'password' => $_POST['password']
]);
} catch (ValidationException $exception) {
    Session::flash('errors', $form->errors());
    Session::flash('old', [
        'email' => $attributes['email']
    ]);
        return redirect('/login');
}

Enter fullscreen mode Exit fullscreen mode

Go to the browser and reload it. It shows an error as an undefined variable '$form'. But we have created and initialized it equal to whatever is returned through the validation method. At the place where we throw a new exception and at the place where we handle this exception, we can't access that variable. To prove this, add a code statement 'dd($form)'. It returns 'Null', and we don't want this value.

ValidationException Class

At this stage, the ValidationException file is empty, so the errors are not accessible, and the last submitted form is not showing to the user as we can't access errors off the exception. To solve this issue, using the ValidationException class directory, we have to initialize the errors and old data as public read-only arrays, as their values are declared once by the developer and are not updated by the user. And we need a static function that uses the throw method to throw errors and old data.

namespace Core;

class ValidationException extends \Exception
{
    public readonly array $errors;
    public readonly array $old;   
    public static function throw($errors, $old){
        $instance = new static;
        $instance-> errors = $errors;
        $instance->old=$old; 
        throw $instance;
        ;
    }
}

Enter fullscreen mode Exit fullscreen mode

And add $exception at the place of the $form variable in the catch method. You can check that the project is working well and everything is showing on the output screen.

Refactor the Try-catch

Now we can refactor the Try-catch block to the upper level. So that we don't have to do this all in the controller logic. For this, go to 'public/index.php' in this file, grab the statement that directs to routing in the try block and catch uses ValidationException, grab the last added catch code to this as the entry point for the project. As the Try-catch block is used in 'public/Index.php', then there is no need to put it in the controller logic, remove this from that file. It redirects to the login page only as it is for LoginForm. To move the user back to the desired page, check which keyword or statement is used by 'dd('$_SERVER')' in the catch block. It shows 'HTTP_REFERER' to redirect it to the login page. As we have a router class, tell it where to redirect the user and what was the previous URL.

try {
    $router->route($uri, $method);
} catch (ValidationException $exception) {
    Session::flash('errors', $exception->errors);
    Session::flash('old', $exception->old);

    return redirect($router->previousUrl());
}

Enter fullscreen mode Exit fullscreen mode

Routing

Add previous URL method in router class and add statement that tells what to return.

public function previousUrl()
    {
        return $_SERVER['HTTP_REFERER'];
    }
Enter fullscreen mode Exit fullscreen mode

LoginForm update

Now, move to the LoginForm and grab the code present in the if validation failed block. Add a method named failed() in this file and paste the grabbed code here. Then, you can inline the present validation method. The error method returns errors, so you can access them anywhere.

public function throw()
    {
        ValidationException::throw($this->errors(), $this->attributes);
    }

    public function failed()
    {
        return count($this->errors);
}
Enter fullscreen mode Exit fullscreen mode

Update controller

Go to the controller file and grab the statement out of the condition and assign it to a variable '$signedIn' that checks whether the user is attempting to log in. If the user is not signed in, then throw an error. The 'LoginForm.php' file becomes:

$signedIn = (new Authenticator)->attempt(
    $attributes['email'], $attributes['password']
);
if (!$signedIn) {
    $form->error(
        'email', 'No matching account found for that email address and password.'
    )->throw();
Enter fullscreen mode Exit fullscreen mode

After refactoring now you can see the look of code is totally changed and looks better than first and hope so that it works.

I hope that you have clearly understood it.

Top comments (0)