How to Efficiently Use Try...Catch Blocks in PHP

How to efficiently use try...catch blocks in PHP

Important note

The following discussion assumes that we are talking about code structured as in the example above: no matter which alternative is chosen, an exception will cause the method to logically stop doing whatever it was in the middle of.


As long as you intend to do the same thing no matter which statement in the try block throws an exception, then it's certainly better to use a single try/catch. For example:

function createCar()
{
try {
install_engine();
install_brakes();
} catch (Exception $e) {
die("I could not create a car");
}
}

Multiple try/catch blocks are useful if you can and intend to handle the failure in a manner specific to what exactly caused it.

function makeCocktail()
{
try {
pour_ingredients();
stir();
} catch (Exception $e) {
die("I could not make you a cocktail");
}

try {
put_decorative_umbrella();
} catch (Exception $e) {
echo "We 're out of umbrellas, but the drink itself is fine"
}
}

When to use Try Catch blocks

It seems to me that this topic is very strange and confused. Could someone lights me up?

Definitely. I'm not a PHP user, but I might have a little insight after having worked with try/catch in ActionScript, Java, and JavaScript. Bear in mind though, that different languages and platforms encourage different uses for try/catch. That said...

The only times I'd recommend using try/catch is if you're using a native language function that

  1. Can throw an error/exception
  2. Does not give you any tools to detect whether you're about to do something stupid that would cause that error/exception. eg: In ActionScript, closing a loader that is not open will result in an error but the loader doesn't have an isOpen property to check so you're forced to wrap it in try/catch to silence an otherwise totally meaningless error.
  3. The error/exception really is meaningless.

Let's take the examples you list and see how they square with that list.

I read someone saying that we should use try-catch blocks only to prevent fatal errors.

In the case of AS's loader.close() function, this is good advice. That's a fatal error, and all from an otherwise trivial misstep. On the other hand, virtually ALL errors in AS will bring your application to a halt. Would you then wrap them all in try/catch? Absolutely not! A "fatal error" is fatal for a reason. It means something terribly wrong has happened and for the application to continue on in a potentially "undefined" state is foolhardy. It's better to know an error happened and then fix it rather than just let it go.

I read someone else saying that we should use it only on unexpected errors

That's even worse. Those are presicely the errors you DON'T want to silence, because silencing them means that you're never going to find them. Maybe you're not swallowing them, though... maybe you're logging them. But why would you try/catch/log/continue as though nothing happened, allowing the program to run in a potentially dangerous and unexpected condition? Just let the error kick you in the teeth and then fix it. There's little more frustrating than trying to debug something that's wrong in a program that someone else wrote because they wrapped everything in a try/catch block and then neglected to log.

Others simply say that try-catch blocks should be used everywhere because they can be also extended (extending the Exception class).

There's potential merit to this if you're the one doing the throwing, and you're trying to alert yourself to an exceptional situation in your program... but why try/catch your own thrown error? Let it kick you in the teeth, then fix it so that you don't need to throw the error anymore.

Finally someone says that PHP try-catch block are totally useless because they are very bad implemented. (On this i find a nice SO question about performance).

Maybe so. I can't answer this one though.

So... this might be a bit of a religious question, and I'm certain people will disagree with me, but from my particular vantage point those are the lessons I've learned over the years about try/catch.

Performance of try-catch in php

One thing to consider is that the cost of a try block where no exception is thrown is a different question from the cost of actually throwing and catching an exception.

If exceptions are only thrown in failure cases, you almost certainly don't care about performance, since you won't fail very many times per execution of your program. If you're failing in a tight loop (a.k.a: banging your head against a brick wall), your application likely has worse problems than being slow. So don't worry about the cost of throwing an exception unless you're somehow forced to use them for regular control flow.

Someone posted an answer talking about profiling code which throws an exception. I've never tested it myself, but I confidently predict that this will show a much bigger performance hit than just going in and out of a try block without throwing anything.

Another thing to consider is that where you nest calls a lot of levels deep, it can even be faster to have a single try...catch right at the top than it is to check return values and propagate errors on every call.

In the opposite of that situation, where you find that you're wrapping every call in its own try...catch block, your code will be slower. And uglier.

Is it best practice to try - catch my entire PHP code, or be as specific as possible?

generally throw locally, catch globally unless an exception handler is specific to a function in which case handle locally.

 class fooException extends Exception{}

// DB CLASS

public function Open(){
// open DB connection
...
if ($this->Conn->connect_errno)
throw new fooException("Could not connect: " . $this->Conn->connect_error);
}

// MAIN CLASS

public final function Main(){
try{
// do stuff
}
catch(fooException $ex){
//handle fooExceptions
}
}

Error handling using try catch block in PHP

try..catch handles exceptions. None of the code you show will ever throw an exception. So the catch block will never be invoked. Errors are something else in PHP which are not exceptions. Errors can only be silenced (using @ or global error_reporting settings) or handled globally using a defined error handler.

try..catch simply isn't applicable to your code, however much you want it to be.


Having said that, you can use a custom error handler to turn any error into an exception. That's what the ErrorException class is for. See its example in the manual. That would enable you to use try..catch for everything. Arguably this isn't a bad idea, since PHP's split error/exception mechanism is weird.

PHP - Wrap a variable block in the same try-catch

Pass a callable, which can be an anonymous function, a regular function, or a class method:

function executeGuzzle(callable $fun) {
try {
return $fun();
} catch (ClientException $clientException) {
// Do stuff
} catch (ConnectException $connectException) {
// Do stuff
} catch (RequestException $requestException) {
// Do stuff
}
}

