123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468 |
- <?php
- /**
- * Lithium: the most rad php framework
- *
- * @copyright Copyright 2013, Union of RAD (http://union-of-rad.org)
- * @license http://opensource.org/licenses/bsd-license.php The BSD License
- */
- namespace lithium\template;
- use lithium\core\Libraries;
- use lithium\template\TemplateException;
- /**
- * As one of the three pillars of the Model-View-Controller design pattern, the `View` class
- * (along with other supporting classes) is responsible for taking the data passed from the
- * request and/or controller, inserting this into the requested template/layout, and then returning
- * the rendered content.
- *
- * The `View` class interacts with a variety of other classes in order to achieve maximum
- * flexibility and configurability at all points in the view rendering and presentation
- * process. The `Loader` class is tasked with locating and reading template files which are then
- * passed to the `Renderer` adapter subclass.
- *
- * In the default configuration, the `File` adapter acts as both renderer and loader, loading files
- * from paths defined in _process steps_ (described below) and rendering them as plain PHP files,
- * augmented with [special syntax](../template).
- *
- * The `View` class operates on _processes_, which define the steps to render a completed view. For
- * example, the default process, which renders a template wrapped in a layout, is comprised of two
- * _steps_: the first step renders the main template and captures it to the rendering context, where
- * it is embedded in the layout in the second step. See the `$_steps` and `$_processes` properties
- * for more information.
- *
- * Using steps and processes, you can create rendering scenarios to suit very complex needs.
- *
- * By default, the `View` class is called during the course of the framework's dispatch cycle by the
- * `Media` class. However, it is also possible to instantiate and call `View` directly, in cases
- * where you wish to bypass all other parts of the framework and simply return rendered content.
- *
- * A simple example, using the `Simple` renderer/loader for string templates:
- *
- * {{{
- * $view = new View(array('loader' => 'Simple', 'renderer' => 'Simple'));
- * echo $view->render('element', array('name' => "Robert"), array('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(array(
- * 'paths' => array(
- * 'template' => '{:library}/views/errors/{:template}.{:type}.php',
- * 'layout' => '{:library}/views/layouts/{:layout}.{:type}.php',
- * )
- * ));
- *
- * $page = $View->render('all', array('content' => $info), array(
- * '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\Object {
- /**
- * Output filters for view rendering.
- *
- * @var array List of filters.
- */
- public $outputFilters = array();
- /**
- * 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 objet 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 = array(
- 'all' => array('template', 'layout'),
- 'template' => array('template'),
- 'element' => array('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' => '<var-name>')` or
- * `array('data' => '<var-name>')`. 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 = array(
- 'template' => array('path' => 'template', 'capture' => array('context' => 'content')),
- 'layout' => array(
- 'path' => 'layout', 'conditions' => 'layout', 'multi' => true, 'capture' => array(
- 'context' => 'content'
- )
- ),
- 'element' => array('path' => 'element')
- );
- /**
- * Auto-configuration parameters.
- *
- * @var array Objects to auto-configure.
- */
- protected $_autoConfig = array(
- '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.
- */
- public function __construct(array $config = array()) {
- $defaults = array(
- 'request' => null,
- 'response' => null,
- 'loader' => 'File',
- 'renderer' => 'File',
- 'steps' => array(),
- 'processes' => array(),
- 'outputFilters' => array()
- );
- 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 (array('loader', 'renderer') as $key) {
- if (is_object($this->_config[$key])) {
- $this->{'_' . $key} = $this->_config[$key];
- continue;
- }
- $class = $this->_config[$key];
- $config = array('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(), array $options = array()) {
- $defaults = array(
- 'type' => 'html',
- 'layout' => null,
- 'template' => null,
- 'context' => array(),
- 'paths' => array(),
- 'data' => array()
- );
- $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 = array()) {
- $step += array('path' => null, 'capture' => null);
- $_renderer = $this->_renderer;
- $_loader = $this->_loader;
- $filters = $this->outputFilters;
- $params = compact('step', 'params', 'options') + array(
- 'data' => $data + $filters,
- 'loader' => $_loader,
- 'renderer' => $_renderer
- );
- $filter = function($self, $params) {
- $template = $params['loader']->template($params['step']['path'], $params['params']);
- return $params['renderer']->render($template, $params['data'], $params['options']);
- };
- $result = $this->_filter(__METHOD__, $params, $filter);
- 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 = array('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 = array();
- 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;
- }
- /**
- * 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.
- *
- * @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) {
- if (count($command) === 1) {
- $params['template'] = current($command);
- return array(array('path' => key($command)) + $defaults);
- }
- return $command;
- }
- }
- ?>
|