diff --git a/UPGRADE-3.0.md b/UPGRADE-3.0.md index 50d13d50b382..50120a3611be 100644 --- a/UPGRADE-3.0.md +++ b/UPGRADE-3.0.md @@ -3,8 +3,8 @@ UPGRADE FROM 2.x to 3.0 ### ClassLoader - * The `UniversalClassLoader` class has been removed in favor of `ClassLoader`. The only difference is that some method - names are different: + * The `UniversalClassLoader` class has been removed in favor of + `ClassLoader`. The only difference is that some method names are different: * `registerNamespaces()` -> `addPrefixes()` * `registerPrefixes()` -> `addPrefixes()` @@ -34,6 +34,16 @@ UPGRADE FROM 2.x to 3.0 * `Symfony\Bridge\Monolog\Logger` * `Symfony\Component\HttpKernel\Log\NullLogger` + * The `Symfony\Component\HttpKernel\Kernel::init()` method has been removed. + + * The following classes have been renamed as they have been moved to the + Debug component: + + * `Symfony\Component\HttpKernel\Debug\ErrorHandler` -> `Symfony\Component\Debug\ErrorHandler` + * `Symfony\Component\HttpKernel\Debug\ExceptionHandler` -> `Symfony\Component\Debug\ExceptionHandler` + * `Symfony\Component\HttpKernel\Exception\FatalErrorException` -> `Symfony\Component\Debug\Exception\FatalErrorException` + * `Symfony\Component\HttpKernel\Exception\FlattenException` -> `Symfony\Component\Debug\Exception\FlattenException` + ### Routing * Some route settings have been renamed: diff --git a/src/Symfony/Component/Debug/.gitignore b/src/Symfony/Component/Debug/.gitignore new file mode 100644 index 000000000000..38c15605edbf --- /dev/null +++ b/src/Symfony/Component/Debug/.gitignore @@ -0,0 +1,5 @@ +vendor/ +composer.lock +phpunit.xml +Tests/ProjectContainer.php +Tests/classes.map \ No newline at end of file diff --git a/src/Symfony/Component/Debug/CHANGELOG.md b/src/Symfony/Component/Debug/CHANGELOG.md new file mode 100644 index 000000000000..2ad5ce695c36 --- /dev/null +++ b/src/Symfony/Component/Debug/CHANGELOG.md @@ -0,0 +1,7 @@ +CHANGELOG +========= + +2.3.0 +----- + + * added the component diff --git a/src/Symfony/Component/Debug/ErrorHandler.php b/src/Symfony/Component/Debug/ErrorHandler.php new file mode 100644 index 000000000000..a12a625ed286 --- /dev/null +++ b/src/Symfony/Component/Debug/ErrorHandler.php @@ -0,0 +1,128 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Debug; + +use Symfony\Component\Debug\Exception\FatalErrorException; +use Psr\Log\LoggerInterface; + +/** + * ErrorHandler. + * + * @author Fabien Potencier + */ +class ErrorHandler +{ + const TYPE_DEPRECATION = -100; + + private $levels = array( + E_WARNING => 'Warning', + E_NOTICE => 'Notice', + E_USER_ERROR => 'User Error', + E_USER_WARNING => 'User Warning', + E_USER_NOTICE => 'User Notice', + E_STRICT => 'Runtime Notice', + E_RECOVERABLE_ERROR => 'Catchable Fatal Error', + E_DEPRECATED => 'Deprecated', + E_USER_DEPRECATED => 'User Deprecated', + E_ERROR => 'Error', + E_CORE_ERROR => 'Core Error', + E_COMPILE_ERROR => 'Compile Error', + E_PARSE => 'Parse', + ); + + private $level; + + private $reservedMemory; + + /** @var LoggerInterface */ + private static $logger; + + /** + * Register 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) + * + * @return The registered error handler + */ + public static function register($level = null) + { + $handler = new static(); + $handler->setLevel($level); + + ini_set('display_errors', 0); + set_error_handler(array($handler, 'handle')); + register_shutdown_function(array($handler, 'handleFatal')); + $handler->reservedMemory = str_repeat('x', 10240); + + return $handler; + } + + public function setLevel($level) + { + $this->level = null === $level ? error_reporting() : $level; + } + + public static function setLogger(LoggerInterface $logger) + { + self::$logger = $logger; + } + + /** + * @throws \ErrorException When error_reporting returns error + */ + public function handle($level, $message, $file, $line, $context) + { + if (0 === $this->level) { + return false; + } + + if ($level & (E_USER_DEPRECATED | E_DEPRECATED)) { + if (null !== self::$logger) { + $stack = version_compare(PHP_VERSION, '5.4', '<') ? array_slice(debug_backtrace(false), 0, 10) : debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 10); + + self::$logger->warning($message, array('type' => self::TYPE_DEPRECATION, 'stack' => $stack)); + } + + return true; + } + + if (error_reporting() & $level && $this->level & $level) { + throw new \ErrorException(sprintf('%s: %s in %s line %d', isset($this->levels[$level]) ? $this->levels[$level] : $level, $message, $file, $line), 0, $level, $file, $line); + } + + return false; + } + + public function handleFatal() + { + if (null === $error = error_get_last()) { + return; + } + + unset($this->reservedMemory); + $type = $error['type']; + if (0 === $this->level || !in_array($type, array(E_ERROR, E_CORE_ERROR, E_COMPILE_ERROR, E_PARSE))) { + return; + } + + // get current exception handler + $exceptionHandler = set_exception_handler(function() {}); + restore_exception_handler(); + + if (is_array($exceptionHandler) && $exceptionHandler[0] instanceof ExceptionHandler) { + $level = isset($this->levels[$type]) ? $this->levels[$type] : $type; + $message = sprintf('%s: %s in %s line %d', $level, $error['message'], $error['file'], $error['line']); + $exception = new FatalErrorException($message, 0, $type, $error['file'], $error['line']); + $exceptionHandler[0]->handle($exception); + } + } +} diff --git a/src/Symfony/Component/Debug/Exception/FatalErrorException.php b/src/Symfony/Component/Debug/Exception/FatalErrorException.php new file mode 100644 index 000000000000..bf37ef809857 --- /dev/null +++ b/src/Symfony/Component/Debug/Exception/FatalErrorException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Debug\Exception; + +/** + * Fatal Error Exception. + * + * @author Konstanton Myakshin + */ +class FatalErrorException extends \ErrorException +{ +} diff --git a/src/Symfony/Component/Debug/Exception/FlattenException.php b/src/Symfony/Component/Debug/Exception/FlattenException.php new file mode 100644 index 000000000000..4f0e815f9706 --- /dev/null +++ b/src/Symfony/Component/Debug/Exception/FlattenException.php @@ -0,0 +1,277 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Debug\Exception; + +use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface; + +/** + * FlattenException wraps a PHP Exception to be able to serialize it. + * + * Basically, this class removes all objects from the trace. + * + * @author Fabien Potencier + */ +class FlattenException +{ + private $message; + private $code; + private $previous; + private $trace; + private $class; + private $statusCode; + private $headers; + private $file; + private $line; + + public static function create(\Exception $exception, $statusCode = null, array $headers = array()) + { + $e = new static(); + $e->setMessage($exception->getMessage()); + $e->setCode($exception->getCode()); + + if ($exception instanceof HttpExceptionInterface) { + $statusCode = $exception->getStatusCode(); + $headers = array_merge($headers, $exception->getHeaders()); + } + + if (null === $statusCode) { + $statusCode = 500; + } + + $e->setStatusCode($statusCode); + $e->setHeaders($headers); + $e->setTraceFromException($exception); + $e->setClass(get_class($exception)); + $e->setFile($exception->getFile()); + $e->setLine($exception->getLine()); + if ($exception->getPrevious()) { + $e->setPrevious(static::create($exception->getPrevious())); + } + + return $e; + } + + public function toArray() + { + $exceptions = array(); + foreach (array_merge(array($this), $this->getAllPrevious()) as $exception) { + $exceptions[] = array( + 'message' => $exception->getMessage(), + 'class' => $exception->getClass(), + 'trace' => $exception->getTrace(), + ); + } + + return $exceptions; + } + + public function getStatusCode() + { + return $this->statusCode; + } + + public function setStatusCode($code) + { + $this->statusCode = $code; + } + + public function getHeaders() + { + return $this->headers; + } + + public function setHeaders(array $headers) + { + $this->headers = $headers; + } + + public function getClass() + { + return $this->class; + } + + public function setClass($class) + { + $this->class = $class; + } + + public function getFile() + { + return $this->file; + } + + public function setFile($file) + { + $this->file = $file; + } + + public function getLine() + { + return $this->line; + } + + public function setLine($line) + { + $this->line = $line; + } + + public function getMessage() + { + return $this->message; + } + + public function setMessage($message) + { + $this->message = $message; + } + + public function getCode() + { + return $this->code; + } + + public function setCode($code) + { + $this->code = $code; + } + + public function getPrevious() + { + return $this->previous; + } + + public function setPrevious(FlattenException $previous) + { + $this->previous = $previous; + } + + public function getAllPrevious() + { + $exceptions = array(); + $e = $this; + while ($e = $e->getPrevious()) { + $exceptions[] = $e; + } + + return $exceptions; + } + + public function getTrace() + { + return $this->trace; + } + + 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()); + } + + public function setTrace($trace, $file, $line) + { + $this->trace = array(); + $this->trace[] = array( + 'namespace' => '', + 'short_class' => '', + 'class' => '', + 'type' => '', + 'function' => '', + 'file' => $file, + 'line' => $line, + 'args' => array(), + ); + foreach ($trace as $entry) { + $class = ''; + $namespace = ''; + if (isset($entry['class'])) { + $parts = explode('\\', $entry['class']); + $class = array_pop($parts); + $namespace = implode('\\', $parts); + } + + $this->trace[] = array( + 'namespace' => $namespace, + 'short_class' => $class, + 'class' => isset($entry['class']) ? $entry['class'] : '', + 'type' => isset($entry['type']) ? $entry['type'] : '', + 'function' => isset($entry['function']) ? $entry['function'] : null, + 'file' => isset($entry['file']) ? $entry['file'] : null, + 'line' => isset($entry['line']) ? $entry['line'] : null, + 'args' => isset($entry['args']) ? $this->flattenArgs($entry['args']) : array(), + ); + } + } + + private function flattenArgs($args, $level = 0) + { + $result = array(); + foreach ($args as $key => $value) { + if (is_object($value)) { + $result[$key] = array('object', get_class($value)); + } elseif (is_array($value)) { + if ($level > 10) { + $result[$key] = array('array', '*DEEP NESTED ARRAY*'); + } else { + $result[$key] = array('array', $this->flattenArgs($value, ++$level)); + } + } elseif (null === $value) { + $result[$key] = array('null', null); + } elseif (is_bool($value)) { + $result[$key] = array('boolean', $value); + } elseif (is_resource($value)) { + $result[$key] = array('resource', get_resource_type($value)); + } elseif ($value instanceof \__PHP_Incomplete_Class) { + // Special case of object, is_object will return false + $result[$key] = array('incomplete-object', $this->getClassNameFromIncomplete($value)); + } else { + $result[$key] = array('string', (string) $value); + } + } + + return $result; + } + + private function getClassNameFromIncomplete(\__PHP_Incomplete_Class $value) + { + $array = new \ArrayObject($value); + + return $array['__PHP_Incomplete_Class_Name']; + } +} diff --git a/src/Symfony/Component/Debug/ExceptionHandler.php b/src/Symfony/Component/Debug/ExceptionHandler.php new file mode 100644 index 000000000000..1b4ba2ea33a4 --- /dev/null +++ b/src/Symfony/Component/Debug/ExceptionHandler.php @@ -0,0 +1,283 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Debug; + +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Debug\Exception\FlattenException; + +if (!defined('ENT_SUBSTITUTE')) { + define('ENT_SUBSTITUTE', 8); +} + +/** + * ExceptionHandler converts an exception to a Response object. + * + * It is mostly useful in debug mode to replace the default PHP/XDebug + * output with something prettier and more useful. + * + * As this class is mainly used during Kernel boot, where nothing is yet + * available, the Response content is always HTML. + * + * @author Fabien Potencier + */ +class ExceptionHandler +{ + private $debug; + private $charset; + + public function __construct($debug = true, $charset = 'UTF-8') + { + $this->debug = $debug; + $this->charset = $charset; + } + + /** + * Register the exception handler. + * + * @param Boolean $debug + * + * @return ExceptionHandler The registered exception handler + */ + public static function register($debug = true) + { + $handler = new static($debug); + + set_exception_handler(array($handler, 'handle')); + + return $handler; + } + + /** + * Sends a Response for the given Exception. + * + * @param \Exception $exception An \Exception instance + */ + public function handle(\Exception $exception) + { + $this->createResponse($exception)->send(); + } + + /** + * Creates the error Response associated with the given Exception. + * + * @param \Exception|FlattenException $exception An \Exception instance + * + * @return Response A Response instance + */ + public function createResponse($exception) + { + if (!$exception instanceof FlattenException) { + $exception = FlattenException::create($exception); + } + + return new Response($this->decorate($this->getContent($exception), $this->getStylesheet($exception)), $exception->getStatusCode(), $exception->getHeaders()); + } + + /** + * Gets the HTML content associated with the given exception. + * + * @param FlattenException $exception A FlattenException instance + * + * @return string The content as a string + */ + public function getContent(FlattenException $exception) + { + switch ($exception->getStatusCode()) { + case 404: + $title = 'Sorry, the page you are looking for could not be found.'; + break; + default: + $title = 'Whoops, looks like something went wrong.'; + } + + $content = ''; + if ($this->debug) { + try { + $count = count($exception->getAllPrevious()); + $total = $count + 1; + foreach ($exception->toArray() as $position => $e) { + $ind = $count - $position + 1; + $class = $this->abbrClass($e['class']); + $message = nl2br($e['message']); + $content .= sprintf(<< +

