Skip to content
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

Merged
merged 10 commits into from
Aug 10, 2017

Conversation

mterwill
Copy link
Contributor

@mterwill mterwill commented Aug 2, 2017

This library overwrites previously set user exception and error handlers. Both set_error_handler and set_exception_handler will return callables for the previously defined handlers if they were set. Switch the library's behavior to respect this.

Closes #392

Matthew Terwilliger added 2 commits August 2, 2017 17:25
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
@@ -69,15 +137,23 @@ public function exceptionHandler($throwable)
*/
public function errorHandler($errno, $errstr, $errfile = '', $errline = 0)
{
if ($this->client->shouldIgnoreErrorCode($errno)) {
return;
}
Copy link
Contributor Author

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.

http://php.net/manual/en/function.set-error-handler.php

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

Copy link
Contributor

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(
Copy link
Contributor

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?

@kattrali
Copy link
Contributor

kattrali commented Aug 8, 2017

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 false.

src/Handler.php Outdated
*
* @return void
*/
public function registerErrorHandler($callPrevious = true)
Copy link
Contributor

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?

Copy link
Contributor Author

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

  1. load their custom handler
  2. call Bugsnag::register
  3. 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. 🤷‍♂️

Copy link
Contributor

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)
Copy link
Contributor

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.

Copy link
Contributor

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.

Copy link
Contributor Author

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.

Copy link
Contributor

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.

Copy link
Contributor

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.
Copy link
Contributor

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.
Copy link
Contributor

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

Matthew Terwilliger added 2 commits August 8, 2017 16:42
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.
@mterwill
Copy link
Contributor Author

mterwill commented Aug 8, 2017

This should be fully backwards compatible now, with a new Handler::registerWithPrevious method added to register bugsnag handlers and preserve previously registered exception handlers.

Your documentation page should probably be updated to include a note about this once it's released.

@kattrali
Copy link
Contributor

kattrali commented Aug 8, 2017

Thanks @mterwill 🎉

Copy link
Contributor

@GrahamCampbell GrahamCampbell left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks pretty solid. 👍

@kattrali kattrali merged commit d277503 into bugsnag:master Aug 10, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants