Skip to content
This repository was archived by the owner on Jan 29, 2020. It is now read-only.

Commit 68ed4f7

Browse files
committed
Merge branch 'feature/zend-view-layouts'
Close #96
2 parents 95b04ce + 511cd56 commit 68ed4f7

File tree

6 files changed

+340
-29
lines changed

6 files changed

+340
-29
lines changed

doc/book/template/zend-view.md

Lines changed: 72 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -59,24 +59,80 @@ $templates = new ZendView($zendView);
5959
## Layouts
6060

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

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

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

68-
```php
69-
$content = $templates->render('blog/entry', [ 'entry' => $entry ]);
70-
$layout = $templates->render('layout/layout', [ 'content' => $content ]);
71-
```
75+
### Layout name passed to constructor
7276

73-
- View models. To accomplish this, you will compose a view model for the
74-
content, and pass it as a value to the layout:
77+
```php
78+
use Zend\Expressive\Template\ZendView;
79+
80+
// Create the engine instance with a layout name:
81+
$zendView = new PhpRenderer(null, 'layout');
82+
```
83+
84+
### Layout view model passed to constructor
85+
86+
```php
87+
use Zend\Expressive\Template\ZendView;
88+
use Zend\View\Model\ViewModel;
89+
90+
// Create the layout view model:
91+
$layout = new ViewModel([
92+
'encoding' => 'utf-8',
93+
'cssPath' => '/css/prod/',
94+
]);
95+
$layout->setTemplate('layout');
96+
97+
// Create the engine instance with the layout:
98+
$zendView = new PhpRenderer(null, $layout);
99+
```
100+
101+
### Provide a layout name when rendering
102+
103+
```php
104+
$content = $templates->render('blog/entry', [
105+
'layout' => 'blog',
106+
'entry' => $entry,
107+
]);
108+
```
109+
110+
### Provide a layout view model when rendering
111+
112+
```php
113+
use Zend\View\Model\ViewModel;
114+
115+
// Create the layout view model:
116+
$layout = new ViewModel([
117+
'encoding' => 'utf-8',
118+
'cssPath' => '/css/blog/',
119+
]);
120+
$layout->setTemplate('layout');
121+
122+
$content = $templates->render('blog/entry', [
123+
'layout' => $layout,
124+
'entry' => $entry,
125+
]);
126+
```
127+
128+
## Recommendations
129+
130+
We recommend the following practices when using the zend-view adapter:
75131

76-
```php
77-
use Zend\View\Model\ViewModel;
78-
79-
$viewModel = new ViewModel(['entry' => $entry]);
80-
$viewModel->setTemplate('blog/entry');
81-
$layout = $templates->render('layout/layout', [ 'content' => $viewModel ]);
82-
```
132+
- If using a layout, create a factory to return the layout view model as a
133+
service; this allows you to inject it into middleware and add variables to it.
134+
- While we support passing the layout as a rendering parameter, be aware that if
135+
you change engines, this may not be supported.
136+
- While you can use alternate resolvers, not all of them will work with the
137+
`addPath()` implementation. As such, we recommend setting up resolvers and
138+
paths only during creation of the template adapter.
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
/**
3+
* Zend Framework (http://framework.zend.com/)
4+
*
5+
* @see http://github.com/zendframework/zend-expressive for the canonical source repository
6+
* @copyright Copyright (c) 2015 Zend Technologies USA Inc. (http://www.zend.com)
7+
* @license https://github.com/zendframework/zend-expressive/blob/master/LICENSE.md New BSD License
8+
*/
9+
10+
namespace Zend\Expressive\Exception;
11+
12+
use DomainException;
13+
14+
class RenderingException extends DomainException implements ExceptionInterface
15+
{
16+
}

src/Template/ZendView.php

Lines changed: 145 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99

1010
namespace Zend\Expressive\Template;
1111

