Jump to content

Do exceptions span all functions and methods?


NotionCommotion

Recommended Posts

Will an uncaught exception in any of the child methods percolate up?  Same thing for functions? Thank you.

<?php
class bla {
    function bla() {
        try {
            if($bad) {throw new Exception('bla bla.');}
            $this->something_else_that_might_throw_an_uncaught_exception();
        } catch (Exception $e) {echo('do something to deal with the exception');}
    }
    function something_else_that_might_throw_an_uncaught_exception(){
        $this->even_something_else_that_might_throw_an_uncaught_exception();
    }
}

?>
Link to comment
Share on other sites

Yes, exceptions travel through the call stack. If you don't catch an exception in the function/method, it's passed on to the caller. If you don't catch it there, it's again passed to the caller etc.

 

On a side note: Never catch the top-level Exception class, only specific subclasses.

Link to comment
Share on other sites

The Exception class covers every possible exception, it could be anything from SecurityBreachException to CPUHasCaughtFireException.

 

If you simply catch any exception (aka Pokémon Exception Handling) and keep the application running, this can have serious consequences. Exceptions exist for a reason, so you shouldn't just stop them at will. If you want to handle a specific problem, then only catch this specific subclass of Exception.

Link to comment
Share on other sites

Gotcha,

 

So what if I wanted to try something, and catch any for example PDOexception plus any exception I throw in my script, and deal with them in the same manner (i.e. delete any inserted records, etc)?  Could I have multiple catch statements, and if so are they executed in sequence and only perform the first match?


On a side note (sorry for asking more), are local variables in my try statement available in my catch?

try {
    $bla=123;
    db::db()->exec('some bad SQL');
    if($bad) {throw new Exception('bla bla.');}
}
catch (PDOException $e) {echo('deal with SQL exceptions and do the same thing '.$bla);}
catch (Exception $e) {echo('deal with script thrown exceptions and do the same thing. '$bla);}
Link to comment
Share on other sites

There's an easy way to find out: Try it.

 

Yes, you can have multiple catch blocks, and the first match is executed. Yes, you can use the variables anywhere in the function or method, because try statements don't create a new scope.

 

No, you still shouldn't catch the generic Exception. In this case, it doesn't even make sense, because there might be a fatal error which is not an exception. If you need a cleanup procedure, use something like register_shutdown_function() which will be called when the script ends (regardless of the reason) and allows you to check if there was a fatal error of any kind. However, there are usually more elegant solutions. For example, wrap the sequence of INSERT queries in a transaction. If one of them fails, the entire transaction is rolled back automatically, and there won't be any garbage data left.

 

Should you actually encounter a situation where you need to catch all exceptions (I can't think of any), then rethrow the exception when you're done:

<?php

try
{

}
catch (Exception $error)    // for some special reason, we need to catch every exception here
{
	// do the error handling
	
	// rethrow the exception
	throw $error;
}
Link to comment
Share on other sites

If there was a single entry point into the application, such as a "router" or "front controller", then I would catch Exception there for one primary reason: to avoid a white screen of death. Without a single entry point you can use a global exception handler (ie, set_exception_handler) to the same effect.

Meanwhile shutdown functions are a bit limited in what they can work with, lest you trigger undefined behavior for instance, so at that point it may be too late to take particular types of actions.

 

I would also catch Exception if there was a place in code that had to guarantee that it does not throw exceptions of any kind. The only place I can think of where that might be the case is inside a __toString(): if you throw an exception from there then PHP will fatal out saying "Method class::__toString() must not throw an exception". Most other cases can be dealt with using a try/finally:

try {
    // code
} finally {
    // clean up after previous code
}
Edited by requinix
Link to comment
Share on other sites

 

There's an easy way to find out: Try it.

 

 

...

 

For example, wrap the sequence of INSERT queries in a transaction. If one of them fails, the entire transaction is rolled back automatically, and there won't be any garbage data left

 

 

Try it:  Agree.  Nice pun :)

 

Transactions: Most of my reasons for these questions were driven by trying to implement transactions.  One tries three sequential queries, and one fails, so you catch the (PDO) exception and roll it back.  Do I have this right?  But what if there was some business logic in the mix?  Could I throw an exception so it is rolled back just as if it was a PDO exception?

Link to comment
Share on other sites

Most other cases can be dealt with using a try/finally:}

 

 

I have never used finally.  Is it an often used pattern that I should probably be using more often?

 

Do you have any thoughts about inserting a business exception in between PDO exceptions, and dealing with them similarly, yet letting other exceptions remain uncaught?

Link to comment
Share on other sites

I have never used finally.  Is it an often used pattern that I should probably be using more often?

Don't know if I'd call it a "pattern"... I suppose it is, kinda.

 

