Commit c32a202d by Qiang Xue

refactored MVC.

parent b0cc86df
...@@ -47,6 +47,11 @@ class Controller extends Component ...@@ -47,6 +47,11 @@ class Controller extends Component
* by [[run()]] when it is called by [[Application]] to run an action. * by [[run()]] when it is called by [[Application]] to run an action.
*/ */
public $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 * @param string $id the ID of this controller
...@@ -135,7 +140,7 @@ class Controller extends Component ...@@ -135,7 +140,7 @@ class Controller extends Component
} elseif ($pos > 0) { } elseif ($pos > 0) {
return $this->module->runAction($route, $params); return $this->module->runAction($route, $params);
} else { } else {
return \Yii::$app->runAction(ltrim($route, '/'), $params); return Yii::$app->runAction(ltrim($route, '/'), $params);
} }
} }
...@@ -293,6 +298,37 @@ class Controller extends Component ...@@ -293,6 +298,37 @@ class Controller extends Component
/** /**
* Renders a view and applies layout if available. * 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 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. * @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. * These parameters will not be available in the layout.
...@@ -301,10 +337,11 @@ class Controller extends Component ...@@ -301,10 +337,11 @@ class Controller extends Component
*/ */
public function render($view, $params = array()) 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(); $layoutFile = $this->findLayoutFile();
if ($layoutFile !== false) { if ($layoutFile !== false) {
return Yii::$app->getView()->renderFile($layoutFile, array('content' => $output), $this); return $this->getView()->renderFile($layoutFile, array('content' => $output), $this);
} else { } else {
return $output; return $output;
} }
...@@ -313,14 +350,14 @@ class Controller extends Component ...@@ -313,14 +350,14 @@ class Controller extends Component
/** /**
* Renders a view. * Renders a view.
* This method differs from [[render()]] in that it does not apply any layout. * 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. * @param array $params the parameters (name-value pairs) that should be made available in the view.
* @return string the rendering result. * @return string the rendering result.
* @throws InvalidParamException if the view file does not exist. * @throws InvalidParamException if the view file does not exist.
*/ */
public function renderPartial($view, $params = array()) 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 ...@@ -332,7 +369,30 @@ class Controller extends Component
*/ */
public function renderFile($file, $params = array()) 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 ...@@ -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. * 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. * @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 * @throws InvalidParamException if an invalid path alias is used to specify the layout
*/ */
protected function findLayoutFile() protected function findLayoutFile()
......
...@@ -79,22 +79,29 @@ class View extends Component ...@@ -79,22 +79,29 @@ class View extends Component
/** /**
* Renders a view. * Renders a view.
* *
* This method will call [[findViewFile()]] to convert the view name into the corresponding view * This method delegates the call to the [[context]] object:
* file path, and it will then call [[renderFile()]] to render the view.
* *
* @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 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 * @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. * @throws InvalidParamException if the view cannot be resolved or the view file does not exist.
* @see renderFile * @see renderFile
* @see findViewFile
*/ */
public function render($view, $params = array(), $context = null) public function render($view, $params = array())
{ {
$viewFile = $this->findViewFile($context, $view); if ($this->context instanceof Controller) {
return $this->renderFile($viewFile, $params, $context); 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 ...@@ -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. * Creates a widget.
* This method will use [[Yii::createObject()]] to create the widget. * This method will use [[Yii::createObject()]] to create the widget.
* @param string $class the widget class name or path alias * @param string $class the widget class name or path alias
...@@ -265,7 +229,10 @@ class View extends Component ...@@ -265,7 +229,10 @@ class View extends Component
public function createWidget($class, $properties = array()) public function createWidget($class, $properties = array())
{ {
$properties['class'] = $class; $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 ...@@ -341,7 +308,6 @@ class View extends Component
return $this->beginWidget('yii\widgets\Clip', array( return $this->beginWidget('yii\widgets\Clip', array(
'id' => $id, 'id' => $id,
'renderInPlace' => $renderInPlace, 'renderInPlace' => $renderInPlace,
'view' => $this,
)); ));
} }
...@@ -355,17 +321,25 @@ class View extends Component ...@@ -355,17 +321,25 @@ class View extends Component
/** /**
* Begins the rendering of content that is to be decorated by the specified view. * 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. * This method can be used to implement nested layout. For example, a layout can be embedded
* Please refer to [[View::findViewFile()]] on how to set this property. * 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. * @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 * @return \yii\widgets\ContentDecorator the ContentDecorator widget instance
* @see \yii\widgets\ContentDecorator * @see \yii\widgets\ContentDecorator
*/ */
public function beginContent($view, $params = array()) public function beginContent($viewFile, $params = array())
{ {
return $this->beginWidget('yii\widgets\ContentDecorator', array( return $this->beginWidget('yii\widgets\ContentDecorator', array(
'view' => $this, 'viewFile' => $viewFile,
'viewName' => $view,
'params' => $params, 'params' => $params,
)); ));
} }
...@@ -400,7 +374,6 @@ class View extends Component ...@@ -400,7 +374,6 @@ class View extends Component
public function beginCache($id, $properties = array()) public function beginCache($id, $properties = array())
{ {
$properties['id'] = $id; $properties['id'] = $id;
$properties['view'] = $this;
/** @var $cache \yii\widgets\FragmentCache */ /** @var $cache \yii\widgets\FragmentCache */
$cache = $this->beginWidget('yii\widgets\FragmentCache', $properties); $cache = $this->beginWidget('yii\widgets\FragmentCache', $properties);
if ($cache->getCachedContent() !== false) { if ($cache->getCachedContent() !== false) {
......
...@@ -19,9 +19,11 @@ use yii\helpers\FileHelper; ...@@ -19,9 +19,11 @@ use yii\helpers\FileHelper;
class Widget extends Component 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. * @var string id of the widget.
*/ */
...@@ -32,17 +34,6 @@ class Widget extends Component ...@@ -32,17 +34,6 @@ class Widget extends Component
private static $_counter = 0; 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. * Returns the ID of the widget.
* @param boolean $autoGenerate whether to generate an ID if it is not set previously * @param boolean $autoGenerate whether to generate an ID if it is not set previously
* @return string ID of the widget. * @return string ID of the widget.
...@@ -73,6 +64,18 @@ class Widget extends Component ...@@ -73,6 +64,18 @@ class Widget extends Component
/** /**
* Renders a view. * 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 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. * @param array $params the parameters (name-value pairs) that should be made available in the view.
* @return string the rendering result. * @return string the rendering result.
...@@ -80,7 +83,7 @@ class Widget extends Component ...@@ -80,7 +83,7 @@ class Widget extends Component
*/ */
public function render($view, $params = array()) 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 ...@@ -92,7 +95,7 @@ class Widget extends Component
*/ */
public function renderFile($file, $params = array()) 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 ...@@ -106,4 +109,28 @@ class Widget extends Component
$class = new \ReflectionClass($className); $class = new \ReflectionClass($className);
return dirname($class->getFileName()) . DIRECTORY_SEPARATOR . 'views'; 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 ...@@ -25,11 +25,6 @@ class PageCache extends ActionFilter
*/ */
public $varyByRoute = true; 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. * @var string the application component ID of the [[\yii\caching\Cache|cache]] object.
*/ */
public $cache = 'cache'; public $cache = 'cache';
......
...@@ -22,11 +22,6 @@ class Clip extends Widget ...@@ -22,11 +22,6 @@ class Clip extends Widget
*/ */
public $id; 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, * @var boolean whether to render the clip content in place. Defaults to false,
* meaning the captured clip will not be displayed. * meaning the captured clip will not be displayed.
*/ */
...@@ -51,7 +46,6 @@ class Clip extends Widget ...@@ -51,7 +46,6 @@ class Clip extends Widget
if ($this->renderClip) { if ($this->renderClip) {
echo $clip; echo $clip;
} }
$view = $this->view !== null ? $this->view : Yii::$app->getView(); $this->view->clips[$this->id] = $clip;
$view->clips[$this->id] = $clip;
} }
} }
\ No newline at end of file
...@@ -7,10 +7,8 @@ ...@@ -7,10 +7,8 @@
namespace yii\widgets; namespace yii\widgets;
use Yii;
use yii\base\InvalidConfigException; use yii\base\InvalidConfigException;
use yii\base\Widget; use yii\base\Widget;
use yii\base\View;
/** /**
* @author Qiang Xue <qiang.xue@gmail.com> * @author Qiang Xue <qiang.xue@gmail.com>
...@@ -19,15 +17,10 @@ use yii\base\View; ...@@ -19,15 +17,10 @@ use yii\base\View;
class ContentDecorator extends Widget class ContentDecorator extends Widget
{ {
/** /**
* @var View the view object for rendering [[viewName]]. If not set, the view registered with the application * @var string the view file that will be used to decorate the content enclosed by this widget.
* will be used. * This can be specified as either the view file path or path alias.
*/ */
public $view; public $viewFile;
/**
* @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;
/** /**
* @var array the parameters (name=>value) to be extracted and made available in the decorative view. * @var array the parameters (name=>value) to be extracted and made available in the decorative view.
*/ */
...@@ -38,8 +31,8 @@ class ContentDecorator extends Widget ...@@ -38,8 +31,8 @@ class ContentDecorator extends Widget
*/ */
public function init() public function init()
{ {
if ($this->viewName === null) { if ($this->viewFile === null) {
throw new InvalidConfigException('ContentDecorator::viewName must be set.'); throw new InvalidConfigException('ContentDecorator::viewFile must be set.');
} }
ob_start(); ob_start();
ob_implicit_flush(false); ob_implicit_flush(false);
...@@ -53,7 +46,7 @@ class ContentDecorator extends Widget ...@@ -53,7 +46,7 @@ class ContentDecorator extends Widget
{ {
$params = $this->params; $params = $this->params;
$params['content'] = ob_get_clean(); $params['content'] = ob_get_clean();
$view = $this->view !== null ? $this->view : Yii::$app->getView(); // render under the existing context
echo $view->render($this->viewName, $params); echo $this->view->renderFile($this->viewFile, $params);
} }
} }
...@@ -64,11 +64,6 @@ class FragmentCache extends Widget ...@@ -64,11 +64,6 @@ class FragmentCache extends Widget
*/ */
public $enabled = true; 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 * @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. * is used internally to implement the content caching feature. Do not modify it.
*/ */
...@@ -81,10 +76,6 @@ class FragmentCache extends Widget ...@@ -81,10 +76,6 @@ class FragmentCache extends Widget
{ {
parent::init(); parent::init();
if ($this->view === null) {
$this->view = Yii::$app->getView();
}
if (!$this->enabled) { if (!$this->enabled) {
$this->cache = null; $this->cache = null;
} elseif (is_string($this->cache)) { } 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