Skip to content
This repository was archived by the owner on Jan 29, 2020. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 72 additions & 16 deletions doc/book/template/zend-view.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,24 +59,80 @@ $templates = new ZendView($zendView);
## Layouts

Unlike the other supported template engines, zend-view does not support layouts
out-of-the-box.
out-of-the-box. Expressive abstracts this fact away, providing two facilities
for doing so:

Layouts are accomplished in one of two ways:
- You may pass a layout template name or `Zend\View\Model\ModelInterface`
instance representing the layout as the second argument to the constructor.
- You may pass a "layout" parameter during rendering, with a value of either a
layout template name or a `Zend\View\Model\ModelInterface`
instance representing the layout. Passing a layout this way will override any
layout provided to the constructor.

- Multiple rendering passes:
In each case, the zend-view implementation will do a depth-first, recursive
render in order to provide content within the selected layout.

```php
$content = $templates->render('blog/entry', [ 'entry' => $entry ]);
$layout = $templates->render('layout/layout', [ 'content' => $content ]);
```
### Layout name passed to constructor

- View models. To accomplish this, you will compose a view model for the
content, and pass it as a value to the layout:
```php
use Zend\Expressive\Template\ZendView;

// Create the engine instance with a layout name:
$zendView = new PhpRenderer(null, 'layout');
```

### Layout view model passed to constructor

```php
use Zend\Expressive\Template\ZendView;
use Zend\View\Model\ViewModel
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

missing ;


// Create the layout view model:
$layout = new ViewModel([
'encoding' => 'utf-8',
'cssPath' => '/css/prod/',
]);
$layout->setTemplate('layout');

// Create the engine instance with the layout:
$zendView = new PhpRenderer(null, $layout);
```

### Provide a layout name when rendering

```php
$content = $templates->render('blog/entry', [
'layout' => 'blog',
'entry' => $entry,
]);
```

### Provide a layout view model when rendering

```php
use Zend\View\Model\ViewModel
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

missing ;


// Create the layout view model:
$layout = new ViewModel([
'encoding' => 'utf-8',
'cssPath' => '/css/blog/',
]);
$layout->setTemplate('layout');

$content = $templates->render('blog/entry', [
'layout' => $layout,
'entry' => $entry,
]);
```

## Recommendations

We recommend the following practices when using the zend-view adapter:

```php
use Zend\View\Model\ViewModel;
$viewModel = new ViewModel(['entry' => $entry]);
$viewModel->setTemplate('blog/entry');
$layout = $templates->render('layout/layout', [ 'content' => $viewModel ]);
```
- If using a layout, create a factory to return the layout view model as a
service; this allows you to inject it into middleware and add variables to it.
- While we support passing the layout as a rendering parameter, be aware that if
you change engines, this may not be supported.
- While you can use alternate resolvers, not all of them will work with the
`addPath()` implementation. As such, we recommend setting up resolvers and
paths only during creation of the template adapter.
16 changes: 16 additions & 0 deletions src/Exception/RenderingException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php
/**
* Zend Framework (http://framework.zend.com/)
*
* @see http://github.com/zendframework/zend-expressive for the canonical source repository
* @copyright Copyright (c) 2015 Zend Technologies USA Inc. (http://www.zend.com)
* @license https://github.com/zendframework/zend-expressive/blob/master/LICENSE.md New BSD License
*/

namespace Zend\Expressive\Exception;

use DomainException;

class RenderingException extends DomainException implements ExceptionInterface
{
}
156 changes: 145 additions & 11 deletions src/Template/ZendView.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@

namespace Zend\Expressive\Template;

