view.php 8.84 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
	 * Create a new view instance.
42
	 *
43 44 45 46 47 48 49 50 51 52 53
	 * <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>
	 *
54 55
	 * @param  string  $view
	 * @param  array   $data
56 57
	 * @return void
	 */
58
	public function __construct($view, $data = array())
59
	{
60 61 62
		$this->view = $view;
		$this->data = $data;
		$this->path = $this->path($view);
Taylor Otwell committed
63

64 65 66
		// If a session driver has been specified, we will bind an instance of the
		// validation error message container to every view. If an errors instance
		// exists in the session, we will use that instance.
Taylor Otwell committed
67
		//
68 69 70
		// This makes error display in the view extremely convenient, since the
		// developer can always assume they have a message container instance
		// available to them in the view.
71
		if ( ! isset($this->data['errors']))
Taylor Otwell committed
72
		{
73
			if (Session::started() and Session::has('errors'))
Taylor Otwell committed
74
			{
75 76 77 78 79 80
				$this->data['errors'] = Session::get('errors');
			}
			else
			{
				$this->data['errors'] = new Messages;
			}
Taylor Otwell committed
81
		}
82 83 84 85 86 87 88 89 90 91 92 93
	}

	/**
	 * Get the path to a given view on disk.
	 *
	 * @param  string  $view
	 * @return string
	 */
	protected function path($view)
	{
		$view = str_replace('.', '/', $view);

94 95 96 97 98 99
		$root = Bundle::path(Bundle::name($view)).'views/';

		// Views may have the normal PHP extension or the Blade PHP extension, so
		// we need to check if either of them exist in the base views directory
		// for the bundle. We'll check for the PHP extension first since that
		// is probably the more common of the two.
100 101
		foreach (array(EXT, BLADE_EXT) as $extension)
		{
102
			if (file_exists($path = $root.Bundle::element($view).$extension))
103 104 105
			{
				return $path;
			}
106 107
		}

108
		throw new \Exception("View [$view] does not exist.");
109 110 111
	}

	/**
112 113
	 * Create a new view instance.
	 *
114 115 116 117
	 * <code>
	 *		// Create a new view instance
	 *		$view = View::make('home.index');
	 *
118 119 120
	 *		// Create a new view instance of a bundle's view
	 *		$view = View::make('admin::home.index');
	 *
121 122 123 124
	 *		// Create a new view instance with bound data
	 *		$view = View::make('home.index', array('name' => 'Taylor'));
	 * </code>
	 *
Taylor Otwell committed
125 126
	 * @param  string  $view
	 * @param  array   $data
127 128
	 * @return View
	 */
129
	public static function make($view, $data = array())
130
	{
131
		return new static($view, $data);
132 133 134
	}

	/**
135
	 * Create a new view instance of a named view.
136
	 *
137
	 * <code>
138 139
	 *		// Create a new named view instance
	 *		$view = View::of('profile');
140
	 *
141 142
	 *		// Create a new named view instance with bound data
	 *		$view = View::of('profile', array('name' => 'Taylor'));
143 144
	 * </code>
	 *
145 146 147 148
	 * @param  string  $name
	 * @param  array   $data
	 * @return View
	 */
149
	public static function of($name, $data = array())
150
	{
151
		return new static(static::$names[$name], $data);
152 153 154
	}

	/**
155 156 157 158 159
	 * Assign a name to a view.
	 *
	 * <code>
	 *		// Assign a name to a view
	 *		View::name('partials.profile', 'profile');
Taylor Otwell committed
160
	 *
161 162 163
	 *		// Resolve an instance of a named view
	 *		$view = View::of('profile');
	 * </code>
164
	 *
165
	 * @param  string  $view
Taylor Otwell committed
166
	 * @param  string  $name
167
	 * @return void
Taylor Otwell committed
168
	 */
169
	public static function name($view, $name)
Taylor Otwell committed
170
	{
171
		static::$names[$name] = $view;
Taylor Otwell committed
172 173 174
	}

	/**
175
	 * Register a view composer with the Event class.
Taylor Otwell committed
176
	 *
177 178 179 180 181 182 183 184 185 186
	 * <code>
	 *		// Register a composer for the "home.index" view
	 *		View::composer('home.index', function($view)
	 *		{
	 *			$view['title'] = 'Home';
	 *		});
	 * </code>
	 *
	 * @param  string   $view
	 * @param  Closure  
Taylor Otwell committed
187 188
	 * @return void
	 */
189
	public static function composer($view, $composer)
Taylor Otwell committed
190
	{
191
		Event::listen("composing: {$view}", $composer);
Taylor Otwell committed
192 193 194
	}

	/**
195
	 * Get the evaluated string content of the view.
Taylor Otwell committed
196
	 *
197
	 * @return string
Taylor Otwell committed
198
	 */
199
	public function render()