%d/%d %s: %s

+ +
+
    + +EOF + , $ind, $total, $class, $message); + foreach ($e['trace'] as $trace) { + $content .= '
  1. '; + if ($trace['function']) { + $content .= sprintf('at %s%s%s(%s)', $this->abbrClass($trace['class']), $trace['type'], $trace['function'], $this->formatArgs($trace['args'])); + } + if (isset($trace['file']) && isset($trace['line'])) { + if ($linkFormat = ini_get('xdebug.file_link_format')) { + $link = str_replace(array('%f', '%l'), array($trace['file'], $trace['line']), $linkFormat); + $content .= sprintf(' in %s line %s', $link, $trace['file'], $trace['line']); + } else { + $content .= sprintf(' in %s line %s', $trace['file'], $trace['line']); + } + } + $content .= "
  2. \n"; + } + + $content .= "
\n
\n"; + } + } catch (\Exception $e) { + // something nasty happened and we cannot throw an exception anymore + if ($this->debug) { + $title = sprintf('Exception thrown when handling an exception (%s: %s)', get_class($exception), $exception->getMessage()); + } else { + $title = 'Whoops, looks like something went wrong.'; + } + } + } + + return << +

$title

+ $content + +EOF; + } + + /** + * Gets the stylesheet associated with the given exception. + * + * @param FlattenException $exception A FlattenException instance + * + * @return string The stylesheet as a string + */ + public function getStylesheet(FlattenException $exception) + { + return << + + + + + + + + $content + + +EOF; + } + + private function abbrClass($class) + { + $parts = explode('\\', $class); + + return sprintf("%s", $class, array_pop($parts)); + } + + /** + * Formats an array as a string. + * + * @param array $args The argument array + * + * @return string + */ + private function formatArgs(array $args) + { + $result = array(); + foreach ($args as $key => $item) { + if ('object' === $item[0]) { + $formattedValue = sprintf("object(%s)", $this->abbrClass($item[1])); + } elseif ('array' === $item[0]) { + $formattedValue = sprintf("array(%s)", is_array($item[1]) ? $this->formatArgs($item[1]) : $item[1]); + } elseif ('string' === $item[0]) { + $formattedValue = sprintf("'%s'", htmlspecialchars($item[1], ENT_QUOTES | ENT_SUBSTITUTE, $this->charset)); + } elseif ('null' === $item[0]) { + $formattedValue = 'null'; + } elseif ('boolean' === $item[0]) { + $formattedValue = ''.strtolower(var_export($item[1], true)).''; + } elseif ('resource' === $item[0]) { + $formattedValue = 'resource'; + } else { + $formattedValue = str_replace("\n", '', var_export(htmlspecialchars((string) $item[1], ENT_QUOTES | ENT_SUBSTITUTE, $this->charset), true)); + } + + $result[] = is_int($key) ? $formattedValue : sprintf("'%s' => %s", $key, $formattedValue); + } + + return implode(', ', $result); + } +} diff --git a/src/Symfony/Component/Debug/LICENSE b/src/Symfony/Component/Debug/LICENSE new file mode 100644 index 000000000000..88a57f8d8da4 --- /dev/null +++ b/src/Symfony/Component/Debug/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-2013 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 +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/src/Symfony/Component/Debug/README.md b/src/Symfony/Component/Debug/README.md new file mode 100644 index 000000000000..de4dac70b0a3 --- /dev/null +++ b/src/Symfony/Component/Debug/README.md @@ -0,0 +1,30 @@ +Debug Component +=============== + +Debug provides tools to make debugging easier. + +Here is classic usage of the main provided tools:: + + use Symfony\Component\Debug\ErrorHandler; + use Symfony\Component\Debug\ExceptionHandler; + + error_reporting(-1); + + ErrorHandler::register($this->errorReportingLevel); + if ('cli' !== php_sapi_name()) { + ExceptionHandler::register(); + } elseif (!ini_get('log_errors') || ini_get('error_log')) { + ini_set('display_errors', 1); + } + + // from the ClassLoader component + DebugClassLoader::enable(); + +Resources +--------- + +You can run the unit tests with the following command: + + $ cd path/to/Symfony/Component/Debug/ + $ composer.phar install --dev + $ phpunit diff --git a/src/Symfony/Component/HttpKernel/Tests/Debug/ErrorHandlerTest.php b/src/Symfony/Component/Debug/Tests/ErrorHandlerTest.php similarity index 96% rename from src/Symfony/Component/HttpKernel/Tests/Debug/ErrorHandlerTest.php rename to src/Symfony/Component/Debug/Tests/ErrorHandlerTest.php index 637c7ad376a0..db06f6107e6e 100644 --- a/src/Symfony/Component/HttpKernel/Tests/Debug/ErrorHandlerTest.php +++ b/src/Symfony/Component/Debug/Tests/ErrorHandlerTest.php @@ -9,9 +9,9 @@ * file that was distributed with this source code. */ -namespace Symfony\Component\HttpKernel\Tests\Debug; +namespace Symfony\Component\Debug\Tests; -use Symfony\Component\HttpKernel\Debug\ErrorHandler; +use Symfony\Component\Debug\ErrorHandler; /** * ErrorHandlerTest diff --git a/src/Symfony/Component/HttpKernel/Tests/Exception/FlattenExceptionTest.php b/src/Symfony/Component/Debug/Tests/Exception/FlattenExceptionTest.php similarity index 98% rename from src/Symfony/Component/HttpKernel/Tests/Exception/FlattenExceptionTest.php rename to src/Symfony/Component/Debug/Tests/Exception/FlattenExceptionTest.php index d51ab6aa0715..4a1d99e816a1 100644 --- a/src/Symfony/Component/HttpKernel/Tests/Exception/FlattenExceptionTest.php +++ b/src/Symfony/Component/Debug/Tests/Exception/FlattenExceptionTest.php @@ -9,9 +9,9 @@ * file that was distributed with this source code. */ -namespace Symfony\Component\HttpKernel\Tests\Exception; +namespace Symfony\Component\Debug\Tests\Exception; -use Symfony\Component\HttpKernel\Exception\FlattenException; +use Symfony\Component\Debug\Exception\FlattenException; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException; use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException; diff --git a/src/Symfony/Component/HttpKernel/Tests/Debug/ExceptionHandlerTest.php b/src/Symfony/Component/Debug/Tests/ExceptionHandlerTest.php similarity index 95% rename from src/Symfony/Component/HttpKernel/Tests/Debug/ExceptionHandlerTest.php rename to src/Symfony/Component/Debug/Tests/ExceptionHandlerTest.php index 4ccbe7dea4e4..f187e2d09929 100644 --- a/src/Symfony/Component/HttpKernel/Tests/Debug/ExceptionHandlerTest.php +++ b/src/Symfony/Component/Debug/Tests/ExceptionHandlerTest.php @@ -9,9 +9,9 @@ * file that was distributed with this source code. */ -namespace Symfony\Component\HttpKernel\Tests\Debug; +namespace Symfony\Component\Debug\Tests; -use Symfony\Component\HttpKernel\Debug\ExceptionHandler; +use Symfony\Component\Debug\ExceptionHandler; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException; diff --git a/src/Symfony/Component/Debug/composer.json b/src/Symfony/Component/Debug/composer.json new file mode 100644 index 000000000000..69c9295884ca --- /dev/null +++ b/src/Symfony/Component/Debug/composer.json @@ -0,0 +1,31 @@ +{ + "name": "symfony/debug", + "type": "library", + "description": "Symfony Debug Component", + "keywords": [], + "homepage": "http://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + } + ], + "require": { + "php": ">=5.3.3" + }, + "autoload": { + "psr-0": { "Symfony\\Component\\Debug\\": "" } + }, + "target-dir": "Symfony/Component/Debug", + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "2.3-dev" + } + } +} diff --git a/src/Symfony/Component/Debug/phpunit.xml.dist b/src/Symfony/Component/Debug/phpunit.xml.dist new file mode 100644 index 000000000000..8bab165e1053 --- /dev/null +++ b/src/Symfony/Component/Debug/phpunit.xml.dist @@ -0,0 +1,29 @@ + + + + + + ./Tests/ + + + + + + ./ + + ./Tests + ./vendor + + + + diff --git a/src/Symfony/Component/HttpKernel/CHANGELOG.md b/src/Symfony/Component/HttpKernel/CHANGELOG.md index 719ead835fbb..1da7d7a31943 100644 --- a/src/Symfony/Component/HttpKernel/CHANGELOG.md +++ b/src/Symfony/Component/HttpKernel/CHANGELOG.md @@ -4,6 +4,9 @@ CHANGELOG 2.3.0 ----- + * deprecated `Symfony\Component\HttpKernel\Debug\ErrorHandler`, `Symfony\Component\HttpKernel\Debug\ExceptionHandler`, + `Symfony\Component\HttpKernel\Exception\FatalErrorException`, and `Symfony\Component\HttpKernel\Exception\FlattenException` + * deprecated `Symfony\Component\HttpKernel\Kernel::init()`` * added the possibility to specify an id an extra attributes to hinclude tags * added the collect of data if a controller is a Closure in the Request collector diff --git a/src/Symfony/Component/HttpKernel/Debug/ErrorHandler.php b/src/Symfony/Component/HttpKernel/Debug/ErrorHandler.php index 3c1cf72d2d7c..2718f891df2d 100644 --- a/src/Symfony/Component/HttpKernel/Debug/ErrorHandler.php +++ b/src/Symfony/Component/HttpKernel/Debug/ErrorHandler.php @@ -11,118 +11,15 @@ namespace Symfony\Component\HttpKernel\Debug; -use Symfony\Component\HttpKernel\Exception\FatalErrorException; -use Psr\Log\LoggerInterface; +use Symfony\Component\Debug\ErrorHandler as DebugErrorHandler; /** * ErrorHandler. * * @author Fabien Potencier + * + * @deprecated Deprecated in 2.3, to be removed in 3.0. Use the same class from the Debug component instead. */ -class ErrorHandler +class ErrorHandler extends DebugErrorHandler { - const TYPE_DEPRECATION = -100; - - private $levels = array( - E_WARNING => 'Warning', - E_NOTICE => 'Notice', - E_USER_ERROR => 'User Error', - E_USER_WARNING => 'User Warning', - E_USER_NOTICE => 'User Notice', - E_STRICT => 'Runtime Notice', - E_RECOVERABLE_ERROR => 'Catchable Fatal Error', - E_DEPRECATED => 'Deprecated', - E_USER_DEPRECATED => 'User Deprecated', - E_ERROR => 'Error', - E_CORE_ERROR => 'Core Error', - E_COMPILE_ERROR => 'Compile Error', - E_PARSE => 'Parse', - ); - - private $level; - - private $reservedMemory; - - /** @var LoggerInterface */ - private static $logger; - - /** - * Register 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) - * - * @return The registered error handler - */ - public static function register($level = null) - { - $handler = new static(); - $handler->setLevel($level); - - ini_set('display_errors', 0); - set_error_handler(array($handler, 'handle')); - register_shutdown_function(array($handler, 'handleFatal')); - $handler->reservedMemory = str_repeat('x', 10240); - - return $handler; - } - - public function setLevel($level) - { - $this->level = null === $level ? error_reporting() : $level; - } - - public static function setLogger(LoggerInterface $logger) - { - self::$logger = $logger; - } - - /** - * @throws \ErrorException When error_reporting returns error - */ - public function handle($level, $message, $file, $line, $context) - { - if (0 === $this->level) { - return false; - } - - if ($level & (E_USER_DEPRECATED | E_DEPRECATED)) { - if (null !== self::$logger) { - $stack = version_compare(PHP_VERSION, '5.4', '<') ? array_slice(debug_backtrace(false), 0, 10) : debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 10); - - self::$logger->warning($message, array('type' => self::TYPE_DEPRECATION, 'stack' => $stack)); - } - - return true; - } - - if (error_reporting() & $level && $this->level & $level) { - throw new \ErrorException(sprintf('%s: %s in %s line %d', isset($this->levels[$level]) ? $this->levels[$level] : $level, $message, $file, $line), 0, $level, $file, $line); - } - - return false; - } - - public function handleFatal() - { - if (null === $error = error_get_last()) { - return; - } - - unset($this->reservedMemory); - $type = $error['type']; - if (0 === $this->level || !in_array($type, array(E_ERROR, E_CORE_ERROR, E_COMPILE_ERROR, E_PARSE))) { - return; - } - - // get current exception handler - $exceptionHandler = set_exception_handler(function() {}); - restore_exception_handler(); - - if (is_array($exceptionHandler) && $exceptionHandler[0] instanceof ExceptionHandler) { - $level = isset($this->levels[$type]) ? $this->levels[$type] : $type; - $message = sprintf('%s: %s in %s line %d', $level, $error['message'], $error['file'], $error['line']); - $exception = new FatalErrorException($message, 0, $type, $error['file'], $error['line']); - $exceptionHandler[0]->handle($exception); - } - } } diff --git a/src/Symfony/Component/HttpKernel/Debug/ExceptionHandler.php b/src/Symfony/Component/HttpKernel/Debug/ExceptionHandler.php index a2d0dd42ec9c..581e29cd8498 100644 --- a/src/Symfony/Component/HttpKernel/Debug/ExceptionHandler.php +++ b/src/Symfony/Component/HttpKernel/Debug/ExceptionHandler.php @@ -11,273 +11,15 @@ namespace Symfony\Component\HttpKernel\Debug; -use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\HttpKernel\Exception\FlattenException; - -if (!defined('ENT_SUBSTITUTE')) { - define('ENT_SUBSTITUTE', 8); -} +use Symfony\Component\Debug\ExceptionHandler as DebugExceptionHandler; /** * ExceptionHandler converts an exception to a Response object. * - * It is mostly useful in debug mode to replace the default PHP/XDebug - * output with something prettier and more useful. - * - * As this class is mainly used during Kernel boot, where nothing is yet - * available, the Response content is always HTML. - * * @author Fabien Potencier + * + * @deprecated Deprecated in 2.3, to be removed in 3.0. Use the same class from the Debug component instead. */ -class ExceptionHandler +class ExceptionHandler extends DebugExceptionHandler { - private $debug; - private $charset; - - public function __construct($debug = true, $charset = 'UTF-8') - { - $this->debug = $debug; - $this->charset = $charset; - } - - /** - * Register the exception handler. - * - * @param Boolean $debug - * - * @return ExceptionHandler The registered exception handler - */ - public static function register($debug = true) - { - $handler = new static($debug); - - set_exception_handler(array($handler, 'handle')); - - return $handler; - } - - /** - * Sends a Response for the given Exception. - * - * @param \Exception $exception An \Exception instance - */ - public function handle(\Exception $exception) - { - $this->createResponse($exception)->send(); - } - - /** - * Creates the error Response associated with the given Exception. - * - * @param \Exception|FlattenException $exception An \Exception instance - * - * @return Response A Response instance - */ - public function createResponse($exception) - { - if (!$exception instanceof FlattenException) { - $exception = FlattenException::create($exception); - } - - return new Response($this->decorate($this->getContent($exception), $this->getStylesheet($exception)), $exception->getStatusCode(), $exception->getHeaders()); - } - - /** - * Gets the HTML content associated with the given exception. - * - * @param FlattenException $exception A FlattenException instance - * - * @return string The content as a string - */ - public function getContent(FlattenException $exception) - { - switch ($exception->getStatusCode()) { - case 404: - $title = 'Sorry, the page you are looking for could not be found.'; - break; - default: - $title = 'Whoops, looks like something went wrong.'; - } - - $content = ''; - if ($this->debug) { - try { - $count = count($exception->getAllPrevious()); - $total = $count + 1; - foreach ($exception->toArray() as $position => $e) { - $ind = $count - $position + 1; - $class = $this->abbrClass($e['class']); - $message = nl2br($e['message']); - $content .= sprintf(<< -

