wcobalt
wcobalt

Reputation: 501

Custom exception for each invalid input value

I have backend project in which there's own "parent-to-all exception" exists, something like this (also I have InvalidArgumentException derived from this exception):

class CaliException extends \Exception {
    private $caliCode;

    public function __construct(string $message = "", string $caliCode = self::UNDEFINED_CODE,
                                int $code = 0, Throwable $previous = null) {
        parent::__construct($message, $code, $previous);

        $this->caliCode = $caliCode;
    }

    function getCaliCode(): string {
        return $this->caliCode;
    }
}

It's clear from code above, that purpose of custom class is ability to keep string codes. Why do I need string codes? Because I have HTTP API which works in the following way:

  1. Get JSON request
  2. Do something
  3. Produce JSON output with 'state' field which contains either code of a thrown exception or the "200" response. These state codes are quite useful for client which can distinguish different problems and react to them accordingly.

So there's a problem. System above produces code like this:

class UsernameFormatException extends InvalidArgumentException {
    public const USERNAME_FORMAT_UNDEFINED_CODE = "DM:USERCA:0001";

    public function __construct(string $message = "", string $calistoCode = self::USERNAME_FORMAT_UNDEFINED_CODE,
                                int $code = 0, \Throwable $previous = null) {
        parent::__construct($message, $calistoCode, $code, $previous);
    }
}

class PasswordFormatException extends InvalidArgumentException {
    public const PASSWORD_FORMAT_UNDEFINED_CODE = "DM:USERCA:0002";

    public function __construct(string $message = "", string $calistoCode = self::PASSWORD_FORMAT_UNDEFINED_CODE,
                                int $code = 0, \Throwable $previous = null) {
        parent::__construct($message, $calistoCode, $code, $previous);
    }
}

class InvalidLastActivityTimestampException extends InvalidArgumentException {
    public const INVALID_TIMESTAMP = "DM:USERCA:0003";

    public function __construct(string $message = "", string $calistoCode = self::INVALID_TIMESTAMP,
                                int $code = 0, \Throwable $previous = null) {
        parent::__construct($message, $calistoCode, $code, $previous);
    }
}

class InvalidCreationTimestampException extends InvalidArgumentException {
    public const INVALID_TIMESTAMP = "DM:USERCA:0004";

    public function __construct(string $message = "", string $calistoCode = self::INVALID_TIMESTAMP,
                                int $code = 0, \Throwable $previous = null) {
        parent::__construct($message, $calistoCode, $code, $previous);
    }
}

As seen, for each invalid-argument-case I create new CaliException-derived exception. I consider it as not cool. Is it good? How can I improve the code?

Bonus question: I read that I should throw \InvalidArgumentException when it's a programmer's mistake, so an exception must be not caught. But in my code there's no \InvalidArgumentException only my own version of this. Is it acceptable in the context of best practices and so on? And how to distinguish when it's a programmer's mistake and when it's mistake of user (invalid user input)? (After all, any invalid values passed to a function are invalid inputs relatively to this function)

Upvotes: 1

Views: 789

Answers (1)

tereško
tereško

Reputation: 58444

From your description it seem that you are actually making something like a REST API. Or at least something inspired by that concept.

The thing about exceptions of InvalidArgumentException is that, someone passed you 2nd or 3rd hand knowledge. Those exceptions must be caught, but not at domain entity or service layers. Instead, you catch them at the point .. well .. bootstrap-level, where you render a "pretty error json" response with all the headers and status codes.

As for your original question. Yes, having very specific exceptions is OK. Though, I would recommend instead of PasswordFormatException to name it MalformedPassword. Basically, ditche the *Exeception suffix, for the same reason as you do not have class UserClass suffix - it is worthless, PHP has namespaces now (since 2005 .. I think).

This is just a quick'n'drunk comment. I am not even sure it answers your question.

Upvotes: 1

Related Questions