-
Notifications
You must be signed in to change notification settings - Fork 131
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
6 changed files
with
582 additions
and
59 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,186 @@ | ||
<?php | ||
|
||
namespace Bugsnag\BugsnagLaravel\Internal; | ||
|
||
/** | ||
* The criteria for an error to be unhandled in a Laravel or Lumen app is as | ||
* follows. | ||
* | ||
* 1. All unhandled exceptions must pass through one of the `HANDLER_CLASSES` | ||
* report method | ||
* 2. Unhandled exceptions will have had a caller from inside a vendor namespace | ||
* or the App exception handler | ||
* 3. The above exception handler must have originally been called from | ||
* within a vendor namespace | ||
*/ | ||
final class BacktraceProcessor | ||
{ | ||
/** | ||
* Searching for a frame where the framework's error handler is called. | ||
*/ | ||
const STATE_FRAMEWORK_HANDLER = 1; | ||
|
||
/** | ||
* Searching for a frame where the framework's error handler was called by | ||
* the framework itself, or the user's exception handler. | ||
*/ | ||
const STATE_HANDLER_CALLER = 2; | ||
|
||
/** | ||
* Deciding if the frame was unhandled. | ||
*/ | ||
const STATE_IS_UNHANDLED = 3; | ||
|
||
/** | ||
* A state to signal that we're done processing frames and know if the error | ||
* was unhandled. | ||
*/ | ||
const STATE_DONE = 4; | ||
|
||
/** | ||
* Laravel and Lumen use different exception handlers which live in different | ||
* namespaces. | ||
*/ | ||
const LARAVEL_HANDLER_CLASS = \Illuminate\Foundation\Exceptions\Handler::class; | ||
const LUMEN_HANDLER_CLASS = \Laravel\Lumen\Exceptions\Handler::class; | ||
|
||
/** | ||
* The method used by the x_HANDLER_CLASS to report errors. | ||
*/ | ||
const HANDLER_METHOD = 'report'; | ||
|
||
/** | ||
* Laravel uses the "Exception" namespace, Lumen uses the plural "Exceptions" | ||
* for the default app exception handler. | ||
*/ | ||
const LARAVEL_APP_EXCEPTION_HANDLER = \App\Exception\Handler::class; | ||
const LUMEN_APP_EXCEPTION_HANDLER = \App\Exceptions\Handler::class; | ||
|
||
/** | ||
* Namespaces used by Laravel and Lumen so we can determine if code was | ||
* called by the user's app or the framework itself. | ||
* | ||
* Note this is not a mistake - Lumen uses the 'Laravel' namespace but | ||
* Laravel itself does not | ||
*/ | ||
const LARAVEL_VENDOR_NAMESPACE = 'Illuminate\\'; | ||
const LUMEN_VENDOR_NAMESPACE = 'Laravel\\'; | ||
|
||
/** | ||
* The current state; one of the self::STATE_ constants. | ||
* | ||
* @var int | ||
*/ | ||
private $state = self::STATE_FRAMEWORK_HANDLER; | ||
|
||
/** | ||
* This flag will be set to 'true' if we determine the error was unhandled. | ||
* | ||
* @var bool | ||
*/ | ||
private $unhandled = false; | ||
|
||
/** | ||
* A backtrace matching the format of PHP's 'debug_backtrace'. | ||
* | ||
* @var array | ||
*/ | ||
private $backtrace; | ||
|
||
/** | ||
* @param array $backtrace | ||
*/ | ||
public function __construct(array $backtrace) | ||
{ | ||
$this->backtrace = $backtrace; | ||
} | ||
|
||
/** | ||
* Determine if the backtrace was from an unhandled error. | ||
* | ||
* @return bool | ||
*/ | ||
public function isUnhandled() | ||
{ | ||
foreach ($this->backtrace as $frame) { | ||
$this->processFrame($frame); | ||
|
||
// stop iterating early if we know we're done | ||
if ($this->state === self::STATE_DONE) { | ||
break; | ||
} | ||
} | ||
|
||
return $this->unhandled; | ||
} | ||
|
||
/** | ||
* @param array $frame | ||
* | ||
* @return void | ||
*/ | ||
private function processFrame(array $frame) | ||
{ | ||
if (!isset($frame['class'])) { | ||
return; | ||
} | ||
|
||
$class = $frame['class']; | ||
|
||
switch ($this->state) { | ||
case self::STATE_FRAMEWORK_HANDLER: | ||
// if this class is a framework exception handler and the function | ||
// matches self::HANDLER_METHOD, we can move on to searching for | ||
// the caller | ||
if (($class === self::LARAVEL_HANDLER_CLASS || $class === self::LUMEN_HANDLER_CLASS) | ||
&& isset($frame['function']) | ||
&& $frame['function'] === self::HANDLER_METHOD | ||
) { | ||
$this->state = self::STATE_HANDLER_CALLER; | ||
} | ||
|
||
break; | ||
|
||
case self::STATE_HANDLER_CALLER: | ||
// if this is an app exception handler or a framework class, we | ||
// can move on to determine if this was unhandled or not | ||
if ($class === self::LARAVEL_APP_EXCEPTION_HANDLER | ||
|| $class === self::LUMEN_APP_EXCEPTION_HANDLER | ||
|| $this->isVendor($class) | ||
) { | ||
$this->state = self::STATE_IS_UNHANDLED; | ||
} | ||
|
||
break; | ||
|
||
case self::STATE_IS_UNHANDLED: | ||
// we are only interested in running this once so move immediately | ||
// into the "done" state. This ensures we only check the frame | ||
// immediately before the caller of the exception handler | ||
$this->state = self::STATE_DONE; | ||
|
||
// if this class is internal to the framework then the exception | ||
// was unhandled | ||
if ($this->isVendor($class)) { | ||
$this->unhandled = true; | ||
} | ||
|
||
break; | ||
} | ||
} | ||
|
||
/** | ||
* Does the given class belong to a vendor namespace? | ||
* | ||
* @see self::VENDOR_NAMESPACES | ||
* | ||
* @param string $class | ||
* | ||
* @return bool | ||
*/ | ||
private function isVendor($class) | ||
{ | ||
return substr($class, 0, strlen(self::LARAVEL_VENDOR_NAMESPACE)) === self::LARAVEL_VENDOR_NAMESPACE | ||
|| substr($class, 0, strlen(self::LUMEN_VENDOR_NAMESPACE)) === self::LUMEN_VENDOR_NAMESPACE; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.