I have a set of domain objects which try to find a solution to a problem.
The top level object has an algorithm which splits up the problem and delegates it to lower level objects, which in turn do the same.
I want the top level algorithm to throw an exception if it did not find any solutions, giving a breakdown of possible causes.
My question is, how should(n't) this be implemented?
The way I was thinking of doing this:
The algorithms at all levels throw an exception if it fails to find a solution.
For a given level, call it the parent algorithm, it would accumulate exceptions thrown by its children, and if all attempts by its children throw an exception, the parent will itself throw an exception which contains its childrens' exceptions.
My implementation is as follows:
class ExceptionWithCauses extends \Exception {
private $causes = array();
public function addCause(\Exception $ex){
$this->causes[] = $ex;
$this->message .= "\n".$ex->getMessage();
}
}
I'm using it like this:
function findAnOption()
{
$noOptionsException = new ExceptionWithCauses("findAnOption(): No options found");
$possibilities = array(1, 2, 3, 4);
foreach ($possibilities as $possibility)
{
try {
useOption($possibility);
return $possibility;
} catch (Exception $e) {
$noOptionsException->addCause($e);
}
}
throw $noOptionsException;
}
function useOption($x)
{
if ($x < 5){
throw new ExceptionWithCauses("useOption(): $x is too small");
}
return $x;
}
And when findAnOption()
is called, a exception is thrown with the following message:
findAnOption(): No options found
useOption(): 1 is too small
useOption(): 2 is too small
useOption(): 3 is too small
useOption(): 4 is too small
I'm worried that this is bad practice. I've read that exceptions should only be used in exceptional circumstances. So with that in mind, perhaps I should only be throwing an exception at the top level. But then at that top level, how should the exception accumulate details on what was tried by lower levels? The only time that information will be needed is when the top level throws that exception, so in a way it makes sense that it's all handled by exceptions, and that I have exceptions at every level?
On the one hand, this question on Programmers SE explains why using exceptions in control-flow is an anti-pattern, but at the same time, it seems like the tidiest way of accumulating a log for why a top level exception occurred?