The most common mistakes with exceptions in PHP

Exceptions are an essential part of PHP development. They allow you to separate the normal processing of an application from error handling. However, many projects still use them incorrectly, or sometimes choose not to use them at all.

Here are the most common mistakes I have encountered, along with good practices to avoid them. Of course, this is based on my personal experience: some points may be debatable and may not be accepted by everyone.

Catching all exceptions without doing anything

It may seem surprising, but I have seen this more than once:

try {
     saveUser($user);
} catch (Exception $e) {
}

 

The problem is obvious: an error occurs, but nobody will know about it. The program continues as if nothing happened. At the very least, the error should be recorded in the logs:

try {
    saveUser($user);
} catch (Exception $e) {
    error_log($e);
}

   

However, there are some legitimate cases where an empty catch block can be acceptable, for example when we intentionally ignore an expected error:

try {
    unlink($file);
} catch (FileNotFoundException $e) {
    // The file no longer exists, this is not a problem
}

   

In this case, the exception represents an expected state and does not necessarily need to be logged. There are other cases where this can be acceptable, but it should be used sparingly to avoid creating confusing behavior.

Using exceptions to control program flow

An exception should not replace a simple condition check.

Bad example:

try {
    $user = getUser($id);
} catch (UserNotFoundException $e) {
    createUser();
}

   

If the absence of a user is a normal case, it is often better for the function to return `null` rather than throw an exception. Exceptions should be reserved for situations where normal processing cannot continue, and should not be used as a simple control flow mechanism. Otherwise, this can also harm readability.

Catching `Throwable` or `Exception` everywhere

I sometimes see:

try {
   process();
} catch (Throwable $e) {
    error_log($e);
}

// or:

catch (Exception $e)

This can hide important programming errors. It is often preferable to catch only the exceptions that you actually know how to handle:

catch (InvalidArgumentException $e) {
    // ...
}
catch (DatabaseException $e) {
    // ...

}

 

Throwing a too generic exception

I see this too often:

throw new Exception("Error");

This exception provides almost no information. Creating a specific exception makes the code much easier to understand:

throw new InvalidEmailException(
    "Invalid email address."
);

   

The calling code immediately understands the nature of the problem.

Losing the original exception

Let's take this example:

try {
    saveFile();
} catch (Exception $e) {
    throw new Exception("Upload failed");
}

The original exception is lost. The recommended approach is to keep it as the previous exception:

try {
    saveFile();
} catch (Exception $e) {
    throw new UploadException(
        "Upload failed",
        0,
        $e
    );
}

   

This preserves the original cause of the error and its complete context during debugging.

Using exceptions to handle cases that are not errors

An exception is often appropriate when a function receives invalid data and cannot continue its processing. For example:

function createUser(string $email)
{
    if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
        throw new InvalidArgumentException(
            "Invalid email address."
        );
    }
    // create user
}

Here, the caller provided an invalid argument. The function cannot fulfill its purpose with this value, so interrupting the process is logical.

However, exceptions should not be used as a simple control flow mechanism. Writing code like this is often unnecessarily complex:

try {
    validateEmail($email);
} catch (InvalidArgumentException $e) {
    // Display an error message to the user
}

If a validation error is an expected case and must be handled directly (for example, displaying a message in a form), a regular return value may sometimes be more appropriate.

The important thing is to distinguish invalid data that prevents an operation from working from a normal situation where a validation result is simply expected.

Forgetting the `finally` block

The `finally` block is executed whether or not an exception is thrown. It is essential for releasing resources:

$file = fopen("data.txt", "r");
try {
    processFile($file);
} finally {
    if (is_resource($file)) {
        fclose($file);
    }
}

Directly displaying an exception message

A common mistake is to directly display:

echo $e->getMessage();

During development, this can be convenient. In production, it can reveal:

  • server paths;
  • SQL queries;
  • table names;
  • sensitive information.

The user should receive a generic message that does not provide useful information to potential attackers, while the details should be stored in logs.

Mixing business errors and technical errors

These two types of errors should be distinguished.

A business error:

> Insufficient balance.

A technical error:

> Unable to connect to the database.

They should not be handled in the same way.

Never creating your own exceptions

PHP provides many built-in exceptions:

  • `InvalidArgumentException`
  • `RuntimeException`
  • `LogicException`
  • `OutOfBoundsException`

However, in an application, it is often useful to create specific exceptions:

final class PaymentFailedException extends RuntimeException
{

}

final class InvalidCouponException extends RuntimeException

{

}

The code becomes much more explicit and readable.

Always rethrowing an exception without adding information

Sometimes we see:

try {
    process();
} catch (Exception $e) {
    throw $e;
}

This block serves no purpose other than adding unnecessary code. An exception should only be caught if you add information, perform cleanup, or transform it into a more appropriate exception.

Conclusion

Exceptions are an excellent mechanism for handling errors, but they must be used carefully. They do not replace validation or good architecture.

When used correctly, exceptions make code more robust, more readable, and much easier to maintain. I admit that I sometimes use these bad practices myself; nobody is perfect 🙂