%d/%d %s: %s

- -
-
    - -EOF - , $ind, $total, $class, $message); - foreach ($e['trace'] as $trace) { - $content .= '
  1. '; - if ($trace['function']) { - $content .= sprintf('at %s%s%s(%s)', $this->abbrClass($trace['class']), $trace['type'], $trace['function'], $this->formatArgs($trace['args'])); - } - if (isset($trace['file']) && isset($trace['line'])) { - if ($linkFormat = ini_get('xdebug.file_link_format')) { - $link = str_replace(array('%f', '%l'), array($trace['file'], $trace['line']), $linkFormat); - $content .= sprintf(' in %s line %s', $link, $trace['file'], $trace['line']); - } else { - $content .= sprintf(' in %s line %s', $trace['file'], $trace['line']); - } - } - $content .= "
  2. \n"; - } - - $content .= "
\n
\n"; - } - } catch (\Exception $e) { - // something nasty happened and we cannot throw an exception anymore - if ($this->debug) { - $title = sprintf('Exception thrown when handling an exception (%s: %s)', get_class($exception), $exception->getMessage()); - } else { - $title = 'Whoops, looks like something went wrong.'; - } - } - } - - return << -

$title

- $content - -EOF; - } - - /** - * Gets the stylesheet associated with the given exception. - * - * @param FlattenException $exception A FlattenException instance - * - * @return string The stylesheet as a string - */ - public function getStylesheet(FlattenException $exception) - { - return << - - - - - - - - $content - - -EOF; - } - - private function abbrClass($class) - { - $parts = explode('\\', $class); - - return sprintf("%s", $class, array_pop($parts)); - } - - /** - * Formats an array as a string. - * - * @param array $args The argument array - * - * @return string - */ - private function formatArgs(array $args) - { - $result = array(); - foreach ($args as $key => $item) { - if ('object' === $item[0]) { - $formattedValue = sprintf("object(%s)", $this->abbrClass($item[1])); - } elseif ('array' === $item[0]) { - $formattedValue = sprintf("array(%s)", is_array($item[1]) ? $this->formatArgs($item[1]) : $item[1]); - } elseif ('string' === $item[0]) { - $formattedValue = sprintf("'%s'", htmlspecialchars($item[1], ENT_QUOTES | ENT_SUBSTITUTE, $this->charset)); - } elseif ('null' === $item[0]) { - $formattedValue = 'null'; - } elseif ('boolean' === $item[0]) { - $formattedValue = ''.strtolower(var_export($item[1], true)).''; - } elseif ('resource' === $item[0]) { - $formattedValue = 'resource'; - } else { - $formattedValue = str_replace("\n", '', var_export(htmlspecialchars((string) $item[1], ENT_QUOTES | ENT_SUBSTITUTE, $this->charset), true)); - } - - $result[] = is_int($key) ? $formattedValue : sprintf("'%s' => %s", $key, $formattedValue); - } - - return implode(', ', $result); - } } diff --git a/src/Symfony/Component/HttpKernel/Exception/FatalErrorException.php b/src/Symfony/Component/HttpKernel/Exception/FatalErrorException.php index a082f80dc0a0..1f1ef1a276a9 100644 --- a/src/Symfony/Component/HttpKernel/Exception/FatalErrorException.php +++ b/src/Symfony/Component/HttpKernel/Exception/FatalErrorException.php @@ -11,12 +11,15 @@ namespace Symfony\Component\HttpKernel\Exception; +use Symfony\Component\Debug\Exception\FatalErrorException as DebugFatalErrorException; + /** * Fatal Error Exception. * * @author Konstanton Myakshin + * + * @deprecated Deprecated in 2.3, to be removed in 3.0. Use the same class from the Debug component instead. */ -class FatalErrorException extends \ErrorException +class FatalErrorException extends DebugFatalErrorException { - } diff --git a/src/Symfony/Component/HttpKernel/Exception/FlattenException.php b/src/Symfony/Component/HttpKernel/Exception/FlattenException.php index 66cafed8da57..0168afca169d 100644 --- a/src/Symfony/Component/HttpKernel/Exception/FlattenException.php +++ b/src/Symfony/Component/HttpKernel/Exception/FlattenException.php @@ -11,265 +11,17 @@ namespace Symfony\Component\HttpKernel\Exception; +use Symfony\Component\Debug\Exception\FlattenException as DebugFlattenException; + /** * FlattenException wraps a PHP Exception to be able to serialize it. * * Basically, this class removes all objects from the trace. * * @author Fabien Potencier + * + * @deprecated Deprecated in 2.3, to be removed in 3.0. Use the same class from the Debug component instead. */ -class FlattenException +class FlattenException extends DebugFlattenException { - private $message; - private $code; - private $previous; - private $trace; - private $class; - private $statusCode; - private $headers; - private $file; - private $line; - - public static function create(\Exception $exception, $statusCode = null, array $headers = array()) - { - $e = new static(); - $e->setMessage($exception->getMessage()); - $e->setCode($exception->getCode()); - - if ($exception instanceof HttpExceptionInterface) { - $statusCode = $exception->getStatusCode(); - $headers = array_merge($headers, $exception->getHeaders()); - } - - if (null === $statusCode) { - $statusCode = 500; - } - - $e->setStatusCode($statusCode); - $e->setHeaders($headers); - $e->setTraceFromException($exception); - $e->setClass(get_class($exception)); - $e->setFile($exception->getFile()); - $e->setLine($exception->getLine()); - if ($exception->getPrevious()) { - $e->setPrevious(static::create($exception->getPrevious())); - } - - return $e; - } - - public function toArray() - { - $exceptions = array(); - foreach (array_merge(array($this), $this->getAllPrevious()) as $exception) { - $exceptions[] = array( - 'message' => $exception->getMessage(), - 'class' => $exception->getClass(), - 'trace' => $exception->getTrace(), - ); - } - - return $exceptions; - } - - public function getStatusCode() - { - return $this->statusCode; - } - - public function setStatusCode($code) - { - $this->statusCode = $code; - } - - public function getHeaders() - { - return $this->headers; - } - - public function setHeaders(array $headers) - { - $this->headers = $headers; - } - - public function getClass() - { - return $this->class; - } - - public function setClass($class) - { - $this->class = $class; - } - - public function getFile() - { - return $this->file; - } - - public function setFile($file) - { - $this->file = $file; - } - - public function getLine() - { - return $this->line; - } - - public function setLine($line) - { - $this->line = $line; - } - - public function getMessage() - { - return $this->message; - } - - public function setMessage($message) - { - $this->message = $message; - } - - public function getCode() - { - return $this->code; - } - - public function setCode($code) - { - $this->code = $code; - } - - public function getPrevious() - { - return $this->previous; - } - - public function setPrevious(FlattenException $previous) - { - $this->previous = $previous; - } - - public function getAllPrevious() - { - $exceptions = array(); - $e = $this; - while ($e = $e->getPrevious()) { - $exceptions[] = $e; - } - - return $exceptions; - } - - public function getTrace() - { - return $this->trace; - } - - 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()); - } - - public function setTrace($trace, $file, $line) - { - $this->trace = array(); - $this->trace[] = array( - 'namespace' => '', - 'short_class' => '', - 'class' => '', - 'type' => '', - 'function' => '', - 'file' => $file, - 'line' => $line, - 'args' => array(), - ); - foreach ($trace as $entry) { - $class = ''; - $namespace = ''; - if (isset($entry['class'])) { - $parts = explode('\\', $entry['class']); - $class = array_pop($parts); - $namespace = implode('\\', $parts); - } - - $this->trace[] = array( - 'namespace' => $namespace, - 'short_class' => $class, - 'class' => isset($entry['class']) ? $entry['class'] : '', - 'type' => isset($entry['type']) ? $entry['type'] : '', - 'function' => isset($entry['function']) ? $entry['function'] : null, - 'file' => isset($entry['file']) ? $entry['file'] : null, - 'line' => isset($entry['line']) ? $entry['line'] : null, - 'args' => isset($entry['args']) ? $this->flattenArgs($entry['args']) : array(), - ); - } - } - - private function flattenArgs($args, $level = 0) - { - $result = array(); - foreach ($args as $key => $value) { - if (is_object($value)) { - $result[$key] = array('object', get_class($value)); - } elseif (is_array($value)) { - if ($level > 10) { - $result[$key] = array('array', '*DEEP NESTED ARRAY*'); - } else { - $result[$key] = array('array', $this->flattenArgs($value, ++$level)); - } - } elseif (null === $value) { - $result[$key] = array('null', null); - } elseif (is_bool($value)) { - $result[$key] = array('boolean', $value); - } elseif (is_resource($value)) { - $result[$key] = array('resource', get_resource_type($value)); - } elseif ($value instanceof \__PHP_Incomplete_Class) { - // Special case of object, is_object will return false - $result[$key] = array('incomplete-object', $this->getClassNameFromIncomplete($value)); - } else { - $result[$key] = array('string', (string) $value); - } - } - - return $result; - } - - private function getClassNameFromIncomplete(\__PHP_Incomplete_Class $value) - { - $array = new \ArrayObject($value); - - return $array['__PHP_Incomplete_Class_Name']; - } } diff --git a/src/Symfony/Component/HttpKernel/Kernel.php b/src/Symfony/Component/HttpKernel/Kernel.php index 03100e3e27b1..d139c81d4d46 100644 --- a/src/Symfony/Component/HttpKernel/Kernel.php +++ b/src/Symfony/Component/HttpKernel/Kernel.php @@ -27,13 +27,10 @@ use Symfony\Component\HttpKernel\Config\FileLocator; use Symfony\Component\HttpKernel\DependencyInjection\MergeExtensionConfigurationPass; use Symfony\Component\HttpKernel\DependencyInjection\AddClassesToCachePass; -use Symfony\Component\HttpKernel\Debug\ErrorHandler; -use Symfony\Component\HttpKernel\Debug\ExceptionHandler; use Symfony\Component\Config\Loader\LoaderResolver; use Symfony\Component\Config\Loader\DelegatingLoader; use Symfony\Component\Config\ConfigCache; use Symfony\Component\ClassLoader\ClassCollectionLoader; -use Symfony\Component\ClassLoader\DebugClassLoader; /** * The Kernel is the heart of the Symfony system. @@ -94,21 +91,11 @@ public function __construct($environment, $debug) $this->init(); } + /** + * @deprecated Deprecated since version 2.3, to be removed in 3.0. Move your logic in the constructor instead. + */ public function init() { - ini_set('display_errors', 0); - - if ($this->debug) { - error_reporting(-1); - - DebugClassLoader::enable(); - ErrorHandler::register($this->errorReportingLevel); - if ('cli' !== php_sapi_name()) { - ExceptionHandler::register(); - } elseif (!ini_get('log_errors') || ini_get('error_log')) { - ini_set('display_errors', 1); - } - } } public function __clone()