Compare commits

...

35 Commits

Author SHA1 Message Date
Fabien Potencier 189da713c1 Merge branch '2.4' into 2.5
* 2.4:
  added missing test
  fixed CS
  [HttpFoundation] Remove content-related headers if content is empty
  bumped Symfony version to 2.4.8
  removed defaults from PHPUnit configuration

Conflicts:
	src/Symfony/Component/HttpKernel/Kernel.php
2014-07-09 11:05:48 +02:00
Fabien Potencier c9532f4021 Merge branch '2.3' into 2.4
* 2.3:
  fixed CS
  [HttpFoundation] Remove content-related headers if content is empty
  removed defaults from PHPUnit configuration
2014-07-09 11:04:55 +02:00
Fabien Potencier 3e0f14c5ab Merge branch '2.4' into 2.5
* 2.4:
  updated VERSION for 2.4.7
  updated CHANGELOG for 2.4.7
  bumped Symfony version to 2.3.18
  updated VERSION for 2.3.17
  update CONTRIBUTORS for 2.3.17
  updated CHANGELOG for 2.3.17
  added XSD to PHPUnit configuration
  add missing docblock for ProcessBuilder::addEnvironmentVariables()
  bug #11319 [HttpKernel] Ensure the storage exists before purging it in ProfilerTest
  [Translation] Added unescaping of ids in PoFileLoader
  updated italian translation for validation messages
  [DomCrawler] Fix docblocks and formatting.
  [DomCrawler] Remove the query string and the anchor of the uri of a link
  Simplified the Travis test command
  Remove Expression Language services when the component is unavailable
  [Console] Make sure formatter is the same

Conflicts:
	src/Symfony/Component/HttpKernel/Kernel.php
2014-07-08 14:21:33 +02:00
Fabien Potencier 1ae44befec Merge branch '2.3' into 2.4
* 2.3:
  bumped Symfony version to 2.3.18
  updated VERSION for 2.3.17
  update CONTRIBUTORS for 2.3.17
  updated CHANGELOG for 2.3.17
  added XSD to PHPUnit configuration
  bug #11319 [HttpKernel] Ensure the storage exists before purging it in ProfilerTest
  [Translation] Added unescaping of ids in PoFileLoader
  updated italian translation for validation messages
  [DomCrawler] Fix docblocks and formatting.
  [DomCrawler] Remove the query string and the anchor of the uri of a link
  Simplified the Travis test command
  [Console] Make sure formatter is the same

Conflicts:
	src/Symfony/Component/HttpKernel/Kernel.php
2014-07-08 13:46:35 +02:00
Christian Raue 91e3a1480c removed defaults from PHPUnit configuration 2014-07-07 12:13:42 +02:00
Christian Raue 732b41060b added XSD to PHPUnit configuration 2014-07-07 11:57:21 +02:00
Nicolas Grekas b24abbba99 [Debug] work-around https://bugs.php.net/61272 2014-06-11 20:54:02 +02:00
Nicolas Grekas 80fa7316f3 [Debug] simplify code path to remove potential blank pages 2014-06-11 12:05:25 +02:00
Nicolas Grekas 4ebbb9592c [Debug] fix wrong case mismatch exception 2014-06-02 09:03:03 +02:00
Fabien Potencier 7fd8006e46 minor #10988 [Debug] preserve modified error level (nicolas-grekas)
This PR was merged into the 2.5 branch.

Discussion
----------

[Debug] preserve modified error level

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

Minor edge case, but still a bug fix.
Replaces https://github.com/symfony/symfony/pull/10978

Commits
-------

e40b717 [Debug] preserve modified error level
2014-05-25 16:44:21 +02:00
Nicolas Grekas 3002c206bc [Debug] throw even in stacking mode to preserve code paths 2014-05-25 12:15:31 +02:00
Nicolas Grekas 4a463783a3 [Debug] preserve modified error level 2014-05-25 11:36:54 +02:00
Nicolas Grekas 560cda449b [Debug] better ouf of memory error handling 2014-05-21 14:34:28 +02:00
Nicolas Grekas 2e8dc2e20c [Debug] cleanup interfaces before 2.5-final 2014-05-20 09:56:04 +02:00
Nicolas Grekas b293ddb67a [Debug] enhance perf of DebugClassLoader 2014-05-08 10:31:54 +02:00
Nicolas Grekas e25750cda2 [Debug] fix handling deprecated warnings and stacked errors turned into exceptions 2014-05-05 07:50:37 +00:00
Fabien Potencier 6e721ae2cd Merge branch '2.3' into 2.4
* 2.3:
  [Debug] fix #10313: FlattenException not found because of https://bugs.php.net/42098
2014-04-30 08:22:28 +02:00
Nicolas Grekas ca764f8af9 [Debug] fix #10313: FlattenException not found because of https://bugs.php.net/42098 2014-04-29 21:42:43 +02:00
Fabien Potencier e5edd3ae91 Merge branch '2.4'
* 2.4:
  [Debug] fix ErrorHandlerTest when context is not an array
2014-04-29 08:56:05 +02:00
Fabien Potencier ec8b67232e Merge branch '2.3' into 2.4
* 2.3:
  [Debug] fix ErrorHandlerTest when context is not an array

Conflicts:
	src/Symfony/Component/Debug/Tests/ErrorHandlerTest.php
