'Simple', 'renderer' => 'Simple']); * echo $view->render('element', ['name' => "Robert"], ['element' => 'Hello, {:name}!']); * * // Output: * "Hello, Robert!"; * ``` * * _Note_: This is easily adapted for XML templating. * * Another example, this time of something that could be used in an application * error handler: * * ``` * $view = new View([ * 'paths' => [ * 'template' => '{:library}/views/errors/{:template}.{:type}.php', * 'layout' => '{:library}/views/layouts/{:layout}.{:type}.php', * ] * ]); * * $page = $View->render('all', ['content' => $info], [ * 'template' => '404', * 'layout' => 'error' * ]); * ``` * * To learn more about processes and process steps, see the `$_processes` and `$_steps` properties, * respectively. * * @see lithium\template\view\Renderer * @see lithium\template\view\adapter * @see lithium\net\http\Media */ class View extends \lithium\core\ObjectDeprecated { /** * Output filters for view rendering. * * @var array List of filters. */ public $outputFilters = []; /** * Holds the details of the current request that originated the call to this view, if * applicable. May be empty if this does not apply. For example, if the View class is * created to render an email. * * @see lithium\action\Request * @var object `Request` object instance. */ protected $_request = null; /** * Holds a reference to the `Response` object that will be returned at the end of the current * dispatch cycle. Allows headers and other response attributes to be assigned in the templating * layer. * * @see lithium\action\Response * @var object `Response` object instance. */ protected $_response = null; /** * The object responsible for loading template files. * * @var object Loader object. */ protected $_loader = null; /** * Object responsible for rendering output. * * @var object Renderer object. */ protected $_renderer = null; /** * Path used to look up view loading and rendering adapters. * * @var string */ protected $_adapters = 'adapter.template.view'; /** * View processes are aggregated lists of steps taken to to create a complete, rendered view. * For example, the default process, `'all'`, renders a template, then renders a layout, using * the rendered template content. A process can be defined using one or more steps defined in * the `$_steps` property. Each process definition is a simple array of ordered values, where * each value is a key in the `$_steps` array. * * @see lithium\template\View::$_steps * @see lithium\template\View::render() * @var array */ protected $_processes = [ 'all' => ['template', 'layout'], 'template' => ['template'], 'element' => ['element'] ]; /** * The list of available rendering steps. Each step contains instructions for how to render one * piece of a multi-step view rendering. The `View` class combines multiple steps into * _processes_ to create the final output. * * Each step is named by its key in the `$_steps` array, and can have the following options: * * - `'path'` _string_: Indicates the set of paths to use when loading templates. * * - `'conditions'` _mixed_: Make the step dependent on a value being present, or on some other * arbitrary condition. If a `'conditions'` is a string, it indicates that a key with that * name must be present in the `$options` passed to `render()`, and must be set to a * non-empty value. If a closure, it will be executed with the rendering parameters, and must * return `true` or `false`. In either case, if the condition is satisfied, the step is * processed. Otherwise, it is skipped. See the `_conditions()` method for more information. * * - `'capture'` _array_: If specified, allows the results of this rendering step to be assigned * to a template variable used in subsequent steps, or to the templating context for use in * subsequent steps. If can be specified in the form of `array('context' => '')` or * `array('data' => '')`. If the `'context'` key is used, the results are captured * to the rendering context. Likewise with the `'data'` key, results are captured to a * template variable. * * - `'multi'` _boolean_: If set to `true`, the rendering parameter matching the name of this * step can be an array containing multiple values, in which case this step is executed * multiple times, once for each value of the array. * * @see lithium\template\View::$_processes * @see lithium\template\View::render() * @var array */ protected $_steps = [ 'template' => ['path' => 'template', 'capture' => ['context' => 'content']], 'layout' => [ 'path' => 'layout', 'conditions' => 'layout', 'multi' => true, 'capture' => [ 'context' => 'content' ] ], 'element' => ['path' => 'element'] ]; /** * Auto-configuration parameters. * * @var array Objects to auto-configure. */ protected $_autoConfig = [ 'request', 'response', 'processes' => 'merge', 'steps' => 'merge' ]; /** * Constructor. * * @see lithium\template\View::$_steps * @see lithium\template\View::$_processes * @param array $config Class configuration parameters The available options are: * - `'loader'` _mixed_: Instance or name of the class used for locating and reading * template content. Defaults to `File`, which reads template content from PHP files. * - `'renderer'` _mixed_: Instance or name of the class that populates template * content with the data passed in to the view layer, typically from a controller. * Defaults to `'File'`, which executes templates as standard PHP files, using path * information returned from the `loader` class. Both `loader` and `renderer` * classes are looked up using the `'adapter.template.view'` path, which locates * classes in the `extensions\adapter\template\view` sub-namespace of an application * or plugin. * - `'request'`: The `Request` object to be made available in the templates. * Defaults to `null`. * - `'steps'` _array_: The array of step configurations to add to the built-in * configurations. Will be merged with the defaults, with any configurations passed * in overwriting built-in steps. See the `$_steps` property for more information. * - `'processes'` _array_: The array of process steps to add to the built-in * configurations. Will be merged with the defaults, with any configurations passed * in overwriting built-in processes. See the `$_processes` property for more * information. * - `'outputFilters'` _array_: An array of filters to be used when handling output. By * default, the class is initialized with one filter, `h`, which is used in automatic * output escaping. * @return void */ public function __construct(array $config = []) { $defaults = [ 'request' => null, 'response' => null, 'loader' => 'File', 'renderer' => 'File', 'steps' => [], 'processes' => [], 'outputFilters' => [] ]; parent::__construct($config + $defaults); } /** * Perform initialization of the View. * * Looks up and initializes loader and renderer classes, and initializes the output escape * handler, matching the encoding from the `Response` object. * * @return void */ protected function _init() { parent::_init(); $encoding = 'UTF-8'; if ($this->_response) { $encoding =& $this->_response->encoding; } $h = function($data) use (&$encoding) { return htmlspecialchars((string) $data, ENT_QUOTES, $encoding); }; $this->outputFilters += compact('h') + $this->_config['outputFilters']; foreach (['loader', 'renderer'] as $key) { if (is_object($this->_config[$key])) { $this->{'_' . $key} = $this->_config[$key]; continue; } $class = $this->_config[$key]; $config = ['view' => $this] + $this->_config; $this->{'_' . $key} = Libraries::instance($this->_adapters, $class, $config); } } /** * Executes a named rendering process by running each process step in sequence and aggregating * the results. The `View` class comes with 3 built-in processes: `'all'`, `'template'`, and * `'element'`. The `'all'` process is the default two-step rendered view, where a template is * wrapped in a layout containing a header and footer. * * @see lithium\template\View::_conditions() * @see lithium\template\View::$_processes * @see lithium\template\View::$_steps * @param string $process A named set of rendering steps defined in the `$_processes` array. * @param array $data An associative array of data to be rendered in the set of templates. * @param array $options Options used when rendering. Available keys are as follows: * - `'type'` _string_: The type of content to render. Defaults to `'html'`. * - `'layout'` _string_: The name of the layout to use in the default two-step * rendering process. Defaults to `null`. * - `'template'` _string_: The name of the template to render. Defaults to `null`. * - `'context'` _array_: An associative array of information to inject into the * rendering context. * - `'paths'` _array_: A nested array of paths to use for rendering steps. The * top-level keys should match the `'path'` key in a step configuration (i.e.: * `'template'`, `'layout'`, or `'element'`), and the second level is an array * of path template strings to search (can be a string if there's only one path). * These path strings generally take the following form: * `'{:library}/views/{:controller}/{:template}.{:type}.php'`. These template * strings are specific to the `File` loader, but can take any form useful to the * template loader being used. * @return string Returns the result of the rendering process, typically by rendering a template * first, then rendering a layout (using the default configuration of the `'all'` * process). */ public function render($process, array $data = [], array $options = []) { $defaults = [ 'type' => 'html', 'layout' => null, 'template' => null, 'context' => [], 'paths' => [], 'data' => [] ]; $options += $defaults; $data += $options['data']; $paths = $options['paths']; unset($options['data'], $options['paths']); $params = array_filter($options, function($val) { return $val && is_string($val); }); $result = null; foreach ($this->_process($process, $params) as $name => $step) { if (isset($paths[$name]) && $paths[$name] === false) { continue; } if (!$this->_conditions($step, $params, $data, $options)) { continue; } if ($step['multi'] && isset($options[$name])) { foreach ((array) $options[$name] as $value) { $params[$name] = $value; $result = $this->_step($step, $params, $data, $options); } continue; } $result = $this->_step((array) $step, $params, $data, $options); } return $result; } /** * Evaluates a step condition to determine if the step should be executed. * * @see lithium\template\View::$_steps * @param array $step The array of instructions that define a rendering step. * @param array $params The parameters associated with this rendering operation, as passed to * `render()` (filtered from the `$options` parameter). * @param array $data The associative array of template variables passed to `render()`. * @param array $options The `$options` parameter, as passed to `render()`. * @return boolean Returns `true` if the step should be executed, or `false` if the step should * be skipped. If the step array has a `'conditions'` key which is a string, it checks * to see if the rendering options (`$options`) contain a key of the same name, and if * that key evaluates to `true`. If `'conditions'` is a closure, that closure is * executed with the rendering parameters (`$params`, `$data`, and `$options`), and the * result is determined by the return value of the closure. If a step definition has no * `'conditions'` key, it is always executed. */ protected function _conditions(array $step, array $params, array $data, array $options) { if (!$conditions = $step['conditions']) { return true; } $isCallable = is_callable($conditions) && !is_string($conditions); if ($isCallable && !$conditions($params, $data, $options)) { return false; } if (is_string($conditions) && !(isset($options[$conditions]) && $options[$conditions])) { return false; } return true; } /** * Performs a rendering step. * * @see lithium\template\view\adapter\File::template() * @see lithium\template\view\Renderer::render() * @see lithium\template\view\adapter\File::render() * @param array $step The array defining the step configuration to render. * @param array $params An associative array of string values used in the template lookup * process. See the `$params` argument of `File::template()`. * @param array $data associative array for template data. * @param array $options An associative array of options to pass to the renderer. See the * `$options` parameter of `Renderer::render()` or `File::render()`. * @return string * @filter */ protected function _step(array $step, array $params, array &$data, array &$options = []) { $step += ['path' => null, 'capture' => null]; $_renderer = $this->_renderer; $_loader = $this->_loader; $filters = $this->outputFilters; $params = compact('step', 'params', 'options') + [ 'data' => $data + $filters, 'loader' => $_loader, 'renderer' => $_renderer ]; $result = Filters::run($this, __FUNCTION__, $params, function($params) { $template = $params['loader']->template($params['step']['path'], $params['params']); return $params['renderer']->render($template, $params['data'], $params['options']); }); if (is_array($step['capture'])) { switch (key($step['capture'])) { case 'context': $options['context'][current($step['capture'])] = $result; break; case 'data': $data[current($step['capture'])] = $result; break; } } return $result; } /** * Converts a process name to an array containing the rendering steps to be executed for each * process. * * @param string $process A named set of rendering steps. * @param array $params * @return array A 2-dimensional array that defines the rendering process. The first dimension * is a numerically-indexed array containing each rendering step. The second dimension * represents the parameters for each step. */ protected function _process($process, &$params) { $defaults = ['conditions' => null, 'multi' => false]; if (!is_array($process)) { if (!isset($this->_processes[$process])) { throw new TemplateException("Undefined rendering process '{$process}'."); } $process = $this->_processes[$process]; } if (is_string(key($process))) { return $this->_convertSteps($process, $params, $defaults); } $result = []; foreach ($process as $step) { if (is_array($step)) { $result[] = $step + $defaults; continue; } if (!isset($this->_steps[$step])) { throw new TemplateException("Undefined rendering step '{$step}'."); } $result[$step] = $this->_steps[$step] + $defaults; } return $result; } /* Deprecated / BC */ /** * Handles API backward compatibility by converting an array-based rendering instruction passed * to `render()` as a process, to a set of rendering steps, rewriting any associated rendering * parameters as necessary. * * @deprecated * @param array $command A deprecated rendering instruction, i.e. * `array('template' => '/path/to/template')`. * @param array $params The array of associated rendering parameters, passed by reference. * @param array $defaults Default step rendering options to be merged with the passed rendering * instruction information. * @return array Returns a converted set of rendering steps, to be executed in `render()`. */ protected function _convertSteps(array $command, array &$params, $defaults) { $message = "Deprecated rendering instructions (`['template' => '/path/to/tmpl']`) found. "; $message .= "Please use long syntax (`['template' => ['path' => '/path/to/tmpl']]`). "; $message .= "Rendering instruction was: " . var_export($command, true); trigger_error($message, E_USER_DEPRECATED); if (count($command) === 1) { $params['template'] = current($command); return [['path' => key($command)] + $defaults]; } return $command; } } ?>