View.php 14.5 KB
Newer Older
Qiang Xue committed
1 2 3
<?php
/**
 * @link http://www.yiiframework.com/
Qiang Xue committed
4
 * @copyright Copyright (c) 2008 Yii Software LLC
Qiang Xue committed
5 6 7 8 9
 * @license http://www.yiiframework.com/license/
 */

namespace yii\base;

Qiang Xue committed
10
use Yii;
Qiang Xue committed
11
use yii\base\Application;
Qiang Xue committed
12
use yii\helpers\FileHelper;
Qiang Xue committed
13

Qiang Xue committed
14
/**
Qiang Xue committed
15
 * View represents a view object in the MVC pattern.
Qiang Xue committed
16
 *
Qiang Xue committed
17
 * View provides a set of methods (e.g. [[render()]]) for rendering purpose.
Qiang Xue committed
18
 *
Qiang Xue committed
19 20 21 22 23
 * @author Qiang Xue <qiang.xue@gmail.com>
 * @since 2.0
 */
class View extends Component
{
24 25 26 27 28 29 30 31 32
	/**
	 * @event Event an event that is triggered by [[renderFile()]] right before it renders a view file.
	 */
	const EVENT_BEFORE_RENDER = 'beforeRender';
	/**
	 * @event Event an event that is triggered by [[renderFile()]] right after it renders a view file.
	 */
	const EVENT_AFTER_RENDER = 'afterRender';

Qiang Xue committed
33
	/**
Qiang Xue committed
34 35
	 * @var object the object that owns this view. This can be a controller, a widget, or any other object.
	 */
Qiang Xue committed
36
	public $context;
Qiang Xue committed
37 38 39
	/**
	 * @var ViewContent
	 */
Qiang Xue committed
40
	public $page;
Qiang Xue committed
41
	/**
Qiang Xue committed
42
	 * @var mixed custom parameters that are shared among view templates.
Qiang Xue committed
43
	 */
Qiang Xue committed
44
	public $params;
Qiang Xue committed
45
	/**
Qiang Xue committed
46 47
	 * @var ViewRenderer|array the view renderer object or the configuration array for
	 * creating the view renderer. If not set, view files will be treated as normal PHP files.
Qiang Xue committed
48
	 */
Qiang Xue committed
49
	public $renderer;
50
	/**
Qiang Xue committed
51 52
	 * @var Theme|array the theme object or the configuration array for creating the theme.
	 * If not set, it means theming is not enabled.
53
	 */
Qiang Xue committed
54
	public $theme;
Qiang Xue committed
55
	/**
Qiang Xue committed
56 57
	 * @var array a list of named output blocks. The keys are the block names and the values
	 * are the corresponding block content. You can call [[beginBlock()]] and [[endBlock()]]
Qiang Xue committed
58 59 60
	 * to capture small fragments of a view. They can be later accessed at somewhere else
	 * through this property.
	 */
Qiang Xue committed
61
	public $blocks;
Qiang Xue committed
62
	/**
Qiang Xue committed
63
	 * @var Widget[] the widgets that are currently being rendered (not ended). This property
64
	 * is maintained by [[beginWidget()]] and [[endWidget()]] methods. Do not modify it.
Qiang Xue committed
65 66 67 68 69 70 71 72 73 74
	 */
	public $widgetStack = array();
	/**
	 * @var array a list of currently active fragment cache widgets. This property
	 * is used internally to implement the content caching feature. Do not modify it.
	 */
	public $cacheStack = array();
	/**
	 * @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.
Qiang Xue committed
75
	 */
Qiang Xue committed
76
	public $dynamicPlaceholders = array();
Qiang Xue committed
77 78


Qiang Xue committed
79
	/**
Qiang Xue committed
80
	 * Initializes the view component.
Qiang Xue committed
81
	 */
Qiang Xue committed
82
	public function init()
Qiang Xue committed
83
	{
Qiang Xue committed
84 85 86 87 88 89
		parent::init();
		if (is_array($this->renderer)) {
			$this->renderer = Yii::createObject($this->renderer);
		}
		if (is_array($this->theme)) {
			$this->theme = Yii::createObject($this->theme);
Qiang Xue committed
90
		}
Qiang Xue committed
91 92
		if (is_array($this->page)) {
			$this->page = Yii::createObject($this->page);
Qiang Xue committed
93
		} else {
Qiang Xue committed
94
			$this->page = new ViewContent;
Qiang Xue committed
95
		}
Qiang Xue committed
96 97
	}

Qiang Xue committed
98
	/**
Qiang Xue committed
99
	 * Renders a view.
Qiang Xue committed
100
	 *
Qiang Xue committed
101
	 * This method delegates the call to the [[context]] object:
Qiang Xue committed
102
	 *
Qiang Xue committed
103 104 105 106 107 108
	 * - 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.
Qiang Xue committed
109
	 * @param array $params the parameters (name-value pairs) that will be extracted and made available in the view file.
110
	 * @return string the rendering result
Qiang Xue committed
111
	 * @throws InvalidCallException if [[context]] is neither a controller nor a widget.
Qiang Xue committed
112 113
	 * @throws InvalidParamException if the view cannot be resolved or the view file does not exist.
	 * @see renderFile
Qiang Xue committed
114
	 */
Qiang Xue committed
115
	public function render($view, $params = array())
Qiang Xue committed
116
	{
Qiang Xue committed
117 118 119 120 121 122 123
		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.');
		}
Qiang Xue committed
124 125
	}