2014-04-29 08:55:57 +02:00
Nicolas Grekas 9fb191fa62 [Debug] fix ErrorHandlerTest when context is not an array 2014-04-28 20:50:14 +02:00
Fabien Potencier 0f428217e4 Merge branch '2.4'
* 2.4:
  [Debug] ErrorHandler: remove $GLOBALS from context in PHP5.3 fix #10292
  Allow File instance to be passed to BinaryFileResponse
  Add upgrade instructions for the LoggerInterface
  fixed CS
  Removed strict check when found variables inside a translation
  [ExpressionLanguage] Test for the non-strict in_array check in parsePrimaryExpression in Parser.php
  Strict in_array check in Parser.php
  Updated Serbian latin validation translation

Conflicts:
	src/Symfony/Component/Debug/ErrorHandler.php
2014-04-28 19:44:51 +02:00
Fabien Potencier 3e375dc708 Merge branch '2.3' into 2.4
* 2.3:
  [Debug] ErrorHandler: remove $GLOBALS from context in PHP5.3 fix #10292
  Allow File instance to be passed to BinaryFileResponse
  Add upgrade instructions for the LoggerInterface
  fixed CS
  Removed strict check when found variables inside a translation
2014-04-28 19:43:58 +02:00
Nicolas Grekas b2e922174d [Debug] ErrorHandler: remove $GLOBALS from context in PHP5.3 fix #10292 2014-04-28 18:43:50 +02:00
Nicolas Grekas 0aaf712be0 [Debug] less intrusive work around for https://bugs.php.net/54275 2014-04-28 18:08:18 +02:00
Nicolas Grekas 79bb38a360 [Debug] fix #10771 DebugClassLoader can't load PSR4 libs 2014-04-24 17:48:28 +02:00
Nicolas Grekas 88a4b49c96 [Debug] Handled errors 2014-04-19 10:12:16 +02:00
Fabien Potencier e242aa8295 Merge branch '2.4'
* 2.4:
  fixed types in phpdocs
  fixed types in phpdocs

Conflicts:
	src/Symfony/Component/Security/Core/Encoder/BasePasswordEncoder.php
	src/Symfony/Component/Security/Http/RememberMe/TokenBasedRememberMeServices.php
	src/Symfony/Component/Serializer/Encoder/JsonEncoder.php
	src/Symfony/Component/Serializer/Normalizer/GetSetMethodNormalizer.php
	src/Symfony/Component/Validator/Mapping/ClassMetadata.php
	src/Symfony/Component/Validator/Mapping/ElementMetadata.php
	src/Symfony/Component/Validator/Mapping/MemberMetadata.php
	src/Symfony/Component/Validator/MetadataFactoryInterface.php
2014-04-16 12:36:21 +02:00
Fabien Potencier 6a8eb9aba5 fixed types in phpdocs 2014-04-16 12:34:42 +02:00
Fabien Potencier c3e530107b made {@inheritdoc} annotations consistent across the board 2014-04-16 10:09:16 +02:00
Fabien Potencier a6ae0150dc Merge branch '2.4'
* 2.4:
  made types consistent with those defined in Hack
  made {@inheritdoc} annotations consistent across the board
  made {@inheritdoc} annotations consistent across the board
  fixed types in phpdocs
  [Debug] Fixed ClassNotFoundFatalErrorHandler on windows.
  made phpdoc types consistent with those defined in Hack
  Add support Thai translations
  [Validator] Add missing czech translations
  made types consistent with those defined in Hack
  removed extra/unsupported arguments
  [HttpKernel] fixed an error message
  [TwigBundle] removed undefined argument
  [Translation] Make IcuDatFileLoader/IcuResFileLoader::load invalid resource compatible with HHVM.

Conflicts:
	src/Symfony/Component/Console/Helper/TableHelper.php
	src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php
	src/Symfony/Component/Form/FormError.php
	src/Symfony/Component/HttpKernel/Debug/TraceableEventDispatcher.php
	src/Symfony/Component/Process/ProcessPipes.php
	src/Symfony/Component/PropertyAccess/PropertyAccessor.php
	src/Symfony/Component/Security/Acl/Dbal/MutableAclProvider.php
	src/Symfony/Component/Security/Http/RememberMe/TokenBasedRememberMeServices.php
	src/Symfony/Component/Translation/Dumper/FileDumper.php
	src/Symfony/Component/Validator/ConstraintViolation.php
	src/Symfony/Component/Validator/Constraints/EmailValidator.php
	src/Symfony/Component/Validator/ExecutionContextInterface.php
	src/Symfony/Component/Validator/Mapping/BlackholeMetadataFactory.php
2014-04-16 10:08:40 +02:00
Fabien Potencier e630309d00 made types consistent with those defined in Hack 2014-04-16 10:04:32 +02:00
Fabien Potencier 56c0a30907 Merge branch '2.3' into 2.4
* 2.3:
  made {@inheritdoc} annotations consistent across the board
  fixed types in phpdocs
  made phpdoc types consistent with those defined in Hack
  Add support Thai translations
  made types consistent with those defined in Hack
  removed extra/unsupported arguments
  [HttpKernel] fixed an error message
  [TwigBundle] removed undefined argument
  [Translation] Make IcuDatFileLoader/IcuResFileLoader::load invalid resource compatible with HHVM.

