1: <?php namespace Laravel; use Closure, ArrayAccess;
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: * The view data.
14: *
15: * @var array
16: */
17: public $data;
18:
19: /**
20: * The path to the view on disk.
21: *
22: * @var string
23: */
24: public $path;
25:
26: /**
27: * All of the shared view data.
28: *
29: * @var array
30: */
31: public static $shared = array();
32:
33: /**
34: * All of the registered view names.
35: *
36: * @var array
37: */
38: public static $names = array();
39:
40: /**
41: * The cache content of loaded view files.
42: *
43: * @var array
44: */
45: public static $cache = array();
46:
47: /**
48: * THe last view to be rendered.
49: *
50: * @var string
51: */
52: public static $last;
53:
54: /**
55: * The render operations taking place.
56: *
57: * @var int
58: */
59: public static $render_count = 0;
60:
61: /**
62: * The Laravel view loader event name.
63: *
64: * @var string
65: */
66: const loader = 'laravel.view.loader';
67:
68: /**
69: * The Laravel view engine event name.
70: *
71: * @var string
72: */
73: const engine = 'laravel.view.engine';
74:
75: /**
76: * Create a new view instance.
77: *
78: * <code>
79: * // Create a new view instance
80: * $view = new View('home.index');
81: *
82: * // Create a new view instance of a bundle's view
83: * $view = new View('admin::home.index');
84: *
85: * // Create a new view instance with bound data
86: * $view = new View('home.index', array('name' => 'Taylor'));
87: * </code>
88: *
89: * @param string $view
90: * @param array $data
91: * @return void
92: */
93: public function __construct($view, $data = array())
94: {
95: $this->view = $view;
96: $this->data = $data;
97:
98: // In order to allow developers to load views outside of the normal loading
99: // conventions, we'll allow for a raw path to be given in place of the
100: // typical view name, giving total freedom on view loading.
101: if (starts_with($view, 'path: '))
102: {
103: $this->path = substr($view, 6);
104: }
105: else
106: {
107: $this->path = $this->path($view);
108: }
109:
110: // If a session driver has been specified, we will bind an instance of the
111: // validation error message container to every view. If an error instance
112: // exists in the session, we will use that instance.
113: if ( ! isset($this->data['errors']))
114: {
115: if (Session::started() and Session::has('errors'))
116: {
117: $this->data['errors'] = Session::get('errors');
118: }
119: else
120: {
121: $this->data['errors'] = new Messages;
122: }
123: }
124: }
125:
126: /**
127: * Determine if the given view exists.
128: *
129: * @param string $view
130: * @param boolean $return_path
131: * @return string|bool
132: */
133: public static function exists($view, $return_path = false)
134: {
135: if (starts_with($view, 'name: ') and array_key_exists($name = substr($view, 6), static::$names))
136: {
137: $view = static::$names[$name];
138: }
139:
140: list($bundle, $view) = Bundle::parse($view);
141:
142: $view = str_replace('.', '/', $view);
143:
144: // We delegate the determination of view paths to the view loader event
145: // so that the developer is free to override and manage the loading
146: // of views in any way they see fit for their application.
147: $path = Event::until(static::loader, array($bundle, $view));
148:
149: if ( ! is_null($path))
150: {
151: return $return_path ? $path : true;
152: }
153:
154: return false;
155: }
156:
157: /**
158: * Get the path to a given view on disk.
159: *
160: * @param string $view
161: * @return string
162: */
163: protected function path($view)
164: {
165: if ($path = $this->exists($view,true))
166: {
167: return $path;
168: }
169:
170: throw new \Exception("View [$view] doesn't exist.");
171: }
172:
173: /**
174: * Get the path to a view using the default folder convention.
175: *
176: * @param string $bundle
177: * @param string $view
178: * @param string $directory
179: * @return string
180: */
181: public static function file($bundle, $view, $directory)
182: {
183: $directory = str_finish($directory, DS);
184:
185: // Views may have either the default PHP file extension or the "Blade"
186: // extension, so we will need to check for both in the view path
187: // and return the first one we find for the given view.
188: if (file_exists($path = $directory.$view.EXT))
189: {
190: return $path;
191: }
192: elseif (file_exists($path = $directory.$view.BLADE_EXT))
193: {
194: return $path;
195: }
196: }
197:
198: /**
199: * Create a new view instance.
200: *
201: * <code>
202: * // Create a new view instance
203: * $view = View::make('home.index');
204: *
205: * // Create a new view instance of a bundle's view
206: * $view = View::make('admin::home.index');
207: *
208: * // Create a new view instance with bound data
209: * $view = View::make('home.index', array('name' => 'Taylor'));
210: * </code>
211: *
212: * @param string $view
213: * @param array $data
214: * @return View
215: */
216: public static function make($view, $data = array())
217: {
218: return new static($view, $data);
219: }
220:
221: /**
222: * Create a new view instance of a named view.
223: *
224: * <code>
225: * // Create a new named view instance
226: * $view = View::of('profile');
227: *
228: * // Create a new named view instance with bound data
229: * $view = View::of('profile', array('name' => 'Taylor'));
230: * </code>
231: *
232: * @param string $name
233: * @param array $data
234: * @return View
235: */
236: public static function of($name, $data = array())
237: {
238: return new static(static::$names[$name], $data);
239: }
240:
241: /**
242: * Assign a name to a view.
243: *
244: * <code>
245: * // Assign a name to a view
246: * View::name('partials.profile', 'profile');
247: *
248: * // Resolve an instance of a named view
249: * $view = View::of('profile');
250: * </code>
251: *
252: * @param string $view
253: * @param string $name
254: * @return void
255: */
256: public static function name($view, $name)
257: {
258: static::$names[$name] = $view;
259: }
260:
261: /**
262: * Register a view composer with the Event class.
263: *
264: * <code>
265: * // Register a composer for the "home.index" view
266: * View::composer('home.index', function($view)
267: * {
268: * $view['title'] = 'Home';
269: * });
270: * </code>
271: *
272: * @param string|array $views
273: * @param Closure $composer
274: * @return void
275: */
276: public static function composer($views, $composer)
277: {
278: $views = (array) $views;
279:
280: foreach ($views as $view)
281: {
282: Event::listen("laravel.composing: {$view}", $composer);
283: }
284: }
285:
286: /**
287: * Get the rendered contents of a partial from a loop.
288: *
289: * @param string $view
290: * @param array $data
291: * @param string $iterator
292: * @param string $empty
293: * @return string
294: */
295: public static function render_each($view, array $data, $iterator, $empty = 'raw|')
296: {
297: $result = '';
298:
299: // If is actually data in the array, we will loop through the data and
300: // append an instance of the partial view to the final result HTML,
301: // passing in the iterated value of the data array.
302: if (count($data) > 0)
303: {
304: foreach ($data as $key => $value)
305: {
306: $with = array('key' => $key, $iterator => $value);
307:
308: $result .= render($view, $with);
309: }
310: }
311:
312: // If there is no data in the array, we will render the contents of
313: // the "empty" view. Alternatively, the "empty view" can be a raw
314: // string that is prefixed with "raw|" for convenience.
315: else
316: {
317: if (starts_with($empty, 'raw|'))
318: {
319: $result = substr($empty, 4);
320: }
321: else
322: {
323: $result = render($empty);
324: }
325: }
326:
327: return $result;
328: }
329:
330: /**
331: * Get the evaluated string content of the view.
332: *
333: * @return string
334: */
335: public function render()
336: {
337: static::$render_count++;
338:
339: Event::fire("laravel.composing: {$this->view}", array($this));
340:
341: $contents = null;
342:
343: // If there are listeners to the view engine event, we'll pass them
344: // the view so they can render it according to their needs, which
345: // allows easy attachment of other view parsers.
346: if (Event::listeners(static::engine))
347: {
348: $result = Event::until(static::engine, array($this));
349:
350: if ( ! is_null($result)) $contents = $result;
351: }
352:
353: if (is_null($contents)) $contents = $this->get();
354:
355: static::$render_count--;
356:
357: if (static::$render_count == 0)
358: {
359: Section::$sections = array();
360: }
361:
362: return $contents;
363: }
364:
365: /**
366: * Get the evaluated contents of the view.
367: *
368: * @return string
369: */
370: public function get()
371: {
372: $__data = $this->data();
373:
374: // The contents of each view file is cached in an array for the
375: // request since partial views may be rendered inside of for
376: // loops which could incur performance penalties.
377: $__contents = $this->load();
378:
379: ob_start() and extract($__data, EXTR_SKIP);
380:
381: // We'll include the view contents for parsing within a catcher
382: // so we can avoid any WSOD errors. If an exception occurs we
383: // will throw it out to the exception handler.
384: try
385: {
386: eval('?>'.$__contents);
387: }
388:
389: // If we caught an exception, we'll silently flush the output
390: // buffer so that no partially rendered views get thrown out
391: // to the client and confuse the user with junk.
392: catch (\Exception $e)
393: {
394: ob_get_clean(); throw $e;
395: }
396:
397: $content = ob_get_clean();
398:
399: // The view filter event gives us a last chance to modify the
400: // evaluated contents of the view and return them. This lets
401: // us do something like run the contents through Jade, etc.
402: if (Event::listeners('view.filter'))
403: {
404: return Event::first('view.filter', array($content, $this->path));
405: }
406:
407: return $content;
408: }
409:
410: /**
411: * Get the contents of the view file from disk.
412: *
413: * @return string
414: */
415: protected function load()
416: {
417: static::$last = array('name' => $this->view, 'path' => $this->path);
418:
419: if (isset(static::$cache[$this->path]))
420: {
421: return static::$cache[$this->path];
422: }
423: else
424: {
425: return static::$cache[$this->path] = file_get_contents($this->path);
426: }
427: }
428:
429: /**
430: * Get the array of view data for the view instance.
431: *
432: * The shared view data will be combined with the view data.
433: *
434: * @return array
435: */
436: public function data()
437: {
438: $data = array_merge($this->data, static::$shared);
439:
440: // All nested views and responses are evaluated before the main view.
441: // This allows the assets used by nested views to be added to the
442: // asset container before the main view is evaluated.
443: foreach ($data as $key => $value)
444: {
445: if ($value instanceof View or $value instanceof Response)
446: {
447: $data[$key] = $value->render();
448: }
449: }
450:
451: return $data;
452: }
453:
454: /**
455: * Add a view instance to the view data.
456: *
457: * <code>
458: * // Add a view instance to a view's data
459: * $view = View::make('foo')->nest('footer', 'partials.footer');
460: *
461: * // Equivalent functionality using the "with" method
462: * $view = View::make('foo')->with('footer', View::make('partials.footer'));
463: * </code>
464: *
465: * @param string $key
466: * @param string $view
467: * @param array $data
468: * @return View
469: */
470: public function nest($key, $view, $data = array())
471: {
472: return $this->with($key, static::make($view, $data));
473: }
474:
475: /**
476: * Add a key / value pair to the view data.
477: *
478: * Bound data will be available to the view as variables.
479: *
480: * @param string $key
481: * @param mixed $value
482: * @return View
483: */
484: public function with($key, $value = null)
485: {
486: if (is_array($key))
487: {
488: $this->data = array_merge($this->data, $key);
489: }
490: else
491: {
492: $this->data[$key] = $value;
493: }
494:
495: return $this;
496: }
497:
498: /**
499: * Add a key / value pair to the shared view data.
500: *
501: * Shared view data is accessible to every view created by the application.
502: *
503: * @param string $key
504: * @param mixed $value
505: * @return View
506: */
507: public function shares($key, $value)
508: {
509: static::share($key, $value);
510: return $this;
511: }
512:
513: /**
514: * Add a key / value pair to the shared view data.
515: *
516: * Shared view data is accessible to every view created by the application.
517: *
518: * @param string $key
519: * @param mixed $value
520: * @return void
521: */
522: public static function share($key, $value)
523: {
524: static::$shared[$key] = $value;
525: }
526:
527: /**
528: * Implementation of the ArrayAccess offsetExists method.
529: */
530: public function offsetExists($offset)
531: {
532: return array_key_exists($offset, $this->data);
533: }
534:
535: /**
536: * Implementation of the ArrayAccess offsetGet method.
537: */
538: public function offsetGet($offset)
539: {
540: if (isset($this[$offset])) return $this->data[$offset];
541: }
542:
543: /**
544: * Implementation of the ArrayAccess offsetSet method.
545: */
546: public function offsetSet($offset, $value)
547: {
548: $this->data[$offset] = $value;
549: }
550:
551: /**
552: * Implementation of the ArrayAccess offsetUnset method.
553: */
554: public function offsetUnset($offset)
555: {
556: unset($this->data[$offset]);
557: }
558:
559: /**
560: * Magic Method for handling dynamic data access.
561: */
562: public function __get($key)
563: {
564: return $this->data[$key];
565: }
566:
567: /**
568: * Magic Method for handling the dynamic setting of data.
569: */
570: public function __set($key, $value)
571: {
572: $this->data[$key] = $value;
573: }
574:
575: /**
576: * Magic Method for checking dynamically-set data.
577: */
578: public function __isset($key)
579: {
580: return isset($this->data[$key]);
581: }
582:
583: /**
584: * Get the evaluated string content of the view.
585: *
586: * @return string
587: */
588: public function __toString()
589: {
590: return $this->render();
591: }
592:
593: /**
594: * Magic Method for handling dynamic functions.
595: *
596: * This method handles calls to dynamic with helpers.
597: */
598: public function __call($method, $parameters)
599: {
600: if (strpos($method, 'with_') === 0)
601: {
602: $key = substr($method, 5);
603: return $this->with($key, $parameters[0]);
604: }
605:
606: throw new \Exception("Method [$method] is not defined on the View class.");
607: }
608:
609: }