Compare commits

...

6 Commits

Author SHA1 Message Date
Nicolas Grekas
726bf9651d [Debug] Ignore silencing for deprecations 2015-08-29 13:12:16 +02:00
Nicolas Grekas
22b295edff [Debug/VarDumper] minor cleanups 2015-08-17 10:58:33 +02:00
Nicolas Grekas
8e122417c0 bug #15443 [Debug] Enhance DebugClassLoader performance on MacOSX (nicolas-grekas)
This PR was merged into the 2.7 branch.

Discussion
----------

[Debug] Enhance DebugClassLoader performance on MacOSX

| Q             | A
| ------------- | ---
| Bug fix?      | yes
| New feature?  | no
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | #15442
| License       | MIT
| Doc PR        | -

Commits
-------

af0b8eb [Debug] Enhance DebugClassLoader performance on MacOSX
2015-08-07 08:54:57 +02:00
Elan Ruusamäe
9cb55ce279 fix debug-ext 003.phpt
in my environment the filename is filled, not "-"

$ php --version
PHP 5.5.27 (cli) (built: Jul 17 2015 12:32:05)
Copyright (c) 1997-2015 The PHP Group
Zend Engine v2.5.0, Copyright (c) 1998-2015 Zend Technologies
2015-08-07 08:53:47 +02:00
Nicolas Grekas
2651b63b15 [Debug] Enhance DebugClassLoader performance on MacOSX 2015-08-04 12:48:55 +02:00
Nicolas Grekas
aea19edb4a [Debug] cleanup ExceptionHandlerTest 2015-07-31 17:18:52 +02:00
8 changed files with 194 additions and 77 deletions

View File

