GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Push — master ( 611a27...a833ec )
by Olivier
01:50
created

lib/RequestDispatcher.php (1 issue)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
/*
4
 * This file is part of the ICanBoogie package.
5
 *
6
 * (c) Olivier Laviale <[email protected]>
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\HTTP;
13
14
use ICanBoogie\Exception\RescueEvent;
15
use ICanBoogie\HTTP\RequestDispatcher\BeforeDispatchEvent;
16
use ICanBoogie\HTTP\RequestDispatcher\DispatchEvent;
17
18
/**
19
 * Dispatches HTTP requests.
20
 *
21
 * ## Events
22
 *
23
 * - `ICanBoogie\HTTP\RequestDispatcher::dispatch:before` of class {@link BeforeDispatchEvent}.
24
 * - `ICanBoogie\HTTP\RequestDispatcher::dispatch` of class {@link DispatchEvent}.
25
 * - `ICanBoogie\HTTP\RequestDispatcher::rescue` of class {@link ICanBoogie\Exception\RescueEvent}.
26
 */
27
class RequestDispatcher implements \ArrayAccess, \IteratorAggregate, Dispatcher
28
{
29
	/**
30
	 * The dispatchers called during the dispatching of the request.
31
	 *
32
	 * @var array
33
	 */
34
	protected $dispatchers = [];
35
36
	/**
37
	 * The weights of the dispatchers.
38
	 *
39
	 * @var array
40
	 */
41
	protected $dispatchers_weight = [];
42
43
	protected $dispatchers_order;
44
45
	/**
46
	 * Initializes the {@link $dispatchers} property.
47
	 *
48
	 * Dispatchers can be defined as callable or class name. If a dispatcher definition is not a
49
	 * callable it is used as class name to instantiate a dispatcher.
50
	 *
51
	 * @param array $dispatchers
52
	 */
53
	public function __construct(array $dispatchers = [])
54
	{
55
		foreach ($dispatchers as $dispatcher_id => $dispatcher)
56
		{
57
			$this[$dispatcher_id] = $dispatcher;
58
		}
59
	}
60
61
	/**
62
	 * Dispatches the request to retrieve a {@link Response}.
63
	 *
64
	 * The request is dispatched by the {@link dispatch()} method. If an exception is thrown
65
	 * during the dispatch the {@link rescue()} method is used to rescue the exception and
66
	 * retrieve a {@link Response}.
67
	 *
68
	 * ## HEAD requests
69
	 *
70
	 * If a {@link NotFound} exception is caught during the dispatching of a request with a
71
	 * {@link Request::METHOD_HEAD} method the following happens:
72
	 *
73
	 * 1. The request is cloned and the method of the cloned request is changed to
74
	 * {@link Request::METHOD_GET}.
75
	 * 2. The cloned method is dispatched.
76
	 * 3. If the result is *not* a {@link Response} instance, the result is returned.
77
	 * 4. Otherwise, a new {@link Response} instance is created with a `null` body, but the status
78
	 * code and headers of the original response.
79
	 * 5. The new response is returned.
80
	 *
81
	 * @param Request $request
82
	 *
83
	 * @return Response
84
	 */
85
	public function __invoke(Request $request)
86
	{
87
		$response = $this->handle($request);
88
89
		if ($request->is_head && $response->body)
90
		{
91
			return new Response(null, $response->status, $response->headers);
0 ignored issues
show
$response->status is of type object<ICanBoogie\HTTP\Status>, but the function expects a integer.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
92
		}
93
94
		return $response;
95
	}
96
97
	/**
98
	 * Dispatches the request and try to rescue it if it fails.
99
	 *
100
	 * If a {@link NotFound} exception is caught and the request method is `HEAD`, the request
101
	 * is passed to {@link handle_head()}.
102
	 *
103
	 * @param Request $request
104
	 *
105
	 * @return Response
106
	 * @throws \Exception
107
	 */
108
	private function handle(Request $request)
109
	{
110
		try
111
		{
112
			return $this->dispatch($request);
113
		}
114
		catch (\Exception $e)
115
		{
116
			if ($e instanceof NotFound && $request->is_head)
117
			{
118
				try
119
				{
120
					return $this->handle_head($request);
121
				}
122
				catch (\Exception $head_exception)
123
				{
124
					#
125
					# We don't care about this one, let's rescue the original exception.
126
					#
127
				}
128
			}
129
130
			return $this->rescue($e, $request);
131
		}
132
	}
133
134
	/**
135
	 * Trying to rescue a NotFound HEAD request using GET instead.
136
	 *
137
	 * @param Request $request
138
	 *
139
	 * @return Response
140
	 */
141
	private function handle_head(Request $request)
142
	{
143
		$response = $this->handle($request->with([ 'is_get' => true ]));
144
145
		if ($response->content_length === null && !$response->body instanceof \Closure)
146
		{
147
			try
148
			{
149
				$response->content_length = strlen((string) $response->body);
150
			}
151
			catch (\Exception $e)
152
			{
153
				#
154
				# It's not that bad if we can't obtain the length of the body.
155
				#
156
			}
157
		}
158
159
		return $response;
160
	}
161
162
	/**
163
	 * Checks if the dispatcher is defined.
164
	 *
165
	 * @param string $dispatcher_id The identifier of the dispatcher.
166
	 *
167
	 * @return bool `true` if the dispatcher is defined, `false` otherwise.
168
	 */
169
	public function offsetExists($dispatcher_id)
170
	{
171
		return isset($this->dispatchers[$dispatcher_id]);
172
	}
173
174
	/**
175
	 * Returns a dispatcher.
176
	 *
177
	 * @param string $dispatcher_id The identifier of the dispatcher.
178
	 *
179
	 * @return mixed
180
	 */
181
	public function offsetGet($dispatcher_id)
182
	{
183
		if (!$this->offsetExists($dispatcher_id))
184
		{
185
			throw new DispatcherNotDefined($dispatcher_id);
186
		}
187
188
		return $this->dispatchers[$dispatcher_id];
189
	}
190
191
	/**
192
	 * Defines a dispatcher.
193
	 *
194
	 * @param string $dispatcher_id The identifier of the dispatcher.
195
	 * @param mixed $dispatcher The dispatcher class or callback.
196
	 */
197
	public function offsetSet($dispatcher_id, $dispatcher)
198
	{
199
		$weight = 0;
200
201
		if ($dispatcher instanceof WeightedDispatcher)
202
		{
203
			$weight = $dispatcher->weight;
204
			$dispatcher = $dispatcher->dispatcher;
205
		}
206
207
		$this->dispatchers[$dispatcher_id] = $dispatcher;
208
		$this->dispatchers_weight[$dispatcher_id] = $weight;
209
		$this->dispatchers_order = null;
210
	}
211
212
	/**
213
	 * Removes a dispatcher.
214
	 *
215
	 * @param string $dispatcher_id The identifier of the dispatcher.
216
	 */
217
	public function offsetUnset($dispatcher_id)
218
	{
219
		unset($this->dispatchers[$dispatcher_id]);
220
	}
221
222
	/**
223
	 * @inheritdoc
224
	 */
225
	public function getIterator()
226
	{
227
		if (!$this->dispatchers_order)
228
		{
229
			$weights = $this->dispatchers_weight;
230
231
			$this->dispatchers_order = \ICanBoogie\sort_by_weight($this->dispatchers, function($v, $k) use ($weights) {
232
233
				return $weights[$k];
234
235
			});
236
		}
237
238
		return new \ArrayIterator($this->dispatchers_order);
239
	}
240
241
	/**
242
	 * Dispatches a request using the defined dispatchers.
243
	 *
244
	 * The method iterates over the defined dispatchers until one of them returns a
245
	 * {@link Response} instance. If an exception is throw during the dispatcher execution and
246
	 * the dispatcher implements the {@link Dispatcher} interface then its
247
	 * {@link Dispatcher::rescue} method is invoked to rescue the exception, otherwise the
248
	 * exception is just re-thrown.
249
	 *
250
	 * {@link BeforeDispatchEvent} is fired before dispatchers are traversed. If a
251
	 * response is provided the dispatchers are skipped.
252
	 *
253
	 * {@link DispatchEvent} is fired before the response is returned. The event is
254
	 * fired event if the dispatchers did'nt return a response. It's the last chance to get one.
255
	 *
256
	 * @param Request $request
257
	 *
258
	 * @return Response
259
	 *
260
	 * @throws \Exception If the dispatcher that raised an exception during dispatch doesn't implement
261
	 * {@link Dispatcher}.
262
	 * @throws NotFound when neither the events nor the dispatchers were able to provide
263
	 * a {@link Response}.
264
	 */
265
	protected function dispatch(Request $request)
266
	{
267
		$response = null;
268
269
		new BeforeDispatchEvent($this, $request, $response);
270
271
		if (!$response)
272
		{
273
			foreach ($this as $id => $dispatcher)
274
			{
275
				if (!$dispatcher instanceof Dispatcher)
276
				{
277
					#
278
					# If the dispatcher is not a callable then it is considered as a class name, which
279
					# is used to instantiate a dispatcher.
280
					#
281
282
					$this->dispatchers[$id] = $dispatcher = is_callable($dispatcher)
283
						? new CallableDispatcher($dispatcher)
284
						: new $dispatcher;
285
				}
286
287
				$response = $this->dispatch_with_dispatcher($dispatcher, $request);
288
289
				if ($response) break;
290
			}
291
		}
292
293
		new DispatchEvent($this, $request, $response);
294
295
		if (!$response)
296
		{
297
			throw new NotFound;
298
		}
299
300
		return $response;
301
	}
302
303
	/**
304
	 * Dispatches the request using a dispatcher.
305
	 *
306
	 * @param Dispatcher $dispatcher
307
	 * @param Request $request
308
	 *
309
	 * @return Response
310
	 *
311
	 * @throws \Exception
312
	 */
313
	protected function dispatch_with_dispatcher(Dispatcher $dispatcher, Request $request)
314
	{
315
		try
316
		{
317
			$request->context->dispatcher = $dispatcher;
318
319
			$response = $dispatcher($request);
320
		}
321
		catch (\Exception $e)
322
		{
323
			$response = $dispatcher->rescue($e, $request);
324
		}
325
326
		$request->context->dispatcher = null;
327
328
		return $response;
329
	}
330
331
	/**
332
	 * Tries to get a {@link Response} object from an exception.
333
	 *
334
	 * {@link \ICanBoogie\Exception\RescueEvent} is fired with the exception as target.
335
	 * The response provided by one of the event hooks is returned. If there is no response the
336
	 * exception is thrown again.
337
	 *
338
	 * If a response is finally obtained, the `X-ICanBoogie-Rescued-Exception` header is added to
339
	 * indicate where the exception was thrown from.
340
	 *
341
	 * @param \Exception $exception The exception to rescue.
342
	 * @param Request $request The current request.
343
	 *
344
	 * @return Response
345
	 *
346
	 * @throws \Exception The exception is re-thrown if it could not be rescued.
347
	 */
348
	public function rescue(\Exception $exception, Request $request)
349
	{
350
		/* @var $response Response */
351
		$response = null;
352
353
		new RescueEvent($exception, $request, $response);
354
355
		if (!$response)
356
		{
357
			if ($exception instanceof ForceRedirect)
358
			{
359
				return new RedirectResponse($exception->location, $exception->getCode());
360
			}
361
362
			throw $exception;
363
		}
364
365
		$this->alter_response_with_exception($response, $exception);
366
367
		return $response;
368
	}
369
370
	/**
371
	 * Alters a response with an exception.
372
	 *
373
	 * The methods adds the `X-ICanBoogie-Rescued-Exception` header to the response, which
374
	 * describes the filename and the line where the exception occurred.
375
	 *
376
	 * @param Response $response
377
	 * @param \Exception $exception
378
	 */
379
	protected function alter_response_with_exception(Response $response, \Exception $exception)
380
	{
381
		$pathname = $exception->getFile();
382
		$root = $_SERVER['DOCUMENT_ROOT'];
383
384
		if ($root && strpos($pathname, $root) === 0)
385
		{
386
			$pathname = substr($pathname, strlen($root));
387
		}
388
389
		$response->headers['X-ICanBoogie-Rescued-Exception'] = $pathname . '@' . $exception->getLine();
390
	}
391
}
392