view.php 11.4 KB
Newer Older
1
<?php namespace Laravel; use Closure, ArrayAccess;
Taylor Otwell committed
2

3
class View implements ArrayAccess {
4 5

	/**
6
	 * The name of the view.
7
	 *
8
	 * @var string
9
	 */
10
	public $view;
11 12

	/**
13 14 15 16 17 18 19 20
	 * The view data.
	 *
	 * @var array
	 */
	public $data;

	/**
	 * The path to the view on disk.
21 22 23
	 *
	 * @var string
	 */
24
	public $path;
25 26

	/**
27 28 29 30 31 32 33 34
	 * All of the shared view data.
	 *
	 * @var array
	 */
	public static $shared = array();

	/**
	 * All of the registered view names.
35
	 *
36
	 * @var array
37
	 */
38
	public static $names = array();
39 40

	/**
41 42 43 44 45 46 47
	 * The cache content of loaded view files.
	 *
	 * @var array
	 */
	public static $cache = array();

	/**
48 49 50 51 52 53 54
	 * The Laravel view loader event name.
	 *
	 * @var string
	 */
	const loader = 'laravel.view.loader';

	/**
55 56 57 58 59 60 61
	 * The Laravel view engine event name.
	 *
	 * @var string
	 */
	const engine = 'laravel.view.engine';

	/**
62
	 * Create a new view instance.
63
	 *
64 65 66 67 68 69 70 71 72 73 74
	 * <code>
	 *		// Create a new view instance
	 *		$view = new View('home.index');
	 *
	 *		// Create a new view instance of a bundle's view
	 *		$view = new View('admin::home.index');
	 *
	 *		// Create a new view instance with bound data
	 *		$view = new View('home.index', array('name' => 'Taylor'));
	 * </code>
	 *
75 76
	 * @param  string  $view
	 * @param  array   $data
77 78
	 * @return void
	 */
79
	public function __construct($view, $data = array())
80
	{
81 82
		$this->view = $view;
		$this->data = $data;
83 84 85 86 87 88 89 90 91 92 93 94

		// In order to allow developers to load views outside of the normal loading
		// conventions, we'll allow for a raw path to be given in place of the
		// typical view name, giving total freedom on view loading.
		if (starts_with($view, 'path: '))
		{
			$this->path = substr($view, 6);
		}
		else
		{
			$this->path = $this->path($view);
		}
Taylor Otwell committed
95

96
		// If a session driver has been specified, we will bind an instance of the
97
		// validation error message container to every view. If an error instance
98
		// exists in the session, we will use that instance.
99
		if ( ! isset($this->data['errors']))
Taylor Otwell committed
100
		{
101
			if (Session::started() and Session::has('errors'))
Taylor Otwell committed
102
			{
103 104 105 106 107 108
				$this->data['errors'] = Session::get('errors');
			}
			else
			{
				$this->data['errors'] = new Messages;
			}
Taylor Otwell committed
109
		}
110 111 112
	}

	/**
Taylor Otwell committed
113 114 115 116 117
	 * Determine if the given view exists.
	 *
	 * @param  string       $view
	 * @param  boolean      $return_path
	 * @return string|bool
118
	 */
119
	public static function exists($view, $return_path = false)
120
	{
121
		list($bundle, $view) = Bundle::parse($view);
122

123
		$view = str_replace('.', '/', $view);
124

125 126 127
		// We delegate the determination of view paths to the view loader event
		// so that the developer is free to override and manage the loading
		// of views in any way they see fit for their application.
128
		$path = Event::first(static::loader, array($bundle, $view));
129

130
		if ( ! is_null($path))
131
		{
132 133
			return $return_path ? $path : true;
		}
Taylor Otwell committed
134

135 136 137 138 139 140 141 142 143 144 145 146 147
		return false;
	}

	/**
	 * Get the path to a given view on disk.
	 *
	 * @param  string  $view
	 * @return string
	 */
	protected function path($view)
	{
		if ($path = $this->exists($view,true))
		{
148
			return $path;
149 150
		}

151
		throw new \Exception("View [$view] doesn't exist.");
152 153 154
	}

	/**
155 156 157 158
	 * Get the path to a view using the default folder convention.
	 *
	 * @param  string  $bundle
	 * @param  string  $view
159
	 * @param  string  $directory
160 161
	 * @return string
	 */
162
	public static function file($bundle, $view, $directory)
163
	{
164
		$directory = str_finish($directory, DS);
165

166
		// Views may have either the default PHP file extension of the "Blade"
167 168
		// extension, so we will need to check for both in the view path
		// and return the first one we find for the given view.
169
		if (file_exists($path = $directory.$view.EXT))
170 171 172
		{
			return $path;
		}
173
		elseif (file_exists($path = $directory.$view.BLADE_EXT))
174 175 176
		{
			return $path;
		}
177 178 179
	}

