Compare commits

...

13 Commits

Author SHA1 Message Date
Jerome TAMARELLE
3c1b5f218d Check headers sent before sending PHP response
If the response contents has been sent before an error occurs, PHP
triggers the warning "Cannot modify header information - headers already sent"

This change ensure that the error message is echoed, while it's impossible
to change the HTTP status code and headers.
2014-03-26 19:06:50 +01:00
Fabien Potencier
78dc94dce6 changed some PHPUnit assertions to more specific ones 2014-03-01 18:25:29 +01:00
Fabien Potencier
4885c636af fixed various inconsistencies 2014-02-11 11:29:24 +01:00
sun
6a55635898 Fixed recursion level incrementing in FlattenException::flattenArgs(). 2014-02-04 08:06:39 +01:00
Luis Cordova
7f875d2aae update year on licenses 2014-01-07 08:19:25 -05:00
Pascal Borreli
13d5fe432f Fixed typo 2013-12-28 12:31:17 +01:00
Fabien Potencier
89151cc3fe fixed CS for lambdas 2013-12-28 08:46:05 +01:00
Fabien Potencier
93f26330c7 fixed CS 2013-12-12 17:08:59 +01:00
Fabien Potencier
b4ea949b41 [Debug] ensured that a fatal PHP error is actually fatal after being handled by our error handler 2013-11-28 17:41:31 +01:00
Fabien Potencier
264eb40f8d [Debug] fixed unit tests 2013-11-28 11:16:43 +01:00
Christian Schmidt
ff1f1fc483 Avoid notice from being *eaten* by fatal error. 2013-11-28 11:16:19 +01:00
Fabien Potencier
5da7e8c937 fixed typos 2013-11-25 16:00:46 +01:00
Fabien Potencier
085d4fd990 fixed CS 2013-10-30 09:30:20 +01:00
9 changed files with 174 additions and 19 deletions

View File