Qiang Xue committed
126 127
	/**
	 * Renders a view file.
Qiang Xue committed
128
	 *
Qiang Xue committed
129 130
	 * If [[theme]] is enabled (not null), it will try to render the themed version of the view file as long
	 * as it is available.
Qiang Xue committed
131
	 *
Qiang Xue committed
132 133 134 135 136 137
	 * The method will call [[FileHelper::localize()]] to localize the view file.
	 *
	 * If [[renderer]] is enabled (not null), the method will use it to render the view file.
	 * Otherwise, it will simply include the view file as a normal PHP file, capture its output and
	 * return it as a string.
	 *
Qiang Xue committed
138
	 * @param string $viewFile the view file. This can be either a file path or a path alias.
Qiang Xue committed
139
	 * @param array $params the parameters (name-value pairs) that will be extracted and made available in the view file.
Qiang Xue committed
140 141
	 * @param object $context the context that the view should use for rendering the view. If null,
	 * existing [[context]] will be used.
Qiang Xue committed
142
	 * @return string the rendering result
Qiang Xue committed
143
	 * @throws InvalidParamException if the view file does not exist
Qiang Xue committed
144
	 */
Qiang Xue committed
145
	public function renderFile($viewFile, $params = array(), $context = null)
Qiang Xue committed
146
	{
Qiang Xue committed
147 148 149 150 151 152 153 154 155 156
		$viewFile = Yii::getAlias($viewFile);
		if (is_file($viewFile)) {
			if ($this->theme !== null) {
				$viewFile = $this->theme->applyTo($viewFile);
			}
			$viewFile = FileHelper::localize($viewFile);
		} else {
			throw new InvalidParamException("The view file does not exist: $viewFile");
		}

Qiang Xue committed
157
		$oldContext = $this->context;
Qiang Xue committed
158 159 160
		if ($context !== null) {
			$this->context = $context;
		}
Qiang Xue committed
161

162 163 164 165 166 167 168 169
		$output = '';
		if ($this->beforeRender($viewFile)) {
			if ($this->renderer !== null) {
				$output = $this->renderer->render($this, $viewFile, $params);
			} else {
				$output = $this->renderPhpFile($viewFile, $params);
			}
			$this->afterRender($viewFile, $output);
Qiang Xue committed
170
		}
Qiang Xue committed
171 172 173 174

		$this->context = $oldContext;

		return $output;
Qiang Xue committed
175 176
	}

177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208
	/**
	 * This method is invoked right before [[renderFile()]] renders a view file.
	 * The default implementation will trigger the [[EVENT_BEFORE_RENDER]] event.
	 * If you override this method, make sure you call the parent implementation first.
	 * @param string $viewFile the view file to be rendered
	 * @return boolean whether to continue rendering the view file.
	 */
	public function beforeRender($viewFile)
	{
		$event = new ViewEvent($viewFile);
		$this->trigger(self::EVENT_BEFORE_RENDER, $event);
		return $event->isValid;
	}

	/**
	 * This method is invoked right after [[renderFile()]] renders a view file.
	 * The default implementation will trigger the [[EVENT_AFTER_RENDER]] event.
	 * If you override this method, make sure you call the parent implementation first.
	 * @param string $viewFile the view file to be rendered
	 * @param string $output the rendering result of the view file. Updates to this parameter
	 * will be passed back and returned by [[renderFile()]].
	 */
	public function afterRender($viewFile, &$output)
	{
		if ($this->hasEventHandlers(self::EVENT_AFTER_RENDER)) {
			$event = new ViewEvent($viewFile);
			$event->output = $output;
			$this->trigger(self::EVENT_AFTER_RENDER, $event);
			$output = $event->output;
		}
	}

