1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
<?php
/**
* Zend Framework (http://framework.zend.com/)
*
* @link http://github.com/zendframework/zf2 for the canonical source repository
* @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
namespace Zend\View;
use Zend\EventManager\EventManager;
use Zend\EventManager\EventManagerAwareInterface;
use Zend\EventManager\EventManagerInterface;
use Zend\Stdlib\RequestInterface as Request;
use Zend\Stdlib\ResponseInterface as Response;
use Zend\View\Model\ModelInterface as Model;
use Zend\View\Renderer\RendererInterface as Renderer;
use Zend\View\Renderer\TreeRendererInterface;
class View implements EventManagerAwareInterface
{
/**
* @var EventManagerInterface
*/
protected $events;
/**
* @var Request
*/
protected $request;
/**
* @var Response
*/
protected $response;
/**
* Set MVC request object
*
* @param Request $request
* @return View
*/
public function setRequest(Request $request)
{
$this->request = $request;
return $this;
}
/**
* Set MVC response object
*
* @param Response $response
* @return View
*/
public function setResponse(Response $response)
{
$this->response = $response;
return $this;
}
/**
* Get MVC request object
*
* @return null|Request
*/
public function getRequest()
{
return $this->request;
}
/**
* Get MVC response object
*
* @return null|Response
*/
public function getResponse()
{
return $this->response;
}
/**
* Set the event manager instance
*
* @param EventManagerInterface $events
* @return View
*/
public function setEventManager(EventManagerInterface $events)
{
$events->setIdentifiers([
__CLASS__,
get_class($this),
]);
$this->events = $events;
return $this;
}
/**
* Retrieve the event manager instance
*
* Lazy-loads a default instance if none available
*
* @return EventManagerInterface
*/
public function getEventManager()
{
if (! $this->events instanceof EventManagerInterface) {
$this->setEventManager(new EventManager());
}
return $this->events;
}
/**
* Add a rendering strategy
*
* Expects a callable. Strategies should accept a ViewEvent object, and should
* return a Renderer instance if the strategy is selected.
*
* Internally, the callable provided will be subscribed to the "renderer"
* event, at the priority specified.
*
* @param callable $callable
* @param int $priority
* @return View
*/
public function addRenderingStrategy($callable, $priority = 1)
{
$this->getEventManager()->attach(ViewEvent::EVENT_RENDERER, $callable, $priority);
return $this;
}
/**
* Add a response strategy
*
* Expects a callable. Strategies should accept a ViewEvent object. The return
* value will be ignored.
*
* Typical usages for a response strategy are to populate the Response object.
*
* Internally, the callable provided will be subscribed to the "response"
* event, at the priority specified.
*
* @param callable $callable
* @param int $priority
* @return View
*/
public function addResponseStrategy($callable, $priority = 1)
{
$this->getEventManager()->attach(ViewEvent::EVENT_RESPONSE, $callable, $priority);
return $this;
}
/**
* Render the provided model.
*
* Internally, the following workflow is used:
*
* - Trigger the "renderer" event to select a renderer.
* - Call the selected renderer with the provided Model
* - Trigger the "response" event
*
* @triggers renderer(ViewEvent)
* @triggers response(ViewEvent)
* @param Model $model
* @throws Exception\RuntimeException
* @return void
*/
public function render(Model $model)
{
$event = $this->getEvent();
$event->setModel($model);
$event->setName(ViewEvent::EVENT_RENDERER);
$events = $this->getEventManager();
$results = $events->triggerEventUntil(function ($result) {
return ($result instanceof Renderer);
}, $event);
$renderer = $results->last();
if (! $renderer instanceof Renderer) {
throw new Exception\RuntimeException(sprintf(
'%s: no renderer selected!',
__METHOD__
));
}
$event->setRenderer($renderer);
$event->setName(ViewEvent::EVENT_RENDERER_POST);
$events->triggerEvent($event);
// If EVENT_RENDERER or EVENT_RENDERER_POST changed the model, make sure
// we use this new model instead of the current $model
$model = $event->getModel();
// If we have children, render them first, but only if:
// a) the renderer does not implement TreeRendererInterface, or
// b) it does, but canRenderTrees() returns false
if ($model->hasChildren()
&& (! $renderer instanceof TreeRendererInterface
|| ! $renderer->canRenderTrees())
) {
$this->renderChildren($model);
}
// Reset the model, in case it has changed, and set the renderer
$event->setModel($model);
$event->setRenderer($renderer);
$rendered = $renderer->render($model);
// If this is a child model, return the rendered content; do not
// invoke the response strategy.
$options = $model->getOptions();
if (array_key_exists('has_parent', $options) && $options['has_parent']) {
return $rendered;
}
$event->setResult($rendered);
$event->setName(ViewEvent::EVENT_RESPONSE);
$events->triggerEvent($event);
}
/**
* Loop through children, rendering each
*
* @param Model $model
* @throws Exception\DomainException
* @return void
*/
protected function renderChildren(Model $model)
{
foreach ($model as $child) {
if ($child->terminate()) {
throw new Exception\DomainException('Inconsistent state; child view model is marked as terminal');
}
$child->setOption('has_parent', true);
$result = $this->render($child);
$child->setOption('has_parent', null);
$capture = $child->captureTo();
if (! empty($capture)) {
if ($child->isAppend()) {
$oldResult = $model->{$capture};
$model->setVariable($capture, $oldResult . $result);
} else {
$model->setVariable($capture, $result);
}
}
}
}
/**
* Create and return ViewEvent used by render()
*
* @return ViewEvent
*/
protected function getEvent()
{
$event = new ViewEvent();
$event->setTarget($this);
if (null !== ($request = $this->getRequest())) {
$event->setRequest($request);
}
if (null !== ($response = $this->getResponse())) {
$event->setResponse($response);
}
return $event;
}
}