-
Notifications
You must be signed in to change notification settings - Fork 77
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Call previously defined exception and error handlers #395
Conversation
PHPUnit's default behavior is to translate PHP errors into exceptions and raise those. Rely on this assumption in testErrorHandler. Ref: https://phpunit.de/manual/current/en/writing-tests-for-phpunit.html
Both set_exception_handler and set_error_handler return the values to which they were previously set. Be respectful and call these handlers as it may introduce unexpected behavior in the user's code and also provides no way to attach an error handler besides manually wrapping the bugsnag library. PHPUnit's default behavior is to translate PHP errors into exceptions and raise those. Rely on this assumption in our tests of the error handler. Ref: https://phpunit.de/manual/current/en/writing-tests-for-phpunit.html
566202f
to
92d05e3
Compare
@@ -69,15 +137,23 @@ public function exceptionHandler($throwable) | |||
*/ | |||
public function errorHandler($errno, $errstr, $errfile = '', $errline = 0) | |||
{ | |||
if ($this->client->shouldIgnoreErrorCode($errno)) { | |||
return; | |||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FWIW, the interpreter interprets this null
return value differently than the false
you were returning below.
It is important to remember that the standard PHP error handler is completely bypassed for the error types specified by error_types unless the callback function returns FALSE.
We can verify this:
$ cat test-exits-null.php
<?php
set_error_handler(
function ($errno, $errstr) {
return;
}
);
trigger_error("Uh oh!", E_USER_WARNING);
$ php test-exits-null.php
$ # warning not outputted
$ cat test-exits-false.php
<?php
set_error_handler(
function ($errno, $errstr) {
return false;
}
);
trigger_error("Uh oh!", E_USER_WARNING);
$ php test-exits-false.php
PHP Warning: Uh oh! in /private/tmp/test-exits-false.php on line 9
Warning: Uh oh! in /private/tmp/test-exits-false.php on line 9
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FWIW, the interpreter interprets this null return value differently than the false you were returning below.
Looks like a bug. Either our doc was wrong, and we meant bool|null
, or we meant to return false
.
Since this library needs to be compatible with older versions of PHP, CI tests run on two versions of PHPUnit. 4.8 doesn't have PHPUnit_Framework_TestCase::expectException, however both newer and older versions of PHPUnit provide support for the annotation @ExpectedException, so use that instead.
src/Handler.php
Outdated
|
||
return false; | ||
if ($this->previousErrorHandler) { | ||
call_user_func( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this call be returned?
This works well. I left a comment for the one question I had, around returning the value of the previous handler, in the case it will return |
src/Handler.php
Outdated
* | ||
* @return void | ||
*/ | ||
public function registerErrorHandler($callPrevious = true) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Setting this default to true is a backwards compatibility breaker. It should be set to false by default I think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Up to y'all. Setting the default to false will preserve the current broken behavior for new installations and create a bug that can lurk for a while.
This change will only break backwards compatibility people who
- load their custom handler
- call
Bugsnag::register
- re-register their custom handler on top of that, saving the values returned by
set_x_handler
and re-calling that
I'm guessing those who have already worked around this will have just called Bugsnag\Handler::xHandler
directly, in which case previousXHandler
will be null
since register
is never called.
I'd think calling the user's function twice is better than never. 🤷♂️
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Setting the default to false will preserve the current broken behavior for new installations and create a bug that can lurk for a while.
I don't think the current behaviour is broken actually. The handler is intentionally replacing existing ones. It's a feature if we want to have one that doesn't behave like this.
src/Handler.php
Outdated
* | ||
* @return static | ||
*/ | ||
public static function register($client = null) | ||
public static function register($client = null, $callPrevious = true) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should be very careful adding new parameters to public or protected methods in PHP since it's technically a BC break. A way around this would be to define a new method called registerWithPrevious
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not only that, but it's more readable. Boolean parameters can be confusing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why would optional parameters that preserve the existing behavior break backwards compatibility? [obviously this does not preserve existing behavior, but we can continue that discussion above]
I'm generally not the biggest fan of boolean flags either, but in this case I think removing it would either (a) add a lot of duplicated code or (b) add unnecessary complexity to the existing code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why would optional parameters that preserve the existing behavior break backwards compatibility? [obviously this does not preserve existing behavior, but we can continue that discussion above]
Because PHP allows you to call methods with too many parameters without errors, and this class is non-final so could have been extended with a different signature for the 2nd parameter. Given the current handler didn't support preserving the previous handler, I'm sure other people will have extended it in their projects to add this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm generally not the biggest fan of boolean flags either, but in this case I think removing it would either (a) add a lot of duplicated code or (b) add unnecessary complexity to the existing code.
I'm only suggesting we don't have it in the register function. The others are fine. :)
src/Handler.php
Outdated
|
||
/** | ||
* Register the bugsnag exception handler and save the previous one | ||
* (if it exists) to call later. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please ensure that "short descriptions" are less than 8 chars and don't overflow past one line.
src/Handler.php
Outdated
protected $previousErrorHandler; | ||
|
||
/** | ||
* The previously registered exception handler returned by the interpreter. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think The previously registered exception handler.
is probably fine, and similar for above. While the main implementation of the PHP language is an interpreter (after a compilation phase to an intermediate language), there exist other implementations of the language, so no reason to assume the runtime is actually an interpreter. Just something very minor. :P
This test should have failed when changing the default behavior not to call the previous error handler. Instead of returning false, which is the default from the Bugsnag handler, and thus proves nothing, return a sentinel value and assert based on that.
Since these are new methods expected to be invoked primarily by register or registerWithPrevious, make the boolean flag a required parameter. A user might wish to only register certain handlers themselves, so it makes sense to leave these public, but the behavior is different depending on the flag. The caller should be forced to make an explicit decision.
2dacf25
to
8e0407a
Compare
This should be fully backwards compatible now, with a new Your documentation page should probably be updated to include a note about this once it's released. |
Thanks @mterwill 🎉 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks pretty solid. 👍
This library overwrites previously set user exception and error handlers. Both
set_error_handler
andset_exception_handler
will return callables for the previously defined handlers if they were set. Switch the library's behavior to respect this.Closes #392