From 420637443c1485c3ec92eacb9100f7f5703108b3 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Thu, 13 Jun 2013 23:39:18 -0400 Subject: [PATCH] Refactored the way of sending response in controller actions. --- .../backend/controllers/SiteController.php | 10 +- apps/advanced/environments/dev/yii | 2 +- apps/advanced/environments/prod/yii | 2 +- .../frontend/controllers/SiteController.php | 16 +- apps/basic/controllers/SiteController.php | 16 +- apps/basic/yii | 2 +- .../protected/controllers/SiteController.php | 2 +- docs/guide/performance.md | 8 +- framework/yii/base/Action.php | 4 +- framework/yii/base/Application.php | 136 ++---- framework/yii/base/Controller.php | 23 +- framework/yii/base/ErrorHandler.php | 82 ++-- framework/yii/base/InlineAction.php | 4 +- framework/yii/base/Module.php | 12 +- framework/yii/base/Response.php | 75 +--- framework/yii/base/ResponseEvent.php | 40 ++ framework/yii/console/Application.php | 31 ++ framework/yii/views/errorHandler/error.php | 66 +++ framework/yii/views/errorHandler/exception.php | 491 ++++++++++++++++++++ framework/yii/views/errorHandler/main.php | 492 --------------------- framework/yii/web/Application.php | 25 +- framework/yii/web/CaptchaAction.php | 21 +- framework/yii/web/Response.php | 56 +-- framework/yii/web/User.php | 4 +- 24 files changed, 822 insertions(+), 798 deletions(-) create mode 100644 framework/yii/base/ResponseEvent.php create mode 100644 framework/yii/views/errorHandler/error.php create mode 100644 framework/yii/views/errorHandler/exception.php delete mode 100644 framework/yii/views/errorHandler/main.php diff --git a/apps/advanced/backend/controllers/SiteController.php b/apps/advanced/backend/controllers/SiteController.php index 0306c97..851fcec 100644 --- a/apps/advanced/backend/controllers/SiteController.php +++ b/apps/advanced/backend/controllers/SiteController.php @@ -10,16 +10,16 @@ class SiteController extends Controller { public function actionIndex() { - echo $this->render('index'); + return $this->render('index'); } public function actionLogin() { $model = new LoginForm(); if ($this->populate($_POST, $model) && $model->login()) { - Yii::$app->response->redirect(array('site/index')); + return Yii::$app->response->redirect(array('site/index')); } else { - echo $this->render('login', array( + return $this->render('login', array( 'model' => $model, )); } @@ -27,7 +27,7 @@ class SiteController extends Controller public function actionLogout() { - Yii::$app->getUser()->logout(); - Yii::$app->getResponse()->redirect(array('site/index')); + Yii::$app->user->logout(); + return Yii::$app->response->redirect(array('site/index')); } } diff --git a/apps/advanced/environments/dev/yii b/apps/advanced/environments/dev/yii index d763217..b81fc10 100644 --- a/apps/advanced/environments/dev/yii +++ b/apps/advanced/environments/dev/yii @@ -22,4 +22,4 @@ $config = yii\helpers\ArrayHelper::merge( ); $application = new yii\console\Application($config); -$application->run(); +return $application->run(); diff --git a/apps/advanced/environments/prod/yii b/apps/advanced/environments/prod/yii index 395aede..0d4ebe2 100644 --- a/apps/advanced/environments/prod/yii +++ b/apps/advanced/environments/prod/yii @@ -22,4 +22,4 @@ $config = yii\helpers\ArrayHelper::merge( ); $application = new yii\console\Application($config); -$application->run(); +return $application->run(); diff --git a/apps/advanced/frontend/controllers/SiteController.php b/apps/advanced/frontend/controllers/SiteController.php index cd3339c..b0f8ec2 100644 --- a/apps/advanced/frontend/controllers/SiteController.php +++ b/apps/advanced/frontend/controllers/SiteController.php @@ -20,16 +20,16 @@ class SiteController extends Controller public function actionIndex() { - echo $this->render('index'); + return $this->render('index'); } public function actionLogin() { $model = new LoginForm(); if ($this->populate($_POST, $model) && $model->login()) { - Yii::$app->response->redirect(array('site/index')); + return Yii::$app->response->redirect(array('site/index')); } else { - echo $this->render('login', array( + return $this->render('login', array( 'model' => $model, )); } @@ -37,8 +37,8 @@ class SiteController extends Controller public function actionLogout() { - Yii::$app->getUser()->logout(); - Yii::$app->getResponse()->redirect(array('site/index')); + Yii::$app->user->logout(); + return Yii::$app->response->redirect(array('site/index')); } public function actionContact() @@ -46,9 +46,9 @@ class SiteController extends Controller $model = new ContactForm; if ($this->populate($_POST, $model) && $model->contact(Yii::$app->params['adminEmail'])) { Yii::$app->session->setFlash('contactFormSubmitted'); - Yii::$app->response->refresh(); + return Yii::$app->response->refresh(); } else { - echo $this->render('contact', array( + return $this->render('contact', array( 'model' => $model, )); } @@ -56,6 +56,6 @@ class SiteController extends Controller public function actionAbout() { - echo $this->render('about'); + return $this->render('about'); } } diff --git a/apps/basic/controllers/SiteController.php b/apps/basic/controllers/SiteController.php index ff3b8b4..d79b728 100644 --- a/apps/basic/controllers/SiteController.php +++ b/apps/basic/controllers/SiteController.php @@ -20,16 +20,16 @@ class SiteController extends Controller public function actionIndex() { - echo $this->render('index'); + return $this->render('index'); } public function actionLogin() { $model = new LoginForm(); if ($this->populate($_POST, $model) && $model->login()) { - Yii::$app->response->redirect(array('site/index')); + return Yii::$app->response->redirect(array('site/index')); } else { - echo $this->render('login', array( + return $this->render('login', array( 'model' => $model, )); } @@ -37,8 +37,8 @@ class SiteController extends Controller public function actionLogout() { - Yii::$app->getUser()->logout(); - Yii::$app->getResponse()->redirect(array('site/index')); + Yii::$app->user->logout(); + return Yii::$app->response->redirect(array('site/index')); } public function actionContact() @@ -46,9 +46,9 @@ class SiteController extends Controller $model = new ContactForm; if ($this->populate($_POST, $model) && $model->contact(Yii::$app->params['adminEmail'])) { Yii::$app->session->setFlash('contactFormSubmitted'); - Yii::$app->response->refresh(); + return Yii::$app->response->refresh(); } else { - echo $this->render('contact', array( + return $this->render('contact', array( 'model' => $model, )); } @@ -56,6 +56,6 @@ class SiteController extends Controller public function actionAbout() { - echo $this->render('about'); + return $this->render('about'); } } diff --git a/apps/basic/yii b/apps/basic/yii index 0793523..be27cac 100755 --- a/apps/basic/yii +++ b/apps/basic/yii @@ -19,4 +19,4 @@ require(__DIR__ . '/vendor/autoload.php'); $config = require(__DIR__ . '/config/console.php'); $application = new yii\console\Application($config); -$application->run(); +return $application->run(); diff --git a/apps/benchmark/protected/controllers/SiteController.php b/apps/benchmark/protected/controllers/SiteController.php index 16089d0..47cf796 100644 --- a/apps/benchmark/protected/controllers/SiteController.php +++ b/apps/benchmark/protected/controllers/SiteController.php @@ -8,6 +8,6 @@ class SiteController extends Controller public function actionHello() { - echo 'hello world'; + return 'hello world'; } } diff --git a/docs/guide/performance.md b/docs/guide/performance.md index 35efa67..1fa3529 100644 --- a/docs/guide/performance.md +++ b/docs/guide/performance.md @@ -97,7 +97,7 @@ return array( ``` You can use `CacheSession` to store sessions using cache. Note that some -cache storages such as memcached has no guaranteee that session data will not +cache storage such as memcached has no guarantee that session data will not be lost leading to unexpected logouts. Improving application @@ -152,14 +152,14 @@ class PostController extends Controller public function actionIndex() { $posts = Post::find()->orderBy('id DESC')->limit(100)->asArray()->all(); - echo $this->render('index', array( + return $this->render('index', array( 'posts' => $posts, )); } } ``` -In the view you should access fields of each invidual record from `$posts` as array: +In the view you should access fields of each individual record from `$posts` as array: ```php foreach ($posts as $post) { @@ -178,4 +178,4 @@ request later if there's no need for immediate response. - Cron jobs + console. - queues + handlers. -TBD \ No newline at end of file +TBD diff --git a/framework/yii/base/Action.php b/framework/yii/base/Action.php index 7142539..8778cab 100644 --- a/framework/yii/base/Action.php +++ b/framework/yii/base/Action.php @@ -66,7 +66,7 @@ class Action extends Component * Runs this action with the specified parameters. * This method is mainly invoked by the controller. * @param array $params the parameters to be bound to the action's run() method. - * @return integer the exit status (0 means normal, non-zero means abnormal). + * @return mixed the result of the action * @throws InvalidConfigException if the action class does not have a run() method */ public function runWithParams($params) @@ -75,6 +75,6 @@ class Action extends Component throw new InvalidConfigException(get_class($this) . ' must define a "run()" method.'); } $args = $this->controller->bindActionParams($this, $params); - return (int)call_user_func_array(array($this, 'run'), $args); + return call_user_func_array(array($this, 'run'), $args); } } diff --git a/framework/yii/base/Application.php b/framework/yii/base/Application.php index 9969ecd..e94ca65 100644 --- a/framework/yii/base/Application.php +++ b/framework/yii/base/Application.php @@ -16,16 +16,12 @@ use yii\web\HttpException; * @author Qiang Xue * @since 2.0 */ -class Application extends Module +abstract class Application extends Module { /** - * @event Event an event that is triggered at the beginning of [[run()]]. + * @event ResponseEvent an event that is triggered after [[handle()]] returns a response. */ - const EVENT_BEFORE_RUN = 'beforeRun'; - /** - * @event Event an event that is triggered at the end of [[run()]]. - */ - const EVENT_AFTER_RUN = 'afterRun'; + const EVENT_RESPONSE = 'response'; /** * @var string the application name. */ @@ -54,7 +50,7 @@ class Application extends Module */ public $preload = array(); /** - * @var \yii\web\Controller|\yii\console\Controller the currently active controller instance + * @var Controller the currently active controller instance */ public $controller; /** @@ -63,8 +59,6 @@ class Application extends Module */ public $layout = 'main'; - private $_ended = false; - /** * @var string Used to reserve memory for fatal error handler. */ @@ -143,67 +137,32 @@ class Application extends Module } /** - * Terminates the application. - * This method replaces PHP's exit() function by calling [[afterRequest()]] before exiting. - * @param integer $status exit status (value 0 means normal exit while other values mean abnormal exit). - * @param boolean $exit whether to exit the current request. - * It defaults to true, meaning the PHP's exit() function will be called at the end of this method. - */ - public function end($status = 0, $exit = true) - { - if (!$this->_ended) { - $this->_ended = true; - $this->getResponse()->end(); - $this->afterRun(); - } - - if ($exit) { - exit($status); - } - } - - /** * Runs the application. * This is the main entrance of an application. * @return integer the exit status (0 means normal, non-zero values mean abnormal) */ public function run() { - $this->beforeRun(); - $response = $this->getResponse(); - $response->begin(); - register_shutdown_function(array($this, 'end'), 0, false); - $status = $this->processRequest(); - $response->end(); - $this->afterRun(); - return $status; - } + $response = $this->handle($this->getRequest()); - /** - * Raises the [[EVENT_BEFORE_RUN]] event right BEFORE the application processes the request. - */ - public function beforeRun() - { - $this->trigger(self::EVENT_BEFORE_RUN); - } + $event = new ResponseEvent($response); + $this->trigger(self::EVENT_RESPONSE, $event); + $event->response->send(); - /** - * Raises the [[EVENT_AFTER_RUN]] event right AFTER the application processes the request. - */ - public function afterRun() - { - $this->trigger(self::EVENT_AFTER_RUN); + return $response->exitStatus; } /** - * Processes the request. - * Child classes should override this method with actual request processing logic. - * @return integer the exit status of the controller action (0 means normal, non-zero values mean abnormal) + * Handles the specified request. + * + * This method should return an instance of [[Response]] or its child class + * which represents the handling result of the request. + * + * @param Request $request the request to be handled + * @return Response the resulting response */ - public function processRequest() - { - return 0; - } + abstract public function handle($request); + private $_runtimePath; @@ -325,15 +284,6 @@ class Application extends Module } /** - * Returns the response component. - * @return \yii\web\Response|\yii\console\Response the response component - */ - public function getResponse() - { - return $this->getComponent('response'); - } - - /** * Returns the view object. * @return View the view object that is used to render various view files. */ @@ -400,7 +350,7 @@ class Application extends Module * This method is implemented as a PHP exception handler. It requires * that constant YII_ENABLE_ERROR_HANDLER be defined true. * - * @param \Exception $exception exception that is not caught + * @param \Exception $exception the exception that is not caught */ public function handleException($exception) { @@ -410,17 +360,13 @@ class Application extends Module try { $this->logException($exception); - if (($handler = $this->getErrorHandler()) !== null) { $handler->handle($exception); } else { - $this->renderException($exception); + echo $this->renderException($exception); } - - $this->end(1); - } catch (\Exception $e) { - // exception could be thrown in end() or ErrorHandler::handle() + // exception could be thrown in ErrorHandler::handle() $msg = (string)$e; $msg .= "\nPrevious exception:\n"; $msg .= (string)$exception; @@ -468,29 +414,28 @@ class Application extends Module */ public function handleFatalError() { - if (YII_ENABLE_ERROR_HANDLER) { - $error = error_get_last(); - - if (ErrorException::isFatalError($error)) { - unset($this->_memoryReserve); - $exception = new ErrorException($error['message'], $error['type'], $error['type'], $error['file'], $error['line']); - // use error_log because it's too late to use Yii log - error_log($exception); - - if (($handler = $this->getErrorHandler()) !== null) { - $handler->handle($exception); - } else { - $this->renderException($exception); - } + $error = error_get_last(); + + if (ErrorException::isFatalError($error)) { + unset($this->_memoryReserve); + $exception = new ErrorException($error['message'], $error['type'], $error['type'], $error['file'], $error['line']); + // use error_log because it's too late to use Yii log + error_log($exception); - exit(1); + 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) { @@ -499,14 +444,17 @@ class Application extends Module } else { $message = YII_DEBUG ? (string)$exception : 'Error: ' . $exception->getMessage(); } - if (PHP_SAPI) { - echo $message . "\n"; + if (PHP_SAPI === 'cli') { + return $message . "\n"; } else { - echo '
' . htmlspecialchars($message, ENT_QUOTES, $this->charset) . '
'; + return '
' . htmlspecialchars($message, ENT_QUOTES, $this->charset) . '
'; } } - // todo: to be polished + /** + * Logs the given exception + * @param \Exception $exception the exception to be logged + */ protected function logException($exception) { $category = get_class($exception); diff --git a/framework/yii/base/Controller.php b/framework/yii/base/Controller.php index af33b63..c22b584 100644 --- a/framework/yii/base/Controller.php +++ b/framework/yii/base/Controller.php @@ -100,7 +100,7 @@ class Controller extends Component * If the action ID is empty, the method will use [[defaultAction]]. * @param string $id the ID of the action to be executed. * @param array $params the parameters (name-value pairs) to be passed to the action. - * @return integer the status of the action execution. 0 means normal, other values mean abnormal. + * @return mixed the result of the action * @throws InvalidRouteException if the requested action ID cannot be resolved into an action successfully. * @see createAction */ @@ -110,16 +110,16 @@ class Controller extends Component if ($action !== null) { $oldAction = $this->action; $this->action = $action; - $status = 1; + $result = null; if ($this->module->beforeAction($action)) { if ($this->beforeAction($action)) { - $status = $action->runWithParams($params); + $result = $action->runWithParams($params); $this->afterAction($action); } $this->module->afterAction($action); } $this->action = $oldAction; - return $status; + return $result; } else { throw new InvalidRouteException('Unable to resolve the request: ' . $this->getUniqueId() . '/' . $id); } @@ -161,21 +161,6 @@ class Controller extends Component } /** - * Forwards the current execution flow to handle a new request specified by a route. - * The only difference between this method and [[run()]] is that after calling this method, - * the application will exit. - * @param string $route the route to be handled, e.g., 'view', 'comment/view', '/admin/comment/view'. - * @param array $params the parameters to be passed to the action. - * @return integer the status code returned by the action execution. 0 means normal, and other values mean abnormal. - * @see run - */ - public function forward($route, $params = array()) - { - $status = $this->run($route, $params); - Yii::$app->end($status); - } - - /** * Creates an action based on the given action ID. * The method first checks if the action ID has been declared in [[actions()]]. If so, * it will use the configuration declared there to create the action object. diff --git a/framework/yii/base/ErrorHandler.php b/framework/yii/base/ErrorHandler.php index fe9eef3..a6e282b 100644 --- a/framework/yii/base/ErrorHandler.php +++ b/framework/yii/base/ErrorHandler.php @@ -9,6 +9,7 @@ namespace yii\base; use Yii; use yii\web\HttpException; +use yii\web\Response; /** * ErrorHandler handles uncaught PHP errors and exceptions. @@ -42,9 +43,13 @@ class ErrorHandler extends Component */ public $errorAction; /** - * @var string the path of the view file for rendering exceptions and errors. + * @var string the path of the view file for rendering exceptions without call stack information. */ - public $mainView = '@yii/views/errorHandler/main.php'; + public $errorView = '@yii/views/errorHandler/error.php'; + /** + * @var string the path of the view file for rendering exceptions. + */ + public $exceptionView = '@yii/views/errorHandler/exception.php'; /** * @var string the path of the view file for rendering exceptions and errors call stack element. */ @@ -73,26 +78,31 @@ class ErrorHandler extends Component } /** - * Renders exception. - * @param \Exception $exception to be handled. + * Renders the exception. + * @param \Exception $exception the exception to be handled. */ protected function renderException($exception) { - if ($this->errorAction !== null) { - Yii::$app->runAction($this->errorAction); - } elseif (!(Yii::$app instanceof \yii\web\Application)) { - Yii::$app->renderException($exception); - } else { - $response = Yii::$app->getResponse(); - if (!headers_sent()) { - if ($exception instanceof HttpException) { - $response->setStatusCode($exception->statusCode); - } else { - $response->setStatusCode(500); - } + if (Yii::$app instanceof \yii\console\Application) { + echo Yii::$app->renderException($exception); + return; + } + + $useErrorView = !YII_DEBUG || $exception instanceof UserException; + + if ($useErrorView && $this->errorAction !== null) { + $result = Yii::$app->runAction($this->errorAction); + if ($result instanceof Response) { + $response = $result; + } else { + $response = new Response; + $response->content = $result; } + } else { + $response = new Response; if (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] === 'XMLHttpRequest') { - Yii::$app->renderException($exception); + // AJAX request + $response->content = Yii::$app->renderException($exception); } else { // if there is an error during error rendering it's useful to // display PHP error in debug mode instead of a blank screen @@ -101,19 +111,20 @@ class ErrorHandler extends Component } $view = new View(); - $request = ''; - foreach (array('_GET', '_POST', '_SERVER', '_FILES', '_COOKIE', '_SESSION', '_ENV') as $name) { - if (!empty($GLOBALS[$name])) { - $request .= '$' . $name . ' = ' . var_export($GLOBALS[$name], true) . ";\n\n"; - } - } - $request = rtrim($request, "\n\n"); - $response->content = $view->renderFile($this->mainView, array( + $file = $useErrorView ? $this->errorView : $this->exceptionView; + $response->content = $view->renderFile($file, array( 'exception' => $exception, - 'request' => $request, ), $this); } } + + if ($exception instanceof HttpException) { + $response->setStatusCode($exception->statusCode); + } else { + $response->setStatusCode(500); + } + + $response->send(); } /** @@ -133,7 +144,9 @@ class ErrorHandler extends Component { // the following manual level counting is to deal with zlib.output_compression set to On for ($level = ob_get_level(); $level > 0; --$level) { - @ob_end_clean(); + if (!@ob_end_clean()) { + ob_clean(); + } } } @@ -230,6 +243,21 @@ class ErrorHandler extends Component } /** + * Renders the request information. + * @return string the rendering result + */ + public function renderRequest() + { + $request = ''; + foreach (array('_GET', '_POST', '_SERVER', '_FILES', '_COOKIE', '_SESSION', '_ENV') as $name) { + if (!empty($GLOBALS[$name])) { + $request .= '$' . $name . ' = ' . var_export($GLOBALS[$name], true) . ";\n\n"; + } + } + return '
' . rtrim($request, "\n") . '
'; + } + + /** * Determines whether given name of the file belongs to the framework. * @param string $file name to be checked. * @return boolean whether given name of the file belongs to the framework. diff --git a/framework/yii/base/InlineAction.php b/framework/yii/base/InlineAction.php index c5afe28..75728e0 100644 --- a/framework/yii/base/InlineAction.php +++ b/framework/yii/base/InlineAction.php @@ -39,11 +39,11 @@ class InlineAction extends Action * Runs this action with the specified parameters. * This method is mainly invoked by the controller. * @param array $params action parameters - * @return integer the exit status (0 means normal, non-zero means abnormal). + * @return mixed the result of the action */ public function runWithParams($params) { $args = $this->controller->bindActionParams($this, $params); - return (int)call_user_func_array(array($this->controller, $this->actionMethod), $args); + return call_user_func_array(array($this->controller, $this->actionMethod), $args); } } diff --git a/framework/yii/base/Module.php b/framework/yii/base/Module.php index cc7c849..6603b28 100644 --- a/framework/yii/base/Module.php +++ b/framework/yii/base/Module.php @@ -571,20 +571,20 @@ abstract class Module extends Component * If the route is empty, the method will use [[defaultRoute]]. * @param string $route the route that specifies the action. * @param array $params the parameters to be passed to the action - * @return integer the status code returned by the action execution. 0 means normal, and other values mean abnormal. + * @return mixed the result of the action. * @throws InvalidRouteException if the requested route cannot be resolved into an action successfully */ public function runAction($route, $params = array()) { - $result = $this->createController($route); - if (is_array($result)) { + $parts = $this->createController($route); + if (is_array($parts)) { /** @var $controller Controller */ - list($controller, $actionID) = $result; + list($controller, $actionID) = $parts; $oldController = Yii::$app->controller; Yii::$app->controller = $controller; - $status = $controller->runAction($actionID, $params); + $result = $controller->runAction($actionID, $params); Yii::$app->controller = $oldController; - return $status; + return $result; } else { throw new InvalidRouteException('Unable to resolve the request "' . trim($this->getUniqueId() . '/' . $route, '/') . '".'); } diff --git a/framework/yii/base/Response.php b/framework/yii/base/Response.php index 29bddb0..83fef93 100644 --- a/framework/yii/base/Response.php +++ b/framework/yii/base/Response.php @@ -14,79 +14,12 @@ namespace yii\base; class Response extends Component { /** - * @event Event an event raised when the application begins to generate the response. + * @var integer the exit status. Exit statuses should be in the range 0 to 254. + * The status 0 means the program terminates successfully. */ - const EVENT_BEGIN_RESPONSE = 'beginResponse'; - /** - * @event Event an event raised when the generation of the response finishes. - */ - const EVENT_END_RESPONSE = 'endResponse'; - - /** - * Starts output buffering - */ - public function beginBuffer() - { - ob_start(); - ob_implicit_flush(false); - } + public $exitStatus = 0; - /** - * Returns contents of the output buffer and stops the buffer. - * @return string output buffer contents - */ - public function endBuffer() - { - return ob_get_clean(); - } - - /** - * Returns contents of the output buffer - * @return string output buffer contents - */ - public function getBuffer() - { - return ob_get_contents(); - } - - /** - * Discards the output buffer - * @param boolean $all if true, it will discards all output buffers. - */ - public function cleanBuffer($all = true) - { - if ($all) { - for ($level = ob_get_level(); $level > 0; --$level) { - if (!@ob_end_clean()) { - ob_clean(); - } - } - } else { - ob_end_clean(); - } - } - - /** - * Begins generating the response. - * This method is called at the beginning of [[Application::run()]]. - * The default implementation will trigger the [[EVENT_BEGIN_RESPONSE]] event. - * If you overwrite this method, make sure you call the parent implementation so that - * the event can be triggered. - */ - public function begin() - { - $this->trigger(self::EVENT_BEGIN_RESPONSE); - } - - /** - * Ends generating the response. - * This method is called at the end of [[Application::run()]]. - * The default implementation will trigger the [[EVENT_END_RESPONSE]] event. - * If you overwrite this method, make sure you call the parent implementation so that - * the event can be triggered. - */ - public function end() + public function send() { - $this->trigger(self::EVENT_END_RESPONSE); } } diff --git a/framework/yii/base/ResponseEvent.php b/framework/yii/base/ResponseEvent.php new file mode 100644 index 0000000..1521a97 --- /dev/null +++ b/framework/yii/base/ResponseEvent.php @@ -0,0 +1,40 @@ + + * @since 2.0 + */ +class ResponseEvent extends Event +{ + /** + * @var Response the response object associated with this event. + * You may modify the content in this response or replace it + * with a new response object. The updated or new response will + * be used as the final out. + */ + public $response; + + /** + * Constructor. + * @param Response $response the response object associated with this event. + * @param array $config the configuration array for initializing the newly created object. + */ + public function __construct($response, $config = array()) + { + $this->response = $response; + parent::__construct($config); + } +} diff --git a/framework/yii/console/Application.php b/framework/yii/console/Application.php index 6cc114a..6c3617e 100644 --- a/framework/yii/console/Application.php +++ b/framework/yii/console/Application.php @@ -60,6 +60,10 @@ class Application extends \yii\base\Application * Defaults to true. */ public $enableCoreCommands = true; + /** + * @var Controller the currently active controller instance + */ + public $controller; /** * Initialize the application. @@ -81,6 +85,24 @@ class Application extends \yii\base\Application } /** + * Handles the specified request. + * @param Request $request the request to be handled + * @return Response the resulting response + */ + public function handle($request) + { + list ($route, $params) = $request->resolve(); + $result = $this->runAction($route, $params); + if ($result instanceof Response) { + return $result; + } else { + $response = $this->getResponse(); + $response->exitStatus = (int)$result; + return $response; + } + } + + /** * Processes the request. * The request is represented in terms of a controller route and action parameters. * @return integer the exit status of the controller action (0 means normal, non-zero values mean abnormal) @@ -99,6 +121,15 @@ class Application extends \yii\base\Application } /** + * Returns the response component. + * @return Response the response component + */ + public function getResponse() + { + return $this->getComponent('response'); + } + + /** * Runs a controller action specified by a route. * This method parses the specified route and creates the corresponding child module(s), controller and action * instances. It then calls [[Controller::runAction()]] to run the action with the given parameters. diff --git a/framework/yii/views/errorHandler/error.php b/framework/yii/views/errorHandler/error.php new file mode 100644 index 0000000..fc5dbc7 --- /dev/null +++ b/framework/yii/views/errorHandler/error.php @@ -0,0 +1,66 @@ +context; +$title = $context->htmlEncode($exception instanceof \yii\base\Exception ? $exception->getName() : get_class($exception)); +?> + + + + + <?php echo $title?> + + + + + +

+

htmlEncode($exception->getMessage()))?>

+

+ The above error occurred while the Web server was processing your request. +

+

+ Please contact us if you think this is a server error. Thank you. +

+
+ +
+ + diff --git a/framework/yii/views/errorHandler/exception.php b/framework/yii/views/errorHandler/exception.php new file mode 100644 index 0000000..b0c1f1c --- /dev/null +++ b/framework/yii/views/errorHandler/exception.php @@ -0,0 +1,491 @@ +context; +?> + + + + + + + <?php + if ($exception instanceof \yii\web\HttpException) { + echo (int) $exception->statusCode . ' ' . $context->htmlEncode($exception->getName()); + } elseif ($exception instanceof \yii\base\Exception) { + echo $context->htmlEncode($exception->getName() . ' – ' . get_class($exception)); + } else { + echo $context->htmlEncode(get_class($exception)); + } + ?> + + + + + +
+ + Gears +

+ htmlEncode($exception->getName()); ?> + – addTypeLinks(get_class($exception)); ?> +

+ + Attention +

' . $context->createHttpStatusLink($exception->statusCode, $context->htmlEncode($exception->getName())) . ''; + echo ' – ' . $context->addTypeLinks(get_class($exception)); + } elseif ($exception instanceof \yii\base\Exception) { + echo '' . $context->htmlEncode($exception->getName()) . ''; + echo ' – ' . $context->addTypeLinks(get_class($exception)); + } else { + echo '' . $context->htmlEncode(get_class($exception)) . ''; + } + ?>

+ +

htmlEncode($exception->getMessage()); ?>

+ renderPreviousExceptions($exception); ?> +
+ +
+
    + renderCallStackItem($exception->getFile(), $exception->getLine(), null, null, 1); ?> + getTrace(), $length = count($trace); $i < $length; ++$i): ?> + renderCallStackItem(@$trace[$i]['file'] ?: null, @$trace[$i]['line'] ?: null, + @$trace[$i]['class'] ?: null, @$trace[$i]['function'] ?: null, $i + 1); ?> + +
+
+ +
+
+ renderRequest(); ?> +
+
+ + + + + + + + + + + diff --git a/framework/yii/views/errorHandler/main.php b/framework/yii/views/errorHandler/main.php deleted file mode 100644 index 05e217e..0000000 --- a/framework/yii/views/errorHandler/main.php +++ /dev/null @@ -1,492 +0,0 @@ -context; -?> - - - - - - - <?php - if ($exception instanceof \yii\web\HttpException) { - echo (int) $exception->statusCode . ' ' . $context->htmlEncode($exception->getName()); - } elseif ($exception instanceof \yii\base\Exception) { - echo $context->htmlEncode($exception->getName() . ' – ' . get_class($exception)); - } else { - echo $context->htmlEncode(get_class($exception)); - } - ?> - - - - - -
- - Gears -

- htmlEncode($exception->getName()); ?> - – addTypeLinks(get_class($exception)); ?> -

- - Attention -

' . $context->createHttpStatusLink($exception->statusCode, $context->htmlEncode($exception->getName())) . ''; - echo ' – ' . $context->addTypeLinks(get_class($exception)); - } elseif ($exception instanceof \yii\base\Exception) { - echo '' . $context->htmlEncode($exception->getName()) . ''; - echo ' – ' . $context->addTypeLinks(get_class($exception)); - } else { - echo '' . $context->htmlEncode(get_class($exception)) . ''; - } - ?>

- -

htmlEncode($exception->getMessage()); ?>

- renderPreviousExceptions($exception); ?> -
- -
-
    - renderCallStackItem($exception->getFile(), $exception->getLine(), null, null, 1); ?> - getTrace(), $length = count($trace); $i < $length; ++$i): ?> - renderCallStackItem(@$trace[$i]['file'] ?: null, @$trace[$i]['line'] ?: null, - @$trace[$i]['class'] ?: null, @$trace[$i]['function'] ?: null, $i + 1); ?> - -
-
- -
-
-
htmlEncode($request); ?>
-
-
- - - - - - - - - - - diff --git a/framework/yii/web/Application.php b/framework/yii/web/Application.php index ce326a2..54df2a7 100644 --- a/framework/yii/web/Application.php +++ b/framework/yii/web/Application.php @@ -42,18 +42,23 @@ class Application extends \yii\base\Application * Defaults to null, meaning catch-all is not effective. */ public $catchAll; + /** + * @var Controller the currently active controller instance + */ + public $controller; /** - * Processes the request. - * @return integer the exit status of the controller action (0 means normal, non-zero values mean abnormal) - * @throws HttpException if the request cannot be resolved. + * Handles the specified request. + * @param Request $request the request to be handled + * @return Response the resulting response + * @throws HttpException if the requested route is invalid */ - public function processRequest() + public function handle($request) { - $request = $this->getRequest(); Yii::setAlias('@wwwroot', dirname($request->getScriptFile())); Yii::setAlias('@www', $request->getBaseUrl()); + if (empty($this->catchAll)) { list ($route, $params) = $request->resolve(); } else { @@ -61,10 +66,18 @@ class Application extends \yii\base\Application $params = array_splice($this->catchAll, 1); } try { - return $this->runAction($route, $params); + $result = $this->runAction($route, $params); + if ($result instanceof Response) { + return $result; + } else { + $response = $this->getResponse(); + $response->content = $result; + return $response; + } } catch (InvalidRouteException $e) { throw new HttpException(404, $e->getMessage(), $e->getCode(), $e); } + } private $_homeUrl; diff --git a/framework/yii/web/CaptchaAction.php b/framework/yii/web/CaptchaAction.php index 1ed1fb0..53b392d 100644 --- a/framework/yii/web/CaptchaAction.php +++ b/framework/yii/web/CaptchaAction.php @@ -116,7 +116,7 @@ class CaptchaAction extends Action if (isset($_GET[self::REFRESH_GET_VAR])) { // AJAX request for regenerating code $code = $this->getVerifyCode(true); - echo json_encode(array( + return json_encode(array( 'hash1' => $this->generateValidationHash($code), 'hash2' => $this->generateValidationHash(strtolower($code)), // we add a random 'v' parameter so that FireFox can refresh the image @@ -124,9 +124,9 @@ class CaptchaAction extends Action 'url' => $this->controller->createUrl($this->id, array('v' => uniqid())), )); } else { - $this->renderImage($this->getVerifyCode()); + $this->setHttpHeaders(); + return $this->renderImage($this->getVerifyCode()); } - Yii::$app->end(); } /** @@ -230,9 +230,9 @@ class CaptchaAction extends Action protected function renderImage($code) { if (Captcha::checkRequirements() === 'gd') { - $this->renderImageByGD($code); + return $this->renderImageByGD($code); } else { - $this->renderImageByImagick($code); + return $this->renderImageByImagick($code); } } @@ -277,10 +277,10 @@ class CaptchaAction extends Action imagecolordeallocate($image, $foreColor); - $this->sendHttpHeaders(); - + ob_start(); imagepng($image); imagedestroy($image); + return ob_get_clean(); } /** @@ -317,14 +317,13 @@ class CaptchaAction extends Action } $image->setImageFormat('png'); - Yii::$app->getResponse()->content = (string)$image; - $this->sendHttpHeaders(); + return $image; } /** - * Sends the HTTP headers needed by image response. + * Sets the HTTP headers needed by image response. */ - protected function sendHttpHeaders() + protected function setHttpHeaders() { Yii::$app->getResponse()->getHeaders() ->set('Pragma', 'public') diff --git a/framework/yii/web/Response.php b/framework/yii/web/Response.php index 00721ac..696aa1b 100644 --- a/framework/yii/web/Response.php +++ b/framework/yii/web/Response.php @@ -39,13 +39,10 @@ class Response extends \yii\base\Response */ public $statusText; /** - * @var string the charset to use. If not set, [[\yii\base\Application::charset]] will be used. + * @var string the version of the HTTP protocol to use. If not set, it will be determined via `$_SERVER['SERVER_PROTOCOL']`, + * or '1.1' if that is not available. */ - public $charset; - /** - * @var string the version of the HTTP protocol to use - */ - public $version = '1.0'; + public $version; /** * @var array list of HTTP status codes and the corresponding texts */ @@ -129,24 +126,15 @@ class Response extends \yii\base\Response public function init() { - if ($this->charset === null) { - $this->charset = Yii::$app->charset; + if ($this->version === null) { + if (isset($_SERVER['SERVER_PROTOCOL']) && $_SERVER['SERVER_PROTOCOL'] === '1.0') { + $this->version = '1.0'; + } else { + $this->version = '1.1'; + } } } - public function begin() - { - parent::begin(); - $this->beginBuffer(); - } - - public function end() - { - $this->content .= $this->endBuffer(); - $this->send(); - parent::end(); - } - /** * @return integer the HTTP status code to send with the response. */ @@ -204,15 +192,10 @@ class Response extends \yii\base\Response { $this->sendHeaders(); $this->sendContent(); - for ($level = ob_get_level(); $level > 0; --$level) { - if (!@ob_end_flush()) { - ob_clean(); - } - } - flush(); + $this->clear(); } - public function reset() + public function clear() { $this->_headers = null; $this->_statusCode = null; @@ -239,7 +222,6 @@ class Response extends \yii\base\Response header("$name: $value", false); } } - $headers->removeAll(); } $this->sendCookies(); } @@ -272,7 +254,6 @@ class Response extends \yii\base\Response protected function sendContent() { echo $this->content; - $this->content = null; } /** @@ -298,6 +279,7 @@ class Response extends \yii\base\Response * @param string $content the content to be sent. The existing [[content]] will be discarded. * @param string $attachmentName the file name shown to the user. * @param string $mimeType the MIME type of the content. + * @throws HttpException if the requested range is not satisfiable */ public function sendContentAsFile($content, $attachmentName, $mimeType = 'application/octet-stream') { @@ -509,9 +491,9 @@ class Response extends \yii\base\Response * for normal requests, and [[ajaxRedirectCode]] for AJAX requests. * See [[http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html]] * for details about HTTP status code - * @param boolean $terminate whether to terminate the current application + * @return Response the response object itself */ - public function redirect($url, $statusCode = null, $terminate = true) + public function redirect($url, $statusCode = null) { $url = Html::url($url); if (strpos($url, '/') === 0 && strpos($url, '//') !== 0) { @@ -522,22 +504,20 @@ class Response extends \yii\base\Response } $this->getHeaders()->set('Location', $url); $this->setStatusCode($statusCode); - if ($terminate) { - Yii::$app->end(); - } + return $this; } /** * Refreshes the current page. * The effect of this method call is the same as the user pressing the refresh button of his browser * (without re-posting data). - * @param boolean $terminate whether to terminate the current application after calling this method * @param string $anchor the anchor that should be appended to the redirection URL. * Defaults to empty. Make sure the anchor starts with '#' if you want to specify it. + * @return Response the response object itself */ - public function refresh($terminate = true, $anchor = '') + public function refresh($anchor = '') { - $this->redirect(Yii::$app->getRequest()->getUrl() . $anchor, $terminate); + return $this->redirect(Yii::$app->getRequest()->getUrl() . $anchor); } private $_cookies; diff --git a/framework/yii/web/User.php b/framework/yii/web/User.php index f273c1a..0e9c822 100644 --- a/framework/yii/web/User.php +++ b/framework/yii/web/User.php @@ -284,7 +284,9 @@ class User extends Component $this->setReturnUrl($request->getUrl()); } if ($this->loginUrl !== null) { - Yii::$app->getResponse()->redirect($this->loginUrl); + $response = Yii::$app->getResponse(); + $response->redirect($this->loginUrl)->send(); + exit(); } else { throw new HttpException(403, Yii::t('yii', 'Login Required')); }