$res = executeGuzzle(function () use ($client) {
return $client->get(...);
});

What is the advantage of using try {} catch {} versus if {} else {}

I'd use the try/catch block when the normal path through the code should proceed without error unless there are truly some exceptional conditions -- like the server being down, your credentials being expired or incorrect. I wouldn't necessarily use it to handle non-exceptional errors -- say like the current user not being in the correct role. That is, when you can reasonably expect and handle an error that is not an exceptional condition, I think you should do your checks.

In the case that you've described -- setting up and performing a query, a try/catch block is an excellent way to handle it as you normally expect the query to succeed. On the other hand, you'll probably want to check that the contents of result are what you expect with control flow logic rather than just attempting to use data that may not be valid for your purpose.

One thing that you want to look out for is sloppy use of try/catch. Try/catch shouldn't be used to protect yourself from bad programming -- the "I don't know what will happen if I do this so I'm going to wrap it in a try/catch and hope for the best" kind of programming. Typically you'll want to restrict the kinds of exceptions you catch to those that are not related to the code itself (server down, bad credentials, etc.) so that you can find and fix errors that are code related (null pointers, etc.).

Continue with current iteration in a try-catch block | PHP

Put the try block into its own while (true) loop, which you can break on success:

while (true) {
try {
...
break;
} catch (...) {
...
}
}

This retries the same action until no exception is thrown. To avoid endless loops on actions which will simply never succeed, you can add a counter like:

$tries = 0;
while ($tries < 10) {
$tries++;
try { ... }
}

What are the best practices for catching and re-throwing exceptions?

You should not be catching the exception unless you intend to do something meaningful.

"Something meaningful" might be one of these:

Handling the exception

The most obvious meaningful action is to handle the exception, e.g. by displaying an error message and aborting the operation:

try {
$connect = new CONNECT($db, $user, $password, $driver, $host);
}
catch (Exception $e) {
echo "Error while connecting to database!";
die;
}

Logging or partial cleanup

Sometimes you do not know how to properly handle an exception inside a specific context; perhaps you lack information about the "big picture", but you do want to log the failure as close to the point where it happened as possible. In this case, you may want to catch, log, and re-throw:

try {
$connect = new CONNECT($db, $user, $password, $driver, $host);
}
catch (Exception $e) {
logException($e); // does something
throw $e;
}

A related scenario is where you are in the right place to perform some cleanup for the failed operation, but not to decide how the failure should be handled at the top level. In earlier PHP versions this would be implemented as

$connect = new CONNECT($db, $user, $password, $driver, $host);
try {
$connect->insertSomeRecord();
}
catch (Exception $e) {
$connect->disconnect(); // we don't want to keep the connection open anymore
throw $e; // but we also don't know how to respond to the failure
}

PHP 5.5 has introduced the finally keyword, so for cleanup scenarios there is now another way to approach this. If the cleanup code needs to run no matter what happened (i.e. both on error and on success) it's now possible to do this while transparently allowing any thrown exceptions to propagate:

$connect = new CONNECT($db, $user, $password, $driver, $host);
try {
$connect->insertSomeRecord();
}
finally {
$connect->disconnect(); // no matter what
}

Error abstraction (with exception chaining)

A third case is where you want to logically group many possible failures under a bigger umbrella. An example for logical grouping:

class ComponentInitException extends Exception {
// public constructors etc as in Exception
}

class Component {
public function __construct() {
try {
$connect = new CONNECT($db, $user, $password, $driver, $host);
}
catch (Exception $e) {
throw new ComponentInitException($e->getMessage(), $e->getCode(), $e);
}
}
}

In this case, you do not want the users of Component to know that it is implemented using a database connection (maybe you want to keep your options open and use file-based storage in the future). So your specification for Component would say that "in the case of an initialization failure, ComponentInitException will be thrown". This allows consumers of Component to catch exceptions of the expected type while also allowing debugging code to access all the (implementation-dependent) details.

Providing richer context (with exception chaining)

Finally, there are cases where you may want to provide more context for the exception. In this case it makes sense to wrap the exception in another one which holds more information about what you were trying to do when the error occurred. For example:

class FileOperation {
public static function copyFiles() {
try {
$copier = new FileCopier(); // the constructor may throw

// this may throw if the files do no not exist
$copier->ensureSourceFilesExist();

// this may throw if the directory cannot be created
$copier->createTargetDirectory();

// this may throw if copying a file fails
$copier->performCopy();
}
catch (Exception $e) {
throw new Exception("Could not perform copy operation.", 0, $e);
}
}
}

This case is similar to the above (and the example probably not the best one could come up with), but it illustrates the point of providing more context: if an exception is thrown, it tells us that the file copy failed. But why did it fail? This information is provided in the wrapped exceptions (of which there could be more than one level if the example were much more complicated).

The value of doing this is illustrated if you think about a scenario where e.g. creating a UserProfile object causes files to be copied because the user profile is stored in files and it supports transaction semantics: you can "undo" changes because they are only performed on a copy of the profile until you commit.

In this case, if you did

try {
$profile = UserProfile::getInstance();
}

and as a result caught a "Target directory could not be created" exception error, you would have a right to be confused. Wrapping this "core" exception in layers of other exceptions that provide context will make the error much easier to deal with ("Creating profile copy failed" -> "File copy operation failed" -> "Target directory could not be created").



Related Topics



Leave a reply



Submit