Commit f2b20d99 by Carsten Brandt

Merge branch 'master' into refactor-errorhandler

* master: Fixes #2912: Relative view files will be looked for under the directory containing the view currently being rendered app end improvement. improvement of Application::end() handling. typo fix. Fixes #2910: Added `Application::end()` Conflicts: framework/base/Application.php
parents c93231e2 92bd71cd
......@@ -372,6 +372,20 @@ echo $this->render('_profile', [
]);
```
When you call `render()` to render a partial in a current view, you may use different formats to refer to the partial.
The most commonly used format is the so-called relative view name which is as shown in the above example.
The partial view file is relative to the directory containing the current view. If the partial is located under
a subdirectory, you should include the subdirectory name in the view name, e.g., `public/_profile`.
You may use path alias to specify a view, too. For example, `@app/views/common/_profile`.
And you may also use the so-called absolute view names, e.g., `/user/_profile`, `//user/_profile`.
An absolute view name starts with a single slashes or double slashes. If it starts with a single slash,
the view file will be looked for under the view path of the currently active module. Otherwise, it will
will be looked for under the application view path.
### Accessing context
Views are generally used either by controller or by widget. In both cases the object that called view rendering is
......
......@@ -268,13 +268,11 @@ class ApiRenderer extends BaseApiRenderer implements ViewContextInterface
}
/**
* Finds the view file corresponding to the specified relative view name.
* @param string $view a relative view name. The name does NOT start with a slash.
* @return string the view file path. Note that the file may not exist.
* @inheritdoc
*/
public function findViewFile($view)
public function getViewPath()
{
return Yii::getAlias('@yii/apidoc/templates/html/views/' . $view);
return Yii::getAlias('@yii/apidoc/templates/html/views');
}
/**
......
......@@ -7,7 +7,7 @@ $extensions = $panel->getExtensions();
<h1>Configuration</h1>
<?php
echo $this->render('panels/config/table', [
echo $this->render('table', [
'caption' => 'Application Configuration',
'values' => [
'Yii Version' => $panel->data['application']['yii'],
......@@ -18,13 +18,13 @@ echo $this->render('panels/config/table', [
]);
if (!empty($extensions)) {
echo $this->render('panels/config/table', [
echo $this->render('table', [
'caption' => 'Installed Extensions',
'values' => $extensions,
]);
}
echo $this->render('panels/config/table', [
echo $this->render('table', [
'caption' => 'PHP Configuration',
'values' => [
'PHP Version' => $panel->data['php']['version'],
......
......@@ -11,27 +11,27 @@ echo Tabs::widget([
'items' => [
[
'label' => 'Parameters',
'content' => $this->render('panels/request/table', ['caption' => 'Routing', 'values' => ['Route' => $panel->data['route'], 'Action' => $panel->data['action'], 'Parameters' => $panel->data['actionParams']]])
. $this->render('panels/request/table', ['caption' => '$_GET', 'values' => $panel->data['GET']])
. $this->render('panels/request/table', ['caption' => '$_POST', 'values' => $panel->data['POST']])
. $this->render('panels/request/table', ['caption' => '$_FILES', 'values' => $panel->data['FILES']])
. $this->render('panels/request/table', ['caption' => '$_COOKIE', 'values' => $panel->data['COOKIE']])
. $this->render('panels/request/table', ['caption' => 'Request Body', 'values' => $panel->data['requestBody']]),
'content' => $this->render('table', ['caption' => 'Routing', 'values' => ['Route' => $panel->data['route'], 'Action' => $panel->data['action'], 'Parameters' => $panel->data['actionParams']]])
. $this->render('table', ['caption' => '$_GET', 'values' => $panel->data['GET']])
. $this->render('table', ['caption' => '$_POST', 'values' => $panel->data['POST']])
. $this->render('table', ['caption' => '$_FILES', 'values' => $panel->data['FILES']])
. $this->render('table', ['caption' => '$_COOKIE', 'values' => $panel->data['COOKIE']])
. $this->render('table', ['caption' => 'Request Body', 'values' => $panel->data['requestBody']]),
'active' => true,
],
[
'label' => 'Headers',
'content' => $this->render('panels/request/table', ['caption' => 'Request Headers', 'values' => $panel->data['requestHeaders']])
. $this->render('panels/request/table', ['caption' => 'Response Headers', 'values' => $panel->data['responseHeaders']])
'content' => $this->render('table', ['caption' => 'Request Headers', 'values' => $panel->data['requestHeaders']])
. $this->render('table', ['caption' => 'Response Headers', 'values' => $panel->data['responseHeaders']])
],
[
'label' => 'Session',
'content' => $this->render('panels/request/table', ['caption' => '$_SESSION', 'values' => $panel->data['SESSION']])
. $this->render('panels/request/table', ['caption' => 'Flashes', 'values' => $panel->data['flashes']])
'content' => $this->render('table', ['caption' => '$_SESSION', 'values' => $panel->data['SESSION']])
. $this->render('table', ['caption' => 'Flashes', 'values' => $panel->data['flashes']])
],
[
'label' => '$_SERVER',
'content' => $this->render('panels/request/table', ['caption' => '$_SERVER', 'values' => $panel->data['SERVER']]),
'content' => $this->render('table', ['caption' => '$_SERVER', 'values' => $panel->data['SERVER']]),
],
],
]);
......@@ -157,6 +157,7 @@ Yii Framework 2 Change Log
- Enh #2756: Added support for injecting custom `isEmpty` check for all validators (qiangxue)
- Enh #2775: Added `yii\base\Application::bootstrap` and `yii\base\BootstrapInterface` to support running bootstrap classes when starting an application (qiangxue)
- Enh #2892: ActiveRecord dirty attributes are now reset after call to `afterSave()` so information about changed attributes is available in `afterSave`-event (cebe)
- Enh #2910: Added `Application::end()` (qiangxue)
- Enh: Added support for using arrays as option values for console commands (qiangxue)
- Enh: Added `favicon.ico` and `robots.txt` to default application templates (samdark)
- Enh: Added `Widget::autoIdPrefix` to support prefixing automatically generated widget IDs (qiangxue)
......@@ -230,6 +231,7 @@ Yii Framework 2 Change Log
- Removed `yii\web\Controller::getCanonicalUrl`, use `yii\helpers\Url::canonical` instead.
- Chg #2691: Null parameters will not be included in the generated URLs by `UrlManager` (gonimar, qiangxue)
- Chg #2734: `FileCache::keyPrefix` defaults to empty string now (qiangxue)
_ Chg #2912: Relative view files will be looked for under the directory containing the view currently being rendered (qiangxue)
- Chg: Renamed `yii\jui\Widget::clientEventsMap` to `clientEventMap` (qiangxue)
- Chg: Renamed `ActiveRecord::getPopulatedRelations()` to `getRelatedRecords()` (qiangxue)
- Chg: Renamed `attributeName` and `className` to `targetAttribute` and `targetClass` for `UniqueValidator` and `ExistValidator` (qiangxue)
......
......@@ -60,6 +60,35 @@ abstract class Application extends Module
const EVENT_AFTER_ACTION = 'afterAction';
/**
* Application state used by [[state]]: application just started.
*/
const STATE_BEGIN = 0;
/**
* Application state used by [[state]]: application is initializing.
*/
const STATE_INIT = 1;
/**
* Application state used by [[state]]: application is triggering [[EVENT_BEFORE_REQUEST]].
*/
const STATE_BEFORE_REQUEST = 2;
/**
* Application state used by [[state]]: application is handling the request.
*/
const STATE_HANDLING_REQUEST = 3;
/**
* Application state used by [[state]]: application is triggering [[EVENT_AFTER_REQUEST]]..
*/
const STATE_AFTER_REQUEST = 4;
/**
* Application state used by [[state]]: application is about to send response.
*/
const STATE_SENDING_RESPONSE = 5;
/**
* Application state used by [[state]]: application has ended.
*/
const STATE_END = 6;
/**
* @var string the namespace that controller classes are in. If not set,
* it will use the "app\controllers" namespace.
*/
......@@ -131,6 +160,11 @@ abstract class Application extends Module
* will be invoked at the beginning of [[init()]].
*/
public $bootstrap = [];
/**
* @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;
/**
......@@ -143,6 +177,8 @@ abstract class Application extends Module
{
Yii::$app = $this;
$this->state = self::STATE_BEGIN;
// TODO how to deal with exceptions thrown in preInit()
$this->preInit($config);
if (YII_ENABLE_ERROR_HANDLER) {
......@@ -213,6 +249,8 @@ abstract class Application extends Module
*/
public function init()
{
$this->state = self::STATE_INIT;
$this->initExtensions($this->extensions);
foreach ($this->bootstrap as $class) {
/** @var BootstrapInterface $bootstrap */
......@@ -283,12 +321,30 @@ abstract class Application extends Module
*/
public function run()
{
try {
$this->state = self::STATE_BEFORE_REQUEST;
$this->trigger(self::EVENT_BEFORE_REQUEST);
$this->state = self::STATE_HANDLING_REQUEST;
$response = $this->handleRequest($this->getRequest());
$this->state = self::STATE_AFTER_REQUEST;
$this->trigger(self::EVENT_AFTER_REQUEST);
$this->state = self::STATE_SENDING_RESPONSE;
$response->send();
$this->state = self::STATE_END;
return $response->exitStatus;
} catch (ExitException $e) {
$this->end($e->statusCode, isset($response) ? $response : null);
return $e->statusCode;
}
}
/**
......@@ -434,6 +490,15 @@ abstract class Application extends Module
}
/**
* Returns the response component.
* @return \yii\web\Response|\yii\console\Response the response component
*/
public function getResponse()
{
return $this->get('response');
}
/**
* Returns the view object.
* @return View|\yii\web\View the view object that is used to render various view files.
*/
......@@ -505,4 +570,32 @@ abstract class Application extends Module
'assetManager' => ['class' => 'yii\web\AssetManager'],
];
}
/**
* Terminates the application.
* This method replaces the `exit()` function by ensuring the application life cycle is completed
* before terminating the application.
* @param integer $status the exit status (value 0 means normal exit while other values mean abnormal exit).
* @param Response $response the response to be sent. If not set, the default application [[response]] component will be used.
* @throws ExitException if the application is in testing mode
*/
public function end($status = 0, $response = null)
{
if ($this->state === self::STATE_BEFORE_REQUEST || $this->state === self::STATE_HANDLING_REQUEST) {
$this->state = self::STATE_AFTER_REQUEST;
$this->trigger(self::EVENT_AFTER_REQUEST);
}
if ($this->state !== self::STATE_SENDING_RESPONSE && $this->state !== self::STATE_END) {
$this->state = self::STATE_END;
$response = $response ? : $this->getResponse();
$response->send();
}
if (YII_ENV_TEST) {
throw new ExitException($status);
} else {
exit($status);
}
}
}
......@@ -289,7 +289,7 @@ class Controller extends Component implements ViewContextInterface
*
* If the layout name does not contain a file extension, it will use the default one `.php`.
*
* @param string $view the view name. Please refer to [[findViewFile()]] on how to specify a view name.
* @param string $view the view name.
* @param array $params the parameters (name-value pairs) that should be made available in the view.
* These parameters will not be available in the layout.
* @return string the rendering result.
......@@ -368,17 +368,6 @@ class Controller extends Component implements ViewContextInterface
}
/**
* Finds the view file based on the given view name.
* @param string $view the view name or the path alias of the view file. Please refer to [[render()]]
* on how to specify this parameter.
* @return string the view file path. Note that the file may not exist.
*/
public function findViewFile($view)
{
return $this->getViewPath() . DIRECTORY_SEPARATOR . $view;
}
/**
* Finds the applicable layout file.
* @param View $view the view object to render the layout file.
* @return string|boolean the layout file path, or false if layout is not needed.
......
......@@ -71,6 +71,10 @@ class ErrorHandler extends Component
*/
public function handleException($exception)
{
if ($exception instanceof ExitException) {
return;
}
$this->exception = $exception;
// disable error capturing to avoid recursive errors while handling exceptions
......@@ -101,6 +105,8 @@ class ErrorHandler extends Component
error_log($msg);
exit(1);
}
$this->exception = null;
}
/**
......
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\base;
/**
* ExitException represents a normal termination of an application.
*
* Do not catch ExitException. Yii will handle this exception to terminate the application gracefully.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class ExitException extends \Exception
{
/**
* @var integer the exit status code
*/
public $statusCode;
/**
* Constructor.
* @param integer $status the exit status code
* @param string $message error message
* @param integer $code error code
* @param \Exception $previous The previous exception used for the exception chaining.
*/
public function __construct($status = 0, $message = null, $code = 0, \Exception $previous = null)
{
$this->statusCode = $status;
parent::__construct($message, $code, $previous);
}
}
......@@ -98,7 +98,7 @@ class View extends Component
/**
* @var array the view files currently being rendered. There may be multiple view files being
* rendered at a moment because one may render a view file within another.
* rendered at a moment because one view may be rendered within another.
*/
private $_viewFiles = [];
......@@ -127,13 +127,17 @@ class View extends Component
* The actual view file will be looked for under the [[Application::viewPath|view path]] of the application.
* - absolute path within current module (e.g. "/site/index"): the view name starts with a single slash.
* The actual view file will be looked for under the [[Module::viewPath|view path]] of [[module]].
* - resolving any other format will be performed via [[ViewContext::findViewFile()]].
* - relative view (e.g. "index"): the view name does not start with `@` or `/`. The corresponding view file will be
* looked for under the [[ViewContextInterface::getViewPath()|view path]] of the view `$context`.
* If `$context` is not given, it will be looked for under the directory containing the view currently
* being rendered (i.e., this happens when rendering a view within another view).
*
* @param string $view the view name. Please refer to [[Controller::findViewFile()]]
* and [[Widget::findViewFile()]] on how to specify this parameter.
* @param array $params the parameters (name-value pairs) that will be extracted and made available in the view file.
* @param object $context the context that the view should use for rendering the view. If null,
* existing [[context]] will be used.
* @param object $context the context to be assigned to the view and can later be accessed via [[context]]
* in the view. If the context implements [[ViewContextInterface]], it may also be used to locate
* the view file corresponding to a relative view name.
* @return string the rendering result
* @throws InvalidParamException if the view cannot be resolved or the view file does not exist.
* @see renderFile()
......@@ -148,10 +152,12 @@ class View extends Component
* Finds the view file based on the given view name.
* @param string $view the view name or the path alias of the view file. Please refer to [[render()]]
* on how to specify this parameter.
* @param object $context the context that the view should be used to search the view file. If null,
* existing [[context]] will be used.
* @param object $context the context to be assigned to the view and can later be accessed via [[context]]
* in the view. If the context implements [[ViewContextInterface]], it may also be used to locate
* the view file corresponding to a relative view name.
* @return string the view file path. Note that the file may not exist.
* @throws InvalidCallException if [[context]] is required and invalid.
* @throws InvalidCallException if a relative view name is given while there is no active context to
* determine the corresponding view file.
*/
protected function findViewFile($view, $context = null)
{
......@@ -168,16 +174,12 @@ class View extends Component
} else {
throw new InvalidCallException("Unable to locate view file for view '$view': no active controller.");
}
} elseif ($context instanceof ViewContextInterface) {
$file = $context->getViewPath() . DIRECTORY_SEPARATOR . $view;
} elseif (($currentViewFile = $this->getViewFile()) !== false) {
$file = dirname($currentViewFile) . DIRECTORY_SEPARATOR . $view;
} else {
// context required
if ($context === null) {
$context = $this->context;
}
if ($context instanceof ViewContextInterface) {
$file = $context->findViewFile($view);
} else {
throw new InvalidCallException("Unable to locate view file for view '$view': no active view context.");
}
throw new InvalidCallException("Unable to resolve view file for view '$view': no active view context.");
}
if (pathinfo($file, PATHINFO_EXTENSION) !== '') {
......@@ -213,6 +215,7 @@ class View extends Component
public function renderFile($viewFile, $params = [], $context = null)
{
$viewFile = Yii::getAlias($viewFile);
if ($this->theme !== null) {
$viewFile = $this->theme->applyTo($viewFile);
}
......
......@@ -10,7 +10,7 @@ namespace yii\base;
/**
* ViewContextInterface is the interface that should implemented by classes who want to support relative view names.
*
* The method [[findViewFile()]] should be implemented to convert a relative view name into a file path.
* The method [[getViewPath()]] should be implemented to return the view path that may be prefixed to a relative view name.
*
* @author Paul Klimov <klimov.paul@gmail.com>
* @since 2.0
......@@ -18,9 +18,7 @@ namespace yii\base;
interface ViewContextInterface
{
/**
* Finds the view file corresponding to the specified relative view name.
* @param string $view a relative view name. The name does NOT start with a slash.
* @return string the view file path. Note that the file may not exist.
* @return string the view path that may be prefixed to a relative view name.
*/
public function findViewFile($view);
public function getViewPath();
}
......@@ -173,7 +173,7 @@ class Widget extends Component implements ViewContextInterface
*
* If the view name does not contain a file extension, it will use the default one `.php`.
* @param string $view the view name. Please refer to [[findViewFile()]] on how to specify a view name.
* @param string $view the view name.
* @param array $params the parameters (name-value pairs) that should be made available in the view.
* @return string the rendering result.
* @throws InvalidParamException if the view file does not exist.
......@@ -206,15 +206,4 @@ class Widget extends Component implements ViewContextInterface
return dirname($class->getFileName()) . DIRECTORY_SEPARATOR . 'views';
}
/**
* Finds the view file based on the given view name.
* File will be searched under [[viewPath]] directory.
* @param string $view the view name.
* @return string the view file path. Note that the file may not exist.
*/
public function findViewFile($view)
{
return $this->getViewPath() . DIRECTORY_SEPARATOR . $view;
}
}
......@@ -149,15 +149,6 @@ class Application extends \yii\base\Application
}
/**
* Returns the response component.
* @return Response the response component
*/
public function getResponse()
{
return $this->get('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.
......
......@@ -39,11 +39,6 @@ abstract class BaseMailer extends Component implements MailerInterface, ViewCont
*/
const EVENT_AFTER_SEND = 'afterSend';
/**
* @var string directory containing view files for this email messages.
* This can be specified as an absolute path or path alias.
*/
public $viewPath = '@app/mail';
/**
* @var string|boolean HTML layout view name. This is the layout used to render HTML mail body.
* The property can take the following values:
*
......@@ -104,6 +99,10 @@ abstract class BaseMailer extends Component implements MailerInterface, ViewCont
* @var \yii\base\View|array view instance or its array configuration.
*/
private $_view = [];
/**
* @var string the directory containing view files for composing mail messages.
*/
private $_viewPath;
/**
* @param array|View $view view instance or its array configuration that will be used to
......@@ -159,7 +158,7 @@ abstract class BaseMailer extends Component implements MailerInterface, ViewCont
* The view to be rendered can be specified in one of the following formats:
*
* - path alias (e.g. "@app/mail/contact");
* - a relative view name (e.g. "contact"): the actual view file will be resolved by [[findViewFile()]]
* - a relative view name (e.g. "contact") located under [[viewPath]].
*
* @param array $params the parameters (name-value pairs) that will be extracted and made available in the view file.
* @return MessageInterface message instance.
......@@ -319,14 +318,24 @@ abstract class BaseMailer extends Component implements MailerInterface, ViewCont
}
/**
* Finds the view file corresponding to the specified relative view name.
* This method will return the view file by prefixing the view name with [[viewPath]].
* @param string $view a relative view name. The name does NOT start with a slash.
* @return string the view file path. Note that the file may not exist.
* @return string the directory that contains the view files for composing mail messages
* Defaults to '@app/mail'.
*/
public function getViewPath()
{
if ($this->_viewPath === null) {
$this->setViewPath('@app/mail');
}
return $this->_viewPath;
}
/**
* @param string $path the directory that contains the view files for composing mail messages
* This can be specified as an absolute path or a path alias.
*/
public function findViewFile($view)
public function setViewPath($path)
{
return Yii::getAlias($this->viewPath) . DIRECTORY_SEPARATOR . $view;
$this->_viewPath = Yii::getAlias($path);
}
/**
......
......@@ -124,24 +124,6 @@ class Application extends \yii\base\Application
}
/**
* Returns the request component.
* @return Request the request component
*/
public function getRequest()
{
return $this->get('request');
}
/**
* Returns the response component.
* @return Response the response component
*/
public function getResponse()
{
return $this->get('response');
}
/**
* Returns the session component.
* @return Session the session component
*/
......
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