diff --git a/docs/guide/view.md b/docs/guide/view.md
index ca8be497db..16a5546b3d 100644
--- a/docs/guide/view.md
+++ b/docs/guide/view.md
@@ -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
diff --git a/extensions/apidoc/templates/html/ApiRenderer.php b/extensions/apidoc/templates/html/ApiRenderer.php
index d8d8c7fda0..d950ae539e 100644
--- a/extensions/apidoc/templates/html/ApiRenderer.php
+++ b/extensions/apidoc/templates/html/ApiRenderer.php
@@ -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');
}
/**
diff --git a/extensions/debug/views/default/panels/config/detail.php b/extensions/debug/views/default/panels/config/detail.php
index 8fe865d70c..38b883fe4e 100644
--- a/extensions/debug/views/default/panels/config/detail.php
+++ b/extensions/debug/views/default/panels/config/detail.php
@@ -7,7 +7,7 @@ $extensions = $panel->getExtensions();
Configuration
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'],
diff --git a/extensions/debug/views/default/panels/request/detail.php b/extensions/debug/views/default/panels/request/detail.php
index 2dd1312267..96f7d9ee7f 100644
--- a/extensions/debug/views/default/panels/request/detail.php
+++ b/extensions/debug/views/default/panels/request/detail.php
@@ -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']]),
],
],
]);
diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md
index ff9cfdb204..ec682efa95 100644
--- a/framework/CHANGELOG.md
+++ b/framework/CHANGELOG.md
@@ -228,6 +228,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)
diff --git a/framework/base/Controller.php b/framework/base/Controller.php
index 1b1cdd889f..4352bce2f3 100644
--- a/framework/base/Controller.php
+++ b/framework/base/Controller.php
@@ -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.
@@ -367,17 +367,6 @@ class Controller extends Component implements ViewContextInterface
return $this->module->getViewPath() . DIRECTORY_SEPARATOR . $this->id;
}
- /**
- * 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.
diff --git a/framework/base/View.php b/framework/base/View.php
index 869345552e..ce8bf92740 100644
--- a/framework/base/View.php
+++ b/framework/base/View.php
@@ -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);
}
diff --git a/framework/base/ViewContextInterface.php b/framework/base/ViewContextInterface.php
index 04561cc522..f6d26e0abc 100644
--- a/framework/base/ViewContextInterface.php
+++ b/framework/base/ViewContextInterface.php
@@ -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
* @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();
}
diff --git a/framework/base/Widget.php b/framework/base/Widget.php
index b93af8e9c3..febeb01670 100644
--- a/framework/base/Widget.php
+++ b/framework/base/Widget.php
@@ -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;
- }
}
diff --git a/framework/mail/BaseMailer.php b/framework/mail/BaseMailer.php
index c3d7d44e6e..b72049afa6 100644
--- a/framework/mail/BaseMailer.php
+++ b/framework/mail/BaseMailer.php
@@ -38,11 +38,6 @@ abstract class BaseMailer extends Component implements MailerInterface, ViewCont
* @event \yii\base\MailEvent an event raised right after send.
*/
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 findViewFile($view)
+ public function getViewPath()
{
- return Yii::getAlias($this->viewPath) . DIRECTORY_SEPARATOR . $view;
+ 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 setViewPath($path)
+ {
+ $this->_viewPath = Yii::getAlias($path);
}
/**