	/**
180 181
	 * Create a new view instance.
	 *
182 183 184 185
	 * <code>
	 *		// Create a new view instance
	 *		$view = View::make('home.index');
	 *
186 187 188
	 *		// Create a new view instance of a bundle's view
	 *		$view = View::make('admin::home.index');
	 *
189 190 191 192
	 *		// Create a new view instance with bound data
	 *		$view = View::make('home.index', array('name' => 'Taylor'));
	 * </code>
	 *
Taylor Otwell committed
193 194
	 * @param  string  $view
	 * @param  array   $data
195 196
	 * @return View
	 */
197
	public static function make($view, $data = array())
198
	{
199
		return new static($view, $data);
200 201 202
	}

	/**
203
	 * Create a new view instance of a named view.
204
	 *
205
	 * <code>
206 207
	 *		// Create a new named view instance
	 *		$view = View::of('profile');
208
	 *
209 210
	 *		// Create a new named view instance with bound data
	 *		$view = View::of('profile', array('name' => 'Taylor'));
211 212
	 * </code>
	 *
213 214 215 216
	 * @param  string  $name
	 * @param  array   $data
	 * @return View
	 */
217
	public static function of($name, $data = array())
218
	{
219
		return new static(static::$names[$name], $data);
220 221 222
	}

	/**
223 224 225 226 227
	 * Assign a name to a view.
	 *
	 * <code>
	 *		// Assign a name to a view
	 *		View::name('partials.profile', 'profile');
Taylor Otwell committed
228
	 *
229 230 231
	 *		// Resolve an instance of a named view
	 *		$view = View::of('profile');
	 * </code>
232
	 *
233
	 * @param  string  $view
Taylor Otwell committed
234
	 * @param  string  $name
235
	 * @return void
Taylor Otwell committed
236
	 */
237
	public static function name($view, $name)
Taylor Otwell committed
238
	{
239
		static::$names[$name] = $view;
Taylor Otwell committed
240 241 242
	}

	/**
243
	 * Register a view composer with the Event class.
Taylor Otwell committed
244
	 *
245 246 247 248 249 250 251 252
	 * <code>
	 *		// Register a composer for the "home.index" view
	 *		View::composer('home.index', function($view)
	 *		{
	 *			$view['title'] = 'Home';
	 *		});
	 * </code>
	 *
253 254
	 * @param  string|array  $view
	 * @param  Closure       $composer
Taylor Otwell committed
255 256
	 * @return void
	 */
257
	public static function composer($views, $composer)
Taylor Otwell committed
258
	{
259 260 261 262 263 264
		$views = (array) $views;

		foreach ($views as $view)
		{
			Event::listen("laravel.composing: {$view}", $composer);
		}
Taylor Otwell committed
265 266 267
	}

	/**
268
	 * Get the rendered contents of a partial from a loop.
269
	 *
270 271 272 273 274
	 * @param  string  $view
	 * @param  array   $data
	 * @param  string  $iterator
	 * @param  string  $empty
	 * @return string
275
	 */
276
	public static function render_each($view, array $data, $iterator, $empty = 'raw|')
277
	{
278
		$result = '';
279

280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303
		// If is actually data in the array, we will loop through the data and
		// append an instance of the partial view to the final result HTML,
		// passing in the iterated value of the data array.
		if (count($data) > 0)
		{
			foreach ($data as $key => $value)
			{
				$with = array('key' => $key, $iterator => $value);

				$result .= render($view, $with);
			}
		}

		// If there is no data in the array, we will render the contents of
		// the "empty" view. Alternative, the "empty view" can be a raw
		// string that is prefixed with "raw|" for convenience.
		else
		{
			if (starts_with($empty, 'raw|'))
			{
				$result = substr($empty, 4);
			}
			else
			{
304
				$result = render($empty);
305 306
			}
		}
307

308
		return $result;
309 310 311
	}

	/**
312
	 * Get the evaluated string content of the view.
Taylor Otwell committed
313
	 *
314
	 * @return string
Taylor Otwell committed
315
	 */
316
	public function render()
Taylor Otwell committed
317
	{
318
		Event::fire("laravel.composing: {$this->view}", array($this));
319

320 321 322
		// If there are listeners to the view engine event, we'll pass them
		// the view so they can render it according to their needs, which
		// allows easy attachment of other view parsers.
323
		if (Event::listeners(static::engine))
324
		{
325 326 327
			$result = Event::first(static::engine, array($this));

			if ($result !== false) return $result;
328
		}
329 330

		return $this->get();
331 332 333 334 335 336 337 338 339 340
	}

	/**
	 * Get the evaluated contents of the view.
	 *
	 * @return string
	 */
	public function get()
	{
		$__data = $this->data();
341

342 343 344
		// The contents of each view file is cached in an array for the
		// request since partial views may be rendered inside of for
		// loops which could incur performance penalties.
345
		$__contents = $this->load();
346

347
		ob_start() and extract($__data, EXTR_SKIP);
348

349 350
		// We'll include the view contents for parsing within a catcher
		// so we can avoid any WSOD errors. If an exception occurs we
Taylor Otwell committed
351
		// will throw it out to the exception handler.
352
		try
353
		{
354
			eval('?>'.$__contents);
355 356
		}

357 358
		// If we caught an exception, we'll silently flush the output
		// buffer so that no partially rendered views get thrown out
359
		// to the client and confuse the user with junk.
360 361 362 363
		catch (\Exception $e)
		{
			ob_get_clean(); throw $e;
		}
364 365

		return ob_get_clean();
Taylor Otwell committed
366 367 368
	}