Consider code like this:

function processCsvFile($file) {
	$handle = fopen($file, "rb");
	if (!$handle) {
		trigger_error("Cannot open CSV file: {$file}", E_USER_WARNING);
		return false;
	}

	$l = 1;
	$header = fgetcsv($handle);
	if (!$header) {
		trigger_error("First line of CSV file {$file} is not a valid header", E_USER_WARNING);
		fclose($handle);
		return false;
	}

	while (!feof($handle)) {
		$l++;
		$line = fgetcsv($handle);
		
		if (!processCsvLine($header, $line)) {
			trigger_error("Cannot process line {$l} of file {$file}", E_USER_WARNING);
			fclose($handle);
			return false;
		}
	}

	fclose($handle);
	return true;
}
There are three places where errors can occur: when opening the file, when reading the first line, and when reading subsequent lines. For the latter two you should fclose() the file before returning or else the file handle will stay open for the rest of your script.

 

Exceptions make it a bit less obvious that your function can quit early because they can happen at any time: in your code, in the code you call, in anything that code calls, and so on. However you should still make sure to close that file handle.

A try/finally lets you keep an eye out for exceptions without forcing you to handle them; processCsvLine() could throw an exception and processCsvFile() may not want to catch it.

function processCsvFile($file) {
	$handle = fopen($file, "rb");
	if (!$handle) {
		throw new FileOpenException($file, "rb"); // cannot open file $file for mode rb
	}

	try {
		$l = 1;
		$header = fgetcsv($handle);
		if (!$header) {
			throw new InvalidFileDataException($file, "First line of CSV file %s is not a valid header");
		}
	
		while (!feof($handle)) {
			$l++;
			$line = fgetcsv($handle);
			
			processCsvLine($header, $line);
		}
	} finally {
		fclose($handle);
	}
}
Notice that neither of the two functions need to return true/false anymore because "success" is returning at all and "failure" is receiving an exception.

 

Do you have any thoughts about inserting a business exception in between PDO exceptions, and dealing with them similarly, yet letting other exceptions remain uncaught?

A bit too vague for me to make a decision either way but in general I would support that.

At least make sure you're using the $previous argument to Exception's constructor

catch (PDOException $pdoe) {
    throw new BusinessPdoException("Message", other arguments, $pdoe);
    // class BusinessPdoException extends Exception {
    //   public function __construct($message, $other_arguments, Exception $previous = null) {
    //     parent::__construct($message, 0, $previous);
so that logging can (hopefully) get the full trace. Edited by requinix
Link to comment
Share on other sites

Don't know if I'd call it a "pattern"... I suppose it is, kinda.

 

Just spent the last hour teaching algebra to my 11 year old daughter.  I'm going to call it a pattern :)

 

 

throw new FileOpenException($file, "rb");

 

 

Whoah, I looked in the manual, and this the "FileOpenException" definitely doesn't exist!  So, could one could just willynilly make up exceptions for what ever one wishes?  I hope so, and guess this is the crux of my question.

Link to comment
Share on other sites

Whoah, I looked in the manual, and this the "FileOpenException" definitely doesn't exist!  So, could one could just willynilly make up exceptions for what ever one wishes?  I hope so, and guess this is the crux of my question.

No. It was hypothetical. You'd have to make them yourself.
Link to comment
Share on other sites

So, could one could just willynilly make up exceptions for what ever one wishes?  I hope so, and guess this is the crux of my question.

You have to create the exception, but a lot of the time this can be as simple as just extending the exception class, or some sub-class of it.

 

eg:

class FileOpenException extends RuntimeException {
    public function __construct($file, $mode){
         $message = 'Failed to open '.$file.' in mode '.$mode;
         parent::__construct($message);
    }
}
Once defined then you can throw new FileOpenException($file, $mode) just like you would any other exception.

 

In this example the constructor was overridden to accept more specific parameters rather than the default of a generic message. Generally when creating an exception you will either only override the constructor, or not override anything and just extend the class.

  • Like 1
Link to comment
Share on other sites

Okay, I understand that this is not correct since I am catching any exceptions, and not a specific type.  What exception should I throw if I have a problem with the file upload part?

try {
    db::db()->beginTransaction();
    //Do a bunch of queries, and throw PDO exception upon error

    if(!$this->save_XHR_File($file_path)) {throw new Exception('File not uploaded.');}
    if(!$this->verifyFileType($file_path)) {throw new Exception('Invalid file upload type.');}

    db::db()->commit();
} catch (Exception $e) {
    db::db()->rollBack();
    if(file_exists($file_path)) {unlink($file_path);}
}

Link to comment
Share on other sites

This thread is more than a year old. Please don't revive it unless you have something important to add.

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.