Catching multiple exceptions at the same time and for each, call a get method to retrieve a validation error message

1.1k Views Asked by At

I’m using Slim PHP to try and generate and display multiple validation messages at the same time when a user inputs incorrect or no data into a sign up form on my Angular front-end application.

For example if a user leaves the username and password field blank it should have two error messages for both the username and password field being empty, but only the username error if the username error is blank and the password is filled.

The way I’ve implemented it so far is by throwing two exceptions and catching them in my authentication controller. Below is all the logic to make this happen when a user clicks submit on the form.

The two exceptions:

InvalidUsername.php

class InvalidUsername extends \LogicException {
    public function __construct(string $message, $code = 0, Throwable $previous = null) {
        parent::__construct($message, $code, $previous);
    }

    public function getUsernameError() {
        return $this->message;
    }
}

InvalidPassword.php

class InvalidPassword extends \LogicException {
    public function __construct(string $message, $code = 0, Throwable $previous = null) {
        parent::__construct($message, $code, $previous);
    }

    public function getPasswordError(){
        return $this->message;
    }
}

Where the two exceptions are called:

Username.php

class Username {
    private $username, $error_message;

    public function __construct(string $tainted_username) {
        $cleaned_username = filter_var($tainted_username, FILTER_SANITIZE_STRING);

        if($this->isUsernameValid($cleaned_username))
            throw new InvalidUsername($this->error_message);

        $this->username = $cleaned_username;
    }

    private function isUsernameValid($cleaned_username) {
        return $this->isEmpty($cleaned_username) || $this->isTooShort($cleaned_username) ||
        $this->isTooLong($cleaned_username) ? true : false;
    }

    private function isEmpty(string $username) {
 return empty($username) ?
            true && $this->error_message = 'A username is required' : false;
    }

    public function __toString() {
        return $this->username;
    }
}

Password.php

class Password {
    private $password, $error_message;

    public function __construct(string $tainted_password) {
        $cleaned_password = filter_var($tainted_password, FILTER_SANITIZE_STRING);

        if ($this->isPasswordValid($cleaned_password))
        throw new InvalidPassword($this->error_message);

        $this->password = $cleaned_password;
    }

    private function isPasswordValid($cleaned_password) {
        return $this->isEmpty($cleaned_password) || $this->isTooShort($cleaned_password) ||
        $this->isTooLong($cleaned_password) ? true : false;
    }

    private function isEmpty(string $password) {
        return empty($password) ?
            true && $this->error_message = 'A password is required' : false;
    }

    public function __toString() {
        return $this->password;
    }
}

The service and controller classes:

AuthenticateService.php

class AuthenticationService {
    protected $userMapper;

    public function __construct(UserMapper $userMapper) {
        $this->userMapper = $userMapper;
    }

    public function registerUser (string $tainted_username, string $tainted_password) {
        $username = new Username($tainted_username);
        $password = new Password($tainted_password);

        $user = new User($email, $username, $password);

        return $this->userMapper->saveUser($user);
    }
}

AuthenticateController.php

class AuthenticationController {
    private $authenticationService;

    public function __construct(AuthenticationService $authenticationService) {
        $this->authenticationService = $authenticationService;
    }

    public function registerUser($request, $response) {
        $tainted_username = $request->getParsedBody()['username'];
        $tainted_password = $request->getParsedBody()['password'];
        $validationResponse = null;

        try {
            $this->authenticationService->registerUser($tainted_username, $tainted_password);
        }

        catch(InvalidUsername | InvalidPassword $e) {
                $validationResponse = ['usernameError' => true, 'usernameMessage' => $e->getUsernameError(),
                    'passwordError' => true, 'passwordMessage' => $e->getPasswordError()];
        }
        return $jsonResponse = $response->withJson($validationResponse);
    }
}

However it’s not really working as intended. I think the problem lies in the catch statement of the registerUser() method in the AuthenticationController class.

If I leave both the username and password blank and submit I get this response: message: Call to undefined method App\Models\Exceptions\InvalidUsername::getPasswordError()

However if I fill in the username field and leave the password blank I get this response: message: Call to undefined method App\Models\Exceptions\InvalidPassword::getUsernameError()

And if I leave the username field blank and fill in the password field I get this response: message: Call to undefined method App\Models\Exceptions\InvalidUsername::getPasswordError()

It seems to be that I can’t catch both the InvalidUsername and InvalidPassword exceptions at the same time. It seems to catch one exception then tries to call methods that are intended for the other exception.

Is there anyway I can get it so the InvalidUsername and the InvalidPassword are both caught at the same time, so both the getUsernameError() and getPasswordError() can be called and used to set the outgoing json response?

Before I was using the Exception getMessage() method, which did work, but it only made it possible for one error message to be set and displayed at a time. For example it started with username errors, then if the username field was correctly filled out, it then transitioned to the password errors, almost like the validation messages were being set and displayed in a sequence. I soon realised this was probably because the getMessage was final, which is why I have the getUsernameError() and getPasswordError() methods in each of the exceptions.

Any help would be appreciated.

1

There are 1 best solutions below

5
On

Try this for registerUser method:

public function registerUser($request, $response)
{
    $tainted_username   = $request->getParsedBody()['username'];
    $tainted_password   = $request->getParsedBody()['password'];
    $validationResponse = [];

    try {
        $this->authenticationService->registerUser($tainted_username, $tainted_password);
    } catch (InvalidUsername $e) {

        $validationResponse['usernameError'] = true;
        $validationResponse['usernameMessage'] = $e->getUsernameError();
    }
    catch (InvalidPassword $e) {
        $validationResponse['passwordError'] = true;
        $validationResponse['passwordMessage'] = $e->getPasswordError();
    }

    return $jsonResponse = $response->withJson($validationResponse);
}

I'd also recommend to use generic message getters names, instead of getPasswordError() and getUsernameError() why not use generic getMessage() from Exception class? Class name InvalidUsername already tells you that the object is about username, you don't need to duplicate this information in method names.