Commit 21f2602c by Carsten Brandt

Merge pull request #2916 from yiisoft/refactor-errorhandler

Refactor errorhandler
parents c23b671f f6ce9ead
......@@ -10,7 +10,7 @@ use Yii;
try {
10/0;
} catch (ErrorException) {
} catch (ErrorException $e) {
Yii::warning("Tried dividing by zero.");
}
......@@ -19,38 +19,39 @@ try {
As demonstrated above you may handle errors using `try`-`catch`.
Second, even fatal errors in Yii are rendered in a nice way. This means that in debugging mode, you can trace the causes
of fatal errors in order to more quickly identify the cause of the problem.
Rendering errors in a dedicated controller action
-------------------------------------------------
The default Yii error page is great when developing a site, and is acceptable for production sites if `YII_DEBUG`
is turned off in your bootstrap index.php file. But you may want to customize the default error page to make it
is turned off in your bootstrap `index.php` file. But you may want to customize the default error page to make it
more suitable for your project.
The easiest way to create a custom error page it is to use a dedicated controller action for error rendering. First,
you'll need to configure the `errorHandler` component in the application's configuration:
```php
return [
// ...
'components' => [
// ...
'components' => [
// ...
'errorHandler' => [
'errorAction' => 'site/error',
'errorHandler' => [
'errorAction' => 'site/error',
],
]
```
With that configuration in place, whenever an error occurs, Yii will execute the "error" action of the "Site" controller.
With that configuration in place, whenever an error occurs, Yii will execute the `error`-action of the `site`-controller.
That action should look for an exception and, if present, render the proper view file, passing along the exception:
```php
public function actionError()
{
if (\Yii::$app->exception !== null) {
return $this->render('error', ['exception' => \Yii::$app->exception]);
$exception = \Yii::$app->errorHandler->exception;
if ($exception !== null) {
return $this->render('error', ['exception' => $exception]);
}
}
```
......@@ -58,14 +59,13 @@ public function actionError()
Next, you would create the `views/site/error.php` file, which would make use of the exception. The exception object has
the following properties:
- `statusCode`: the HTTP status code (e.g. 403, 500). Available for HTTP exceptions only.
- `statusCode`: the HTTP status code (e.g. 403, 500). Available for [[yii\web\HttpException|HTTP exceptions]] only.
- `code`: the code of the exception.
- `type`: the error type (e.g. HttpException, PHP Error).
- `message`: the error message.
- `file`: the name of the PHP script file where the error occurs.
- `line`: the line number of the code where the error occurs.
- `trace`: the call stack of the error.
- `source`: the context source code where the error occurs.
Rendering errors without a dedicated controller action
------------------------------------------------------
......@@ -91,4 +91,4 @@ automatically be used. The view will be passed three variables:
- `$message`: the error message
- `$exception`: the exception being handled
The `$exception` object will have the same properties outlined above.
The `$exception` object will have the same properties as outlined above.
......@@ -8,6 +8,7 @@
namespace yii\mongodb;
use Yii;
use yii\base\ErrorHandler;
use yii\base\InvalidConfigException;
use yii\di\Instance;
......@@ -49,6 +50,7 @@ class Session extends \yii\web\Session
*/
public $sessionCollection = 'session';
/**
* Initializes the Session component.
* This method will initialize the [[db]] property to make sure it refers to a valid MongoDB connection.
......@@ -148,10 +150,11 @@ class Session extends \yii\web\Session
['upsert' => true]
);
} catch (\Exception $e) {
if (YII_DEBUG) {
echo $e->getMessage();
}
// it is too late to log an error message here
$exception = ErrorHandler::convertExceptionToString($e);
// its too late to use Yii logging here
error_log($exception);
echo $exception;
return false;
}
......
......@@ -219,6 +219,9 @@ Yii Framework 2 Change Log
- Chg #2281: Renamed `ActiveRecord::create()` to `populateRecord()` and changed signature. This method will not call instantiate() anymore (cebe)
- Chg #2405: The CSS class of `MaskedInput` now defaults to `form-control` (qiangxue)
- Chg #2426: Changed URL creation method signatures to be consistent (samdark)
- Chg #2516: Moved error handling from application to ErrorHandler class and fixed problems with HTTP Exception response code (cebe)
- `Yii::$app->exception` has now moved to `Yii::$app->errorHandler->exception`
- `yii\base\ErrorHandler` was split into `yii\web\ErrorHandler` and `yii\console\ErrorHandler`
- Chg #2544: Changed `DetailView`'s `name:format:label` to `attribute:format:label` to match `GridView` (samdark)
- Chg #2603: `yii\base\ErrorException` now extends `\ErrorException` (samdark)
- Chg #2629: `Module::controllerPath` is now read only, and all controller classes must be namespaced under `Module::controllerNamespace`. (qiangxue)
......
......@@ -126,13 +126,6 @@ abstract class Application extends Module
*/
public $layout = 'main';
/**
* @var integer the size of the reserved memory. A portion of memory is pre-allocated so that
* when an out-of-memory issue occurs, the error handler is able to handle the error with
* the help of this reserved memory. If you set this value to be 0, no memory will be reserved.
* Defaults to 256KB.
*/
public $memoryReserveSize = 262144;
/**
* @var string the requested route
*/
public $requestedRoute;
......@@ -168,20 +161,11 @@ abstract class Application extends Module
*/
public $bootstrap = [];
/**
* @var \Exception the exception that is being handled currently. When this is not null,
* it means the application is handling some exception and extra care should be taken.
*/
public $exception;
/**
* @var integer the current application state during a request handling life cycle.
* This property is managed by the application. Do not modify this property.
*/
public $state;
/**
* @var string Used to reserve memory for fatal error handler.
*/
private $_memoryReserve;
/**
* Constructor.
......@@ -195,8 +179,8 @@ abstract class Application extends Module
$this->state = self::STATE_BEGIN;
$this->registerErrorHandler($config);
$this->preInit($config);
$this->registerErrorHandlers();
Component::__construct($config);
}
......@@ -212,13 +196,13 @@ abstract class Application extends Module
public function preInit(&$config)
{
if (!isset($config['id'])) {
throw new InvalidConfigException('The "id" configuration is required.');
throw new InvalidConfigException('The "id" configuration for the Application is required.');
}
if (isset($config['basePath'])) {
$this->setBasePath($config['basePath']);
unset($config['basePath']);
} else {
throw new InvalidConfigException('The "basePath" configuration is required.');
throw new InvalidConfigException('The "basePath" configuration for the Application is required.');
}
if (isset($config['vendorPath'])) {
......@@ -301,18 +285,18 @@ abstract class Application extends Module
}
/**
* Registers error handlers.
* Registers the errorHandler component as a PHP error handler.
*/
public function registerErrorHandlers()
protected function registerErrorHandler(&$config)
{
if (YII_ENABLE_ERROR_HANDLER) {
ini_set('display_errors', 0);
set_exception_handler([$this, 'handleException']);
set_error_handler([$this, 'handleError']);
if ($this->memoryReserveSize > 0) {
$this->_memoryReserve = str_repeat('x', $this->memoryReserveSize);
if (!isset($config['components']['errorHandler']['class'])) {
echo "Error: no errorHandler component is configured.\n";
exit(1);
}
register_shutdown_function([$this, 'handleFatalError']);
$this->set('errorHandler', $config['components']['errorHandler']);
unset($config['components']['errorHandler']);
$this->getErrorHandler()->register();
}
}
......@@ -480,7 +464,7 @@ abstract class Application extends Module
/**
* Returns the error handler component.
* @return ErrorHandler the error handler application component.
* @return \yii\web\ErrorHandler|\yii\console\ErrorHandler the error handler application component.
*/
public function getErrorHandler()
{
......@@ -586,7 +570,6 @@ abstract class Application extends Module
{
return [
'log' => ['class' => 'yii\log\Dispatcher'],
'errorHandler' => ['class' => 'yii\base\ErrorHandler'],
'view' => ['class' => 'yii\web\View'],
'formatter' => ['class' => 'yii\base\Formatter'],
'i18n' => ['class' => 'yii\i18n\I18N'],
......@@ -623,160 +606,4 @@ abstract class Application extends Module
exit($status);
}
}
/**
* Handles uncaught PHP exceptions.
*
* This method is implemented as a PHP exception handler.
*
* @param \Exception $exception the exception that is not caught
*/
public function handleException($exception)
{
if ($exception instanceof ExitException) {
return;
}
$this->exception = $exception;
// disable error capturing to avoid recursive errors while handling exceptions
restore_error_handler();
restore_exception_handler();
try {
$this->logException($exception);
if (($handler = $this->getErrorHandler()) !== null) {
$handler->handle($exception);
} else {
echo $this->renderException($exception);
if (PHP_SAPI === 'cli' && !YII_ENV_TEST) {
exit(1);
}
}
} catch (\Exception $e) {
// exception could be thrown in ErrorHandler::handle()
$msg = (string) $e;
$msg .= "\nPrevious exception:\n";
$msg .= (string) $exception;
if (YII_DEBUG) {
if (PHP_SAPI === 'cli') {
echo $msg . "\n";
} else {
echo '<pre>' . htmlspecialchars($msg, ENT_QUOTES, $this->charset) . '</pre>';
}
}
$msg .= "\n\$_SERVER = " . var_export($_SERVER, true);
error_log($msg);
exit(1);
}
}
/**
* Handles PHP execution errors such as warnings, notices.
*
* This method is used as a PHP error handler. It will simply raise an `ErrorException`.
*
* @param integer $code the level of the error raised
* @param string $message the error message
* @param string $file the filename that the error was raised in
* @param integer $line the line number the error was raised at
*
* @throws ErrorException
*/
public function handleError($code, $message, $file, $line)
{
if (error_reporting() & $code) {
// load ErrorException manually here because autoloading them will not work
// when error occurs while autoloading a class
if (!class_exists('\\yii\\base\\Exception', false)) {
require_once(__DIR__ . '/Exception.php');
}
if (!class_exists('\\yii\\base\\ErrorException', false)) {
require_once(__DIR__ . '/ErrorException.php');
}
$exception = new ErrorException($message, $code, $code, $file, $line);
// in case error appeared in __toString method we can't throw any exception
$trace = debug_backtrace(false);
array_shift($trace);
foreach ($trace as $frame) {
if ($frame['function'] == '__toString') {
$this->handleException($exception);
exit(1);
}
}
throw $exception;
}
}
/**
* Handles fatal PHP errors
*/
public function handleFatalError()
{
unset($this->_memoryReserve);
// load ErrorException manually here because autoloading them will not work
// when error occurs while autoloading a class
if (!class_exists('\\yii\\base\\Exception', false)) {
require_once(__DIR__ . '/Exception.php');
}
if (!class_exists('\\yii\\base\\ErrorException', false)) {
require_once(__DIR__ . '/ErrorException.php');
}
$error = error_get_last();
if (ErrorException::isFatalError($error)) {
$exception = new ErrorException($error['message'], $error['type'], $error['type'], $error['file'], $error['line']);
$this->exception = $exception;
// use error_log because it's too late to use Yii log
error_log($exception);
if (($handler = $this->getErrorHandler()) !== null) {
$handler->handle($exception);
} else {
echo $this->renderException($exception);
}
exit(1);
}
}
/**
* Renders an exception without using rich format.
* @param \Exception $exception the exception to be rendered.
* @return string the rendering result
*/
public function renderException($exception)
{
if ($exception instanceof Exception && ($exception instanceof UserException || !YII_DEBUG)) {
$message = $exception->getName() . ': ' . $exception->getMessage();
if (Yii::$app->controller instanceof \yii\console\Controller) {
$message = Yii::$app->controller->ansiFormat($message, Console::FG_RED);
}
} else {
$message = YII_DEBUG ? (string) $exception : 'Error: ' . $exception->getMessage();
}
if (PHP_SAPI === 'cli') {
return $message . "\n";
} else {
return '<pre>' . htmlspecialchars($message, ENT_QUOTES, $this->charset) . '</pre>';
}
}
/**
* Logs the given exception
* @param \Exception $exception the exception to be logged
*/
protected function logException($exception)
{
$category = get_class($exception);
if ($exception instanceof HttpException) {
$category = 'yii\\web\\HttpException:' . $exception->statusCode;
} elseif ($exception instanceof \ErrorException) {
$category .= ':' . $exception->getSeverity();
}
Yii::error((string) $exception, $category);
}
}
......@@ -29,10 +29,7 @@ class ErrorException extends \ErrorException
*/
public function __construct($message = '', $code = 0, $severity = 1, $filename = __FILE__, $lineno = __LINE__, \Exception $previous = null)
{
parent::__construct($message, $code, $previous);
$this->severity = $severity;
$this->file = $filename;
$this->line = $lineno;
parent::__construct($message, $code, $severity, $filename, $lineno, $previous);
if (function_exists('xdebug_get_function_stack')) {
$trace = array_slice(array_reverse(xdebug_get_function_stack()), 3, -1);
......
......@@ -188,9 +188,20 @@ class Application extends \yii\base\Application
*/
public function coreComponents()
{
return array_merge([
return array_merge(parent::coreComponents(), [
'request' => ['class' => 'yii\console\Request'],
'response' => ['class' => 'yii\console\Response'],
], parent::coreComponents());
]);
}
/**
* Registers the errorHandler component as a PHP error handler.
*/
protected function registerErrorHandler(&$config)
{
if (!isset($config['components']['errorHandler']['class'])) {
$config['components']['errorHandler']['class'] = 'yii\\console\\ErrorHandler';
}
parent::registerErrorHandler($config);
}
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\console;
use Yii;
use yii\base\ErrorException;
use yii\base\UserException;
use yii\helpers\Console;
/**
* ErrorHandler handles uncaught PHP errors and exceptions.
*
* ErrorHandler is configured as an application component in [[\yii\base\Application]] by default.
* You can access that instance via `Yii::$app->errorHandler`.
*
* @author Carsten Brandt <mail@cebe.cc>
* @since 2.0
*/
class ErrorHandler extends \yii\base\ErrorHandler
{
/**
* Renders an exception using ansi format for console output.
* @param \Exception $exception the exception to be rendered.
*/
protected function renderException($exception)
{
if ($exception instanceof Exception && ($exception instanceof UserException || !YII_DEBUG)) {
$message = $this->formatMessage($exception->getName() . ': ') . $exception->getMessage();
} elseif (YII_DEBUG) {
if ($exception instanceof Exception) {
$message = $this->formatMessage("Exception ({$exception->getName()})");
} elseif ($exception instanceof ErrorException) {
$message = $this->formatMessage($exception->getName());
} else {
$message = $this->formatMessage('Exception');
}
$message .= $this->formatMessage(" '" . get_class($exception) . "'", [Console::BOLD, Console::FG_BLUE])
. " with message " . $this->formatMessage("'{$exception->getMessage()}'", [Console::BOLD]) //. "\n"
. "\n\nin " . dirname($exception->getFile()) . DIRECTORY_SEPARATOR . $this->formatMessage(basename($exception->getFile()), [Console::BOLD])
. ':' . $this->formatMessage($exception->getLine(), [Console::BOLD, Console::FG_YELLOW]) . "\n\n"
. $this->formatMessage("Stack trace:\n", [Console::BOLD]) . $exception->getTraceAsString();
} else {
$message = $this->formatMessage('Error: ') . $exception->getMessage();
}
if (PHP_SAPI === 'cli') {
Console::stderr($message . "\n");
} else {
echo $message . "\n";
}
}
/**
* Colorizes a message for console output.
* @param string $message the message to colorize.
* @param array $format the message format.
* @return string the colorized message.
* @see Console::ansiFormat() for details on how to specify the message format.
*/
protected function formatMessage($message, $format = [Console::FG_RED, Console::BOLD])
{
$stream = (PHP_SAPI === 'cli') ? STDERR : STDOUT;
// try controller first to allow check for --color switch
if (Yii::$app->controller instanceof \yii\console\Controller && Yii::$app->controller->isColorEnabled($stream)
|| Yii::$app instanceof \yii\console\Application && Console::streamSupportsAnsiColors($stream)) {
$message = Console::ansiFormat($message, $format);
}
return $message;
}
}
\ No newline at end of file
......@@ -7,6 +7,7 @@
namespace yii\mail;
use yii\base\ErrorHandler;
use yii\base\Object;
use Yii;
......@@ -58,7 +59,7 @@ abstract class BaseMessage extends Object implements MessageInterface
try {
return $this->toString();
} catch (\Exception $e) {
trigger_error($e->getMessage() . "\n\n" . $e->getTraceAsString());
ErrorHandler::convertExceptionToError($e);
return '';
}
}
......
......@@ -146,11 +146,22 @@ class Application extends \yii\base\Application
*/
public function coreComponents()
{
return array_merge([
return array_merge(parent::coreComponents(), [
'request' => ['class' => 'yii\web\Request'],
'response' => ['class' => 'yii\web\Response'],
'session' => ['class' => 'yii\web\Session'],
'user' => ['class' => 'yii\web\User'],
], parent::coreComponents());
]);
}
/**
* Registers the errorHandler component as a PHP error handler.
*/
protected function registerErrorHandler(&$config)
{
if (!isset($config['components']['errorHandler']['class'])) {
$config['components']['errorHandler']['class'] = 'yii\\web\\ErrorHandler';
}
parent::registerErrorHandler($config);
}
}
......@@ -105,10 +105,9 @@ class Controller extends \yii\base\Controller
public function beforeAction($action)
{
if (parent::beforeAction($action)) {
if ($this->enableCsrfValidation && Yii::$app->exception === null && !Yii::$app->getRequest()->validateCsrfToken()) {
if ($this->enableCsrfValidation && Yii::$app->errorHandler->exception === null && !Yii::$app->getRequest()->validateCsrfToken()) {
throw new BadRequestHttpException(Yii::t('yii', 'Unable to verify your data submission.'));
}
return true;
} else {
return false;
......
......@@ -182,10 +182,11 @@ class DbSession extends Session
->execute();
}
} catch (\Exception $e) {
if (YII_DEBUG) {
echo $e->getMessage();
}
// it is too late to log an error message here
$exception = ErrorHandler::convertExceptionToString($e);
// its too late to use Yii logging here
error_log($exception);
echo $exception;
return false;
}
......
......@@ -68,7 +68,7 @@ class ErrorAction extends Action
public function run()
{
if (($exception = Yii::$app->exception) === null) {
if (($exception = Yii::$app->errorHandler->exception) === null) {
return '';
}
......
......@@ -8,6 +8,7 @@ namespace yii\widgets;
use Yii;
use yii\base\Component;
use yii\base\ErrorHandler;
use yii\helpers\ArrayHelper;
use yii\helpers\Html;
use yii\base\Model;
......@@ -140,8 +141,7 @@ class ActiveField extends Component
try {
return $this->render();
} catch (\Exception $e) {
trigger_error($e->getMessage() . "\n\n" . $e->getTraceAsString());
ErrorHandler::convertExceptionToError($e);
return '';
}
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment