1<?php
2/**
3 * Zend Framework (http://framework.zend.com/)
4 *
5 * @link      http://github.com/zendframework/zf2 for the canonical source repository
6 * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
7 * @license   http://framework.zend.com/license/new-bsd New BSD License
8 */
9
10namespace Zend\Mvc;
11
12use Zend\EventManager\EventManagerAwareInterface;
13use Zend\EventManager\EventManagerInterface;
14use Zend\ServiceManager\ServiceManager;
15use Zend\Stdlib\ResponseInterface;
16
17/**
18 * Main application class for invoking applications
19 *
20 * Expects the user will provide a configured ServiceManager, configured with
21 * the following services:
22 *
23 * - EventManager
24 * - ModuleManager
25 * - Request
26 * - Response
27 * - RouteListener
28 * - Router
29 * - DispatchListener
30 * - ViewManager
31 *
32 * The most common workflow is:
33 * <code>
34 * $services = new Zend\ServiceManager\ServiceManager($servicesConfig);
35 * $app      = new Application($appConfig, $services);
36 * $app->bootstrap();
37 * $response = $app->run();
38 * $response->send();
39 * </code>
40 *
41 * bootstrap() opts in to the default route, dispatch, and view listeners,
42 * sets up the MvcEvent, and triggers the bootstrap event. This can be omitted
43 * if you wish to setup your own listeners and/or workflow; alternately, you
44 * can simply extend the class to override such behavior.
45 */
46class Application implements
47    ApplicationInterface,
48    EventManagerAwareInterface
49{
50    const ERROR_CONTROLLER_CANNOT_DISPATCH = 'error-controller-cannot-dispatch';
51    const ERROR_CONTROLLER_NOT_FOUND       = 'error-controller-not-found';
52    const ERROR_CONTROLLER_INVALID         = 'error-controller-invalid';
53    const ERROR_EXCEPTION                  = 'error-exception';
54    const ERROR_ROUTER_NO_MATCH            = 'error-router-no-match';
55
56    /**
57     * @var array
58     */
59    protected $configuration = null;
60
61    /**
62     * Default application event listeners
63     *
64     * @var array
65     */
66    protected $defaultListeners = array(
67        'RouteListener',
68        'DispatchListener',
69        'HttpMethodListener',
70        'ViewManager',
71        'SendResponseListener',
72    );
73
74    /**
75     * MVC event token
76     * @var MvcEvent
77     */
78    protected $event;
79
80    /**
81     * @var EventManagerInterface
82     */
83    protected $events;
84
85    /**
86     * @var \Zend\Stdlib\RequestInterface
87     */
88    protected $request;
89
90    /**
91     * @var ResponseInterface
92     */
93    protected $response;
94
95    /**
96     * @var ServiceManager
97     */
98    protected $serviceManager = null;
99
100    /**
101     * Constructor
102     *
103     * @param mixed $configuration
104     * @param ServiceManager $serviceManager
105     */
106    public function __construct($configuration, ServiceManager $serviceManager)
107    {
108        $this->configuration  = $configuration;
109        $this->serviceManager = $serviceManager;
110
111        $this->setEventManager($serviceManager->get('EventManager'));
112
113        $this->request        = $serviceManager->get('Request');
114        $this->response       = $serviceManager->get('Response');
115    }
116
117    /**
118     * Retrieve the application configuration
119     *
120     * @return array|object
121     */
122    public function getConfig()
123    {
124        return $this->serviceManager->get('Config');
125    }
126
127    /**
128     * Bootstrap the application
129     *
130     * Defines and binds the MvcEvent, and passes it the request, response, and
131     * router. Attaches the ViewManager as a listener. Triggers the bootstrap
132     * event.
133     *
134     * @param array $listeners List of listeners to attach.
135     * @return Application
136     */
137    public function bootstrap(array $listeners = array())
138    {
139        $serviceManager = $this->serviceManager;
140        $events         = $this->events;
141
142        $listeners = array_unique(array_merge($this->defaultListeners, $listeners));
143
144        foreach ($listeners as $listener) {
145            $events->attach($serviceManager->get($listener));
146        }
147
148        // Setup MVC Event
149        $this->event = $event  = new MvcEvent();
150        $event->setTarget($this);
151        $event->setApplication($this)
152              ->setRequest($this->request)
153              ->setResponse($this->response)
154              ->setRouter($serviceManager->get('Router'));
155
156        // Trigger bootstrap events
157        $events->trigger(MvcEvent::EVENT_BOOTSTRAP, $event);
158        return $this;
159    }
160
161    /**
162     * Retrieve the service manager
163     *
164     * @return ServiceManager
165     */
166    public function getServiceManager()
167    {
168        return $this->serviceManager;
169    }
170
171    /**
172     * Get the request object
173     *
174     * @return \Zend\Stdlib\RequestInterface
175     */
176    public function getRequest()
177    {
178        return $this->request;
179    }
180
181    /**
182     * Get the response object
183     *
184     * @return ResponseInterface
185     */
186    public function getResponse()
187    {
188        return $this->response;
189    }
190
191    /**
192     * Get the MVC event instance
193     *
194     * @return MvcEvent
195     */
196    public function getMvcEvent()
197    {
198        return $this->event;
199    }
200
201    /**
202     * Set the event manager instance
203     *
204     * @param  EventManagerInterface $eventManager
205     * @return Application
206     */
207    public function setEventManager(EventManagerInterface $eventManager)
208    {
209        $eventManager->setIdentifiers(array(
210            __CLASS__,
211            get_class($this),
212        ));
213        $this->events = $eventManager;
214        return $this;
215    }
216
217    /**
218     * Retrieve the event manager
219     *
220     * Lazy-loads an EventManager instance if none registered.
221     *
222     * @return EventManagerInterface
223     */
224    public function getEventManager()
225    {
226        return $this->events;
227    }
228
229    /**
230     * Static method for quick and easy initialization of the Application.
231     *
232     * If you use this init() method, you cannot specify a service with the
233     * name of 'ApplicationConfig' in your service manager config. This name is
234     * reserved to hold the array from application.config.php.
235     *
236     * The following services can only be overridden from application.config.php:
237     *
238     * - ModuleManager
239     * - SharedEventManager
240     * - EventManager & Zend\EventManager\EventManagerInterface
241     *
242     * All other services are configured after module loading, thus can be
243     * overridden by modules.
244     *
245     * @param array $configuration
246     * @return Application
247     */
248    public static function init($configuration = array())
249    {
250        $smConfig = isset($configuration['service_manager']) ? $configuration['service_manager'] : array();
251        $serviceManager = new ServiceManager(new Service\ServiceManagerConfig($smConfig));
252        $serviceManager->setService('ApplicationConfig', $configuration);
253        $serviceManager->get('ModuleManager')->loadModules();
254
255        $listenersFromAppConfig     = isset($configuration['listeners']) ? $configuration['listeners'] : array();
256        $config                     = $serviceManager->get('Config');
257        $listenersFromConfigService = isset($config['listeners']) ? $config['listeners'] : array();
258
259        $listeners = array_unique(array_merge($listenersFromConfigService, $listenersFromAppConfig));
260
261        return $serviceManager->get('Application')->bootstrap($listeners);
262    }
263
264    /**
265     * Run the application
266     *
267     * @triggers route(MvcEvent)
268     *           Routes the request, and sets the RouteMatch object in the event.
269     * @triggers dispatch(MvcEvent)
270     *           Dispatches a request, using the discovered RouteMatch and
271     *           provided request.
272     * @triggers dispatch.error(MvcEvent)
273     *           On errors (controller not found, action not supported, etc.),
274     *           populates the event with information about the error type,
275     *           discovered controller, and controller class (if known).
276     *           Typically, a handler should return a populated Response object
277     *           that can be returned immediately.
278     * @return self
279     */
280    public function run()
281    {
282        $events = $this->events;
283        $event  = $this->event;
284
285        // Define callback used to determine whether or not to short-circuit
286        $shortCircuit = function ($r) use ($event) {
287            if ($r instanceof ResponseInterface) {
288                return true;
289            }
290            if ($event->getError()) {
291                return true;
292            }
293            return false;
294        };
295
296        // Trigger route event
297        $result = $events->trigger(MvcEvent::EVENT_ROUTE, $event, $shortCircuit);
298        if ($result->stopped()) {
299            $response = $result->last();
300            if ($response instanceof ResponseInterface) {
301                $event->setTarget($this);
302                $event->setResponse($response);
303                $events->trigger(MvcEvent::EVENT_FINISH, $event);
304                $this->response = $response;
305                return $this;
306            }
307        }
308
309        if ($event->getError()) {
310            return $this->completeRequest($event);
311        }
312
313        // Trigger dispatch event
314        $result = $events->trigger(MvcEvent::EVENT_DISPATCH, $event, $shortCircuit);
315
316        // Complete response
317        $response = $result->last();
318        if ($response instanceof ResponseInterface) {
319            $event->setTarget($this);
320            $event->setResponse($response);
321            $events->trigger(MvcEvent::EVENT_FINISH, $event);
322            $this->response = $response;
323            return $this;
324        }
325
326        $response = $this->response;
327        $event->setResponse($response);
328        $this->completeRequest($event);
329
330        return $this;
331    }
332
333    /**
334     * @deprecated
335     */
336    public function send()
337    {
338    }
339
340    /**
341     * Complete the request
342     *
343     * Triggers "render" and "finish" events, and returns response from
344     * event object.
345     *
346     * @param  MvcEvent $event
347     * @return Application
348     */
349    protected function completeRequest(MvcEvent $event)
350    {
351        $events = $this->events;
352        $event->setTarget($this);
353        $events->trigger(MvcEvent::EVENT_RENDER, $event);
354        $events->trigger(MvcEvent::EVENT_FINISH, $event);
355        return $this;
356    }
357}
358