Skip to content

Commit

Permalink
Avoid notice from being *eaten* by fatal error.
Browse files Browse the repository at this point in the history
  • Loading branch information
Christian Schmidt authored and fabpot committed Nov 28, 2013
1 parent 5307874 commit 2d6c2aa
Show file tree
Hide file tree
Showing 2 changed files with 104 additions and 14 deletions.
12 changes: 11 additions & 1 deletion src/Symfony/Component/Debug/ErrorHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,17 @@ function ($row) {
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);
return true;
}
}

return false;
Expand Down
106 changes: 93 additions & 13 deletions src/Symfony/Component/Debug/Tests/ErrorHandlerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
namespace Symfony\Component\Debug\Tests;

use Symfony\Component\Debug\ErrorHandler;
use Symfony\Component\Debug\ExceptionHandler;

/**
* ErrorHandlerTest
Expand All @@ -20,32 +21,111 @@
*/
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
$exceptionHandler = $this->getMock('Symfony\Component\Debug\ExceptionHandler', array('handle'));
set_exception_handler(array($exceptionHandler, 'handle'));

// 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');
$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 that of _BaseCompileTimeError::foo()', $exception->getMessage());
$that->assertArrayHasKey('bar', $exception->getContext());
};

try {
// trigger compile time error
eval(<<<'PHP'
$exceptionHandler->expects($this->once())
->method('handle')
->will($this->returnCallback($exceptionCheck));
ErrorHandler::register();

// dummy variable to check for in error handler.
$bar = 123;

// trigger compile time error
eval(<<<'PHP'
class _BaseCompileTimeError { function foo() {} }
class _CompileTimeError extends _BaseCompileTimeError { function foo($invalid) {} }
PHP
);
} catch (\Exception $e) {
// if an exception is thrown, the test passed
}
);
restore_error_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__ + 35, $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->exactly(3))
->method('handle')
->will($this->returnCallback($exceptionCheck));
ErrorHandler::register();

self::triggerNotice($this);

ini_set('display_errors', $displayErrors);
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()
{
$handler = ErrorHandler::register(3);
Expand Down Expand Up @@ -92,7 +172,7 @@ public function testHandle()

restore_error_handler();

$logger = $this->getMock('Psr\Log\LoggerInterface');
$logger = $this->getMock('Psr\Log\LoggerInterface', array('warning'));

$that = $this;
$warnArgCheck = function($message, $context) use ($that) {
Expand Down

0 comments on commit 2d6c2aa

Please sign in to comment.