	/**
369 370 371 372 373 374 375 376 377 378 379 380
	 * Get the contents of the view file from disk.
	 *
	 * @return string
	 */
	protected function load()
	{
		if (isset(static::$cache[$this->path]))
		{
			return static::$cache[$this->path];
		}
		else
		{
Taylor Otwell committed
381
			return static::$cache[$this->path] = file_get_contents($this->path);
382 383 384 385
		}
	}

	/**
386
	 * Get the array of view data for the view instance.
387
	 *
388
	 * The shared view data will be combined with the view data.
389 390
	 *
	 * @return array
391
	 */
392
	public function data()
393
	{
394
		$data = array_merge($this->data, static::$shared);
Taylor Otwell committed
395

396
		// All nested views and responses are evaluated before the main view.
397
		// This allows the assets used by nested views to be added to the
Taylor Otwell committed
398
		// asset container before the main view is evaluated.
399
		foreach ($data as $key => $value) 
400
		{
401
			if ($value instanceof View or $value instanceof Response)
402
			{
403
				$data[$key] = $value->render();
404
			}
405 406
		}

407
		return $data;
408 409 410
	}

	/**
Taylor Otwell committed
411
	 * Add a view instance to the view data.
412
	 *
413 414
	 * <code>
	 *		// Add a view instance to a view's data
415
	 *		$view = View::make('foo')->nest('footer', 'partials.footer');
416 417 418 419 420
	 *
	 *		// Equivalent functionality using the "with" method
	 *		$view = View::make('foo')->with('footer', View::make('partials.footer'));
	 * </code>
	 *
421 422 423 424 425
	 * @param  string  $key
	 * @param  string  $view
	 * @param  array   $data
	 * @return View
	 */
426
	public function nest($key, $view, $data = array())
427
	{
428
		return $this->with($key, static::make($view, $data));
429 430 431
	}

	/**
432 433
	 * Add a key / value pair to the view data.
	 *
434 435
	 * Bound data will be available to the view as variables.
	 *
436 437 438 439
	 * @param  string  $key
	 * @param  mixed   $value
	 * @return View
	 */
440
	public function with($key, $value = null)
441
	{
442 443 444 445 446 447 448 449 450
		if (is_array($key))
		{
			$this->data = array_merge($this->data, $key);
		}
		else
		{
			$this->data[$key] = $value;
		}

451 452 453 454
		return $this;
	}

	/**
455 456 457 458 459 460
	 * Add a key / value pair to the shared view data.
	 *
	 * Shared view data is accessible to every view created by the application.
	 *
	 * @param  string  $key
	 * @param  mixed   $value
461 462 463 464 465 466 467 468 469 470 471 472 473 474 475
	 * @return View
	 */
	public function shares($key, $value)
	{
		static::share($key, $value);
		return $this;
	}

	/**
	 * Add a key / value pair to the shared view data.
	 *
	 * Shared view data is accessible to every view created by the application.
	 *
	 * @param  string  $key
	 * @param  mixed   $value
476
	 * @return void
477
	 */
478
	public static function share($key, $value)
479
	{
480
		static::$shared[$key] = $value;
481 482 483
	}

	/**
484
	 * Implementation of the ArrayAccess offsetExists method.
485
	 */
486
	public function offsetExists($offset)
487
	{
488
		return array_key_exists($offset, $this->data);
489 490 491
	}

	/**
492
	 * Implementation of the ArrayAccess offsetGet method.
493
	 */
494
	public function offsetGet($offset)
495
	{
496
		if (isset($this[$offset])) return $this->data[$offset];
497 498 499
	}

	/**
500
	 * Implementation of the ArrayAccess offsetSet method.
501
	 */
502
	public function offsetSet($offset, $value)
503
	{
504
		$this->data[$offset] = $value;
505 506
	}

507
	/**
508
	 * Implementation of the ArrayAccess offsetUnset method.
509
	 */
510
	public function offsetUnset($offset)
511
	{
512
		unset($this->data[$offset]);
513 514 515
	}

	/**
516 517 518 519
	 * Magic Method for handling dynamic data access.
	 */
	public function __get($key)
	{
520
		return $this->data[$key];
521 522 523 524 525 526 527
	}

	/**
	 * Magic Method for handling the dynamic setting of data.
	 */
	public function __set($key, $value)
	{
528 529 530 531 532 533 534 535 536
		$this->data[$key] = $value;
	}

	/**
	 * Magic Method for checking dynamically-set data.
	 */
	public function __isset($key)
	{
		return isset($this->data[$key]);
537 538 539
	}

	/**
540
	 * Get the evaluated string content of the view.
541
	 *
542
	 * @return string
543
	 */
544
	public function __toString()
545
	{
546
		return $this->render();
547 548
	}

549
}