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,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}
0 commit comments