12+
use Zend\View\Model\ModelInterface;
13+
use Zend\View\Model\ViewModel;
1214
use Zend\View\Renderer\RendererInterface;
1315
use Zend\View\Renderer\PhpRenderer;
1416
use Zend\View\Resolver\ResolverInterface;
@@ -22,6 +24,11 @@ class ZendView implements TemplateInterface
2224
{
2325
use ArrayParametersTrait;
2426

27+
/**
28+
* @var ViewModel
29+
*/
30+
private $layout;
31+
2532
/**
2633
* Paths and namespaces data store.
2734
*/
@@ -30,23 +37,55 @@ class ZendView implements TemplateInterface
3037
/**
3138
* @var RendererInterface
3239
*/
33-
private $template;
40+
private $renderer;
3441

3542
/**
3643
* @var ResolverInterface
3744
*/
3845
private $resolver;
3946

4047
/**
41-
* @param null|RendererInterface $template
48+
* Constructor
49+
*
50+
* Allows specifying the renderer to use (any zend-view renderer is
51+
* allowed), and optionally also the layout.
52+
*
53+
* The layout may be:
54+
*
55+
* - a string layout name
56+
* - a ModelInterface instance representing the layout
57+
*
58+
* If no renderer is provided, a default PhpRenderer instance is created;
59+
* omitting the layout indicates no layout should be used by default when
60+
* rendering.
61+
*
62+
* @param null|RendererInterface $renderer
63+
* @param null|string|ModelInterface $layout
64+
* @throws InvalidArgumentException for invalid $layout types
4265
*/
43-
public function __construct(RendererInterface $template = null)
66+
public function __construct(RendererInterface $renderer = null, $layout = null)
4467
{
45-
if (null === $template) {
46-
$template = $this->createRenderer();
68+
if (null === $renderer) {
69+
$renderer = $this->createRenderer();
70+
}
71+
$this->renderer = $renderer;
72+
$this->resolver = $renderer->resolver();
73+
74+
if ($layout && is_string($layout)) {
75+
$model = new ViewModel();
76+
$model->setTemplate($layout);
77+
$layout = $model;
78+
}
79+
80+
if ($layout && ! $layout instanceof ModelInterface) {
81+
throw new Exception\InvalidArgumentException(sprintf(
82+
'Layout must be a string layout template name or a %s instance; received %s',
83+
ModelInterface::class,
84+
(is_object($layout) ? get_class($layout) : gettype($layout))
85+
));
4786
}
48-
$this->template = $template;
49-
$this->resolver = $template->resolver();
87+
88+
$this->layout = $layout;
5089
}
5190

5291
/**
@@ -72,20 +111,32 @@ private function getDefaultResolver()
72111
}
73112

74113
/**
75-
* Render
114+
* Render a template with the given parameters.
115+
*
116+
* If a layout was specified during construction, it will be used;
117+
* alternately, you can specify a layout to use via the "layout"
118+
* parameter, using either:
119+
*
120+
* - a string layout template name
121+
* - a Zend\View\Model\ModelInterface instance
122+
*
123+
* Layouts specified with $params take precedence over layouts passed to
124+
* the constructor.
76125
*
77126
* @param string $name
78127
* @param array|object $params
79128
* @return string
80129
*/
81130
public function render($name, $params = [])
82131
{
83-
$params = $this->normalizeParams($params);
84-
return $this->template->render($name, $params);
132+
return $this->renderModel(
133+
$this->createModel($name, $this->normalizeParams($params)),
134+
$this->renderer
135+
);
85136
}
86137

87138
/**
88-
* Add a path for template
139+
* Add a path for templates.
89140
*
90141
* @param string $path
91142
* @param string $namespace
@@ -112,4 +163,87 @@ public function getPaths()
112163
}
113164
return $paths;
114165
}
166+
167+
/**
168+
* Create a view model from the template and parameters.
169+
*
170+
* Injects the created model in the layout view model, if present.
171+
*
172+
* If the $params contains a non-empty 'layout' key, that value will
173+
* be used to seed a layout view model, if:
174+
*
175+
* - it is a string layout template name
176+
* - it is a ModelInterface instance
177+
*
178+
* If a layout is discovered in this way, it will override the one set in
179+
* the constructor, if any.
180+
*
181+
* @param string $name
182+
* @param array $params
183+
* @return ModelInterface
184+
*/
185+
private function createModel($name, array $params)
186+
{
187+
$layout = $this->layout ? clone $this->layout : null;
188+
if (array_key_exists('layout', $params) && $params['layout']) {
189+
if (is_string($params['layout'])) {
190+
$layout = new ViewModel();
191+
$layout->setTemplate($params['layout']);
192+
unset($params['layout']);
193+
} elseif ($params['layout'] instanceof ModelInterface) {
194+
$layout = $params['layout'];
195+
unset($params['layout']);
196+
}
197+
}
198+
199+
if (array_key_exists('layout', $params) && is_string($params['layout']) && $params['layout']) {
200+
$layout = new ViewModel();
201+
$layout->setTemplate($params['layout']);
202+
unset($params['layout']);
203+
}
204+
205+
$model = new ViewModel($params);
206+
$model->setTemplate($name);
207+
208+
if ($layout) {
209+
$layout->addChild($model);
210+
$model = $layout;
211+
}
212+
213+
return $model;
214+
}
215+
216+
/**
217+
* Do a recursive, depth-first rendering of a view model.
218+
*
219+
* @param ModelInterface $model
220+
* @param RendererInterface $renderer
221+
* @return string
222+
* @throws Exception\RenderingException if it encounters a terminal child.
223+
*/
224+
private function renderModel(ModelInterface $model, RendererInterface $renderer)
225+
{
226+
foreach ($model as $child) {
227+
if ($child->terminate()) {
228+
throw new Exception\RenderingException('Cannot render; encountered a child marked terminal');
229+
}
230+
231+
$capture = $child->captureTo();
232+
if (empty($capture)) {
233+
continue;
234+
}
235+
236+
$result = $this->renderModel($child, $renderer);
237+
238+
if ($child->isAppend()) {
239+
$oldResult = $model->{$capture};
240+
$model->setVariable($capture, $oldResult . $result);
241+
continue;
242+
}
243+
244+
$model->setVariable($capture, $result);
245+
}
246+
247+
return $renderer->render($model);
248+
}
115249
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<title>Layout Page</title>
5+
</head>
6+
<body>
7+
<?= $this->content ?>
8+
</body>
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<title>ALTERNATE LAYOUT PAGE</title>
5+
</head>
6+
<body>
7+
<?= $this->content ?>
8+
</body>

0 commit comments

Comments
 (0)