mirror of
https://github.com/filp/whoops.git
synced 2026-09-04 22:48:11 +00:00
Merge branch 'php_runtime_facade'
This commit is contained in:
+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,152 @@
|
||||
<?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)
|
||||
{
|
||||
if (function_exists('http_response_code')) {
|
||||
return http_response_code($httpCode);
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
return $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