Conflicts:
	src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Fixtures/php/lazy_service.php
	src/Symfony/Bundle/FrameworkBundle/Command/ContainerDebugCommand.php
	src/Symfony/Bundle/FrameworkBundle/Templating/Loader/FilesystemLoader.php
	src/Symfony/Bundle/WebProfilerBundle/EventListener/WebDebugToolbarListener.php
	src/Symfony/Component/Config/Definition/ReferenceDumper.php
	src/Symfony/Component/Console/Helper/DescriptorHelper.php
	src/Symfony/Component/Debug/ErrorHandler.php
	src/Symfony/Component/Finder/Tests/Iterator/RecursiveDirectoryIteratorTest.php
	src/Symfony/Component/Form/Extension/Core/DataTransformer/IntegerToLocalizedStringTransformer.php
	src/Symfony/Component/Form/Tests/Extension/Core/DataMapper/PropertyPathMapperTest.php
	src/Symfony/Component/HttpFoundation/Response.php
	src/Symfony/Component/HttpFoundation/StreamedResponse.php
	src/Symfony/Component/HttpKernel/Debug/TraceableEventDispatcher.php
	src/Symfony/Component/HttpKernel/EventListener/ProfilerListener.php
	src/Symfony/Component/HttpKernel/Fragment/FragmentHandler.php
	src/Symfony/Component/HttpKernel/Fragment/RoutableFragmentRenderer.php
	src/Symfony/Component/HttpKernel/Kernel.php
	src/Symfony/Component/HttpKernel/Tests/Fixtures/KernelForTest.php
	src/Symfony/Component/Intl/NumberFormatter/NumberFormatter.php
	src/Symfony/Component/Security/Core/Authorization/AccessDecisionManager.php
	src/Symfony/Component/Stopwatch/StopwatchPeriod.php
	src/Symfony/Component/Translation/TranslatorInterface.php
	src/Symfony/Component/Validator/ConstraintValidatorFactory.php
