view.php 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307
  1. <?php namespace Laravel; use Closure;
  2. class View {
  3. /**
  4. * The name of the view.
  5. *
  6. * @var string
  7. */
  8. public $view;
  9. /**
  10. * The view data.
  11. *
  12. * @var array
  13. */
  14. public $data;
  15. /**
  16. * The path to the view on disk.
  17. *
  18. * @var string
  19. */
  20. protected $path;
  21. /**
  22. * All of the view composers for the application.
  23. *
  24. * @var array
  25. */
  26. protected static $composers;
  27. /**
  28. * Create a new view instance.
  29. *
  30. * @param string $view
  31. * @param array $data
  32. * @return void
  33. */
  34. public function __construct($view, $data = array())
  35. {
  36. $this->view = $view;
  37. $this->data = $data;
  38. $this->path = $this->path($view);
  39. }
  40. /**
  41. * Get the path to a given view on disk.
  42. *
  43. * The view may be either a "normal" view or a "Blade" view, so we will
  44. * need to check the view directory for either extension.
  45. *
  46. * @param string $view
  47. * @return string
  48. */
  49. protected function path($view)
  50. {
  51. $view = str_replace('.', '/', $view);
  52. foreach (array(EXT, BLADE_EXT) as $extension)
  53. {
  54. if (file_exists($path = VIEW_PATH.$view.$extension)) return $path;
  55. }
  56. throw new \Exception("View [$view] does not exist.");
  57. }
  58. /**
  59. * Create a new view instance.
  60. *
  61. * The name of the view given to this method should correspond to a view
  62. * within your application views directory. Dots or slashes may used to
  63. * reference views within sub-directories.
  64. *
  65. * <code>
  66. * // Create a new view instance
  67. * $view = View::make('home.index');
  68. *
  69. * // Create a new view instance with bound data
  70. * $view = View::make('home.index', array('name' => 'Taylor'));
  71. * </code>
  72. *
  73. * @param string $view
  74. * @param array $data
  75. * @return View
  76. */
  77. public static function make($view, $data = array())
  78. {
  79. return new static($view, $data);
  80. }
  81. /**
  82. * Create a new view instance from a view name.
  83. *
  84. * View names are defined in the application composers file.
  85. *
  86. * <code>
  87. * // Create an instance of the "layout" named view
  88. * $view = View::of('layout');
  89. *
  90. * // Create an instance of the "layout" view with bound data
  91. * $view = View::of('layout', array('name' => 'Taylor'));
  92. * </code>
  93. *
  94. * @param string $name
  95. * @param array $data
  96. * @return View
  97. */
  98. public static function of($name, $data = array())
  99. {
  100. if ( ! is_null($view = static::name($name))) return static::make($view, $data);
  101. throw new \Exception("Named view [$name] is not defined.");
  102. }
  103. /**
  104. * Find the key for a view by name.
  105. *
  106. * The view's key can be used to create instances of the view through the typical
  107. * methods available on the view factory.
  108. *
  109. * @param string $name
  110. * @return string
  111. */
  112. protected static function name($name)
  113. {
  114. if (is_null(static::$composers)) static::$composers = require APP_PATH.'composers'.EXT;
  115. // The view's name may specified in several different ways in the composers file.
  116. // The composer may simple have a string value, which is the name. Or, the composer
  117. // could have an array value in which a "name" key exists.
  118. foreach (static::$composers as $key => $value)
  119. {
  120. if ($name === $value or (is_array($value) and $name === Arr::get($value, 'name'))) return $key;
  121. }
  122. }
  123. /**
  124. * Call the composer for the view instance.
  125. *
  126. * @param View $view
  127. * @return void
  128. */
  129. protected static function compose(View $view)
  130. {
  131. if (is_null(static::$composers)) static::$composers = require APP_PATH.'composers'.EXT;
  132. // The shared composer is called for every view instance. This allows the
  133. // convenient binding of global view data or partials within a single method.
  134. if (isset(static::$composers['shared'])) call_user_func(static::$composers['shared'], $view);
  135. if (isset(static::$composers[$view->view]))
  136. {
  137. foreach ((array) static::$composers[$view->view] as $key => $value)
  138. {
  139. if ($value instanceof Closure) return call_user_func($value, $view);
  140. }
  141. }
  142. }
  143. /**
  144. * Get the evaluated string content of the view.
  145. *
  146. * @return string
  147. */
  148. public function render()
  149. {
  150. static::compose($this);
  151. // All nested views and responses are evaluated before the main view. This allows
  152. // the assets used by these views to be added to the asset container before the
  153. // main view is evaluated and dumps the links to the assets.
  154. foreach ($this->data as &$data)
  155. {
  156. if ($data instanceof View or $data instanceof Response) $data = $data->render();
  157. }
  158. // We don't want the view's contents to be rendered immediately, so we will fire
  159. // up an output buffer to catch the view output. The output of the view will be
  160. // rendered automatically later in the request lifecycle.
  161. ob_start() and extract($this->data, EXTR_SKIP);
  162. // If the view is a "Blade" view, we need to check the view for modifications
  163. // and get the path to the compiled view file. Otherwise, we'll just use the
  164. // regular path to the view.
  165. $view = (strpos($this->path, BLADE_EXT) !== false) ? $this->compile() : $this->path;
  166. try { include $view; } catch (Exception $e) { ob_get_clean(); throw $e; }
  167. return ob_get_clean();
  168. }
  169. /**
  170. * Compile the Bladed view and return the path to the compiled view.
  171. *
  172. * @return string
  173. */
  174. protected function compile()
  175. {
  176. // For simplicity, compiled views are stored in a single directory by the MD5 hash of
  177. // their name. This allows us to avoid recreating the entire view directory structure
  178. // within the compiled views directory.
  179. $compiled = STORAGE_PATH.'views/'.md5($this->view);
  180. // The view will only be re-compiled if the view has been modified since the last compiled
  181. // version of the view was created or no compiled view exists. Otherwise, the path will
  182. // be returned without re-compiling.
  183. if ((file_exists($compiled) and filemtime($this->path) > filemtime($compiled)) or ! file_exists($compiled))
  184. {
  185. file_put_contents($compiled, Blade::parse($this->path));
  186. }
  187. return $compiled;
  188. }
  189. /**
  190. * Add a view instance to the view data.
  191. *
  192. * <code>
  193. * // Add a view instance to a view's data
  194. * $view = View::make('foo')->nest('footer', 'partials.footer');
  195. *
  196. * // Equivalent functionality using the "with" method
  197. * $view = View::make('foo')->with('footer', View::make('partials.footer'));
  198. *
  199. * // Bind a view instance with data
  200. * $view = View::make('foo')->nest('footer', 'partials.footer', array('name' => 'Taylor'));
  201. * </code>
  202. *
  203. * @param string $key
  204. * @param string $view
  205. * @param array $data
  206. * @return View
  207. */
  208. public function nest($key, $view, $data = array())
  209. {
  210. return $this->with($key, static::make($view, $data));
  211. }
  212. /**
  213. * Add a key / value pair to the view data.
  214. *
  215. * Bound data will be available to the view as variables.
  216. *
  217. * @param string $key
  218. * @param mixed $value
  219. * @return View
  220. */
  221. public function with($key, $value)
  222. {
  223. $this->data[$key] = $value;
  224. return $this;
  225. }
  226. /**
  227. * Magic Method for getting items from the view data.
  228. */
  229. public function __get($key)
  230. {
  231. return $this->data[$key];
  232. }
  233. /**
  234. * Magic Method for setting items in the view data.
  235. */
  236. public function __set($key, $value)
  237. {
  238. $this->with($key, $value);
  239. }
  240. /**
  241. * Magic Method for determining if an item is in the view data.
  242. */
  243. public function __isset($key)
  244. {
  245. return array_key_exists($key, $this->data);
  246. }
  247. /**
  248. * Magic Method for removing an item from the view data.
  249. */
  250. public function __unset($key)
  251. {
  252. unset($this->data[$key]);
  253. }
  254. /**
  255. * Magic Method for handling the dynamic creation of named views.
  256. *
  257. * <code>
  258. * // Create an instance of the "layout" named view
  259. * $view = View::of_layout();
  260. *
  261. * // Create an instance of a named view with data
  262. * $view = View::of_layout(array('name' => 'Taylor'));
  263. * </code>
  264. */
  265. public static function __callStatic($method, $parameters)
  266. {
  267. if (strpos($method, 'of_') === 0)
  268. {
  269. return static::of(substr($method, 3), Arr::get($parameters, 0, array()));
  270. }
  271. }
  272. }