bug #11759 [Debug] fix and enhance exception messages (nicolas-grekas)
This PR was merged into the 2.6-dev branch.
Discussion
----------
[Debug] fix and enhance exception messages
| Q | A
| ------------- | ---
| Bug fix? | yes
| New feature? | no
| BC breaks? | no
| Deprecations? | no
| Tests pass? | yes
| Fixed tickets | #11341
| License | MIT
| Doc PR | none
Commits
-------
8df1ce8
[Debug] fix and enhance exception messages
This commit is contained in:
commit
4b507bd7a4
@ -210,9 +210,11 @@ class ExceptionHandler
|
|||||||
$class = $this->formatClass($e['class']);
|
$class = $this->formatClass($e['class']);
|
||||||
$message = nl2br(self::utf8Htmlize($e['message']));
|
$message = nl2br(self::utf8Htmlize($e['message']));
|
||||||
$content .= sprintf(<<<EOF
|
$content .= sprintf(<<<EOF
|
||||||
<div class="block_exception clear_fix">
|
<h2 class="block_exception clear_fix">
|
||||||
<h2><span>%d/%d</span> %s%s:<br />%s</h2>
|
<span class="exception_counter">%d/%d</span>
|
||||||
</div>
|
<span class="exception_title">%s%s:</span>
|
||||||
|
<span class="exception_message">%s</span>
|
||||||
|
</h2>
|
||||||
<div class="block">
|
<div class="block">
|
||||||
<ol class="traces list_exception">
|
<ol class="traces list_exception">
|
||||||
|
|
||||||
@ -269,12 +271,14 @@ EOF;
|
|||||||
.sf-reset abbr { border-bottom: 1px dotted #000; cursor: help; }
|
.sf-reset abbr { border-bottom: 1px dotted #000; cursor: help; }
|
||||||
.sf-reset p { font-size:14px; line-height:20px; color:#868686; padding-bottom:20px }
|
.sf-reset p { font-size:14px; line-height:20px; color:#868686; padding-bottom:20px }
|
||||||
.sf-reset strong { font-weight:bold; }
|
.sf-reset strong { font-weight:bold; }
|
||||||
.sf-reset a { color:#6c6159; }
|
.sf-reset a { color:#6c6159; cursor: default; }
|
||||||
.sf-reset a img { border:none; }
|
.sf-reset a img { border:none; }
|
||||||
.sf-reset a:hover { text-decoration:underline; }
|
.sf-reset a:hover { text-decoration:underline; }
|
||||||
.sf-reset em { font-style:italic; }
|
.sf-reset em { font-style:italic; }
|
||||||
.sf-reset h1, .sf-reset h2 { font: 20px Georgia, "Times New Roman", Times, serif }
|
.sf-reset h1, .sf-reset h2 { font: 20px Georgia, "Times New Roman", Times, serif }
|
||||||
.sf-reset h2 span { background-color: #fff; color: #333; padding: 6px; float: left; margin-right: 10px; }
|
.sf-reset .exception_counter { background-color: #fff; color: #333; padding: 6px; float: left; margin-right: 10px; float: left; display: block; }
|
||||||
|
.sf-reset .exception_title { margin-left: 3em; margin-bottom: 0.7em; display: block; }
|
||||||
|
.sf-reset .exception_message { margin-left: 3em; display: block; }
|
||||||
.sf-reset .traces li { font-size:12px; padding: 2px 4px; list-style-type:decimal; margin-left:20px; }
|
.sf-reset .traces li { font-size:12px; padding: 2px 4px; list-style-type:decimal; margin-left:20px; }
|
||||||
.sf-reset .block { background-color:#FFFFFF; padding:10px 28px; margin-bottom:20px;
|
.sf-reset .block { background-color:#FFFFFF; padding:10px 28px; margin-bottom:20px;
|
||||||
-webkit-border-bottom-right-radius: 16px;
|
-webkit-border-bottom-right-radius: 16px;
|
||||||
@ -352,10 +356,10 @@ EOF;
|
|||||||
if ($linkFormat = ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format')) {
|
if ($linkFormat = ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format')) {
|
||||||
$link = str_replace(array('%f', '%l'), array($path, $line), $linkFormat);
|
$link = str_replace(array('%f', '%l'), array($path, $line), $linkFormat);
|
||||||
|
|
||||||
return sprintf(' <a href="%s" title="Go to source">in %s line %d</a>', $link, $file, $line);
|
return sprintf(' in <a href="%s" title="Go to source">%s line %d</a>', $link, $file, $line);
|
||||||
}
|
}
|
||||||
|
|
||||||
return sprintf(' <a title="in %s line %3$d" ondblclick="var f=this.innerHTML;this.innerHTML=this.title;this.title=f;">in %s line %d</a>', $path, $file, $line);
|
return sprintf(' in <a title="%s line %3$d" ondblclick="var f=this.innerHTML;this.innerHTML=this.title;this.title=f;">%s line %d</a>', $path, $file, $line);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -67,7 +67,7 @@ class ClassNotFoundFatalErrorHandler implements FatalErrorHandlerInterface
|
|||||||
$tail = ' for "'.$tail;
|
$tail = ' for "'.$tail;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$message .= ' Did you forget a "use" statement'.$tail;
|
$message .= "\nDid you forget a \"use\" statement".$tail;
|
||||||
|
|
||||||
return new ClassNotFoundException($message, $exception);
|
return new ClassNotFoundException($message, $exception);
|
||||||
}
|
}
|
||||||
|
@ -76,7 +76,7 @@ class UndefinedFunctionFatalErrorHandler implements FatalErrorHandlerInterface
|
|||||||
} else {
|
} else {
|
||||||
$candidates = '"'.$last;
|
$candidates = '"'.$last;
|
||||||
}
|
}
|
||||||
$message .= ' Did you mean to call '.$candidates;
|
$message .= "\nDid you mean to call ".$candidates;
|
||||||
}
|
}
|
||||||
|
|
||||||
return new UndefinedFunctionException($message, $exception);
|
return new UndefinedFunctionException($message, $exception);
|
||||||
|
@ -52,7 +52,7 @@ class UndefinedMethodFatalErrorHandler implements FatalErrorHandlerInterface
|
|||||||
} else {
|
} else {
|
||||||
$candidates = '"'.$last;
|
$candidates = '"'.$last;
|
||||||
}
|
}
|
||||||
$message .= ' Did you mean to call '.$candidates;
|
$message .= "\nDid you mean to call ".$candidates;
|
||||||
}
|
}
|
||||||
|
|
||||||
return new UndefinedMethodException($message, $exception);
|
return new UndefinedMethodException($message, $exception);
|
||||||
|
@ -25,13 +25,13 @@ class ExceptionHandlerTest extends \PHPUnit_Framework_TestCase
|
|||||||
$response = $handler->createResponse(new \RuntimeException('Foo'));
|
$response = $handler->createResponse(new \RuntimeException('Foo'));
|
||||||
|
|
||||||
$this->assertContains('<h1>Whoops, looks like something went wrong.</h1>', $response->getContent());
|
$this->assertContains('<h1>Whoops, looks like something went wrong.</h1>', $response->getContent());
|
||||||
$this->assertNotContains('<div class="block_exception clear_fix">', $response->getContent());
|
$this->assertNotContains('<h2 class="block_exception clear_fix">', $response->getContent());
|
||||||
|
|
||||||
$handler = new ExceptionHandler(true);
|
$handler = new ExceptionHandler(true);
|
||||||
$response = $handler->createResponse(new \RuntimeException('Foo'));
|
$response = $handler->createResponse(new \RuntimeException('Foo'));
|
||||||
|
|
||||||
$this->assertContains('<h1>Whoops, looks like something went wrong.</h1>', $response->getContent());
|
$this->assertContains('<h1>Whoops, looks like something went wrong.</h1>', $response->getContent());
|
||||||
$this->assertContains('<div class="block_exception clear_fix">', $response->getContent());
|
$this->assertContains('<h2 class="block_exception clear_fix">', $response->getContent());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testStatusCode()
|
public function testStatusCode()
|
||||||
|
@ -41,7 +41,7 @@ class ClassNotFoundFatalErrorHandlerTest extends \PHPUnit_Framework_TestCase
|
|||||||
'file' => 'foo.php',
|
'file' => 'foo.php',
|
||||||
'message' => 'Class \'WhizBangFactory\' not found',
|
'message' => 'Class \'WhizBangFactory\' not found',
|
||||||
),
|
),
|
||||||
'Attempted to load class "WhizBangFactory" from the global namespace. Did you forget a "use" statement?',
|
"Attempted to load class \"WhizBangFactory\" from the global namespace.\nDid you forget a \"use\" statement?",
|
||||||
),
|
),
|
||||||
array(
|
array(
|
||||||
array(
|
array(
|
||||||
@ -50,7 +50,7 @@ class ClassNotFoundFatalErrorHandlerTest extends \PHPUnit_Framework_TestCase
|
|||||||
'file' => 'foo.php',
|
'file' => 'foo.php',
|
||||||
'message' => 'Class \'Foo\\Bar\\WhizBangFactory\' not found',
|
'message' => 'Class \'Foo\\Bar\\WhizBangFactory\' not found',
|
||||||
),
|
),
|
||||||
'Attempted to load class "WhizBangFactory" from namespace "Foo\\Bar". Did you forget a "use" statement for another namespace?',
|
"Attempted to load class \"WhizBangFactory\" from namespace \"Foo\\Bar\".\nDid you forget a \"use\" statement for another namespace?",
|
||||||
),
|
),
|
||||||
array(
|
array(
|
||||||
array(
|
array(
|
||||||
@ -59,7 +59,7 @@ class ClassNotFoundFatalErrorHandlerTest extends \PHPUnit_Framework_TestCase
|
|||||||
'file' => 'foo.php',
|
'file' => 'foo.php',
|
||||||
'message' => 'Class \'UndefinedFunctionException\' not found',
|
'message' => 'Class \'UndefinedFunctionException\' not found',
|
||||||
),
|
),
|
||||||
'Attempted to load class "UndefinedFunctionException" from the global namespace. Did you forget a "use" statement for "Symfony\Component\Debug\Exception\UndefinedFunctionException"?',
|
"Attempted to load class \"UndefinedFunctionException\" from the global namespace.\nDid you forget a \"use\" statement for \"Symfony\Component\Debug\Exception\UndefinedFunctionException\"?",
|
||||||
),
|
),
|
||||||
array(
|
array(
|
||||||
array(
|
array(
|
||||||
@ -68,7 +68,7 @@ class ClassNotFoundFatalErrorHandlerTest extends \PHPUnit_Framework_TestCase
|
|||||||
'file' => 'foo.php',
|
'file' => 'foo.php',
|
||||||
'message' => 'Class \'PEARClass\' not found',
|
'message' => 'Class \'PEARClass\' not found',
|
||||||
),
|
),
|
||||||
'Attempted to load class "PEARClass" from the global namespace. Did you forget a "use" statement for "Symfony_Component_Debug_Tests_Fixtures_PEARClass"?',
|
"Attempted to load class \"PEARClass\" from the global namespace.\nDid you forget a \"use\" statement for \"Symfony_Component_Debug_Tests_Fixtures_PEARClass\"?",
|
||||||
),
|
),
|
||||||
array(
|
array(
|
||||||
array(
|
array(
|
||||||
@ -77,7 +77,7 @@ class ClassNotFoundFatalErrorHandlerTest extends \PHPUnit_Framework_TestCase
|
|||||||
'file' => 'foo.php',
|
'file' => 'foo.php',
|
||||||
'message' => 'Class \'Foo\\Bar\\UndefinedFunctionException\' not found',
|
'message' => 'Class \'Foo\\Bar\\UndefinedFunctionException\' not found',
|
||||||
),
|
),
|
||||||
'Attempted to load class "UndefinedFunctionException" from namespace "Foo\Bar". Did you forget a "use" statement for "Symfony\Component\Debug\Exception\UndefinedFunctionException"?',
|
"Attempted to load class \"UndefinedFunctionException\" from namespace \"Foo\Bar\".\nDid you forget a \"use\" statement for \"Symfony\Component\Debug\Exception\UndefinedFunctionException\"?",
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -42,7 +42,7 @@ class UndefinedFunctionFatalErrorHandlerTest extends \PHPUnit_Framework_TestCase
|
|||||||
'file' => 'foo.php',
|
'file' => 'foo.php',
|
||||||
'message' => 'Call to undefined function test_namespaced_function()',
|
'message' => 'Call to undefined function test_namespaced_function()',
|
||||||
),
|
),
|
||||||
'Attempted to call function "test_namespaced_function" from the global namespace. Did you mean to call "\\symfony\\component\\debug\\tests\\fatalerrorhandler\\test_namespaced_function"?',
|
"Attempted to call function \"test_namespaced_function\" from the global namespace.\nDid you mean to call \"\\symfony\\component\\debug\\tests\\fatalerrorhandler\\test_namespaced_function\"?",
|
||||||
),
|
),
|
||||||
array(
|
array(
|
||||||
array(
|
array(
|
||||||
@ -51,7 +51,7 @@ class UndefinedFunctionFatalErrorHandlerTest extends \PHPUnit_Framework_TestCase
|
|||||||
'file' => 'foo.php',
|
'file' => 'foo.php',
|
||||||
'message' => 'Call to undefined function Foo\\Bar\\Baz\\test_namespaced_function()',
|
'message' => 'Call to undefined function Foo\\Bar\\Baz\\test_namespaced_function()',
|
||||||
),
|
),
|
||||||
'Attempted to call function "test_namespaced_function" from namespace "Foo\\Bar\\Baz". Did you mean to call "\\symfony\\component\\debug\\tests\\fatalerrorhandler\\test_namespaced_function"?',
|
"Attempted to call function \"test_namespaced_function\" from namespace \"Foo\\Bar\\Baz\".\nDid you mean to call \"\\symfony\\component\\debug\\tests\\fatalerrorhandler\\test_namespaced_function\"?",
|
||||||
),
|
),
|
||||||
array(
|
array(
|
||||||
array(
|
array(
|
||||||
|
@ -50,7 +50,7 @@ class UndefinedMethodFatalErrorHandlerTest extends \PHPUnit_Framework_TestCase
|
|||||||
'file' => 'foo.php',
|
'file' => 'foo.php',
|
||||||
'message' => 'Call to undefined method SplObjectStorage::walid()',
|
'message' => 'Call to undefined method SplObjectStorage::walid()',
|
||||||
),
|
),
|
||||||
'Attempted to call method "walid" on class "SplObjectStorage". Did you mean to call "valid"?',
|
"Attempted to call method \"walid\" on class \"SplObjectStorage\".\nDid you mean to call \"valid\"?",
|
||||||
),
|
),
|
||||||
array(
|
array(
|
||||||
array(
|
array(
|
||||||
@ -59,7 +59,7 @@ class UndefinedMethodFatalErrorHandlerTest extends \PHPUnit_Framework_TestCase
|
|||||||
'file' => 'foo.php',
|
'file' => 'foo.php',
|
||||||
'message' => 'Call to undefined method SplObjectStorage::offsetFet()',
|
'message' => 'Call to undefined method SplObjectStorage::offsetFet()',
|
||||||
),
|
),
|
||||||
'Attempted to call method "offsetFet" on class "SplObjectStorage". Did you mean to call e.g. "offsetGet", "offsetSet" or "offsetUnset"?',
|
"Attempted to call method \"offsetFet\" on class \"SplObjectStorage\".\nDid you mean to call e.g. \"offsetGet\", \"offsetSet\" or \"offsetUnset\"?",
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,7 @@ namespace Symfony\Component\HttpKernel\EventListener;
|
|||||||
|
|
||||||
use Psr\Log\LoggerInterface;
|
use Psr\Log\LoggerInterface;
|
||||||
use Symfony\Component\Debug\ErrorHandler;
|
use Symfony\Component\Debug\ErrorHandler;
|
||||||
use Symfony\Component\Debug\AbstractExceptionHandler;
|
use Symfony\Component\Debug\ExceptionHandler;
|
||||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||||
use Symfony\Component\HttpKernel\KernelEvents;
|
use Symfony\Component\HttpKernel\KernelEvents;
|
||||||
|
|
||||||
@ -37,9 +37,7 @@ class DebugHandlersListener implements EventSubscriberInterface
|
|||||||
*/
|
*/
|
||||||
public function __construct($exceptionHandler, LoggerInterface $logger = null, $levels = null, $debug = true)
|
public function __construct($exceptionHandler, LoggerInterface $logger = null, $levels = null, $debug = true)
|
||||||
{
|
{
|
||||||
if (is_callable($exceptionHandler)) {
|
|
||||||
$this->exceptionHandler = $exceptionHandler;
|
$this->exceptionHandler = $exceptionHandler;
|
||||||
}
|
|
||||||
$this->logger = $logger;
|
$this->logger = $logger;
|
||||||
$this->levels = $levels;
|
$this->levels = $levels;
|
||||||
$this->debug = $debug;
|
$this->debug = $debug;
|
||||||
@ -76,7 +74,7 @@ class DebugHandlersListener implements EventSubscriberInterface
|
|||||||
$handler->setExceptionHandler($h);
|
$handler->setExceptionHandler($h);
|
||||||
$handler = is_array($h) ? $h[0] : null;
|
$handler = is_array($h) ? $h[0] : null;
|
||||||
}
|
}
|
||||||
if ($handler instanceof AbstractExceptionHandler) {
|
if ($handler instanceof ExceptionHandler) {
|
||||||
$handler->setHandler($this->exceptionHandler);
|
$handler->setHandler($this->exceptionHandler);
|
||||||
}
|
}
|
||||||
$this->exceptionHandler = null;
|
$this->exceptionHandler = null;
|
||||||
|
@ -0,0 +1,56 @@
|
|||||||
|
<?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\HttpKernel\Tests\EventListener;
|
||||||
|
|
||||||
|
use Psr\Log\LogLevel;
|
||||||
|
use Symfony\Component\Debug\ErrorHandler;
|
||||||
|
use Symfony\Component\Debug\ExceptionHandler;
|
||||||
|
use Symfony\Component\HttpKernel\EventListener\DebugHandlersListener;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DebugHandlersListenerTest
|
||||||
|
*
|
||||||
|
* @author Nicolas Grekas <p@tchwork.com>
|
||||||
|
*/
|
||||||
|
class DebugHandlersListenerTest extends \PHPUnit_Framework_TestCase
|
||||||
|
{
|
||||||
|
public function testConfigure()
|
||||||
|
{
|
||||||
|
$logger = $this->getMock('Psr\Log\LoggerInterface');
|
||||||
|
$userHandler = function() {};
|
||||||
|
$listener = new DebugHandlersListener($userHandler, $logger);
|
||||||
|
$xHandler = new ExceptionHandler();
|
||||||
|
$eHandler = new ErrorHandler();
|
||||||
|
$eHandler->setExceptionHandler(array($xHandler, 'handle'));
|
||||||
|
|
||||||
|
$exception = null;
|
||||||
|
set_error_handler(array($eHandler, 'handleError'));
|
||||||
|
set_exception_handler(array($eHandler, 'handleException'));
|
||||||
|
try {
|
||||||
|
$listener->configure();
|
||||||
|
} catch (\Exception $exception) {
|
||||||
|
}
|
||||||
|
restore_exception_handler();
|
||||||
|
restore_error_handler();
|
||||||
|
|
||||||
|
if (null !== $exception) {
|
||||||
|
throw $exception;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->assertSame($userHandler, $xHandler->setHandler('var_dump'));
|
||||||
|
|
||||||
|
$loggers = $eHandler->setLoggers(array());
|
||||||
|
|
||||||
|
$this->assertArrayHasKey(E_DEPRECATED, $loggers);
|
||||||
|
$this->assertSame(array($logger, LogLevel::INFO), $loggers[E_DEPRECATED]);
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user