Merge branch 'php_runtime_facade'

This commit is contained in:
Denis Sokolov
2016-01-23 23:04:51 +02:00
3 changed files with 379 additions and 29 deletions
+26 -29
View File
@@ -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;
+152
View File
@@ -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);
}
}
+201
View File
@@ -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());
}