diff --git a/core/startup/ErrorControlChain.php b/core/startup/ErrorControlChain.php index 4ac510cbb..dffd8290f 100644 --- a/core/startup/ErrorControlChain.php +++ b/core/startup/ErrorControlChain.php @@ -17,9 +17,25 @@ class ErrorControlChain { public static $fatal_errors = null; // Initialised after class definition + /** + * Is there an error? + * + * @var bool + */ protected $error = false; + + /** + * List of steps + * + * @var array + */ protected $steps = array(); + /** + * True if errors should be hidden + * + * @var bool + */ protected $suppression = true; /** We can't unregister_shutdown_function, so this acts as a flag to enable handling */ @@ -28,6 +44,18 @@ class ErrorControlChain { /** We overload display_errors to hide errors during execution, so we need to remember the original to restore to */ protected $originalDisplayErrors = null; + /** + * Any exceptions passed through the chain + * + * @var Exception + */ + protected $lastException = null; + + /** + * Determine if an error has been found + * + * @return bool + */ public function hasErrored() { return $this->error; } @@ -57,19 +85,43 @@ class ErrorControlChain { return $this; } + /** + * Request that the callback is invoked if not errored + * + * @param callable $callback + * @return $this + */ public function thenWhileGood($callback) { return $this->then($callback, false); } + /** + * Request that the callback is invoked on error + * + * @param callable $callback + * @return $this + */ public function thenIfErrored($callback) { return $this->then($callback, true); } + /** + * Request that the callback is invoked always + * + * @param callable $callback + * @return $this + */ public function thenAlways($callback) { return $this->then($callback, null); } + /** + * Return true if the last error was fatal + * + * @return boolean + */ protected function lastErrorWasFatal() { + if($this->lastException) return true; $error = error_get_last(); return $error && ($error['type'] & self::$fatal_errors) != 0; } @@ -122,7 +174,12 @@ class ErrorControlChain { $step = array_shift($this->steps); if ($step['onErrorState'] === null || $step['onErrorState'] === $this->error) { - call_user_func($step['callback'], $this); + try { + call_user_func($step['callback'], $this); + } catch (Exception $ex) { + $this->lastException = $ex; + throw $ex; + } } $this->step(); diff --git a/docs/en/changelogs/3.2.0.md b/docs/en/changelogs/3.2.0.md index 3d747fd00..9a1fde57a 100644 --- a/docs/en/changelogs/3.2.0.md +++ b/docs/en/changelogs/3.2.0.md @@ -15,6 +15,7 @@ * `SS_Filterable`, `SS_Limitable` and `SS_Sortable` now explicitly extend `SS_List` * `Convert::html2raw` no longer wraps text by default and can decode single quotes. * `Mailer` no longer calls `xml2raw` on all email subject line, and now must be passed in via plain text. + * `ErrorControlChain` now supports reload on exceptions #### Deprecated classes/methods removed diff --git a/tests/core/startup/ErrorControlChainTest.php b/tests/core/startup/ErrorControlChainTest.php index 843fc6b69..b5d9031fc 100644 --- a/tests/core/startup/ErrorControlChainTest.php +++ b/tests/core/startup/ErrorControlChainTest.php @@ -140,6 +140,21 @@ class ErrorControlChainTest extends SapphireTest { ->executeInSubprocess(); $this->assertEquals('Done', $out); + + // Exceptions + + $chain = new ErrorControlChainTest_Chain(); + + list($out, $code) = $chain + ->then(function(){ + throw new Exception("bob"); + }) + ->thenIfErrored(function(){ + echo "Done"; + }) + ->executeInSubprocess(); + + $this->assertEquals('Done', $out); } function testExceptionSuppression() {