Commit c32a202d by Qiang Xue

refactored MVC.

parent b0cc86df
......@@ -47,6 +47,11 @@ class Controller extends Component
* by [[run()]] when it is called by [[Application]] to run an action.
*/
public $action;
/**
* @var View the view object that can be used to render views or view files.
*/
private $_view;
/**
* @param string $id the ID of this controller
......@@ -135,7 +140,7 @@ class Controller extends Component
} elseif ($pos > 0) {
return $this->module->runAction($route, $params);
} else {
return \Yii::$app->runAction(ltrim($route, '/'), $params);
return Yii::$app->runAction(ltrim($route, '/'), $params);
}
}
......@@ -293,6 +298,37 @@ class Controller extends Component
/**
* Renders a view and applies layout if available.
*
* The view to be rendered can be specified in one of the following formats:
*
* - path alias (e.g. "@app/views/site/index");
* - absolute path within application (e.g. "//site/index"): the view name starts with double slashes.
* The actual view file will be looked for under the [[Application::viewPath|view path]] of the application.
* - absolute path within 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]].
* - relative path (e.g. "index"): the actual view file will be looked for under [[viewPath]].
*
* To determine which layout should be applied, the following two steps are conducted:
*
* 1. In the first step, it determines the layout name and the context module:
*
* - If [[layout]] is specified as a string, use it as the layout name and [[module]] as the context module;
* - If [[layout]] is null, search through all ancestor modules of this controller and find the first
* module whose [[Module::layout|layout]] is not null. The layout and the corresponding module
* are used as the layout name and the context module, respectively. If such a module is not found
* or the corresponding layout is not a string, it will return false, meaning no applicable layout.
*
* 2. In the second step, it determines the actual layout file according to the previously found layout name
* and context module. The layout name can be
*
* - a path alias (e.g. "@app/views/layouts/main");
* - an absolute path (e.g. "/main"): the layout name starts with a slash. The actual layout file will be
* looked for under the [[Application::layoutPath|layout path]] of the application;
* - a relative path (e.g. "main"): the actual layout layout file will be looked for under the
* [[Module::viewPath|view path]] of the context module.
*
* 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 array $params the parameters (name-value pairs) that should be made available in the view.
* These parameters will not be available in the layout.
......@@ -301,10 +337,11 @@ class Controller extends Component
*/
public function render($view, $params = array())
{
$output = Yii::$app->getView()->render($view, $params, $this);
$viewFile = $this->findViewFile($view);
$output = $this->getView()->renderFile($viewFile, $params, $this);
$layoutFile = $this->findLayoutFile();
if ($layoutFile !== false) {
return Yii::$app->getView()->renderFile($layoutFile, array('content' => $output), $this);
return $this->getView()->renderFile($layoutFile, array('content' => $output), $this);
} else {
return $output;
}
......@@ -313,14 +350,14 @@ class Controller extends Component
/**
* Renders a view.
* This method differs from [[render()]] in that it does not apply any layout.
* @param string $view the view name. Please refer to [[findViewFile()]] on how to specify a view name.
* @param string $view the view name. Please refer to [[render()]] on how to specify a 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.
*/
public function renderPartial($view, $params = array())
{
return Yii::$app->getView()->render($view, $params, $this);
return $this->getView()->render($view, $params, $this);
}
/**
......@@ -332,7 +369,30 @@ class Controller extends Component
*/
public function renderFile($file, $params = array())
{
return Yii::$app->getView()->renderFile($file, $params, $this);
return $this->getView()->renderFile($file, $params, $this);
}
/**
* Returns the view object that can be used to render views or view files.
* The [[render()]], [[renderPartial()]] and [[renderFile()]] methods will use
* this view object to implement the actual view rendering.
* @return View the view object that can be used to render views or view files.
*/
public function getView()
{
if ($this->_view === null) {
$this->_view = Yii::$app->getView();
}
return $this->_view;
}
/**
* Sets the view object to be used by this controller.
* @param View $view the view object that can be used to render views or view files.
*/
public function setView($view)
{
$this->_view = $view;
}
/**
......@@ -347,30 +407,33 @@ class Controller 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.
* @return string the view file path. Note that the file may not exist.
*/
protected function findViewFile($view)
{
if (strncmp($view, '@', 1) === 0) {
// e.g. "@app/views/main"
$file = Yii::getAlias($view);
} elseif (strncmp($view, '//', 2) === 0) {
// e.g. "//layouts/main"
$file = Yii::$app->getViewPath() . DIRECTORY_SEPARATOR . ltrim($view, '/');
} elseif (strncmp($view, '/', 1) === 0) {
// e.g. "/site/index"
$file = $this->module->getViewPath() . DIRECTORY_SEPARATOR . ltrim($view, '/');
} else {
$file = $this->getViewPath() . DIRECTORY_SEPARATOR . $view;
}
return FileHelper::getExtension($file) === '' ? $file . '.php' : $file;
}
/**
* Finds the applicable layout file.
*
* This method locates an applicable layout file via two steps.
*
* In the first step, it determines the layout name and the context module:
*
* - If [[layout]] is specified as a string, use it as the layout name and [[module]] as the context module;
* - If [[layout]] is null, search through all ancestor modules of this controller and find the first
* module whose [[Module::layout|layout]] is not null. The layout and the corresponding module
* are used as the layout name and the context module, respectively. If such a module is not found
* or the corresponding layout is not a string, it will return false, meaning no applicable layout.
*
* In the second step, it determines the actual layout file according to the previously found layout name
* and context module. The layout name can be
*
* - a path alias (e.g. "@app/views/layouts/main");
* - an absolute path (e.g. "/main"): the layout name starts with a slash. The actual layout file will be
* looked for under the [[Application::layoutPath|layout path]] of the application;
* - a relative path (e.g. "main"): the actual layout layout file will be looked for under the
* [[Module::viewPath|view path]] of the context module.
*
* If the layout name does not contain a file extension, it will use the default one `.php`.
*
* @return string|boolean the layout file path, or false if layout is not needed.
* Please refer to [[render()]] on how to specify this parameter.
* @throws InvalidParamException if an invalid path alias is used to specify the layout
*/
protected function findLayoutFile()
......
......@@ -79,22 +79,29 @@ class View extends Component
/**
* Renders a view.
*
* This method will call [[findViewFile()]] to convert the view name into the corresponding view
* file path, and it will then call [[renderFile()]] to render the view.
* This method delegates the call to the [[context]] object:
*
* @param string $view the view name. Please refer to [[findViewFile()]] on how to specify this parameter.
* - If [[context]] is a controller, the [[Controller::renderPartial()]] method will be called;
* - If [[context]] is a widget, the [[Widget::render()]] method will be called;
* - Otherwise, an InvalidCallException exception will be thrown.
*
* @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.
* @return string the rendering result
* @throws InvalidCallException if [[context]] is neither a controller nor a widget.
* @throws InvalidParamException if the view cannot be resolved or the view file does not exist.
* @see renderFile
* @see findViewFile
*/
public function render($view, $params = array(), $context = null)
public function render($view, $params = array())
{
$viewFile = $this->findViewFile($context, $view);
return $this->renderFile($viewFile, $params, $context);
if ($this->context instanceof Controller) {
return $this->context->renderPartial($view, $params);
} elseif ($this->context instanceof Widget) {
return $this->context->render($view, $params);
} else {
throw new InvalidCallException('View::render() is not supported for the current context.');
}
}
/**
......@@ -213,49 +220,6 @@ class View extends Component
}
/**
* Finds the view file based on the given view name.
*
* A view name can be specified in one of the following formats:
*
* - path alias (e.g. "@app/views/site/index");
* - absolute path within application (e.g. "//site/index"): the view name starts with double slashes.
* The actual view file will be looked for under the [[Application::viewPath|view path]] of the application.
* - absolute path within 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 the currently
* active module.
* - relative path (e.g. "index"): the actual view file will be looked for under [[Controller::viewPath|viewPath]]
* of the context object, assuming the context is either a [[Controller]] or a [[Widget]].
*
* If the view name does not contain a file extension, it will use the default one `.php`.
*
* @param object $context the view context object
* @param string $view the view name or the path alias of the view file.
* @return string the view file path. Note that the file may not exist.
* @throws InvalidParamException if the view file is an invalid path alias or the context cannot be
* used to determine the actual view file corresponding to the specified view.
*/
protected function findViewFile($context, $view)
{
if (strncmp($view, '@', 1) === 0) {
// e.g. "@app/views/main"
$file = Yii::getAlias($view);
} elseif (strncmp($view, '//', 2) === 0) {
// e.g. "//layouts/main"
$file = Yii::$app->getViewPath() . DIRECTORY_SEPARATOR . ltrim($view, '/');
} elseif (strncmp($view, '/', 1) === 0) {
// e.g. "/site/index"
$file = Yii::$app->controller->module->getViewPath() . DIRECTORY_SEPARATOR . ltrim($view, '/');
} elseif ($context instanceof Controller || $context instanceof Widget) {
/** @var $context Controller|Widget */
$file = $context->getViewPath() . DIRECTORY_SEPARATOR . $view;
} else {
throw new InvalidParamException("Unable to resolve the view file for '$view'.");
}
return FileHelper::getExtension($file) === '' ? $file . '.php' : $file;
}
/**
* Creates a widget.
* This method will use [[Yii::createObject()]] to create the widget.
* @param string $class the widget class name or path alias
......@@ -265,7 +229,10 @@ class View extends Component
public function createWidget($class, $properties = array())
{
$properties['class'] = $class;
return Yii::createObject($properties, $this->context);
if (!isset($properties['view'])) {
$properties['view'] = $this;
}
return Yii::createObject($properties, $this);
}
/**
......@@ -341,7 +308,6 @@ class View extends Component
return $this->beginWidget('yii\widgets\Clip', array(
'id' => $id,
'renderInPlace' => $renderInPlace,
'view' => $this,
));
}
......@@ -355,17 +321,25 @@ class View extends Component
/**
* Begins the rendering of content that is to be decorated by the specified view.
* @param string $view the name of the view that will be used to decorate the content enclosed by this widget.
* Please refer to [[View::findViewFile()]] on how to set this property.
* This method can be used to implement nested layout. For example, a layout can be embedded
* in another layout file specified as '@app/view/layouts/base' like the following:
*
* ~~~
* <?php $this->beginContent('@app/view/layouts/base'); ?>
* ...layout content here...
* <?php $this->endContent(); ?>
* ~~~
*
* @param string $viewFile the view file that will be used to decorate the content enclosed by this widget.
* This can be specified as either the view file path or path alias.
* @param array $params the variables (name=>value) to be extracted and made available in the decorative view.
* @return \yii\widgets\ContentDecorator the ContentDecorator widget instance
* @see \yii\widgets\ContentDecorator
*/
public function beginContent($view, $params = array())
public function beginContent($viewFile, $params = array())
{
return $this->beginWidget('yii\widgets\ContentDecorator', array(
'view' => $this,
'viewName' => $view,
'viewFile' => $viewFile,
'params' => $params,
));
}
......@@ -400,7 +374,6 @@ class View extends Component
public function beginCache($id, $properties = array())
{
$properties['id'] = $id;
$properties['view'] = $this;
/** @var $cache \yii\widgets\FragmentCache */
$cache = $this->beginWidget('yii\widgets\FragmentCache', $properties);
if ($cache->getCachedContent() !== false) {
......
......@@ -19,9 +19,11 @@ use yii\helpers\FileHelper;
class Widget extends Component
{
/**
* @var Widget|Controller the owner/creator of this widget. It could be either a widget or a controller.
* @var View the view object that is used to create this widget.
* This property is automatically set by [[View::createWidget()]].
* This property is required by [[render()]] and [[renderFile()]].
*/
public $owner;
public $view;
/**
* @var string id of the widget.
*/
......@@ -32,17 +34,6 @@ class Widget extends Component
private static $_counter = 0;
/**
* Constructor.
* @param Widget|Controller $owner owner/creator of this widget.
* @param array $config name-value pairs that will be used to initialize the object properties
*/
public function __construct($owner, $config = array())
{
$this->owner = $owner;
parent::__construct($config);
}
/**
* Returns the ID of the widget.
* @param boolean $autoGenerate whether to generate an ID if it is not set previously
* @return string ID of the widget.
......@@ -73,6 +64,18 @@ class Widget extends Component
/**
* Renders a view.
* The view to be rendered can be specified in one of the following formats:
*
* - path alias (e.g. "@app/views/site/index");
* - absolute path within application (e.g. "//site/index"): the view name starts with double slashes.
* The actual view file will be looked for under the [[Application::viewPath|view path]] of the application.
* - absolute path within 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 the currently
* active module.
* - relative path (e.g. "index"): the actual view file will be looked for under [[viewPath]].
*
* 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 array $params the parameters (name-value pairs) that should be made available in the view.
* @return string the rendering result.
......@@ -80,7 +83,7 @@ class Widget extends Component
*/
public function render($view, $params = array())
{
return Yii::$app->getView()->render($view, $params, $this);
return $this->view->render($view, $params, $this);
}
/**
......@@ -92,7 +95,7 @@ class Widget extends Component
*/
public function renderFile($file, $params = array())
{
return Yii::$app->getView()->renderFile($file, $params, $this);
return $this->view->renderFile($file, $params, $this);
}
/**
......@@ -106,4 +109,28 @@ class Widget extends Component
$class = new \ReflectionClass($className);
return dirname($class->getFileName()) . DIRECTORY_SEPARATOR . 'views';
}
/**
* 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.
*/
protected function findViewFile($view)
{
if (strncmp($view, '@', 1) === 0) {
// e.g. "@app/views/main"
$file = Yii::getAlias($view);
} elseif (strncmp($view, '//', 2) === 0) {
// e.g. "//layouts/main"
$file = Yii::$app->getViewPath() . DIRECTORY_SEPARATOR . ltrim($view, '/');
} elseif (strncmp($view, '/', 1) === 0 && Yii::$app->controller !== null) {
// e.g. "/site/index"
$file = Yii::$app->controller->module->getViewPath() . DIRECTORY_SEPARATOR . ltrim($view, '/');
} else {
$file = $this->getViewPath() . DIRECTORY_SEPARATOR . ltrim($view, '/');
}
return FileHelper::getExtension($file) === '' ? $file . '.php' : $file;
}
}
\ No newline at end of file
......@@ -25,11 +25,6 @@ class PageCache extends ActionFilter
*/
public $varyByRoute = true;
/**
* @var View the view object that is used to create the fragment cache widget to implement page caching.
* If not set, the view registered with the application will be used.
*/
public $view;
/**
* @var string the application component ID of the [[\yii\caching\Cache|cache]] object.
*/
public $cache = 'cache';
......
......@@ -22,11 +22,6 @@ class Clip extends Widget
*/
public $id;
/**
* @var View the view object for keeping the clip. If not set, the view registered with the application
* will be used.
*/
public $view;
/**
* @var boolean whether to render the clip content in place. Defaults to false,
* meaning the captured clip will not be displayed.
*/
......@@ -51,7 +46,6 @@ class Clip extends Widget
if ($this->renderClip) {
echo $clip;
}
$view = $this->view !== null ? $this->view : Yii::$app->getView();
$view->clips[$this->id] = $clip;
$this->view->clips[$this->id] = $clip;
}
}
\ No newline at end of file
......@@ -7,10 +7,8 @@
namespace yii\widgets;
use Yii;
use yii\base\InvalidConfigException;
use yii\base\Widget;
use yii\base\View;
/**
* @author Qiang Xue <qiang.xue@gmail.com>
......@@ -19,15 +17,10 @@ use yii\base\View;
class ContentDecorator extends Widget
{
/**
* @var View the view object for rendering [[viewName]]. If not set, the view registered with the application
* will be used.
* @var string the view file that will be used to decorate the content enclosed by this widget.
* This can be specified as either the view file path or path alias.
*/
public $view;
/**
* @var string the name of the view that will be used to decorate the content enclosed by this widget.
* Please refer to [[View::findViewFile()]] on how to set this property.
*/
public $viewName;
public $viewFile;
/**
* @var array the parameters (name=>value) to be extracted and made available in the decorative view.
*/
......@@ -38,8 +31,8 @@ class ContentDecorator extends Widget
*/
public function init()
{
if ($this->viewName === null) {
throw new InvalidConfigException('ContentDecorator::viewName must be set.');
if ($this->viewFile === null) {
throw new InvalidConfigException('ContentDecorator::viewFile must be set.');
}
ob_start();
ob_implicit_flush(false);
......@@ -53,7 +46,7 @@ class ContentDecorator extends Widget
{
$params = $this->params;
$params['content'] = ob_get_clean();
$view = $this->view !== null ? $this->view : Yii::$app->getView();
echo $view->render($this->viewName, $params);
// render under the existing context
echo $this->view->renderFile($this->viewFile, $params);
}
}
......@@ -64,11 +64,6 @@ class FragmentCache extends Widget
*/
public $enabled = true;
/**
* @var \yii\base\View the view object within which this widget is used. If not set,
* the view registered with the application will be used. This is mainly used by dynamic content feature.
*/
public $view;
/**
* @var array a list of placeholders for embedding dynamic contents. This property
* is used internally to implement the content caching feature. Do not modify it.
*/
......@@ -81,10 +76,6 @@ class FragmentCache extends Widget
{
parent::init();
if ($this->view === null) {
$this->view = Yii::$app->getView();
}
if (!$this->enabled) {
$this->cache = null;
} elseif (is_string($this->cache)) {
......
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