pidari
pidari

Reputation: 449

How do I catch multiple Exceptions in a validation form

I have a verification form for registering users and I am trying this concept of try-catching for validating input data in my model. Let me show you my code first:

Model Register

public function register (string $username, string $email, string $email_repeat, string $password, string $password_repeat, string $ip, string $reCaptcha) {
    $this->username = trim($username);
    $this->email = trim($email);
    $this->email_repeat = trim($email_repeat);
    $this->password = $password;
    $this->password_repeat = $password_repeat;
    $this->ip = $ip;
    $this->reCaptcha_response = $reCaptcha;
    $this->reCaptcha = new ReCaptcha(GOOGLE_CAPTCHA_SECRET); //the API secret is in the config.php file

    $this->verifyEmail();
    $this->verifyUsername();
    $this->verifyPassword();
    $this->validateReCaptcha($this->reCaptcha_response, $this->ip);

    $this->db->insertRow("INSERT INTO users (username, password, email) VALUES (?, ?, ?)", [$this->username, $this->password, $this->email]);
}

public function verifyEmail () {
    if(empty($this->email) || empty($this->email_repeat)) {
        throw new \Exception('Email address is empty');
    }

    $isTaken = $this->db->getRow("SELECT COUNT(*) as count FROM users WHERE email= ?", [$this->email]);
    if($isTaken->count > 0){
        throw new \Exception('This email is taken');
    }
}


private function verifyUsername () {
    if(strlen($this->username) < 3 || strlen($this->username) > 15) {
        throw new \Exception('Username must be between 3 and 15 symbols');
    }

    $isTaken = $this->db->getRow("SELECT COUNT(*) as count FROM users WHERE username= ?", [$this->username]);
    if($isTaken->count > 0){
        throw new \Exception('This username is taken');
    }
}

Controller:

public function register ()
{
    if($this->post != null) {
        try {
            $register = $this->tableRegister->register(
                $_POST['username'],
                $_POST['email'],
                $_POST['email_repeat'],
                $_POST['password'],
                $_POST['password_repeat'],
                $_SERVER['REMOTE_ADDR'],
                $_POST['g-recaptcha-response']
            );
        } catch (\Exception $err) {
            $this->flashMessage->error($err->getMessage());
        }
    }
    $this->renderView('users/register');
}

As you can see in the model, both methods validateEmail and validateUsername go through the database and check if the email or username are taken. If both of them are taken, I want to catch both of the exceptions, as well as every other exception that might occur - if email is taken, if username is taken, if username is invalid.. everything. Currently my script ends at the first exception. How can I do this?

Upvotes: 1

Views: 1391

Answers (2)

camelsWriteInCamelCase
camelsWriteInCamelCase

Reputation: 1665

When an exception is thrown, code following the statement will not be executed, and PHP will attempt to find the first matching catch block. If an exception is not caught, a PHP Fatal Error will be issued with an "Uncaught Exception ..." message, unless a handler has been defined with set_exception_handler() (source).

A good solution here is to add the errors property, hasErrors and getErrors methods for the model. This will allow you to store and to handle error check for each form field.

For example:

public function verifyEmail () {
    if(empty($this->email) || empty($this->email_repeat)) {
        $this->errors['email'] = 'Email address is empty';
        return false;
    }

    $isTaken = $this->db->getRow("SELECT COUNT(*) as count FROM users WHERE email= ?",
       [$this->email]);
    if($isTaken->count > 0){
        $this->errors['email'] = 'This email is taken';
        return false;
    }

    return true;
}

public function hasErrors() {
    return !empty($this->errors);
}

public function getErrors() {
    return $this->errors;
}

Add check to the register method in the model:

if (!$this->hasErrors()) {
    $this->db->insertRow("INSERT INTO users (username, password, email) VALUES (?, ?, ?)",
     [$this->username, $this->password, $this->email]);
}

Then add check in your controller:

if ($this->tableRegister->hasErrors()) { 
    $this->flashMessage->error('The form has error(s): '
        .implode("; \r\n", $this->tableRegister->getErrors()));
} else {
    // code for user registration here
}

Upvotes: 2

Luke
Luke

Reputation: 1181

You need to have a try-catch in your Model register function, e.g.

public function register (string $username, string $email, string $email_repeat, string $password, string $password_repeat, string $ip, string $reCaptcha) {
    try{
        $this->username = trim($username);
        $this->email = trim($email);
        $this->email_repeat = trim($email_repeat);
        $this->password = $password;
        $this->password_repeat = $password_repeat;
        $this->ip = $ip;
        $this->reCaptcha_response = $reCaptcha;
        $this->reCaptcha = new ReCaptcha(GOOGLE_CAPTCHA_SECRET); //the API secret is in the config.php file

        $this->verifyEmail();
        $this->verifyUsername();
        $this->verifyPassword();
        $this->validateReCaptcha($this->reCaptcha_response, $this->ip);

        $this->db->insertRow("INSERT INTO users (username, password, email) VALUES (?, ?, ?)", [$this->username, $this->password, $this->email]);
    } catch(\Exception ex){
        //handle error
        throw new \Exception("Error registering user"); //this will pass the exception up to the calling method instead of just killing the page
    }
}

If either verifyEmail or verifyUsername throw errors, these stop the rest of the code from executing. As they're not handled correctly, this stops execution of the entire page.

Hope this helps!

Upvotes: 0

Related Questions