99
1010namespace Zend \Expressive \Template ;
1111
12+ use Zend \View \Model \ModelInterface ;
13+ use Zend \View \Model \ViewModel ;
1214use Zend \View \Renderer \RendererInterface ;
1315use Zend \View \Renderer \PhpRenderer ;
1416use 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,39 @@ 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+ * @param null|RendererInterface $renderer
4249 */
43- public function __construct (RendererInterface $ template = null )
50+ public function __construct (RendererInterface $ renderer = null , $ layout = null )
4451 {
45- if (null === $ template ) {
46- $ template = $ this ->createRenderer ();
52+ if (null === $ renderer ) {
53+ $ renderer = $ this ->createRenderer ();
4754 }
48- $ this ->template = $ template ;
49- $ this ->resolver = $ template ->resolver ();
55+ $ this ->renderer = $ renderer ;
56+ $ this ->resolver = $ renderer ->resolver ();
57+
58+ if ($ layout && is_string ($ layout )) {
59+ $ model = new ViewModel ();
60+ $ model ->setTemplate ($ layout );
61+ $ layout = $ model ;
62+ }
63+
64+ if ($ layout && ! $ layout instanceof ModelInterface) {
65+ throw new Exception \InvalidArgumentException (sprintf (
66+ 'Layout must be a string layout template name or a %s instance; received %s ' ,
67+ ModelInterface::class,
68+ (is_object ($ layout ) ? get_class ($ layout ) : gettype ($ layout ))
69+ ));
70+ }
71+
72+ $ this ->layout = $ layout ;
5073 }
5174
5275 /**
@@ -72,20 +95,32 @@ private function getDefaultResolver()
7295 }
7396
7497 /**
75- * Render
98+ * Render a template with the given parameters.
99+ *
100+ * If a layout was specified during construction, it will be used;
101+ * alternately, you can specify a layout to use via the "layout"
102+ * parameter, using either:
103+ *
104+ * - a string layout template name
105+ * - a Zend\View\Model\ModelInterface instance
106+ *
107+ * Layouts specified with $params take precedence over layouts passed to
108+ * the constructor.
76109 *
77110 * @param string $name
78111 * @param array|object $params
79112 * @return string
80113 */
81114 public function render ($ name , $ params = [])
82115 {
83- $ params = $ this ->normalizeParams ($ params );
84- return $ this ->template ->render ($ name , $ params );
116+ return $ this ->renderModel (
117+ $ this ->createModel ($ name , $ this ->normalizeParams ($ params )),
118+ $ this ->renderer
119+ );
85120 }
86121
87122 /**
88- * Add a path for template
123+ * Add a path for templates.
89124 *
90125 * @param string $path
91126 * @param string $namespace
@@ -112,4 +147,87 @@ public function getPaths()
112147 }
113148 return $ paths ;
114149 }
150+
151+ /**
152+ * Create a view model from the template and parameters.
153+ *
154+ * Injects the created model in the layout view model, if present.
155+ *
156+ * If the $params contains a non-empty 'layout' key, that value will
157+ * be used to seed a layout view model, if:
158+ *
159+ * - it is a string layout template name
160+ * - it is a ModelInterface instance
161+ *
162+ * If a layout is discovered in this way, it will override the one set in
163+ * the constructor, if any.
164+ *
165+ * @param string $name
166+ * @param array $params
167+ * @return ModelInterface
168+ */
169+ private function createModel ($ name , array $ params )
170+ {
171+ $ layout = $ this ->layout ? clone $ this ->layout : null ;
172+ if (array_key_exists ('layout ' , $ params ) && $ params ['layout ' ]) {
173+ if (is_string ($ params ['layout ' ])) {
174+ $ layout = new ViewModel ();
175+ $ layout ->setTemplate ($ params ['layout ' ]);
176+ unset($ params ['layout ' ]);
177+ } elseif ($ params ['layout ' ] instanceof ModelInterface) {
178+ $ layout = $ params ['layout ' ];
179+ unset($ params ['layout ' ]);
180+ }
181+ }
182+
183+ if (array_key_exists ('layout ' , $ params ) && is_string ($ params ['layout ' ]) && $ params ['layout ' ]) {
184+ $ layout = new ViewModel ();
185+ $ layout ->setTemplate ($ params ['layout ' ]);
186+ unset($ params ['layout ' ]);
187+ }
188+
189+ $ model = new ViewModel ($ params );
190+ $ model ->setTemplate ($ name );
191+
192+ if ($ layout ) {
193+ $ layout ->addChild ($ model );
194+ $ model = $ layout ;
195+ }
196+
197+ return $ model ;
198+ }
199+
200+ /**
201+ * Do a recursive, depth-first rendering of a view model.
202+ *
203+ * @param ModelInterface $model
204+ * @param PhpRenderer $renderer
205+ * @return string
206+ * @throws Exception\RenderingException if it encounters a terminal child.
207+ */
208+ private function renderModel (ModelInterface $ model , PhpRenderer $ renderer )
209+ {
210+ foreach ($ model as $ child ) {
211+ if ($ child ->terminate ()) {
212+ throw new Exception \RenderingException ('Cannot render; encountered a child marked terminal ' );
213+ }
214+
215+ $ capture = $ child ->captureTo ();
216+ if (empty ($ capture )) {
217+ continue ;
218+ }
219+
220+ $ result = $ this ->renderModel ($ child , $ renderer );
221+
222+ if ($ child ->isAppend ()) {
223+ $ oldResult = $ model ->{$ capture };
224+ $ model ->setVariable ($ capture , $ oldResult . $ result );
225+ continue ;
226+ }
227+
228+ $ model ->setVariable ($ capture , $ result );
229+ }
230+
231+ return $ renderer ->render ($ model );
232+ }
115233}
0 commit comments