Skip to content

Commit

Permalink
Added errorhandler to convert errors to exceptions
Browse files Browse the repository at this point in the history
This is a followup to the discussion in issue #27.
All PHP errors that can be handled with `set_error_handler` are
converted to `Garp_Exception_RuntimeException` instances of the correct
severity.

This makes sure not even a notice slips through.
  • Loading branch information
harmenjanssen committed Jul 4, 2017
1 parent 28f7495 commit fcd1f81
Show file tree
Hide file tree
Showing 16 changed files with 485 additions and 95 deletions.
2 changes: 2 additions & 0 deletions application/init.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
$dotenv->load();
}

Garp_ErrorHandler::registerErrorHandler();

// Sentry integration
if (getenv('SENTRY_API_URL') || (defined('SENTRY_API_URL') && APPLICATION_ENV !== 'development')) {
$sentryApiUrl = getenv('SENTRY_API_URL') ?: SENTRY_API_URL;
Expand Down
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@
"dompdf/dompdf": "^0.7.0",
"tedivm/jshrink": "1.1.0",
"vlucas/phpdotenv": "2.0.1",
"fzaninotto/faker": "dev-master"
"fzaninotto/faker": "dev-master",
"grrr-amsterdam/garp-functional": "*"
},
"require-dev": {
"squizlabs/php_codesniffer": "^2.6",
Expand Down
336 changes: 248 additions & 88 deletions composer.lock

Large diffs are not rendered by default.

35 changes: 35 additions & 0 deletions library/Garp/ErrorHandler.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
<?php
use Garp\Functional as f;

/**
* Garp_ErrorHandler
* class description
Expand All @@ -9,6 +11,39 @@
class Garp_ErrorHandler {
const ERROR_REPORT_MAIL_ADDRESS_FALLBACK = '[email protected]';

public static $severityToExceptionMap = array(
E_WARNING => 'Garp_Exception_RuntimeException_Warning',
E_NOTICE => 'Garp_Exception_RuntimeException_Notice',
E_USER_ERROR => 'Garp_Exception_RuntimeException_UserError',
E_USER_WARNING => 'Garp_Exception_RuntimeException_UserWarning',
E_USER_NOTICE => 'Garp_Exception_RuntimeException_UserNotice',
E_STRICT => 'Garp_Exception_RuntimeException_Strict',
E_RECOVERABLE_ERROR => 'Garp_Exception_RuntimeException_RecoverableError',
E_DEPRECATED => 'Garp_Exception_RuntimeException_Deprecated',
E_USER_DEPRECATED => 'Garp_Exception_RuntimeException_UserDeprecated',
);

/**
* Handle all PHP errors and convert them to exceptions.
*
* @return void
*/
public static function registerErrorHandler() {
set_error_handler(
function ($severity, $msg, $file, $line, array $context) {
// error was suppressed with the @-operator
//if (0 === error_reporting()) {
//return false;
//}
$exceptionClass = f\either(
f\prop($severity, Garp_ErrorHandler::$severityToExceptionMap),
'ErrorException'
);
throw new $exceptionClass($msg, 0, $severity, $file, $line);
}
);
}

/**
* Handles premature exceptions thrown before the MVC ErrorHandler is initialized.
* Exceptions of that kind will result in a blank page if displayErrors is off, instead of
Expand Down
8 changes: 2 additions & 6 deletions library/Garp/Exception.php
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
<?php
/**
* Garp_Exception
* class description
*
* @author Harmen Janssen | grrr.nl
* @version 1.0
* @package Garp_Exception
* @package Garp_Exception
* @author Harmen Janssen <[email protected]>
*/
class Garp_Exception extends Exception {
}
12 changes: 12 additions & 0 deletions library/Garp/Exception/RuntimeException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php
/**
* Base class for specific exception per severity level.
* Note that this extends ErrorException rather than Garp_Exception since ErrorException feels more
* designed for runtime errors (taking line, file and severity as parameters).
*
* @package Garp_Exception
* @author Harmen Janssen <[email protected]>
*/
class Garp_Exception_RuntimeException extends ErrorException {

}
7 changes: 7 additions & 0 deletions library/Garp/Exception/RuntimeException/Deprecated.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?php
/**
* @package Garp_Exception_RuntimeException
* @author Harmen Janssen <[email protected]>
*/
class Garp_Exception_RuntimeException_Deprecated extends Garp_Exception_RuntimeException {
}
7 changes: 7 additions & 0 deletions library/Garp/Exception/RuntimeException/Notice.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?php
/**
* @package Garp_Exception_RuntimeException
* @author Harmen Janssen <[email protected]>
*/
class Garp_Exception_RuntimeException_Notice extends Garp_Exception_RuntimeException {
}
7 changes: 7 additions & 0 deletions library/Garp/Exception/RuntimeException/RecoverableError.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?php
/**
* @package Garp_Exception_RuntimeException
* @author Harmen Janssen <[email protected]>
*/
class Garp_Exception_RuntimeException_RecoverableError extends Garp_Exception_RuntimeException {
}
7 changes: 7 additions & 0 deletions library/Garp/Exception/RuntimeException/Strict.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?php
/**
* @package Garp_Exception_RuntimeException
* @author Harmen Janssen <[email protected]>
*/
class Garp_Exception_RuntimeException_Strict extends Garp_Exception_RuntimeException {
}
7 changes: 7 additions & 0 deletions library/Garp/Exception/RuntimeException/UserDeprecated.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?php
/**
* @package Garp_Exception_RuntimeException
* @author Harmen Janssen <[email protected]>
*/
class Garp_Exception_RuntimeException_UserDeprecated extends Garp_Exception_RuntimeException {
}
7 changes: 7 additions & 0 deletions library/Garp/Exception/RuntimeException/UserError.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?php
/**
* @package Garp_Exception_RuntimeException
* @author Harmen Janssen <[email protected]>
*/
class Garp_Exception_RuntimeException_UserError extends Garp_Exception_RuntimeException {
}
7 changes: 7 additions & 0 deletions library/Garp/Exception/RuntimeException/UserNotice.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?php
/**
* @package Garp_Exception_RuntimeException
* @author Harmen Janssen <[email protected]>
*/
class Garp_Exception_RuntimeException_UserNotice extends Garp_Exception_RuntimeException {
}
7 changes: 7 additions & 0 deletions library/Garp/Exception/RuntimeException/UserWarning.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?php
/**
* @package Garp_Exception_RuntimeException
* @author Harmen Janssen <[email protected]>
*/
class Garp_Exception_RuntimeException_UserWarning extends Garp_Exception_RuntimeException {
}
7 changes: 7 additions & 0 deletions library/Garp/Exception/RuntimeException/Warning.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?php
/**
* @package Garp_Exception_RuntimeException
* @author Harmen Janssen <[email protected]>
*/
class Garp_Exception_RuntimeException_Warning extends Garp_Exception_RuntimeException {
}
121 changes: 121 additions & 0 deletions tests/library/Garp/ErrorHandlerTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
<?php
/**
* @package Tests
* @author Harmen Janssen <[email protected]>
*/
class Garp_ErrorHandlerTest extends Garp_Test_PHPUnit_TestCase {
protected $_oldErrorReporting;

public function setUp() {
$this->_oldErrorReporting = error_reporting(E_ALL);
}

public function tearDown() {
error_reporting($this->_oldErrorReporting);
}

/**
* @expectedException Garp_Exception_RuntimeException_Warning
*/
public function test_warning_exception() {
array_key_exists(array(), 'banana');
}

/**
* Tests E_STRICT warning.
*
* @deprecated Forget it. This does not work with PHP5.3 (it tries to load an instance
* of the Garp_Exception_RuntimeException_Strict class but cannot find it somehow.
* @return void
public function test_strict_exception() {
try {
eval(
'class A { function foo($abc) {} } class B extends A { function foo() {} }'
);
} catch (Exception $e) {
// Apparently both of these are possible and it differs per PHP version
$this->assertTrue(
$e instanceof Garp_Exception_RuntimeException_Strict ||
$e instanceof Garp_Exception_RuntimeException_Warning
);
}
}
*/

/**
* @expectedException Garp_Exception_RuntimeException_Notice
*/
public function test_notice_exception() {
$a = array();
$a['foo'];
}

public function test_another_notice_exception() {
function change(&$var) {
$var += 10;
}
try {
$var = 1;
change(++$var);
} catch (Exception $e) {
// Note: the type of exception differs between PHP versions
$this->assertTrue(
$e instanceof Garp_Exception_RuntimeException_Notice
|| $e instanceof Garp_Exception_RuntimeException_Strict
);
}
}

/**
* @expectedException Garp_Exception_RuntimeException_UserError
*/
public function test_user_error_exception() {
trigger_error('Well darn.', E_USER_ERROR);
}

/**
* @expectedException Garp_Exception_RuntimeException_RecoverableError
*/
public function test_recoverable_exception() {
$recoverable = new stdClass();
(string)$recoverable;
}

public function test_deprecated_exception() {
try {
Foo::bar();
} catch (Exception $e) {
// Note: the type of exception differs between PHP versions
$this->assertTrue(
$e instanceof Garp_Exception_RuntimeException_Strict
|| $e instanceof Garp_Exception_RuntimeException_Deprecated
);
}
}

/**
* @expectedException Garp_Exception_RuntimeException_Notice
*/
public function test_with_at_suppressor() {
$a = array();
@$a['foo'];
}

/**
* @expectedException Garp_Exception_RuntimeException_Warning
*/
public function test_when_error_reporting_is_off() {
error_reporting(0);
array_key_exists(array(), 'banana');
}

}

/**
* @package Tests
* @author Harmen Janssen <[email protected]>
*/
class Foo {
function bar() {
}
}

0 comments on commit fcd1f81

Please sign in to comment.