View.php 14.3 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
	/**
Qiang Xue committed
38
	 * @var mixed custom parameters that are shared among view templates.
Qiang Xue committed
39
	 */
Qiang Xue committed
40
	public $params;
Qiang Xue committed
41
	/**
Qiang Xue committed
42 43
	 * @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
44
	 */
Qiang Xue committed
45
	public $renderer;
46
	/**
Qiang Xue committed
47 48
	 * @var Theme|array the theme object or the configuration array for creating the theme.
	 * If not set, it means theming is not enabled.
49
	 */
Qiang Xue committed
50
	public $theme;
Qiang Xue committed
51 52 53 54 55 56
	/**
	 * @var array a list of named output clips. You can call [[beginClip()]] and [[endClip()]]
	 * to capture small fragments of a view. They can be later accessed at somewhere else
	 * through this property.
	 */
	public $clips;
Qiang Xue committed
57
	/**
Qiang Xue committed
58
	 * @var Widget[] the widgets that are currently being rendered (not ended). This property
59
	 * is maintained by [[beginWidget()]] and [[endWidget()]] methods. Do not modify it.
Qiang Xue committed
60 61 62 63 64 65 66 67 68 69
	 */
	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
70
	 */
Qiang Xue committed
71
	public $dynamicPlaceholders = array();
Qiang Xue committed
72 73


Qiang Xue committed
74
	/**
Qiang Xue committed
75
	 * Initializes the view component.
Qiang Xue committed
76
	 */
Qiang Xue committed
77
	public function init()
Qiang Xue committed
78
	{
Qiang Xue committed
79 80 81 82 83 84
		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
85 86 87
		}
	}

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

Qiang Xue committed
116 117
	/**
	 * Renders a view file.
Qiang Xue committed
118
	 *
Qiang Xue committed
119 120
	 * 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
121
	 *
Qiang Xue committed
122 123 124 125 126 127
	 * 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
128
	 * @param string $viewFile the view file. This can be either a file path or a path alias.
Qiang Xue committed
129
	 * @param array $params the parameters (name-value pairs) that will be extracted and made available in the view file.
Qiang Xue committed
130 131
	 * @param object $context the context that the view should use for rendering the view. If null,
	 * existing [[context]] will be used.
Qiang Xue committed
132
	 * @return string the rendering result
Qiang Xue committed
133
	 * @throws InvalidParamException if the view file does not exist
Qiang Xue committed
134
	 */
Qiang Xue committed
135
	public function renderFile($viewFile, $params = array(), $context = null)
Qiang Xue committed
136
	{
Qiang Xue committed
137 138 139 140 141 142 143 144 145 146
		$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
147
		$oldContext = $this->context;
Qiang Xue committed
148 149 150
		if ($context !== null) {
			$this->context = $context;
		}
Qiang Xue committed
151

152 153 154 155 156 157 158 159
		$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
160
		}
Qiang Xue committed
161 162 163 164

		$this->context = $oldContext;

		return $output;
Qiang Xue committed
165 166
	}

167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198
	/**
	 * 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
199
	/**
Qiang Xue committed
200 201 202 203 204 205
	 * 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
206 207
	 * This method should mainly be called by view renderer or [[renderFile()]].
	 *
Qiang Xue committed
208 209 210
	 * @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
211
	 */
Qiang Xue committed
212
	public function renderPhpFile($_file_, $_params_ = array())
Qiang Xue committed
213
	{
Qiang Xue committed
214 215 216 217 218
		ob_start();
		ob_implicit_flush(false);
		extract($_params_, EXTR_OVERWRITE);
		require($_file_);
		return ob_get_clean();
Qiang Xue committed
219 220
	}

