1 <?php
2
3 /*
4 * This file is part of the ICanBoogie package.
5 *
6 * (c) Olivier Laviale <olivier.laviale@gmail.com>
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11
12 namespace ICanBoogie\Operation;
13
14 use ICanBoogie\HTTP\Request;
15 use ICanBoogie\Operation;
16
17 /**
18 * Dispatches operation requests.
19 */
20 class Dispatcher implements \ICanBoogie\HTTP\IDispatcher
21 {
22 /**
23 * Tries to create an {@link Operation} instance from the specified request. The operation
24 * is then executed and its response returned.
25 *
26 * If an operation could be created from the request, the `operation` property of the request's
27 * context is set to that operation.
28 *
29 * For forwarded operation, successful responses are not returned unless the request is an XHR
30 * or the response has a location.
31 */
32 public function __invoke(Request $request)
33 {
34 $request->context->operation = $operation = Operation::from($request);
35
36 if (!$operation)
37 {
38 return;
39 }
40
41 new Dispatcher\BeforeDispatchEvent($this, $operation, $request, $response);
42
43 if (!$response)
44 {
45 $response = $operation($request);
46 }
47
48 new Dispatcher\DispatchEvent($this, $operation, $request, $response);
49
50 if ($operation->is_forwarded && !$request->is_xhr && !$response->location)
51 {
52 return;
53 }
54
55 return $response;
56 }
57
58 /**
59 * Fires {@link \ICanBoogie\Operation\RescueEvent} and returns the response provided
60 * by third parties. If no response was provided, the exception (or the exception provided by
61 * third parties) is rethrown.
62 *
63 * @param \Exception $exception The exception to rescue.
64 * @param Request $request The request.
65 *
66 * @return \ICanBoogie\HTTP\Response If the exception is rescued, the response is returned,
67 * otherwise the exception is rethrown.
68 */
69 public function rescue(\Exception $exception, Request $request)
70 {
71 if (!empty($request->context->operation))
72 {
73 new Operation\RescueEvent($exception, $request, $request->context->operation, $response);
74
75 if ($response)
76 {
77 return $response;
78 }
79 }
80
81 if ($exception instanceof Failure)
82 {
83 $operation = $exception->operation;
84
85 if ($operation->request->is_xhr)
86 {
87 return $operation->response;
88 }
89
90 #
91 # if the operation was forwarded we simply return so that the response for the actual
92 # URL is returned.
93 #
94
95 else if ($operation->is_forwarded)
96 {
97 return;
98 }
99 }
100
101 throw $exception;
102 }
103 }
104
105 /*
106 * Events
107 */
108
109 namespace ICanBoogie\Operation;
110
111 use ICanBoogie\HTTP\Request;
112 use ICanBoogie\Operation;
113
114 /**
115 * Event class for the `ICanBoogie\Operation::rescue` event.
116 *
117 * The class extends {@link \ICanBoogie\Exception\RescueEvent} to provide the operation object
118 * which processing raised an exception.
119 */
120 class RescueEvent extends \ICanBoogie\Exception\RescueEvent
121 {
122 /**
123 * Operation to rescue.
124 *
125 * @var \ICanBoogie\Operation
126 */
127 public $operation;
128
129 /**
130 * Initializes the {@link $operation} property.
131 *
132 * @param \Exception $target
133 * @param \ICanBoogie\HTTP\Request $request
134 * @param \ICanBoogie\Operation $operation
135 * @param \ICanBoogie\HTTP\Response|null $response
136 */
137 public function __construct(\Exception &$target, Request $request, Operation $operation, &$response)
138 {
139 $this->operation = $operation;
140
141 parent::__construct($target, $request, $response);
142 }
143 }
144
145 /*
146 * Events
147 */
148
149 namespace ICanBoogie\Operation\Dispatcher;
150
151 use ICanBoogie\HTTP\Request;
152 use ICanBoogie\HTTP\Response;
153 use ICanBoogie\Operation;
154 use ICanBoogie\Operation\Dispatcher;
155
156 /**
157 * Event class for the `ICanBoogie\Operation\Dispatcher::dispatch:before` event.
158 *
159 * Third parties may use this event to provide a response to the request before the route is
160 * mapped. The event is usually used by third parties to redirect requests or provide cached
161 * responses.
162 */
163 class BeforeDispatchEvent extends \ICanBoogie\Event
164 {
165 /**
166 * The route.
167 *
168 * @var \ICanBoogie\Operation
169 */
170 public $operation;
171
172 /**
173 * The HTTP request.
174 *
175 * @var \ICanBoogie\HTTP\Request
176 */
177 public $request;
178
179 /**
180 * Reference to the HTTP response.
181 *
182 * @var \ICanBoogie\HTTP\Response
183 */
184 public $response;
185
186 /**
187 * The event is constructed with the type `dispatch:before`.
188 *
189 * @param Dispatcher $target
190 * @param array $payload
191 */
192 public function __construct(Dispatcher $target, Operation $operation, Request $request, &$response)
193 {
194 if ($response !== null && !($response instanceof Response))
195 {
196 throw new \InvalidArgumentException('$response must be an instance of ICanBoogie\HTTP\Response. Given: ' . get_class($response) . '.');
197 }
198
199 $this->operation = $operation;
200 $this->request = $request;
201 $this->response = &$response;
202
203 parent::__construct($target, 'dispatch:before');
204 }
205 }
206
207 /**
208 * Event class for the `ICanBoogie\Operation\Dispatcher::dispatch` event.
209 *
210 * Third parties may use this event to alter the response before it is returned by the dispatcher.
211 */
212 class DispatchEvent extends \ICanBoogie\Event
213 {
214 /**
215 * The operation.
216 *
217 * @var \ICanBoogie\Operation
218 */
219 public $operation;
220
221 /**
222 * The request.
223 *
224 * @var \ICanBoogie\HTTP\Request
225 */
226 public $request;
227
228 /**
229 * Reference to the response.
230 *
231 * @var \ICanBoogie\HTTP\Response|null
232 */
233 public $response;
234
235 /**
236 * The event is constructed with the type `dispatch`.
237 *
238 * @param Dispatcher $target
239 * @param array $payload
240 */
241 public function __construct(Dispatcher $target, Operation $operation, Request $request, &$response)
242 {
243 $this->operation = $operation;
244 $this->request = $request;
245 $this->response = &$response;
246
247 parent::__construct($target, 'dispatch');
248 }
249 }