2014-04-16 10:02:57 +02:00
Grégoire Pineau bfa7900678 [Debug] Fixed ClassNotFoundFatalErrorHandler on windows.
Because $path is realpath'ed. So we have to use DIRECTORY_SEPARATOR
instead of / because the default directory separator on windows is
\. And so the str_replace can not work and can lead to a fatal error
because class could already be loaded. For example, the Response class
in symfony full stack is in the bootstrap.php.cache.
2014-04-15 18:41:05 +02:00
Fabien Potencier bdfb718a6c made phpdoc types consistent with those defined in Hack 2014-04-15 07:41:45 +02:00
18 changed files with 400 additions and 243 deletions
+2
View File
@@ -4,7 +4,9 @@ CHANGELOG
2.5.0
-----
* added ExceptionHandler::setHandler()
* added UndefinedMethodFatalErrorHandler
* deprecated DummyException
2.4.0
-----
+2 -2
View File
@@ -28,8 +28,8 @@ class Debug
* If the Symfony ClassLoader component is available, a special
* class loader is also registered.
*
* @param integer $errorReportingLevel The level of error reporting you want
* @param Boolean $displayErrors Whether to display errors (for development) or just log them (for production)
* @param int $errorReportingLevel The level of error reporting you want
* @param bool $displayErrors Whether to display errors (for development) or just log them (for production)
*/
public static function enable($errorReportingLevel = null, $displayErrors = true)
{
+46 -35
View File
@@ -29,6 +29,7 @@ class DebugClassLoader
private $classLoader;
private $isFinder;
private $wasFinder;
private static $caseCheck;
/**
* Constructor.
@@ -49,6 +50,10 @@ class DebugClassLoader
$this->classLoader = $classLoader;
$this->isFinder = is_array($classLoader) && method_exists($classLoader[0], 'findFile');
}
if (!isset(self::$caseCheck)) {
self::$caseCheck = false !== stripos(PHP_OS, 'win') ? (false !== stripos(PHP_OS, 'darwin') ? 2 : 1) : 0;
}
}
/**
@@ -135,7 +140,7 @@ class DebugClassLoader
*
* @param string $class The name of the class
*
* @return Boolean|null True, if loaded
* @return bool|null True, if loaded
*
* @throws \RuntimeException
*/
@@ -162,52 +167,58 @@ class DebugClassLoader
$exists = class_exists($class, false) || interface_exists($class, false) || (function_exists('trait_exists') && trait_exists($class, false));
if ($exists) {
$name = new \ReflectionClass($class);
$name = $name->getName();
if ('\\' === $class[0]) {
$class = substr($class, 1);
}
if ($name !== $class) {
if ($exists) {
$refl = new \ReflectionClass($class);
$name = $refl->getName();
if ($name !== $class && 0 === strcasecmp($name, $class)) {
throw new \RuntimeException(sprintf('Case mismatch between loaded and declared class names: %s vs %s', $class, $name));
}
}
if ($file) {
if ('\\' == $class[0]) {
$class = substr($class, 1);
}
$i = 0;
$tail = DIRECTORY_SEPARATOR . str_replace('\\', DIRECTORY_SEPARATOR, $class).'.php';
$len = strlen($tail);
do {
$tail = substr($tail, $i);
$len -= $i;
if (0 === substr_compare($file, $tail, -$len, $len, true)) {
if (0 !== substr_compare($file, $tail, -$len, $len, false)) {
if (method_exists($this->classLoader[0], 'getClassMap')) {
$map = $this->classLoader[0]->getClassMap();
} else {
$map = array();
}
if (! isset($map[$class])) {
throw new \RuntimeException(sprintf('Case mismatch between class and source file names: %s vs %s', $class, $file));
}
}
break;
}
} while (false !== $i = strpos($tail, DIRECTORY_SEPARATOR, 1));
if (! $exists) {
if (!$exists) {
if (false !== strpos($class, '/')) {
throw new \RuntimeException(sprintf('Trying to autoload a class with an invalid name "%s". Be careful that the namespace separator is "\" in PHP, not "/".', $class));
}
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)) {
$tail = $tail[0];
$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;
}
}
closedir($h);
}
chdir($cwd);
}
if ( 0 === substr_compare($real, $tail, -strlen($tail), strlen($tail), true)
&& 0 !== substr_compare($real, $tail, -strlen($tail), strlen($tail), false)
) {
throw new \RuntimeException(sprintf('Case mismatch between class and source file names: %s vs %s', $class, $real));
}
}
return true;
}
+106 -64
View File
@@ -15,7 +15,7 @@ use Psr\Log\LogLevel;
use Psr\Log\LoggerInterface;
use Symfony\Component\Debug\Exception\ContextErrorException;
use Symfony\Component\Debug\Exception\FatalErrorException;
use Symfony\Component\Debug\Exception\DummyException;
use Symfony\Component\Debug\Exception\OutOfMemoryException;
use Symfony\Component\Debug\FatalErrorHandler\UndefinedFunctionFatalErrorHandler;
use Symfony\Component\Debug\FatalErrorHandler\UndefinedMethodFatalErrorHandler;
use Symfony\Component\Debug\FatalErrorHandler\ClassNotFoundFatalErrorHandler;
@@ -66,8 +66,8 @@ class ErrorHandler
/**
* Registers the error handler.
*
* @param integer $level The level at which the conversion to Exception is done (null to use the error_reporting() value and 0 to disable)
* @param Boolean $displayErrors Display errors (for dev environment) or just log them (production usage)
* @param int $level The level at which the conversion to Exception is done (null to use the error_reporting() value and 0 to disable)
* @param bool $displayErrors Display errors (for dev environment) or just log them (production usage)
*
* @return ErrorHandler The registered error handler
*/
@@ -88,7 +88,7 @@ class ErrorHandler
/**
* Sets the level at which the conversion to Exception is done.
*
* @param integer|null $level The level (null to use the error_reporting() value and 0 to disable)
* @param int|null $level The level (null to use the error_reporting() value and 0 to disable)
*/
public function setLevel($level)
{
@@ -98,7 +98,7 @@ class ErrorHandler
/**
* Sets the display_errors flag value.
*
* @param integer $displayErrors The display_errors flag value
* @param int $displayErrors The display_errors flag value
*/
public function setDisplayErrors($displayErrors)
{
@@ -117,7 +117,7 @@ class ErrorHandler
}
/**
* @throws ContextErrorException When error_reporting returns error
* @throws \ErrorException When error_reporting returns error
*/
public function handle($level, $message, $file = 'unknown', $line = 0, $context = array())
{
@@ -141,42 +141,33 @@ class ErrorHandler
self::$loggers['deprecation']->warning($message, array('type' => self::TYPE_DEPRECATION, 'stack' => $stack));
}
return true;
}
} elseif ($this->displayErrors && error_reporting() & $level && $this->level & $level) {
if (PHP_VERSION_ID < 50400 && isset($context['GLOBALS']) && is_array($context)) {
$c = $context; // Whatever the signature of the method,
unset($c['GLOBALS'], $context); // $context is always a reference in 5.3
$context = $c;
}
return true;
}
if ($this->displayErrors && error_reporting() & $level && $this->level & $level) {
// 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('var_dump');
restore_exception_handler();
if (is_array($exceptionHandler) && $exceptionHandler[0] instanceof ExceptionHandlerInterface) {
if (self::$stackedErrorLevels) {
self::$stackedErrors[] = func_get_args();
return true;
}
$exception = sprintf('%s: %s in %s line %d', isset($this->levels[$level]) ? $this->levels[$level] : $level, $message, $file, $line);
$exception = sprintf('%s: %s in %s line %d', isset($this->levels[$level]) ? $this->levels[$level] : $level, $message, $file, $line);
if ($context && class_exists('Symfony\Component\Debug\Exception\ContextErrorException')) {
// Checking for class existence is a work around for https://bugs.php.net/42098
$exception = new ContextErrorException($exception, 0, $level, $file, $line, $context);
$exceptionHandler[0]->handle($exception);
// 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();
} else {
$exception = new \ErrorException($exception, 0, $level, $file, $line);
}
if (PHP_VERSION_ID <= 50407 && (PHP_VERSION_ID >= 50400 || PHP_VERSION_ID <= 50317)) {
// Exceptions thrown from error handlers are sometimes not caught by the exception
// handler and shutdown handlers are bypassed before 5.4.8/5.3.18.
// We temporarily re-enable display_errors to prevent any blank page related to this bug.
$exception->errorHandlerCanary = new ErrorHandlerCanary();
}
throw $exception;
}
if (isset(self::$loggers['scream']) && !(error_reporting() & $level)) {
@@ -235,7 +226,11 @@ class ErrorHandler
$level = array_pop(self::$stackedErrorLevels);
if (null !== $level) {
error_reporting($level);
$e = error_reporting($level);
if ($e !== ($level | E_PARSE | E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR)) {
// If the user changed the error level, do not overwrite it
error_reporting($e);
}
}
if (empty(self::$stackedErrorLevels)) {
@@ -256,24 +251,38 @@ class ErrorHandler
public function handleFatal()
{
$this->reservedMemory = '';
gc_collect_cycles();
$error = error_get_last();
while (self::$stackedErrorLevels) {
static::unstackErrors();
// get current exception handler
$exceptionHandler = set_exception_handler('var_dump');
restore_exception_handler();
try {
while (self::$stackedErrorLevels) {
static::unstackErrors();
}
} catch (\Exception $exception) {
if ($exceptionHandler) {
call_user_func($exceptionHandler, $exception);
return;
}
if ($this->displayErrors) {
ini_set('display_errors', 1);
}
throw $exception;
}
if (null === $error) {
return;
}
$type = $error['type'];
if (0 === $this->level || !in_array($type, array(E_ERROR, E_CORE_ERROR, E_COMPILE_ERROR, E_PARSE))) {
if (!$error || !$this->level || !($error['type'] & (E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR | E_PARSE))) {
return;
}
if (isset(self::$loggers['emergency'])) {
$fatal = array(
'type' => $type,
'type' => $error['type'],
'file' => $error['file'],
'line' => $error['line'],
);
@@ -281,16 +290,8 @@ class ErrorHandler
self::$loggers['emergency']->emergency($error['message'], $fatal);
}
if (!$this->displayErrors) {
return;
}
// get current exception handler
$exceptionHandler = set_exception_handler('var_dump');
restore_exception_handler();
if (is_array($exceptionHandler) && $exceptionHandler[0] instanceof ExceptionHandlerInterface) {
$this->handleFatalError($exceptionHandler[0], $error);
if ($this->displayErrors && $exceptionHandler) {
$this->handleFatalError($exceptionHandler, $error);
}
}
@@ -310,18 +311,59 @@ class ErrorHandler
);
}
private function handleFatalError(ExceptionHandlerInterface $exceptionHandler, array $error)
private function handleFatalError($exceptionHandler, array $error)
{
// Let PHP handle any further error
set_error_handler('var_dump', 0);
ini_set('display_errors', 1);
$level = isset($this->levels[$error['type']]) ? $this->levels[$error['type']] : $error['type'];
$message = sprintf('%s: %s in %s line %d', $level, $error['message'], $error['file'], $error['line']);
$exception = new FatalErrorException($message, 0, $error['type'], $error['file'], $error['line']);
if (0 === strpos($error['message'], 'Allowed memory') || 0 === strpos($error['message'], 'Out of memory')) {
$exception = new OutOfMemoryException($message, 0, $error['type'], $error['file'], $error['line'], 3, false);
} else {
$exception = new FatalErrorException($message, 0, $error['type'], $error['file'], $error['line'], 3, true);
foreach ($this->getFatalErrorHandlers() as $handler) {
if ($ex = $handler->handleError($error, $exception)) {
return $exceptionHandler->handle($ex);
foreach ($this->getFatalErrorHandlers() as $handler) {
if ($e = $handler->handleError($error, $exception)) {
$exception = $e;
break;
}
}
}
$exceptionHandler->handle($exception);
try {
call_user_func($exceptionHandler, $exception);
} catch (\Exception $e) {
// The handler failed. Let PHP handle that now.
throw $exception;
}
}
}
/**
* Private class used to work around https://bugs.php.net/54275
*
* @author Nicolas Grekas <p@tchwork.com>
*
* @internal
*/
class ErrorHandlerCanary
{
private static $displayErrors = null;
public function __construct()
{
if (null === self::$displayErrors) {
self::$displayErrors = ini_set('display_errors', 1);
}
}
public function __destruct()
{
if (null !== self::$displayErrors) {
ini_set('display_errors', self::$displayErrors);
self::$displayErrors = null;
}
}
}
+1
View File
@@ -28,5 +28,6 @@ class ClassNotFoundException extends FatalErrorException
$previous->getLine(),
$previous->getPrevious()
);
$this->setTrace($previous->getTrace());
}
}
+2 -2
View File
@@ -12,9 +12,9 @@
namespace Symfony\Component\Debug\Exception;
/**
* Used to stop execution of a PHP script after handling a fatal error.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @deprecated since version 2.5, to be removed in 3.0.
*/
class DummyException extends \ErrorException
{
+50
View File
@@ -14,8 +14,58 @@ namespace Symfony\Component\Debug\Exception;
/**
* Fatal Error Exception.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Konstanton Myakshin <koc-dp@yandex.ru>
* @author Nicolas Grekas <p@tchwork.com>
*/
class FatalErrorException extends \ErrorException
{
public function __construct($message, $code, $severity, $filename, $lineno, $traceOffset = null, $traceArgs = true)
{
parent::__construct($message, $code, $severity, $filename, $lineno);
if (null !== $traceOffset) {
if (function_exists('xdebug_get_function_stack')) {
$trace = xdebug_get_function_stack();
if (0 < $traceOffset) {
array_splice($trace, -$traceOffset);
}
foreach ($trace as &$frame) {
if (!isset($frame['type'])) {
// XDebug pre 2.1.1 doesn't currently set the call type key http://bugs.xdebug.org/view.php?id=695
if (isset($frame['class'])) {
$frame['type'] = '::';
}
} elseif ('dynamic' === $frame['type']) {
$frame['type'] = '->';
} elseif ('static' === $frame['type']) {
$frame['type'] = '::';
}
// XDebug also has a different name for the parameters array
if (!$traceArgs) {
unset($frame['params'], $frame['args']);
} elseif (isset($frame['params']) && !isset($frame['args'])) {
$frame['args'] = $frame['params'];
unset($frame['params']);
}
}
unset($frame);
$trace = array_reverse($trace);
} else {
$trace = array();
}
$this->setTrace($trace);
}
}
protected function setTrace($trace)
{
$traceReflector = new \ReflectionProperty('Exception', 'trace');
$traceReflector->setAccessible(true);
$traceReflector->setValue($this, $trace);
}
}
+1 -30
View File
@@ -172,36 +172,7 @@ class FlattenException
public function setTraceFromException(\Exception $exception)
{
$trace = $exception->getTrace();
if ($exception instanceof FatalErrorException) {
if (function_exists('xdebug_get_function_stack')) {
$trace = array_slice(array_reverse(xdebug_get_function_stack()), 4);
foreach ($trace as $i => $frame) {
// XDebug pre 2.1.1 doesn't currently set the call type key http://bugs.xdebug.org/view.php?id=695
if (!isset($frame['type'])) {
$trace[$i]['type'] = '??';
}
if ('dynamic' === $trace[$i]['type']) {
$trace[$i]['type'] = '->';
} elseif ('static' === $trace[$i]['type']) {
$trace[$i]['type'] = '::';
}
// XDebug also has a different name for the parameters array
if (isset($frame['params']) && !isset($frame['args'])) {
$trace[$i]['args'] = $frame['params'];
unset($trace[$i]['params']);
}
}
} else {
$trace = array_slice(array_reverse($trace), 1);
}
}
$this->setTrace($trace, $exception->getFile(), $exception->getLine());
$this->setTrace($exception->getTrace(), $exception->getFile(), $exception->getLine());
}
public function setTrace($trace, $file, $line)
+21
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;
/**
* Out of memory exception.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
class OutOfMemoryException extends FatalErrorException
{
}
+1
View File
@@ -28,5 +28,6 @@ class UndefinedFunctionException extends FatalErrorException
$previous->getLine(),
$previous->getPrevious()
);
$this->setTrace($previous->getTrace());
}
}
+9 -1
View File
@@ -20,6 +20,14 @@ class UndefinedMethodException extends FatalErrorException
{
public function __construct($message, \ErrorException $previous)
{
parent::__construct($message, $previous->getCode(), $previous->getSeverity(), $previous->getFile(), $previous->getLine(), $previous->getPrevious());
parent::__construct(
$message,
$previous->getCode(),
$previous->getSeverity(),
$previous->getFile(),
$previous->getLine(),
$previous->getPrevious()
);
$this->setTrace($previous->getTrace());
}
}
+96 -6
View File
@@ -13,6 +13,7 @@ namespace Symfony\Component\Debug;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Debug\Exception\FlattenException;
use Symfony\Component\Debug\Exception\OutOfMemoryException;
if (!defined('ENT_SUBSTITUTE')) {
define('ENT_SUBSTITUTE', 8);
@@ -28,11 +29,15 @@ if (!defined('ENT_SUBSTITUTE')) {
* available, the Response content is always HTML.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Nicolas Grekas <p@tchwork.com>
*/
class ExceptionHandler implements ExceptionHandlerInterface
class ExceptionHandler
{
private $debug;
private $charset;
private $handler;
private $caughtBuffer;
private $caughtLength;
public function __construct($debug = true, $charset = 'UTF-8')
{
@@ -43,7 +48,7 @@ class ExceptionHandler implements ExceptionHandlerInterface
/**
* Registers the exception handler.
*
* @param Boolean $debug
* @param bool $debug
*
* @return ExceptionHandler The registered exception handler
*/
@@ -57,8 +62,65 @@ class ExceptionHandler implements ExceptionHandlerInterface
}
/**
* {@inheritDoc}
* Sets a user exception handler.
*
* @param callable $handler An handler that will be called on Exception
*
* @return callable|null The previous exception handler if any
*/
public function setHandler($handler)
{
if (null !== $handler && !is_callable($handler)) {
throw new \LogicException('The exception handler must be a valid PHP callable.');
}
$old = $this->handler;
$this->handler = $handler;
return $old;
}
/**
* Sends a response for the given Exception.
*
* To be as fail-safe as possible, the exception is first handled
* by our simple exception handler, then by the user exception handler.
* The latter takes precedence and any output from the former is cancelled,
* if and only if nothing bad happens in this handling path.
*/
public function handle(\Exception $exception)
{
if (null === $this->handler || $exception instanceof OutOfMemoryException) {
$this->failSafeHandle($exception);
return;
}
$caughtLength = $this->caughtLength = 0;
ob_start(array($this, 'catchOutput'));
$this->failSafeHandle($exception);
while (null === $this->caughtBuffer && ob_end_flush()) {
// Empty loop, everything is in the condition
}
if (isset($this->caughtBuffer[0])) {
ob_start(array($this, 'cleanOutput'));
echo $this->caughtBuffer;
$caughtLength = ob_get_length();
}
$this->caughtBuffer = null;
try {
call_user_func($this->handler, $exception);
$this->caughtLength = $caughtLength;
} catch (\Exception $e) {
if (!$caughtLength) {
// All handlers failed. Let PHP handle that now.
throw $exception;
}
}
}
/**
* Sends a response for the given Exception.
*
* If you have the Symfony HttpFoundation component installed,
@@ -68,10 +130,12 @@ class ExceptionHandler implements ExceptionHandlerInterface
* @see sendPhpResponse
* @see createResponse
*/
public function handle(\Exception $exception)
private function failSafeHandle(\Exception $exception)
{
if (class_exists('Symfony\Component\HttpFoundation\Response')) {
$this->createResponse($exception)->send();
if (class_exists('Symfony\Component\HttpFoundation\Response', false)) {
$response = $this->createResponse($exception);
$response->sendHeaders();
$response->sendContent();
} else {
$this->sendPhpResponse($exception);
}
@@ -315,4 +379,30 @@ EOF;
return implode(', ', $result);
}
/**
* @internal
*/
public function catchOutput($buffer)
{
$this->caughtBuffer = $buffer;
return '';
}
/**
* @internal
*/
public function cleanOutput($buffer)
{
if ($this->caughtLength) {
// use substr_replace() instead of substr() for mbstring overloading resistance
$cleanBuffer = substr_replace($buffer, '', 0, $this->caughtLength);
if (isset($cleanBuffer[0])) {
$buffer = $cleanBuffer;
}
}
return $buffer;
}
}
-27
View File
@@ -1,27 +0,0 @@
<?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;
/**
* An ExceptionHandler does something useful with an exception.
*
* @author Andrew Moore <me@andrewmoore.ca>
*/
interface ExceptionHandlerInterface
{
/**
* Handles an exception.
*
* @param \Exception $exception An \Exception instance
*/
public function handle(\Exception $exception);
}
@@ -159,7 +159,7 @@ class ClassNotFoundFatalErrorHandler implements FatalErrorHandlerInterface
*/
private function convertFileToClass($path, $file)
{
$namespacedClass = str_replace(array($path.'/', '.php', '/'), array('', '', '\\'), $file);
$namespacedClass = str_replace(array($path.DIRECTORY_SEPARATOR, '.php', '/'), array('', '', '\\'), $file);
$pearClass = str_replace('\\', '_', $namespacedClass);
// We cannot use the autoloader here as most of them use require; but if the class
@@ -181,7 +181,7 @@ class ClassNotFoundFatalErrorHandler implements FatalErrorHandlerInterface
/**
* @param string $class
*
* @return Boolean
* @return bool
*/
private function classExists($class)
{
+25 -27
View File
@@ -13,6 +13,7 @@ namespace Symfony\Component\Debug\Tests;
use Symfony\Component\Debug\DebugClassLoader;
use Symfony\Component\Debug\ErrorHandler;
use Symfony\Component\Debug\Exception\ContextErrorException;
class DebugClassLoaderTest extends \PHPUnit_Framework_TestCase
{
@@ -27,7 +28,7 @@ class DebugClassLoaderTest extends \PHPUnit_Framework_TestCase
{
$this->errorReporting = error_reporting(E_ALL | E_STRICT);
$this->loader = new ClassLoader();
spl_autoload_register(array($this->loader, 'loadClass'));
spl_autoload_register(array($this->loader, 'loadClass'), true, true);
DebugClassLoader::enable();
}
@@ -68,7 +69,7 @@ class DebugClassLoaderTest extends \PHPUnit_Framework_TestCase
// See below: this will fail with parse error
// but this should not be @-silenced.
@ class_exists(__NAMESPACE__.'\TestingUnsilencing', true);
@class_exists(__NAMESPACE__.'\TestingUnsilencing', true);
ini_set('log_errors', $bak[0]);
ini_set('display_errors', $bak[1]);
@@ -77,9 +78,6 @@ class DebugClassLoaderTest extends \PHPUnit_Framework_TestCase
$this->assertStringMatchesFormat('%aParse error%a', $output);
}
/**
* @expectedException \Symfony\Component\Debug\Exception\DummyException
*/
public function testStacking()
{
// the ContextErrorException must not be loaded to test the workaround
@@ -88,20 +86,6 @@ class DebugClassLoaderTest extends \PHPUnit_Framework_TestCase
$this->markTestSkipped('The ContextErrorException class is already loaded.');
}
$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_STRICT, $exception->getSeverity());
$that->assertStringStartsWith(__FILE__, $exception->getFile());
$that->assertRegexp('/^Runtime Notice: Declaration/', $exception->getMessage());
};
$exceptionHandler->expects($this->once())
->method('handle')
->will($this->returnCallback($exceptionCheck));
ErrorHandler::register();
try {
@@ -114,15 +98,20 @@ class DebugClassLoaderTest extends \PHPUnit_Framework_TestCase
namespace '.__NAMESPACE__.';
class ChildTestingStacking extends TestingStacking { function foo($bar) {} }
');
$this->fail('ContextErrorException expected');
} catch (\ErrorException $exception) {
// if an exception is thrown, the test passed
restore_error_handler();
restore_exception_handler();
$this->assertEquals(E_STRICT, $exception->getSeverity());
$this->assertStringStartsWith(__FILE__, $exception->getFile());
$this->assertRegexp('/^Runtime Notice: Declaration/', $exception->getMessage());
} catch (\Exception $e) {
restore_error_handler();
restore_exception_handler();
throw $e;
}
restore_error_handler();
restore_exception_handler();
}
/**
@@ -138,6 +127,10 @@ class DebugClassLoaderTest extends \PHPUnit_Framework_TestCase
*/
public function testFileCaseMismatch()
{
if (!file_exists(__DIR__.'/Fixtures/CaseMismatch.php')) {
$this->markTestSkipped('Can only be run on case insensitive filesystems');
}
class_exists(__NAMESPACE__.'\Fixtures\CaseMismatch', true);
}
@@ -158,6 +151,11 @@ class DebugClassLoaderTest extends \PHPUnit_Framework_TestCase
{
$this->assertTrue(class_exists(__NAMESPACE__.'\Fixtures\NotPSR0bis', true));
}
public function testClassAlias()
{
$this->assertTrue(class_exists(__NAMESPACE__.'\Fixtures\ClassAlias', true));
}
}
class ClassLoader
@@ -168,7 +166,7 @@ class ClassLoader
public function getClassMap()
{
return array(__NAMESPACE__.'\Fixtures\NotPSR0bis' => __DIR__ . '/Fixtures/notPsr0Bis.php');
return array(__NAMESPACE__.'\Fixtures\NotPSR0bis' => __DIR__.'/Fixtures/notPsr0Bis.php');
}
public function findFile($class)
@@ -180,13 +178,13 @@ class ClassLoader
} elseif (__NAMESPACE__.'\TestingCaseMismatch' === $class) {
eval('namespace '.__NAMESPACE__.'; class TestingCaseMisMatch {}');
} elseif (__NAMESPACE__.'\Fixtures\CaseMismatch' === $class) {
return __DIR__ . '/Fixtures/casemismatch.php';
return __DIR__.'/Fixtures/CaseMismatch.php';
} elseif (__NAMESPACE__.'\Fixtures\Psr4CaseMismatch' === $class) {
return __DIR__ . '/Fixtures/psr4/Psr4CaseMismatch.php';
return __DIR__.'/Fixtures/psr4/Psr4CaseMismatch.php';
} elseif (__NAMESPACE__.'\Fixtures\NotPSR0' === $class) {
return __DIR__ . '/Fixtures/reallyNotPsr0.php';
return __DIR__.'/Fixtures/reallyNotPsr0.php';
} elseif (__NAMESPACE__.'\Fixtures\NotPSR0bis' === $class) {
return __DIR__ . '/Fixtures/notPsr0Bis.php';
return __DIR__.'/Fixtures/notPsr0Bis.php';
}
}
}
+30 -39
View File
@@ -12,7 +12,7 @@
namespace Symfony\Component\Debug\Tests;
use Symfony\Component\Debug\ErrorHandler;
use Symfony\Component\Debug\Exception\DummyException;
use Symfony\Component\Debug\Exception\ContextErrorException;
/**
* ErrorHandlerTest
@@ -46,42 +46,33 @@ class ErrorHandlerTest extends \PHPUnit_Framework_TestCase
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(__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) {
$this->fail('ContextErrorException expected');
} catch (ContextErrorException $exception) {
// if an exception is thrown, the test passed
restore_error_handler();
$this->assertEquals(E_NOTICE, $exception->getSeverity());
$this->assertEquals(__FILE__, $exception->getFile());
$this->assertRegexp('/^Notice: Undefined variable: (foo|bar)/', $exception->getMessage());
$this->assertArrayHasKey('foobar', $exception->getContext());
$trace = $exception->getTrace();
$this->assertEquals(__FILE__, $trace[0]['file']);
$this->assertEquals('Symfony\Component\Debug\ErrorHandler', $trace[0]['class']);
$this->assertEquals('handle', $trace[0]['function']);
$this->assertEquals('->', $trace[0]['type']);
$this->assertEquals(__FILE__, $trace[1]['file']);
$this->assertEquals(__CLASS__, $trace[1]['class']);
$this->assertEquals('triggerNotice', $trace[1]['function']);
$this->assertEquals('::', $trace[1]['type']);
$this->assertEquals(__CLASS__, $trace[2]['class']);
$this->assertEquals('testNotice', $trace[2]['function']);
$this->assertEquals('->', $trace[2]['type']);
} catch (\Exception $e) {
restore_error_handler();
@@ -121,18 +112,18 @@ class ErrorHandlerTest extends \PHPUnit_Framework_TestCase
{
try {
$handler = ErrorHandler::register(0);
$this->assertFalse($handler->handle(0, 'foo', 'foo.php', 12, 'foo'));
$this->assertFalse($handler->handle(0, 'foo', 'foo.php', 12, array()));
restore_error_handler();
$handler = ErrorHandler::register(3);
$this->assertFalse($handler->handle(4, 'foo', 'foo.php', 12, 'foo'));
$this->assertFalse($handler->handle(4, 'foo', 'foo.php', 12, array()));
restore_error_handler();
$handler = ErrorHandler::register(3);
try {
$handler->handle(111, 'foo', 'foo.php', 12, 'foo');
$handler->handle(111, 'foo', 'foo.php', 12, array());
} catch (\ErrorException $e) {
$this->assertSame('111: foo in foo.php line 12', $e->getMessage());
$this->assertSame(111, $e->getSeverity());
@@ -143,12 +134,12 @@ class ErrorHandlerTest extends \PHPUnit_Framework_TestCase
restore_error_handler();
$handler = ErrorHandler::register(E_USER_DEPRECATED);
$this->assertTrue($handler->handle(E_USER_DEPRECATED, 'foo', 'foo.php', 12, 'foo'));
$this->assertFalse($handler->handle(E_USER_DEPRECATED, 'foo', 'foo.php', 12, array()));
restore_error_handler();
$handler = ErrorHandler::register(E_DEPRECATED);
$this->assertTrue($handler->handle(E_DEPRECATED, 'foo', 'foo.php', 12, 'foo'));
$this->assertFalse($handler->handle(E_DEPRECATED, 'foo', 'foo.php', 12, array()));
restore_error_handler();
@@ -171,7 +162,7 @@ class ErrorHandlerTest extends \PHPUnit_Framework_TestCase
$handler = ErrorHandler::register(E_USER_DEPRECATED);
$handler->setLogger($logger);
$handler->handle(E_USER_DEPRECATED, 'foo', 'foo.php', 12, 'foo');
$this->assertTrue($handler->handle(E_USER_DEPRECATED, 'foo', 'foo.php', 12, array()));
restore_error_handler();
@@ -213,7 +204,7 @@ class ErrorHandlerTest extends \PHPUnit_Framework_TestCase
$m = new \ReflectionMethod($handler, 'handleFatalError');
$m->setAccessible(true);
$m->invoke($handler, $exceptionHandler, $error);
$m->invoke($handler, array($exceptionHandler, 'handle'), $error);
$this->assertInstanceof($class, $exceptionHandler->e);
// class names are case insensitive and PHP/HHVM do not return the same
+3
View File
@@ -0,0 +1,3 @@
<?php
class_alias('Symfony\Component\Debug\Tests\Fixtures\NotPSR0bis', 'Symfony\Component\Debug\Tests\Fixtures\ClassAlias');
+3 -8
View File
@@ -1,14 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
backupStaticAttributes="false"
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/4.1/phpunit.xsd"
backupGlobals="false"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false"
syntaxCheck="false"
bootstrap="vendor/autoload.php"
>
<testsuites>