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); } /**