From c32a202d824eca55dfd86aea3b575c081bcad0cb Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Sun, 31 Mar 2013 20:21:29 -0400 Subject: [PATCH] refactored MVC. --- framework/base/Controller.php | 119 +++++++++++++++++++++++++-------- framework/base/View.php | 93 +++++++++----------------- framework/base/Widget.php | 57 +++++++++++----- framework/web/PageCache.php | 5 -- framework/widgets/Clip.php | 8 +-- framework/widgets/ContentDecorator.php | 21 ++---- framework/widgets/FragmentCache.php | 9 --- 7 files changed, 174 insertions(+), 138 deletions(-) diff --git a/framework/base/Controller.php b/framework/base/Controller.php index ff6d8f7..241c66c 100644 --- a/framework/base/Controller.php +++ b/framework/base/Controller.php @@ -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() diff --git a/framework/base/View.php b/framework/base/View.php index c7087c1..d3d9339 100644 --- a/framework/base/View.php +++ b/framework/base/View.php @@ -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: + * + * ~~~ + * beginContent('@app/view/layouts/base'); ?> + * ...layout content here... + * 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) { diff --git a/framework/base/Widget.php b/framework/base/Widget.php index 24d0685..c6667fa 100644 --- a/framework/base/Widget.php +++ b/framework/base/Widget.php @@ -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 diff --git a/framework/web/PageCache.php b/framework/web/PageCache.php index 29c8cc8..5a50825 100644 --- a/framework/web/PageCache.php +++ b/framework/web/PageCache.php @@ -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'; diff --git a/framework/widgets/Clip.php b/framework/widgets/Clip.php index d540b24..f321209 100644 --- a/framework/widgets/Clip.php +++ b/framework/widgets/Clip.php @@ -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 diff --git a/framework/widgets/ContentDecorator.php b/framework/widgets/ContentDecorator.php index 4c3ae70..3f63621 100644 --- a/framework/widgets/ContentDecorator.php +++ b/framework/widgets/ContentDecorator.php @@ -7,10 +7,8 @@ namespace yii\widgets; -use Yii; use yii\base\InvalidConfigException; use yii\base\Widget; -use yii\base\View; /** * @author Qiang Xue @@ -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); } } diff --git a/framework/widgets/FragmentCache.php b/framework/widgets/FragmentCache.php index 65bb86b..dae538a 100644 --- a/framework/widgets/FragmentCache.php +++ b/framework/widgets/FragmentCache.php @@ -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)) {