Qiang Xue committed
209
	/**
Qiang Xue committed
210 211 212 213 214 215
	 * Renders a view file as a PHP script.
	 *
	 * This method treats the view file as a PHP script and includes the file.
	 * It extracts the given parameters and makes them available in the view file.
	 * The method captures the output of the included view file and returns it as a string.
	 *
Qiang Xue committed
216 217
	 * This method should mainly be called by view renderer or [[renderFile()]].
	 *
Qiang Xue committed
218 219 220
	 * @param string $_file_ the view file.
	 * @param array $_params_ the parameters (name-value pairs) that will be extracted and made available in the view file.
	 * @return string the rendering result
Qiang Xue committed
221
	 */
Qiang Xue committed
222
	public function renderPhpFile($_file_, $_params_ = array())
Qiang Xue committed
223
	{
Qiang Xue committed
224 225 226 227 228
		ob_start();
		ob_implicit_flush(false);
		extract($_params_, EXTR_OVERWRITE);
		require($_file_);
		return ob_get_clean();
Qiang Xue committed
229 230
	}

Qiang Xue committed
231 232 233 234 235 236 237 238 239
	/**
	 * Renders dynamic content returned by the given PHP statements.
	 * This method is mainly used together with content caching (fragment caching and page caching)
	 * when some portions of the content (called *dynamic content*) should not be cached.
	 * The dynamic content must be returned by some PHP statements.
	 * @param string $statements the PHP statements for generating the dynamic content.
	 * @return string the placeholder of the dynamic content, or the dynamic content if there is no
	 * active content cache currently.
	 */
Qiang Xue committed
240 241
	public function renderDynamic($statements)
	{
Qiang Xue committed
242 243
		if (!empty($this->cacheStack)) {
			$n = count($this->dynamicPlaceholders);
Qiang Xue committed
244
			$placeholder = "<![CDATA[YII-DYNAMIC-$n]]>";
Qiang Xue committed
245
			$this->addDynamicPlaceholder($placeholder, $statements);
Qiang Xue committed
246 247 248 249 250 251
			return $placeholder;
		} else {
			return $this->evaluateDynamicContent($statements);
		}
	}

Qiang Xue committed
252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271
	/**
	 * Adds a placeholder for dynamic content.
	 * This method is internally used.
	 * @param string $placeholder the placeholder name
	 * @param string $statements the PHP statements for generating the dynamic content
	 */
	public function addDynamicPlaceholder($placeholder, $statements)
	{
		foreach ($this->cacheStack as $cache) {
			$cache->dynamicPlaceholders[$placeholder] = $statements;
		}
		$this->dynamicPlaceholders[$placeholder] = $statements;
	}

	/**
	 * Evaluates the given PHP statements.
	 * This method is mainly used internally to implement dynamic content feature.
	 * @param string $statements the PHP statements to be evaluated.
	 * @return mixed the return value of the PHP statements.
	 */
Qiang Xue committed
272 273 274 275 276
	public function evaluateDynamicContent($statements)
	{
		return eval($statements);
	}

Qiang Xue committed
277 278 279 280 281 282 283
	/**
	 * Creates a widget.
	 * This method will use [[Yii::createObject()]] to create the widget.
	 * @param string $class the widget class name or path alias
	 * @param array $properties the initial property values of the widget.
	 * @return Widget the newly created widget instance
	 */
Qiang Xue committed
284 285 286
	public function createWidget($class, $properties = array())
	{
		$properties['class'] = $class;
Qiang Xue committed
287 288 289 290
		if (!isset($properties['view'])) {
			$properties['view'] = $this;
		}
		return Yii::createObject($properties, $this);
Qiang Xue committed
291 292
	}

Qiang Xue committed
293 294 295 296 297 298 299 300 301 302
	/**
	 * Creates and runs a widget.
	 * Compared with [[createWidget()]], this method does one more thing: it will
	 * run the widget after it is created.
	 * @param string $class the widget class name or path alias
	 * @param array $properties the initial property values of the widget.
	 * @param boolean $captureOutput whether to capture the output of the widget and return it as a string
	 * @return string|Widget if $captureOutput is true, the output of the widget will be returned;
	 * otherwise the widget object will be returned.
	 */
Qiang Xue committed
303
	public function widget($class, $properties = array(), $captureOutput = false)
Qiang Xue committed
304
	{
Qiang Xue committed
305 306 307 308 309 310 311 312 313 314 315
		if ($captureOutput) {
			ob_start();
			ob_implicit_flush(false);
			$widget = $this->createWidget($class, $properties);
			$widget->run();
			return ob_get_clean();
		} else {
			$widget = $this->createWidget($class, $properties);
			$widget->run();
			return $widget;
		}
Qiang Xue committed
316 317
	}

Qiang Xue committed
318 319
	/**
	 * Begins a widget.
Qiang Xue committed
320 321 322 323
	 * This method is similar to [[createWidget()]] except that it will expect a matching
	 * [[endWidget()]] call after this.
	 * @param string $class the widget class name or path alias
	 * @param array $properties the initial property values of the widget.
Qiang Xue committed
324 325
	 * @return Widget the widget instance
	 */
