<?php namespace Laravel; use Closure; class View { /** * The name of the view. * * @var string */ public $view; /** * The view data. * * @var array */ public $data; /** * The path to the view on disk. * * @var string */ protected $path; /** * All of the view composers for the application. * * @var array */ protected static $composers; /** * Create a new view instance. * * @param string $view * @param array $data * @return void */ public function __construct($view, $data = array()) { $this->view = $view; $this->data = $data; $this->path = $this->path($view); } /** * Get the path to a given view on disk. * * The view may be either a "normal" view or a "Blade" view, so we will * need to check the view directory for either extension. * * @param string $view * @return string */ protected function path($view) { $view = str_replace('.', '/', $view); foreach (array(EXT, BLADE_EXT) as $extension) { if (file_exists($path = VIEW_PATH.$view.$extension)) { return $path; } elseif (file_exists($path = SYS_VIEW_PATH.$view.$extension)) { return $path; } } throw new \Exception("View [$view] does not exist."); } /** * Create a new view instance. * * The name of the view given to this method should correspond to a view * within your application views directory. Dots or slashes may used to * reference views within sub-directories. * * <code> * // Create a new view instance * $view = View::make('home.index'); * * // Create a new view instance with bound data * $view = View::make('home.index', array('name' => 'Taylor')); * </code> * * @param string $view * @param array $data * @return View */ public static function make($view, $data = array()) { return new static($view, $data); } /** * Create a new view instance from a view name. * * View names are defined in the application composers file. * * <code> * // Create an instance of the "layout" named view * $view = View::of('layout'); * * // Create an instance of the "layout" view with bound data * $view = View::of('layout', array('name' => 'Taylor')); * </code> * * @param string $name * @param array $data * @return View */ public static function of($name, $data = array()) { if ( ! is_null($view = static::name($name))) return static::make($view, $data); throw new \Exception("Named view [$name] is not defined."); } /** * Find the key for a view by name. * * The view's key can be used to create instances of the view through the typical * methods available on the view factory. * * @param string $name * @return string */ protected static function name($name) { if (is_null(static::$composers)) static::$composers = require APP_PATH.'composers'.EXT; // The view's name may specified in several different ways in the composers // file. The composer may simple have a string value, which is the name. // Or, it may an array value in which a "name" key exists. foreach (static::$composers as $key => $value) { if ($name === $value or (is_array($value) and $name === Arr::get($value, 'name'))) { return $key; } } } /** * Call the composer for the view instance. * * @param View $view * @return void */ protected static function compose(View $view) { if (is_null(static::$composers)) static::$composers = require APP_PATH.'composers'.EXT; if (isset(static::$composers[$view->view])) { foreach ((array) static::$composers[$view->view] as $key => $value) { if ($value instanceof Closure) return call_user_func($value, $view); } } } /** * Get the evaluated string content of the view. * * @return string */ public function render() { static::compose($this); // All nested views and responses are evaluated before the main view. // This allows the assets used by the nested views to be added to the // asset container before the main view is evaluated and dumps the // links to the assets. foreach ($this->data as &$data) { if ($data instanceof View or $data instanceof Response) { $data = $data->render(); } } ob_start() and extract($this->data, EXTR_SKIP); // If the view is Bladed, we need to check the view for modifications // and get the path to the compiled view file. Otherwise, we'll just // use the regular path to the view. $view = (strpos($this->path, BLADE_EXT) !== false) ? $this->compile() : $this->path; try { include $view; } catch (Exception $e) { ob_get_clean(); throw $e; } return ob_get_clean(); } /** * Compile the Bladed view and return the path to the compiled view. * * @return string */ protected function compile() { // For simplicity, compiled views are stored in a single directory by // the MD5 hash of their name. This allows us to avoid recreating the // entire view directory structure within the compiled views directory. $compiled = STORAGE_PATH.'views/'.md5($this->view); // 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 // without re-compiling. if ((file_exists($compiled) and filemtime($this->path) > filemtime($compiled)) or ! file_exists($compiled)) { file_put_contents($compiled, Blade::parse($this->path)); } return $compiled; } /** * Add a view instance to the view data. * * <code> * // Add a view instance to a view's data * $view = View::make('foo')->nest('footer', 'partials.footer'); * * // Equivalent functionality using the "with" method * $view = View::make('foo')->with('footer', View::make('partials.footer')); * * // Bind a view instance with data * $view = View::make('foo')->nest('footer', 'partials.footer', array('name' => 'Taylor')); * </code> * * @param string $key * @param string $view * @param array $data * @return View */ public function nest($key, $view, $data = array()) { return $this->with($key, static::make($view, $data)); } /** * Add a key / value pair to the view data. * * Bound data will be available to the view as variables. * * @param string $key * @param mixed $value * @return View */ public function with($key, $value) { $this->data[$key] = $value; return $this; } /** * Magic Method for getting items from the view data. */ public function __get($key) { return $this->data[$key]; } /** * Magic Method for setting items in the view data. */ public function __set($key, $value) { $this->with($key, $value); } /** * Magic Method for determining if an item is in the view data. */ public function __isset($key) { return array_key_exists($key, $this->data); } /** * Magic Method for removing an item from the view data. */ public function __unset($key) { unset($this->data[$key]); } /** * Magic Method for handling the dynamic creation of named views. * * <code> * // Create an instance of the "layout" named view * $view = View::of_layout(); * * // Create an instance of a named view with data * $view = View::of_layout(array('name' => 'Taylor')); * </code> */ public static function __callStatic($method, $parameters) { if (strpos($method, 'of_') === 0) { return static::of(substr($method, 3), Arr::get($parameters, 0, array())); } } }