Qiang Xue committed
221 222 223 224 225 226 227 228 229
	/**
	 * 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
230 231
	public function renderDynamic($statements)
	{
Qiang Xue committed
232 233
		if (!empty($this->cacheStack)) {
			$n = count($this->dynamicPlaceholders);
Qiang Xue committed
234
			$placeholder = "<![CDATA[YDP-$n]]>";
Qiang Xue committed
235
			$this->addDynamicPlaceholder($placeholder, $statements);
Qiang Xue committed
236 237 238 239 240 241
			return $placeholder;
		} else {
			return $this->evaluateDynamicContent($statements);
		}
	}

Qiang Xue committed
242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261
	/**
	 * 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
262 263 264 265 266
	public function evaluateDynamicContent($statements)
	{
		return eval($statements);
	}

Qiang Xue committed
267 268 269 270 271 272 273
	/**
	 * 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
274 275 276
	public function createWidget($class, $properties = array())
	{
		$properties['class'] = $class;
Qiang Xue committed
277 278 279 280
		if (!isset($properties['view'])) {
			$properties['view'] = $this;
		}
		return Yii::createObject($properties, $this);
Qiang Xue committed
281 282
	}

Qiang Xue committed
283 284 285 286 287 288 289 290 291 292
	/**
	 * 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
293
	public function widget($class, $properties = array(), $captureOutput = false)
Qiang Xue committed
294
	{
Qiang Xue committed
295 296 297 298 299 300 301 302 303 304 305
		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
306 307
	}

Qiang Xue committed
308 309
	/**
	 * Begins a widget.
Qiang Xue committed
310 311 312 313
	 * 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
314 315
	 * @return Widget the widget instance
	 */
Qiang Xue committed
316 317
	public function beginWidget($class, $properties = array())
	{
318
		$widget = $this->createWidget($class, $properties);
Qiang Xue committed
319
		$this->widgetStack[] = $widget;
320
		return $widget;
Qiang Xue committed
321 322
	}

Qiang Xue committed
323 324 325 326 327 328
	/**
	 * 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
329
	 * @throws InvalidCallException if [[beginWidget()]] and [[endWidget()]] calls are not properly nested
Qiang Xue committed
330
	 */
Qiang Xue committed
331
	public function endWidget()
Qiang Xue committed
332
	{
Qiang Xue committed
333
		$widget = array_pop($this->widgetStack);
Qiang Xue committed
334
		if ($widget instanceof Widget) {
Qiang Xue committed
335
			$widget->run();
Qiang Xue committed
336
			return $widget;
337
		} else {
Qiang Xue committed
338
			throw new InvalidCallException("Unmatched beginWidget() and endWidget() calls.");
Qiang Xue committed
339 340 341
		}
	}

Qiang Xue committed
342 343 344 345 346 347 348
	/**
	 * Begins recording a clip.
	 * This method is a shortcut to beginning [[yii\widgets\Clip]]
	 * @param string $id the clip ID.
	 * @param boolean $renderInPlace whether to render the clip content in place.
	 * Defaults to false, meaning the captured clip will not be displayed.
	 * @return \yii\widgets\Clip the Clip widget instance
Qiang Xue committed
349
	 * @see \yii\widgets\Clip
Qiang Xue committed
350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366
	 */
	public function beginClip($id, $renderInPlace = false)
	{
		return $this->beginWidget('yii\widgets\Clip', array(
			'id' => $id,
			'renderInPlace' => $renderInPlace,
		));
	}

	/**
	 * Ends recording a clip.
	 */
	public function endClip()
	{
		$this->endWidget();
	}

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

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

400 401 402 403 404 405 406 407 408 409 410 411 412 413 414
	/**
	 * 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
415
	 * @param array $properties initial property values for [[\yii\widgets\FragmentCache]]
416 417 418 419 420 421
	 * @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
422
		/** @var $cache \yii\widgets\FragmentCache */
Qiang Xue committed
423
		$cache = $this->beginWidget('yii\widgets\FragmentCache', $properties);
Qiang Xue committed
424
		if ($cache->getCachedContent() !== false) {
425 426 427 428 429 430 431 432 433 434 435 436 437 438
			$this->endCache();
			return false;
		} else {
			return true;
		}
	}

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