mirror of
https://github.com/filp/whoops.git
synced 2026-09-13 11:16:26 +00:00
@@ -0,0 +1,16 @@
|
||||
# http://editorconfig.org
|
||||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
[*.{json,php}]
|
||||
indent_size = 4
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
@@ -1,6 +1,7 @@
|
||||
docs/ export-ignore
|
||||
examples/ export-ignore
|
||||
tests/ export-ignore
|
||||
.editorconfig export-ignore
|
||||
.gitattributes export-ignore
|
||||
.gitignore export-ignore
|
||||
.scrutinizer.yml export-ignore
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
# 2.0.0
|
||||
|
||||
Backwards compatibility breaking changes:
|
||||
|
||||
* `Run` class is now `final`. If you inherited from `Run`, please now instead use a custom `SystemFacade` injected into the `Run` constructor, or contribute your changes to our core.
|
||||
* PHP < 5.5 support dropped.
|
||||
@@ -63,6 +63,8 @@ If you are not using any of these frameworks, here's a very simple way to instal
|
||||
|
||||
For more options, have a look at the **example files** in `examples/` to get a feel for how things work. Also take a look at the [API Documentation](docs/API%20Documentation.md) and the list of available handers below.
|
||||
|
||||
You may also want to override some system calls Whoops does. To do that, extend `Whoops\Util\SystemFacade`, override functions that you want and pass it as the argument to the `Run` constructor.
|
||||
|
||||
### Available Handlers
|
||||
|
||||
**whoops** currently ships with the following built-in handlers, available in the `Whoops\Handler` namespace:
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -29,10 +29,20 @@
|
||||
$start = key($range) + 1;
|
||||
$code = join("\n", $range);
|
||||
?>
|
||||
<style>#frame-code-0 .linenums li:nth-child(<?= $line-$start+1 ?>) {
|
||||
/* Style current line */
|
||||
background-color: hsl(0, 0%, 33%);
|
||||
}</style>
|
||||
<style>
|
||||
#frame-code-0 .linenums li:nth-child(<?= $line-$start ?>) {
|
||||
background-color: rgba(255, 100, 100, .07);
|
||||
padding: 2px;
|
||||
}
|
||||
#frame-code-0 .linenums li:nth-child(<?= $line-$start+1 ?>) {
|
||||
background-color: rgba(255, 100, 100, .17);
|
||||
padding: 2px;
|
||||
}
|
||||
#frame-code-0 .linenums li:nth-child(<?= $line-$start+2 ?>) {
|
||||
background-color: rgba(255, 100, 100, .07);
|
||||
padding: 2px;
|
||||
}
|
||||
</style>
|
||||
<pre class="code-block prettyprint linenums:<?php echo $start ?>"><?php echo $tpl->escape($code) ?></pre>
|
||||
<?php endif ?>
|
||||
<?php endif ?>
|
||||
|
||||
@@ -12,16 +12,21 @@
|
||||
<?php endif ?>
|
||||
</div>
|
||||
|
||||
<span id="plain-exception"><?php echo $tpl->escape($plain_exception) ?></span>
|
||||
<button id="copy-button" class="clipboard" data-clipboard-text="<?php echo $tpl->escape($plain_exception) ?>" title="Copy exception details to clipabord">
|
||||
COPY
|
||||
</button>
|
||||
|
||||
<p class="exc-message">
|
||||
<div class="exc-message">
|
||||
<?php if (!empty($message)): ?>
|
||||
<?php echo $tpl->escape($message) ?>
|
||||
<span><?php echo $tpl->escape($message) ?></span>
|
||||
<?php else: ?>
|
||||
<span class="exc-message-empty-notice">No message</span>
|
||||
<?php endif ?>
|
||||
</p>
|
||||
|
||||
<ul class="search-for-help">
|
||||
<li><a target="_blank" href="https://google.com/search?q=<?php echo urlencode(implode('\\', $name).' '.$message) ?>" title="Search for help on Google."><i class="google"></i></a></li>
|
||||
<li><a target="_blank" href="https://stackoverflow.com/search?q=<?php echo urlencode(implode('\\', $name).' '.$message) ?>" title="Search for help on Stack Overflow."><i class="stackoverflow"></i></a></li>
|
||||
</ul>
|
||||
|
||||
<span id="plain-exception"><?php echo $tpl->escape($plain_exception) ?></span>
|
||||
<button id="copy-button" class="clipboard" data-clipboard-text="<?php echo $tpl->escape($plain_exception) ?>" title="Copy exception details to clipabord">
|
||||
COPY
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+26
-29
@@ -13,6 +13,7 @@ use Whoops\Handler\CallbackHandler;
|
||||
use Whoops\Handler\Handler;
|
||||
use Whoops\Handler\HandlerInterface;
|
||||
use Whoops\Util\Misc;
|
||||
use Whoops\Util\SystemFacade;
|
||||
|
||||
final class Run
|
||||
{
|
||||
@@ -36,6 +37,13 @@ final class Run
|
||||
|
||||
protected $silencedPatterns = array();
|
||||
|
||||
protected $system;
|
||||
|
||||
public function __construct(SystemFacade $system = null)
|
||||
{
|
||||
$this->system = $system ?: new SystemFacade;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pushes a handler to the end of the stack
|
||||
*
|
||||
@@ -114,9 +122,9 @@ final class Run
|
||||
class_exists("\\Whoops\\Exception\\Frame");
|
||||
class_exists("\\Whoops\\Exception\\Inspector");
|
||||
|
||||
set_error_handler(array($this, self::ERROR_HANDLER));
|
||||
set_exception_handler(array($this, self::EXCEPTION_HANDLER));
|
||||
register_shutdown_function(array($this, self::SHUTDOWN_HANDLER));
|
||||
$this->system->setErrorHandler(array($this, self::ERROR_HANDLER));
|
||||
$this->system->setExceptionHandler(array($this, self::EXCEPTION_HANDLER));
|
||||
$this->system->registerShutdownFunction(array($this, self::SHUTDOWN_HANDLER));
|
||||
|
||||
$this->isRegistered = true;
|
||||
}
|
||||
@@ -131,8 +139,8 @@ final class Run
|
||||
public function unregister()
|
||||
{
|
||||
if ($this->isRegistered) {
|
||||
restore_exception_handler();
|
||||
restore_error_handler();
|
||||
$this->system->restoreExceptionHandler();
|
||||
$this->system->restoreErrorHandler();
|
||||
|
||||
$this->isRegistered = false;
|
||||
}
|
||||
@@ -239,7 +247,7 @@ final class Run
|
||||
// Capture output produced while handling the exception,
|
||||
// we might want to send it straight away to the client,
|
||||
// or return it silently.
|
||||
ob_start();
|
||||
$this->system->startOutputBuffering();
|
||||
|
||||
// Just in case there are no handlers:
|
||||
$handlerResponse = null;
|
||||
@@ -266,7 +274,7 @@ final class Run
|
||||
|
||||
$willQuit = $handlerResponse == Handler::QUIT && $this->allowQuit();
|
||||
|
||||
$output = ob_get_clean();
|
||||
$output = $this->system->cleanOutputBuffer();
|
||||
|
||||
// If we're allowed to, send output generated by handlers directly
|
||||
// to the output, otherwise, and if the script doesn't quit, return
|
||||
@@ -276,8 +284,8 @@ final class Run
|
||||
// If we're going to quit execution, cleanup all other output
|
||||
// buffers before sending our own output:
|
||||
if ($willQuit) {
|
||||
while (ob_get_level() > 0) {
|
||||
ob_end_clean();
|
||||
while ($this->system->getOutputBufferLevel() > 0) {
|
||||
$this->system->endOutputBuffering();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -285,8 +293,10 @@ final class Run
|
||||
}
|
||||
|
||||
if ($willQuit) {
|
||||
flush(); // HHVM fix for https://github.com/facebook/hhvm/issues/4055
|
||||
exit(1);
|
||||
// HHVM fix for https://github.com/facebook/hhvm/issues/4055
|
||||
$this->system->flushOutputBuffer();
|
||||
|
||||
$this->system->stopExecution(1);
|
||||
}
|
||||
|
||||
return $output;
|
||||
@@ -308,7 +318,7 @@ final class Run
|
||||
*/
|
||||
public function handleError($level, $message, $file = null, $line = null)
|
||||
{
|
||||
if ($level & error_reporting()) {
|
||||
if ($level & $this->system->getErrorReportingLevel()) {
|
||||
foreach ($this->silencedPatterns as $entry) {
|
||||
$pathMatches = (bool) preg_match($entry["pattern"], $file);
|
||||
$levelMatches = $level & $entry["levels"];
|
||||
@@ -345,7 +355,7 @@ final class Run
|
||||
// to the exception handler. Pass that information along.
|
||||
$this->canThrowExceptions = false;
|
||||
|
||||
$error = error_get_last();
|
||||
$error = $this->system->getLastError();
|
||||
if ($error && Misc::isLevelFatal($error['type'])) {
|
||||
// If there was a fatal error,
|
||||
// it was not handled in handleError yet.
|
||||
@@ -372,22 +382,9 @@ final class Run
|
||||
private function writeToOutputNow($output)
|
||||
{
|
||||
if ($this->sendHttpCode() && \Whoops\Util\Misc::canSendHeaders()) {
|
||||
$httpCode = $this->sendHttpCode();
|
||||
|
||||
if (function_exists('http_response_code')) {
|
||||
http_response_code($httpCode);
|
||||
} else {
|
||||
// http_response_code is added in 5.4.
|
||||
// For compatibility with 5.3 we use the third argument in header call
|
||||
// First argument must be a real header.
|
||||
// If it is empty, PHP will ignore the third argument.
|
||||
// If it is invalid, such as a single space, Apache will handle it well,
|
||||
// but the PHP development server will hang.
|
||||
// Setting a full status line would require us to hardcode
|
||||
// string values for all different status code, and detect the protocol.
|
||||
// which is an extra error-prone complexity.
|
||||
header('X-Ignore-This: 1', true, $httpCode);
|
||||
}
|
||||
$this->system->setHttpResponseCode(
|
||||
$this->sendHttpCode()
|
||||
);
|
||||
}
|
||||
|
||||
echo $output;
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
<?php
|
||||
/**
|
||||
* Whoops - php errors for cool kids
|
||||
* @author Filipe Dobreira <http://github.com/filp>
|
||||
*/
|
||||
|
||||
namespace Whoops\Util;
|
||||
|
||||
class SystemFacade
|
||||
{
|
||||
/**
|
||||
* Turns on output buffering.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function startOutputBuffering()
|
||||
{
|
||||
return ob_start();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param callable $handler
|
||||
* @param int $types
|
||||
*
|
||||
* @return callable|null
|
||||
*/
|
||||
public function setErrorHandler(callable $handler, $types = 'use-php-defaults')
|
||||
{
|
||||
// Workaround for PHP 5.5
|
||||
if ($types === 'use-php-defaults') {
|
||||
$types = E_ALL | E_STRICT;
|
||||
}
|
||||
return set_error_handler($handler, $types);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param callable $handler
|
||||
*
|
||||
* @return callable|null
|
||||
*/
|
||||
public function setExceptionHandler(callable $handler)
|
||||
{
|
||||
return set_exception_handler($handler);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function restoreExceptionHandler()
|
||||
{
|
||||
restore_exception_handler();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function restoreErrorHandler()
|
||||
{
|
||||
restore_error_handler();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param callable $function
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function registerShutdownFunction(callable $function)
|
||||
{
|
||||
register_shutdown_function($function);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|false
|
||||
*/
|
||||
public function cleanOutputBuffer()
|
||||
{
|
||||
return ob_get_clean();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getOutputBufferLevel()
|
||||
{
|
||||
return ob_get_level();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function endOutputBuffering()
|
||||
{
|
||||
return ob_end_clean();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function flushOutputBuffer()
|
||||
{
|
||||
flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getErrorReportingLevel()
|
||||
{
|
||||
return error_reporting();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array|null
|
||||
*/
|
||||
public function getLastError()
|
||||
{
|
||||
return error_get_last();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $httpCode
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function setHttpResponseCode($httpCode)
|
||||
{
|
||||
return http_response_code($httpCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $exitStatus
|
||||
*/
|
||||
public function stopExecution($exitStatus)
|
||||
{
|
||||
exit($exitStatus);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
<?php
|
||||
namespace Whoops\Util;
|
||||
|
||||
class SystemFacadeTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
/**
|
||||
* @var \Mockery\Mock
|
||||
*/
|
||||
public static $runtime;
|
||||
|
||||
/**
|
||||
* @var SystemFacade
|
||||
*/
|
||||
private $facade;
|
||||
|
||||
public static function delegate($fn, array $args = [])
|
||||
{
|
||||
return self::$runtime
|
||||
? call_user_func_array([self::$runtime, $fn], $args)
|
||||
: call_user_func_array("\\$fn", $args);
|
||||
}
|
||||
|
||||
protected function setUp()
|
||||
{
|
||||
self::$runtime = \Mockery::mock(['ob_start' => true]);
|
||||
$this->facade = new SystemFacade();
|
||||
}
|
||||
|
||||
protected function tearDown()
|
||||
{
|
||||
self::$runtime = null;
|
||||
\Mockery::close();
|
||||
}
|
||||
|
||||
public function test_it_delegates_output_buffering_to_the_native_implementation()
|
||||
{
|
||||
self::$runtime->shouldReceive('ob_start')->once();
|
||||
|
||||
$this->facade->startOutputBuffering();
|
||||
}
|
||||
|
||||
public function test_it_delegates_cleaning_output_buffering_to_the_native_implementation()
|
||||
{
|
||||
self::$runtime->shouldReceive('ob_get_clean')->once();
|
||||
|
||||
$this->facade->cleanOutputBuffer();
|
||||
}
|
||||
|
||||
public function test_it_delegates_getting_the_current_buffer_level_to_the_native_implementation()
|
||||
{
|
||||
self::$runtime->shouldReceive('ob_get_level')->once();
|
||||
|
||||
$this->facade->getOutputBufferLevel();
|
||||
}
|
||||
|
||||
public function test_it_delegates_ending_the_current_buffer_to_the_native_implementation()
|
||||
{
|
||||
self::$runtime->shouldReceive('ob_end_clean')->once();
|
||||
|
||||
$this->facade->endOutputBuffering();
|
||||
}
|
||||
|
||||
public function test_it_delegates_flushing_the_current_buffer_to_the_native_implementation()
|
||||
{
|
||||
self::$runtime->shouldReceive('flush')->once();
|
||||
|
||||
$this->facade->flushOutputBuffer();
|
||||
}
|
||||
|
||||
public function test_it_delegates_error_handling_to_the_native_implementation()
|
||||
{
|
||||
self::$runtime->shouldReceive('set_error_handler')->once();
|
||||
|
||||
$this->facade->setErrorHandler(function(){});
|
||||
}
|
||||
|
||||
public function test_it_delegates_error_handling_with_level_to_the_native_implementation()
|
||||
{
|
||||
self::$runtime->shouldReceive('set_error_handler')->once();
|
||||
|
||||
$this->facade->setErrorHandler(function(){}, E_CORE_ERROR);
|
||||
}
|
||||
|
||||
public function test_it_delegates_exception_handling_to_the_native_implementation()
|
||||
{
|
||||
self::$runtime->shouldReceive('set_exception_handler')->once();
|
||||
|
||||
$this->facade->setExceptionHandler(function(){});
|
||||
}
|
||||
|
||||
public function test_it_delegates_restoring_the_exception_handler_to_the_native_implementation()
|
||||
{
|
||||
self::$runtime->shouldReceive('restore_exception_handler')->once();
|
||||
|
||||
$this->facade->restoreExceptionHandler();
|
||||
}
|
||||
|
||||
public function test_it_delegates_restoring_the_error_handler_to_the_native_implementation()
|
||||
{
|
||||
self::$runtime->shouldReceive('restore_error_handler')->once();
|
||||
|
||||
$this->facade->restoreErrorHandler();
|
||||
}
|
||||
|
||||
public function test_it_delegates_registering_a_shutdown_function_to_the_native_implementation()
|
||||
{
|
||||
self::$runtime->shouldReceive('register_shutdown_function')->once();
|
||||
|
||||
$this->facade->registerShutdownFunction(function(){});
|
||||
}
|
||||
|
||||
public function test_it_delegates_error_reporting_to_the_native_implementation()
|
||||
{
|
||||
self::$runtime->shouldReceive('error_reporting')->once()->withNoArgs();
|
||||
|
||||
$this->facade->getErrorReportingLevel();
|
||||
}
|
||||
|
||||
public function test_it_delegates_getting_the_last_error_to_the_native_implementation()
|
||||
{
|
||||
self::$runtime->shouldReceive('error_get_last')->once()->withNoArgs();
|
||||
|
||||
$this->facade->getLastError();
|
||||
}
|
||||
|
||||
public function test_it_delegates_sending_an_http_response_code_to_the_native_implementation()
|
||||
{
|
||||
self::$runtime->shouldReceive('http_response_code')->once()->with(230);
|
||||
|
||||
$this->facade->setHttpResponseCode(230);
|
||||
}
|
||||
}
|
||||
|
||||
function ob_start()
|
||||
{
|
||||
return SystemFacadeTest::delegate('ob_start');
|
||||
}
|
||||
|
||||
function ob_get_clean()
|
||||
{
|
||||
return SystemFacadeTest::delegate('ob_get_clean');
|
||||
}
|
||||
|
||||
function ob_get_level()
|
||||
{
|
||||
return SystemFacadeTest::delegate('ob_get_level');
|
||||
}
|
||||
|
||||
function ob_end_clean()
|
||||
{
|
||||
return SystemFacadeTest::delegate('ob_end_clean');
|
||||
}
|
||||
|
||||
function flush()
|
||||
{
|
||||
return SystemFacadeTest::delegate('flush');
|
||||
}
|
||||
|
||||
function set_error_handler(callable $handler, $types = 'use-php-defaults')
|
||||
{
|
||||
// Workaround for PHP 5.5
|
||||
if ($types === 'use-php-defaults') {
|
||||
$types = E_ALL | E_STRICT;
|
||||
}
|
||||
return SystemFacadeTest::delegate('set_error_handler', func_get_args());
|
||||
}
|
||||
|
||||
function set_exception_handler(callable $handler)
|
||||
{
|
||||
return SystemFacadeTest::delegate('set_exception_handler', func_get_args());
|
||||
}
|
||||
|
||||
function restore_exception_handler()
|
||||
{
|
||||
return SystemFacadeTest::delegate('restore_exception_handler');
|
||||
}
|
||||
|
||||
function restore_error_handler()
|
||||
{
|
||||
return SystemFacadeTest::delegate('restore_error_handler');
|
||||
}
|
||||
|
||||
function register_shutdown_function()
|
||||
{
|
||||
return SystemFacadeTest::delegate('register_shutdown_function', func_get_args());
|
||||
}
|
||||
|
||||
function error_reporting($level = null)
|
||||
{
|
||||
return SystemFacadeTest::delegate('error_reporting', func_get_args());
|
||||
}
|
||||
|
||||
function error_get_last()
|
||||
{
|
||||
return SystemFacadeTest::delegate('error_get_last', func_get_args());
|
||||
}
|
||||
|
||||
function http_response_code($code = null)
|
||||
{
|
||||
return SystemFacadeTest::delegate('http_response_code', func_get_args());
|
||||
}
|
||||
Reference in New Issue
Block a user