Taylor Otwell committed
200
	{
201 202 203 204 205 206
		// To allow bundles or other pieces of the application to modify the
		// view before it is rendered, we will fire an event, passing in the
		// view instance so it can modified by any of the listeners.
		Event::fire("composing: {$this->view}", array($this));

		$data = $this->data();
207

208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224
		ob_start() and extract($data, EXTR_SKIP);

		// If the view is Bladed, we need to check the view for changes and
		// get the path to the compiled view file. Otherwise, we'll just
		// use the regular path to the view.
		//
		// Also, if the Blade view has expired or doesn't exist it will be
		// re-compiled and placed in the view storage directory. The Blade
		// views are re-compiled each time the original view is changed.
		if (strpos($this->path, BLADE_EXT) !== false)
		{
			$this->path = $this->compile();
		}

		try {include $this->path;} catch(\Exception $e) {ob_get_clean(); throw $e;}

		return ob_get_clean();
Taylor Otwell committed
225 226 227
	}

	/**
228
	 * Get the array of view data for the view instance.
229
	 *
230 231 232
	 * The shared view data will be combined with the view data for the instance.
	 *
	 * @return array
233
	 */
234
	protected function data()
235
	{
236
		$data = array_merge($this->data, static::$shared);
Taylor Otwell committed
237

238
		// All nested views and responses are evaluated before the main view.
239 240 241 242
		// This allows the assets used by nested views to be added to the
		// asset container before the main view is evaluated and dumps
		// the links to the assets into the HTML.
		foreach ($data as &$value) 
243
		{
244
			if ($value instanceof View or $value instanceof Response)
245
			{
246
				$value = $value->render();
247
			}
248 249
		}

250
		return $data;
251 252 253
	}

	/**
254
	 * Get the path to the compiled version of the Blade view.
255
	 *
Taylor Otwell committed
256 257 258 259
	 * @return string
	 */
	protected function compile()
	{
260 261 262 263 264
		// Compiled views are stored in the storage directory using the MD5
		// hash of their path. This allows us to easily store the views in
		// the directory without worrying about re-creating the entire
		// application view directory structure.
		$compiled = STORAGE_PATH.'views/'.md5($this->path);
Taylor Otwell committed
265

266 267 268
		// The view will only be re-compiled if the view has been modified
		// since the last compiled version of the view was created or no
		// compiled view exists. Otherwise, the path will be returned
269
		// without re-compiling the view.
270
		if ( ! file_exists($compiled) or (filemtime($this->path) > filemtime($compiled)))
Taylor Otwell committed
271
		{
272
			file_put_contents($compiled, Blade::compile($this->path));
Taylor Otwell committed
273 274
		}

275
		return $compiled;
Taylor Otwell committed
276 277 278 279
	}

	/**
	 * Add a view instance to the view data.
280
	 *
281 282
	 * <code>
	 *		// Add a view instance to a view's data
283
	 *		$view = View::make('foo')->nest('footer', 'partials.footer');
284 285 286 287 288
	 *
	 *		// Equivalent functionality using the "with" method
	 *		$view = View::make('foo')->with('footer', View::make('partials.footer'));
	 * </code>
	 *
289 290 291 292 293
	 * @param  string  $key
	 * @param  string  $view
	 * @param  array   $data
	 * @return View
	 */
294
	public function nest($key, $view, $data = array())
295
	{
296
		return $this->with($key, static::make($view, $data));
297 298 299
	}

	/**
300 301
	 * Add a key / value pair to the view data.
	 *
302 303
	 * Bound data will be available to the view as variables.
	 *
304 305 306 307
	 * @param  string  $key
	 * @param  mixed   $value
	 * @return View
	 */
308
	public function with($key, $value)
309 310 311 312 313 314
	{
		$this->data[$key] = $value;
		return $this;
	}

	/**
315 316 317 318 319 320 321
	 * 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
	 * @return void
322
	 */
323
	public static function share($key, $value)
324
	{
325
		static::$shared[$key] = $value;
326 327 328
	}

	/**
329
	 * Implementation of the ArrayAccess offsetExists method.
330
	 */
331
	public function offsetExists($offset)
332
	{
333
		return array_key_exists($offset, $this->data);
334 335 336
	}

	/**
337
	 * Implementation of the ArrayAccess offsetGet method.
338
	 */
339
	public function offsetGet($offset)
340
	{
341
		if (isset($this[$offset])) return $this->data[$offset];
342 343 344
	}

	/**
345
	 * Implementation of the ArrayAccess offsetSet method.
346
	 */
347
	public function offsetSet($offset, $value)
348
	{
349
		$this->data[$offset] = $value;
350 351
	}

352
	/**
353
	 * Implementation of the ArrayAccess offsetUnset method.
354
	 */
355
	public function offsetUnset($offset)
356
	{
357
		unset($this->data[$offset]);
358 359 360
	}

	/**
361 362 363 364
	 * Magic Method for handling dynamic data access.
	 */
	public function __get($key)
	{
365
		return $this->data[$key];
366 367 368 369 370 371 372
	}

	/**
	 * Magic Method for handling the dynamic setting of data.
	 */
	public function __set($key, $value)
	{
373 374 375 376 377 378 379 380 381
		$this->data[$key] = $value;
	}

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

	/**
385
	 * Get the evaluated string content of the view.
386
	 *
387
	 * @return string
388
	 */
389
	public function __toString()
390
	{
391
		return $this->render();
392 393
	}

394
}