use Zend\View\Model\ModelInterface;
use Zend\View\Model\ViewModel;
use Zend\View\Renderer\RendererInterface;
use Zend\View\Renderer\PhpRenderer;
use Zend\View\Resolver\ResolverInterface;
Expand All @@ -22,6 +24,11 @@ class ZendView implements TemplateInterface
{
use ArrayParametersTrait;

/**
* @var ViewModel
*/
private $layout;

/**
* Paths and namespaces data store.
*/
Expand All @@ -30,23 +37,55 @@ class ZendView implements TemplateInterface
/**
* @var RendererInterface
*/
private $template;
private $renderer;

/**
* @var ResolverInterface
*/
private $resolver;

/**
* @param null|RendererInterface $template
* Constructor
*
* Allows specifying the renderer to use (any zend-view renderer is
* allowed), and optionally also the layout.
*
* The layout may be:
*
* - a string layout name
* - a ModelInterface instance representing the layout
*
* If no renderer is provided, a default PhpRenderer instance is created;
* omitting the layout indicates no layout should be used by default when
* rendering.
*
* @param null|RendererInterface $renderer
* @param null|string|ModelInterface $layout
* @throws InvalidArgumentException for invalid $layout types
*/
public function __construct(RendererInterface $template = null)
public function __construct(RendererInterface $renderer = null, $layout = null)
{
if (null === $template) {
$template = $this->createRenderer();
if (null === $renderer) {
$renderer = $this->createRenderer();
}
$this->renderer = $renderer;
$this->resolver = $renderer->resolver();

if ($layout && is_string($layout)) {
$model = new ViewModel();
$model->setTemplate($layout);
$layout = $model;
}

if ($layout && ! $layout instanceof ModelInterface) {
throw new Exception\InvalidArgumentException(sprintf(
'Layout must be a string layout template name or a %s instance; received %s',
ModelInterface::class,
(is_object($layout) ? get_class($layout) : gettype($layout))
));
}
$this->template = $template;
$this->resolver = $template->resolver();

$this->layout = $layout;
}

/**
Expand All @@ -72,20 +111,32 @@ private function getDefaultResolver()
}

/**
* Render
* Render a template with the given parameters.
*
* If a layout was specified during construction, it will be used;
* alternately, you can specify a layout to use via the "layout"
* parameter, using either:
*
* - a string layout template name
* - a Zend\View\Model\ModelInterface instance
*
* Layouts specified with $params take precedence over layouts passed to
* the constructor.
*
* @param string $name
* @param array|object $params
* @return string
*/
public function render($name, $params = [])
{
$params = $this->normalizeParams($params);
return $this->template->render($name, $params);
return $this->renderModel(
$this->createModel($name, $this->normalizeParams($params)),
$this->renderer
);
}

/**
* Add a path for template
* Add a path for templates.
*
* @param string $path
* @param string $namespace
Expand All @@ -112,4 +163,87 @@ public function getPaths()
}
return $paths;
}

/**
* Create a view model from the template and parameters.
*
* Injects the created model in the layout view model, if present.
*
* If the $params contains a non-empty 'layout' key, that value will
* be used to seed a layout view model, if:
*
* - it is a string layout template name
* - it is a ModelInterface instance
*
* If a layout is discovered in this way, it will override the one set in
* the constructor, if any.
*
* @param string $name
* @param array $params
* @return ModelInterface
*/
private function createModel($name, array $params)
{
$layout = $this->layout ? clone $this->layout : null;
if (array_key_exists('layout', $params) && $params['layout']) {
if (is_string($params['layout'])) {
$layout = new ViewModel();
$layout->setTemplate($params['layout']);
unset($params['layout']);
} elseif ($params['layout'] instanceof ModelInterface) {
$layout = $params['layout'];
unset($params['layout']);
}
}

if (array_key_exists('layout', $params) && is_string($params['layout']) && $params['layout']) {
$layout = new ViewModel();
$layout->setTemplate($params['layout']);
unset($params['layout']);
}

$model = new ViewModel($params);
$model->setTemplate($name);

if ($layout) {
$layout->addChild($model);
$model = $layout;
}

return $model;
}

/**
* Do a recursive, depth-first rendering of a view model.
*
* @param ModelInterface $model
* @param RendererInterface $renderer
* @return string
* @throws Exception\RenderingException if it encounters a terminal child.
*/
private function renderModel(ModelInterface $model, RendererInterface $renderer)
{
foreach ($model as $child) {
if ($child->terminate()) {
throw new Exception\RenderingException('Cannot render; encountered a child marked terminal');
}

$capture = $child->captureTo();
if (empty($capture)) {
continue;
}

$result = $this->renderModel($child, $renderer);

if ($child->isAppend()) {
$oldResult = $model->{$capture};
$model->setVariable($capture, $oldResult . $result);
continue;
}

$model->setVariable($capture, $result);
}

return $renderer->render($model);
}
}
8 changes: 8 additions & 0 deletions test/Template/TestAsset/zendview-layout.phtml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<!DOCTYPE html>
<html>
<head>
<title>Layout Page</title>
</head>
<body>
<?= $this->content ?>
</body>
8 changes: 8 additions & 0 deletions test/Template/TestAsset/zendview-layout2.phtml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<!DOCTYPE html>
<html>
<head>
<title>ALTERNATE LAYOUT PAGE</title>
</head>
<body>
<?= $this->content ?>
</body>
Loading