@@ -13,6 +13,7 @@ namespace Symfony\Component\Debug;
use Symfony\Component\Debug\Exception\FatalErrorException;
use Symfony\Component\Debug\Exception\ContextErrorException;
use Symfony\Component\Debug\Exception\DummyException;
use Psr\Log\LoggerInterface;
/**
@@ -125,7 +126,34 @@ class ErrorHandler
require __DIR__.'/Exception/ContextErrorException.php';
}
throw new ContextErrorException(sprintf('%s: %s in %s line %d', isset($this->levels[$level]) ? $this->levels[$level] : $level, $message, $file, $line), 0, $level, $file, $line, $context);
$exception = new ContextErrorException(sprintf('%s: %s in %s line %d', isset($this->levels[$level]) ? $this->levels[$level] : $level, $message, $file, $line), 0, $level, $file, $line, $context);
// Exceptions thrown from error handlers are sometimes not caught by the exception
// handler, so we invoke it directly (https://bugs.php.net/bug.php?id=54275)
$exceptionHandler = set_exception_handler(function () {});
restore_exception_handler();
if (is_array($exceptionHandler) && $exceptionHandler[0] instanceof ExceptionHandler) {
$exceptionHandler[0]->handle($exception);
if (!class_exists('Symfony\Component\Debug\Exception\DummyException')) {
require __DIR__.'/Exception/DummyException.php';
}
// we must stop the PHP script execution, as the exception has
// already been dealt with, so, let's throw an exception that
// will be caught by a dummy exception handler
set_exception_handler(function (\Exception $e) use ($exceptionHandler) {
if (!$e instanceof DummyException) {
// happens if our dummy exception is caught by a
// catch-all from user code, in which case, let's the
// current handler handle this "new" exception
call_user_func($exceptionHandler, $e);
}
});
throw new DummyException();
}
}
return false;
@@ -158,7 +186,7 @@ class ErrorHandler
}
// get current exception handler
$exceptionHandler = set_exception_handler(function() {});
$exceptionHandler = set_exception_handler(function () {});
restore_exception_handler();
if (is_array($exceptionHandler) && $exceptionHandler[0] instanceof ExceptionHandler) {

View File

@@ -27,7 +27,7 @@ class ContextErrorException extends \ErrorException
}
/**
* @return array Array of variables that existed when the exception occured
* @return array Array of variables that existed when the exception occurred
*/
public function getContext()
{

View File

@@ -0,0 +1,21 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Debug\Exception;
/**
* Used to stop execution of a PHP script after handling a fatal error.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class DummyException extends \ErrorException
{
}

View File

@@ -249,7 +249,7 @@ class FlattenException
if ($level > 10) {
$result[$key] = array('array', '*DEEP NESTED ARRAY*');
} else {
$result[$key] = array('array', $this->flattenArgs($value, ++$level));
$result[$key] = array('array', $this->flattenArgs($value, $level + 1));
}
} elseif (null === $value) {
$result[$key] = array('null', null);

View File

@@ -91,9 +91,11 @@ class ExceptionHandler
$exception = FlattenException::create($exception);
}
header(sprintf('HTTP/1.0 %s', $exception->getStatusCode()));
foreach ($exception->getHeaders() as $name => $value) {
header($name.': '.$value, false);
if (!headers_sent()) {
header(sprintf('HTTP/1.0 %s', $exception->getStatusCode()));
foreach ($exception->getHeaders() as $name => $value) {
header($name.': '.$value, false);
}
}
echo $this->decorate($this->getContent($exception), $this->getStylesheet($exception));

View File

@@ -1,4 +1,4 @@
Copyright (c) 2004-2013 Fabien Potencier
Copyright (c) 2004-2014 Fabien Potencier
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@@ -12,6 +12,7 @@
namespace Symfony\Component\Debug\Tests;
use Symfony\Component\Debug\ErrorHandler;
use Symfony\Component\Debug\Exception\DummyException;
/**
* ErrorHandlerTest
@@ -20,30 +21,133 @@ use Symfony\Component\Debug\ErrorHandler;
*/
class ErrorHandlerTest extends \PHPUnit_Framework_TestCase
{
/**
* @var int Error reporting level before running tests.
*/
protected $errorReporting;
/**
* @var string Display errors setting before running tests.
*/
protected $displayErrors;
public function setUp()
{
$this->errorReporting = error_reporting(E_ALL | E_STRICT);
$this->displayErrors = ini_get('display_errors');
ini_set('display_errors', '1');
}
public function tearDown()
{
ini_set('display_errors', $this->displayErrors);
error_reporting($this->errorReporting);
}
public function testCompileTimeError()
{
// the ContextErrorException must not be loaded for this test to work
// the ContextErrorException must not be loaded to test the workaround
// for https://bugs.php.net/bug.php?id=65322.
if (class_exists('Symfony\Component\Debug\Exception\ContextErrorException', false)) {
$this->markTestSkipped('The ContextErrorException class is already loaded.');
}
$handler = ErrorHandler::register(E_ALL | E_STRICT);
$displayErrors = ini_get('display_errors');
ini_set('display_errors', '1');
$exceptionHandler = $this->getMock('Symfony\Component\Debug\ExceptionHandler', array('handle'));
// the following code forces some PHPUnit classes to be loaded
// so that they will be available in the exception handler
// as they won't be autoloaded by PHP
class_exists('PHPUnit_Framework_MockObject_Invocation_Object');
$this->assertInstanceOf('stdClass', new \stdClass());
$this->assertEquals(1, 1);
$this->assertStringStartsWith('foo', 'foobar');
$this->assertArrayHasKey('bar', array('bar' => 'foo'));
$that = $this;
$exceptionCheck = function ($exception) use ($that) {
$that->assertInstanceOf('Symfony\Component\Debug\Exception\ContextErrorException', $exception);
$that->assertEquals(E_STRICT, $exception->getSeverity());
$that->assertEquals(2, $exception->getLine());
$that->assertStringStartsWith('Runtime Notice: Declaration of _CompileTimeError::foo() should be compatible with', $exception->getMessage());
$that->assertArrayHasKey('bar', $exception->getContext());
};
$exceptionHandler->expects($this->once())
->method('handle')
->will($this->returnCallback($exceptionCheck))
;
ErrorHandler::register();
set_exception_handler(array($exceptionHandler, 'handle'));
// dummy variable to check for in error handler.
$bar = 123;
// trigger compile time error
try {
// trigger compile time error
eval(<<<'PHP'
class _BaseCompileTimeError { function foo() {} }
class _CompileTimeError extends _BaseCompileTimeError { function foo($invalid) {} }
PHP
);
} catch(\Exception $e) {
} catch (DummyException $e) {
// if an exception is thrown, the test passed
}
ini_set('display_errors', $displayErrors);
restore_error_handler();
restore_exception_handler();
}
public function testNotice()
{
$exceptionHandler = $this->getMock('Symfony\Component\Debug\ExceptionHandler', array('handle'));
set_exception_handler(array($exceptionHandler, 'handle'));
$that = $this;
$exceptionCheck = function ($exception) use ($that) {
$that->assertInstanceOf('Symfony\Component\Debug\Exception\ContextErrorException', $exception);
$that->assertEquals(E_NOTICE, $exception->getSeverity());
$that->assertEquals(__LINE__ + 40, $exception->getLine());
$that->assertEquals(__FILE__, $exception->getFile());
$that->assertRegexp('/^Notice: Undefined variable: (foo|bar)/', $exception->getMessage());
$that->assertArrayHasKey('foobar', $exception->getContext());
$trace = $exception->getTrace();
$that->assertEquals(__FILE__, $trace[0]['file']);
$that->assertEquals('Symfony\Component\Debug\ErrorHandler', $trace[0]['class']);
$that->assertEquals('handle', $trace[0]['function']);
$that->assertEquals('->', $trace[0]['type']);
$that->assertEquals(__FILE__, $trace[1]['file']);
$that->assertEquals(__CLASS__, $trace[1]['class']);
$that->assertEquals('triggerNotice', $trace[1]['function']);
$that->assertEquals('::', $trace[1]['type']);
$that->assertEquals(__CLASS__, $trace[2]['class']);
$that->assertEquals('testNotice', $trace[2]['function']);
$that->assertEquals('->', $trace[2]['type']);
};
$exceptionHandler->expects($this->once())
->method('handle')
->will($this->returnCallback($exceptionCheck));
ErrorHandler::register();
try {
self::triggerNotice($this);
} catch (DummyException $e) {
// if an exception is thrown, the test passed
}
restore_error_handler();
}
// dummy function to test trace in error handler.
private static function triggerNotice($that)
{
// dummy variable to check for in error handler.
$foobar = 123;
$that->assertSame('', $foo.$foo.$bar);
}
public function testConstruct()
@@ -95,7 +199,7 @@ PHP
$logger = $this->getMock('Psr\Log\LoggerInterface');
$that = $this;
$warnArgCheck = function($message, $context) use ($that) {
$warnArgCheck = function ($message, $context) use ($that) {
$that->assertEquals('foo', $message);
$that->assertArrayHasKey('type', $context);
$that->assertEquals($context['type'], ErrorHandler::TYPE_DEPRECATION);

View File

@@ -113,7 +113,7 @@ class FlattenExceptionTest extends \PHPUnit_Framework_TestCase
$this->assertEquals($exception->getMessage(), $flattened->getMessage(), 'The message is copied from the original exception.');
$this->assertEquals($exception->getCode(), $flattened->getCode(), 'The code is copied from the original exception.');
$this->assertEquals(get_class($exception), $flattened->getClass(), 'The class is set to the class of the original exception');
$this->assertInstanceOf($flattened->getClass(), $exception, 'The class is set to the class of the original exception');
}

View File

@@ -64,6 +64,6 @@ class ExceptionHandlerTest extends \PHPUnit_Framework_TestCase
public function testNestedExceptions()
{
$handler = new ExceptionHandler(true);
$response = $handler->createResponse(new \RuntimeException('Foo', null, new \RuntimeException('Bar')));
$response = $handler->createResponse(new \RuntimeException('Foo', 0, new \RuntimeException('Bar')));
}
}