Qiang Xue committed
326 327
	public function beginWidget($class, $properties = array())
	{
328
		$widget = $this->createWidget($class, $properties);
Qiang Xue committed
329
		$this->widgetStack[] = $widget;
330
		return $widget;
Qiang Xue committed
331 332
	}

Qiang Xue committed
333 334 335 336 337 338
	/**
	 * Ends a widget.
	 * Note that the rendering result of the widget is directly echoed out.
	 * If you want to capture the rendering result of a widget, you may use
	 * [[createWidget()]] and [[Widget::run()]].
	 * @return Widget the widget instance
Qiang Xue committed
339
	 * @throws InvalidCallException if [[beginWidget()]] and [[endWidget()]] calls are not properly nested
Qiang Xue committed
340
	 */
Qiang Xue committed
341
	public function endWidget()
Qiang Xue committed
342
	{
Qiang Xue committed
343
		$widget = array_pop($this->widgetStack);
Qiang Xue committed
344
		if ($widget instanceof Widget) {
Qiang Xue committed
345
			$widget->run();
Qiang Xue committed
346
			return $widget;
347
		} else {
Qiang Xue committed
348
			throw new InvalidCallException("Unmatched beginWidget() and endWidget() calls.");
Qiang Xue committed
349 350 351
		}
	}

Qiang Xue committed
352
	/**
Qiang Xue committed
353 354 355 356 357 358
	 * Begins recording a block.
	 * This method is a shortcut to beginning [[yii\widgets\Block]]
	 * @param string $id the block ID.
	 * @param boolean $renderInPlace whether to render the block content in place.
	 * Defaults to false, meaning the captured block will not be displayed.
	 * @return \yii\widgets\Block the Block widget instance
Qiang Xue committed
359
	 */
Qiang Xue committed
360
	public function beginBlock($id, $renderInPlace = false)
Qiang Xue committed
361
	{
Qiang Xue committed
362
		return $this->beginWidget('yii\widgets\Block', array(
Qiang Xue committed
363 364 365 366 367 368
			'id' => $id,
			'renderInPlace' => $renderInPlace,
		));
	}

	/**
Qiang Xue committed
369
	 * Ends recording a block.
Qiang Xue committed
370
	 */
Qiang Xue committed
371
	public function endBlock()
Qiang Xue committed
372 373 374 375
	{
		$this->endWidget();
	}

Qiang Xue committed
376 377
	/**
	 * Begins the rendering of content that is to be decorated by the specified view.
Qiang Xue committed
378 379 380 381 382 383 384 385 386 387 388
	 * 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.
Qiang Xue committed
389 390 391 392
	 * @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
	 */
Qiang Xue committed
393
	public function beginContent($viewFile, $params = array())
Qiang Xue committed
394 395
	{
		return $this->beginWidget('yii\widgets\ContentDecorator', array(
Qiang Xue committed
396
			'viewFile' => $viewFile,
Qiang Xue committed
397 398 399 400 401 402 403 404 405 406 407 408
			'params' => $params,
		));
	}

	/**
	 * Ends the rendering of content.
	 */
	public function endContent()
	{
		$this->endWidget();
	}

409 410 411 412 413 414 415 416 417 418 419 420 421 422 423
	/**
	 * Begins fragment caching.
	 * This method will display cached content if it is available.
	 * If not, it will start caching and would expect an [[endCache()]]
	 * call to end the cache and save the content into cache.
	 * A typical usage of fragment caching is as follows,
	 *
	 * ~~~
	 * if($this->beginCache($id)) {
	 *     // ...generate content here
	 *     $this->endCache();
	 * }
	 * ~~~
	 *
	 * @param string $id a unique ID identifying the fragment to be cached.
Qiang Xue committed
424
	 * @param array $properties initial property values for [[\yii\widgets\FragmentCache]]
425 426 427 428 429 430
	 * @return boolean whether you should generate the content for caching.
	 * False if the cached version is available.
	 */
	public function beginCache($id, $properties = array())
	{
		$properties['id'] = $id;
Qiang Xue committed
431
		/** @var $cache \yii\widgets\FragmentCache */
Qiang Xue committed
432
		$cache = $this->beginWidget('yii\widgets\FragmentCache', $properties);
Qiang Xue committed
433
		if ($cache->getCachedContent() !== false) {
434 435 436 437 438 439 440 441 442 443 444 445 446 447
			$this->endCache();
			return false;
		} else {
			return true;
		}
	}

	/**
	 * Ends fragment caching.
	 */
	public function endCache()
	{
		$this->endWidget();
	}
Qiang Xue committed
448
}