@@ -32,6 +32,7 @@ class DebugClassLoader
private static $caseCheck;
private static $deprecated = array();
private static $php7Reserved = array('int', 'float', 'bool', 'string', 'true', 'false', 'null');
private static $darwinCache = array('/' => array('/', array()));
/**
* Constructor.
@@ -220,33 +221,73 @@ class DebugClassLoader
throw new \RuntimeException(sprintf('The autoloader expected class "%s" to be defined in file "%s". The file was found but the class was not in it, the class name or namespace probably has a typo.', $class, $file));
}
if (self::$caseCheck && preg_match('#([/\\\\][a-zA-Z_\x7F-\xFF][a-zA-Z0-9_\x7F-\xFF]*)+\.(php|hh)$#D', $file, $tail)) {
if (self::$caseCheck && preg_match('#(?:[/\\\\][a-zA-Z_\x7F-\xFF][a-zA-Z0-9_\x7F-\xFF]*+)++\.(?:php|hh)$#D', $file, $tail)) {
$tail = $tail[0];
$tailLen = strlen($tail);
$real = $refl->getFileName();
if (2 === self::$caseCheck) {
// realpath() on MacOSX doesn't normalize the case of characters
$cwd = getcwd();
$basename = strrpos($real, '/');
chdir(substr($real, 0, $basename));
$basename = substr($real, $basename + 1);
// glob() patterns are case-sensitive even if the underlying fs is not
if (!in_array($basename, glob($basename.'*', GLOB_NOSORT), true)) {
$real = getcwd().'/';
$h = opendir('.');
while (false !== $f = readdir($h)) {
if (0 === strcasecmp($f, $basename)) {
$real .= $f;
break;
$i = 1 + strrpos($real, '/');
$file = substr($real, $i);
$real = substr($real, 0, $i);
if (isset(self::$darwinCache[$real])) {
$kDir = $real;
} else {
$kDir = strtolower($real);
if (isset(self::$darwinCache[$kDir])) {
$real = self::$darwinCache[$kDir][0];
} else {
$dir = getcwd();
chdir($real);
$real = getcwd().'/';
chdir($dir);
$dir = $real;
$k = $kDir;
$i = strlen($dir) - 1;
while (!isset(self::$darwinCache[$k])) {
self::$darwinCache[$k] = array($dir, array());
self::$darwinCache[$dir] = &self::$darwinCache[$k];
while ('/' !== $dir[--$i]) {
}
$k = substr($k, 0, ++$i);
$dir = substr($dir, 0, $i--);
}
}
closedir($h);
}
chdir($cwd);
$dirFiles = self::$darwinCache[$kDir][1];
if (isset($dirFiles[$file])) {
$kFile = $file;
} else {
$kFile = strtolower($file);
if (!isset($dirFiles[$kFile])) {
foreach (scandir($real, 2) as $f) {
if ('.' !== $f[0]) {
$dirFiles[$f] = $f;
if ($f === $file) {
$kFile = $k = $file;
} elseif ($f !== $k = strtolower($f)) {
$dirFiles[$k] = $f;
}
}
}
self::$darwinCache[$kDir][1] = $dirFiles;
}
}
$real .= $dirFiles[$kFile];
}
if (0 === substr_compare($real, $tail, -strlen($tail), strlen($tail), true)
&& 0 !== substr_compare($real, $tail, -strlen($tail), strlen($tail), false)
if (0 === substr_compare($real, $tail, -$tailLen, $tailLen, true)
&& 0 !== substr_compare($real, $tail, -$tailLen, $tailLen, false)
) {
throw new \RuntimeException(sprintf('Case mismatch between class and source file names: %s vs %s', $class, $real));
}

View File

@@ -251,7 +251,7 @@ class ErrorHandler
public function throwAt($levels, $replace = false)
{
$prev = $this->thrownErrors;
$this->thrownErrors = ($levels | E_RECOVERABLE_ERROR | E_USER_ERROR) & ~E_USER_DEPRECATED & ~E_DEPRECATED;
$this->thrownErrors = (E_ALL | E_STRICT) & ($levels | E_RECOVERABLE_ERROR | E_USER_ERROR) & ~E_USER_DEPRECATED & ~E_DEPRECATED;
if (!$replace) {
$this->thrownErrors |= $prev;
}
@@ -352,7 +352,7 @@ class ErrorHandler
*/
public function handleError($type, $message, $file, $line, array $context, array $backtrace = null)
{
$level = error_reporting() | E_RECOVERABLE_ERROR | E_USER_ERROR;
$level = error_reporting() | E_RECOVERABLE_ERROR | E_USER_ERROR | E_DEPRECATED | E_USER_DEPRECATED;
$log = $this->loggedErrors & $type;
$throw = $this->thrownErrors & $type & $level;
$type &= $level | $this->screamedErrors;
@@ -445,7 +445,7 @@ class ErrorHandler
}
/**
* Handles an exception by logging then forwarding it to an other handler.
* Handles an exception by logging then forwarding it to another handler.
*
* @param \Exception|\Throwable $exception An exception to handle
* @param array $error An array as returned by error_get_last()

View File

@@ -38,7 +38,7 @@ class ExceptionHandler
public function __construct($debug = true, $charset = null, $fileLinkFormat = null)
{
if (false !== strpos($charset, '%') xor false === strpos($fileLinkFormat, '%')) {
if (false !== strpos($charset, '%')) {
// Swap $charset and $fileLinkFormat for BC reasons
$pivot = $fileLinkFormat;
$fileLinkFormat = $charset;
@@ -153,19 +153,22 @@ class ExceptionHandler
* it will fallback to plain PHP functions.
*
* @param \Exception $exception An \Exception instance
*
* @see sendPhpResponse()
* @see createResponse()
*/
private function failSafeHandle(\Exception $exception)
{
if (class_exists('Symfony\Component\HttpFoundation\Response', false)) {
if (class_exists('Symfony\Component\HttpFoundation\Response', false)
&& __CLASS__ !== get_class($this)
&& ($reflector = new \ReflectionMethod($this, 'createResponse'))
&& __CLASS__ !== $reflector->class
) {
$response = $this->createResponse($exception);
$response->sendHeaders();
$response->sendContent();
} else {
$this->sendPhpResponse($exception);
return;
}
$this->sendPhpResponse($exception);
}
/**

View File

@@ -56,7 +56,7 @@ Symfony\Component\Debug\Exception\UndefinedFunctionException Object
[message:protected] => Attempted to call function "notexist" from namespace "Symfony\Component\Debug".
[string:Exception:private] =>
[code:protected] => 0
[file:protected] => -
[file:protected] => %s
[line:protected] => %d
[trace:Exception:private] => Array
(

View File

@@ -268,6 +268,28 @@ class ErrorHandlerTest extends \PHPUnit_Framework_TestCase
}
}
public function testHandleDeprecation()
{
$that = $this;
$logArgCheck = function ($level, $message, $context) use ($that) {
$that->assertEquals(LogLevel::INFO, $level);
$that->assertArrayHasKey('level', $context);
$that->assertEquals(E_RECOVERABLE_ERROR | E_USER_ERROR | E_DEPRECATED | E_USER_DEPRECATED, $context['level']);
$that->assertArrayHasKey('stack', $context);
};
$logger = $this->getMock('Psr\Log\LoggerInterface');
$logger
->expects($this->once())
->method('log')
->will($this->returnCallback($logArgCheck))
;
$handler = new ErrorHandler();
$handler->setDefaultLogger($logger);
@$handler->handleError(E_USER_DEPRECATED, 'Foo deprecation', __FILE__, __LINE__, array());
}
public function testHandleException()
{
try {

View File

@@ -13,71 +13,97 @@ namespace Symfony\Component\Debug\Tests;
use Symfony\Component\Debug\ExceptionHandler;
use Symfony\Component\Debug\Exception\OutOfMemoryException;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException;
require_once __DIR__.'/HeaderMock.php';
class ExceptionHandlerTest extends \PHPUnit_Framework_TestCase
{
protected function setUp()
{
testHeader();
}
protected function tearDown()
{
testHeader();
}
public function testDebug()
{
$handler = new ExceptionHandler(false);
$response = $handler->createResponse(new \RuntimeException('Foo'));
$this->assertContains('<h1>Whoops, looks like something went wrong.</h1>', $response->getContent());
$this->assertNotContains('<h2 class="block_exception clear_fix">', $response->getContent());
ob_start();
$handler->sendPhpResponse(new \RuntimeException('Foo'));
$response = ob_get_clean();
$this->assertContains('<h1>Whoops, looks like something went wrong.</h1>', $response);
$this->assertNotContains('<h2 class="block_exception clear_fix">', $response);
$handler = new ExceptionHandler(true);
$response = $handler->createResponse(new \RuntimeException('Foo'));
$this->assertContains('<h1>Whoops, looks like something went wrong.</h1>', $response->getContent());
$this->assertContains('<h2 class="block_exception clear_fix">', $response->getContent());
ob_start();
$handler->sendPhpResponse(new \RuntimeException('Foo'));
$response = ob_get_clean();
$this->assertContains('<h1>Whoops, looks like something went wrong.</h1>', $response);
$this->assertContains('<h2 class="block_exception clear_fix">', $response);
}
public function testStatusCode()
{
$handler = new ExceptionHandler(false);
$handler = new ExceptionHandler(false, 'iso8859-1');
$response = $handler->createResponse(new \RuntimeException('Foo'));
$this->assertEquals('500', $response->getStatusCode());
$this->assertContains('Whoops, looks like something went wrong.', $response->getContent());
ob_start();
$handler->sendPhpResponse(new NotFoundHttpException('Foo'));
$response = ob_get_clean();
$response = $handler->createResponse(new NotFoundHttpException('Foo'));
$this->assertEquals('404', $response->getStatusCode());
$this->assertContains('Sorry, the page you are looking for could not be found.', $response->getContent());
$this->assertContains('Sorry, the page you are looking for could not be found.', $response);
$expectedHeaders = array(
array('HTTP/1.0 404', true, null),
array('Content-Type: text/html; charset=iso8859-1', true, null),
);
$this->assertSame($expectedHeaders, testHeader());
}
public function testHeaders()
{
$handler = new ExceptionHandler(false);
$handler = new ExceptionHandler(false, 'iso8859-1');
$response = $handler->createResponse(new MethodNotAllowedHttpException(array('POST')));
$this->assertEquals('405', $response->getStatusCode());
$this->assertEquals('POST', $response->headers->get('Allow'));
ob_start();
$handler->sendPhpResponse(new MethodNotAllowedHttpException(array('POST')));
$response = ob_get_clean();
$expectedHeaders = array(
array('HTTP/1.0 405', true, null),
array('Allow: POST', false, null),
array('Content-Type: text/html; charset=iso8859-1', true, null),
);
$this->assertSame($expectedHeaders, testHeader());
}
public function testNestedExceptions()
{
$handler = new ExceptionHandler(true);
$response = $handler->createResponse(new \RuntimeException('Foo', 0, new \RuntimeException('Bar')));
ob_start();
$handler->sendPhpResponse(new \RuntimeException('Foo', 0, new \RuntimeException('Bar')));
$response = ob_get_clean();
$this->assertStringMatchesFormat('%A<span class="exception_message">Foo</span>%A<span class="exception_message">Bar</span>%A', $response);
}
public function testHandle()
{
$exception = new \Exception('foo');
if (class_exists('Symfony\Component\HttpFoundation\Response')) {
$handler = $this->getMock('Symfony\Component\Debug\ExceptionHandler', array('createResponse'));
$handler
->expects($this->exactly(2))
->method('createResponse')
->will($this->returnValue(new Response()));
} else {
$handler = $this->getMock('Symfony\Component\Debug\ExceptionHandler', array('sendPhpResponse'));
$handler
->expects($this->exactly(2))
->method('sendPhpResponse');
}
$handler = $this->getMock('Symfony\Component\Debug\ExceptionHandler', array('sendPhpResponse'));
$handler
->expects($this->exactly(2))
->method('sendPhpResponse');
$handler->handle($exception);
@@ -93,18 +119,10 @@ class ExceptionHandlerTest extends \PHPUnit_Framework_TestCase
{
$exception = new OutOfMemoryException('foo', 0, E_ERROR, __FILE__, __LINE__);
if (class_exists('Symfony\Component\HttpFoundation\Response')) {
$handler = $this->getMock('Symfony\Component\Debug\ExceptionHandler', array('createResponse'));
$handler
->expects($this->once())
->method('createResponse')
->will($this->returnValue(new Response()));
} else {
$handler = $this->getMock('Symfony\Component\Debug\ExceptionHandler', array('sendPhpResponse'));
$handler
->expects($this->once())
->method('sendPhpResponse');
}
$handler = $this->getMock('Symfony\Component\Debug\ExceptionHandler', array('sendPhpResponse'));
$handler
->expects($this->once())
->method('sendPhpResponse');
$that = $this;
$handler->setHandler(function ($e) use ($that) {

38
Tests/HeaderMock.php Normal file
View File

@@ -0,0 +1,38 @@
<?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;
function headers_sent()
{
return false;
}
function header($str, $replace = true, $status = null)
{
Tests\testHeader($str, $replace, $status);
}
namespace Symfony\Component\Debug\Tests;
function testHeader()
{
static $headers = array();
if (!$h = func_get_args()) {
$h = $headers;
$headers = array();
return $h;
}
$headers[] = func_get_args();
}

View File

@@ -25,12 +25,7 @@
"require-dev": {
"symfony/phpunit-bridge": "~2.7",
"symfony/class-loader": "~2.2",
"symfony/http-kernel": "~2.3.24|~2.5.9|~2.6,>=2.6.2",
"symfony/http-foundation": "~2.1"
},
"suggest": {
"symfony/http-foundation": "",
"symfony/http-kernel": ""
"symfony/http-kernel": "~2.3.24|~2.5.9|~2.6,>=2.6.2"
},
"autoload": {
"psr-4": { "Symfony\\Component\\Debug\\": "" }