Completed
Pull Request — master (#143)
by
unknown
04:07
created

Request   C

Complexity

Total Complexity 54

Size/Duplication

Total Lines 353
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 3

Test Coverage

Coverage 75.2%

Importance

Changes 0
Metric Value
wmc 54
lcom 1
cbo 3
dl 0
loc 353
ccs 94
cts 125
cp 0.752
rs 6.4799
c 0
b 0
f 0

12 Methods

Rating   Name   Duplication   Size   Complexity  
A __toString() 0 4 1
A __construct() 0 19 5
A prepareForErrorForwards() 0 12 3
A processPreRoutines() 0 22 5
A processPosRoutines() 0 29 5
A forwardErrors() 0 16 6
A catchExceptions() 0 15 5
A catchErrors() 0 15 5
B response() 0 48 9
A routineCall() 0 23 4
A extractRouteParam() 0 21 5
A forward() 0 6 1

How to fix   Complexity   

Complex Class

Complex classes like Request often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Request, and based on these observations, apply Extract Interface, too.

1
<?php
2
/*
3
 * This file is part of the Respect\Rest package.
4
 *
5
 * For the full copyright and license information, please view the LICENSE
6
 * file that was distributed with this source code.
7
 */
8
9
namespace Respect\Rest;
10
11
use ReflectionFunctionAbstract;
12
use ReflectionParameter;
13
use Respect\Rest\Routes\AbstractRoute;
14
use Respect\Rest\Routines\Routinable;
15
use Respect\Rest\Routines\ProxyableBy;
16
use Respect\Rest\Routines\ProxyableThrough;
17
use Respect\Rest\Routines\ParamSynced;
18
19
/** A routed HTTP Request */
20
class Request
21
{
22
    /** @var string The HTTP method (commonly GET, POST, PUT, DELETE, HEAD) */
23
    public $method = '';
24
25
    /**
26
     * @var array A numeric array containing valid URL parameters. For a route
27
     * path like /users/*, a Request for /users/alganet should have an array
28
     * equivalent to ['alganet']
29
     */
30
    public $params = array();
31
32
    /** @var AbstractRoute A route matched for this request */
33
    public $route;
34
35
    /** @var string The called URI */
36
    public $uri = '';
37
38
    /**
39
     * @param string $method The HTTP method
40
     * @param string $uri    The called URI
41
     */
42 130
    public function __construct($method = null, $uri = null)
43
    {
44
        //Tries to infer request variables only if null
45 130
        if (is_null($method)) {
46 5
            $method = isset($_SERVER['REQUEST_METHOD'])
47 5
                ? $_SERVER['REQUEST_METHOD']
48 5
                : 'GET';
49
        }
50
51 130
        if (is_null($uri)) {
52 6
            $uri = isset($_SERVER['REQUEST_URI'])
53 6
                ? $_SERVER['REQUEST_URI']
54 6
                : '/';
55
        }
56
57 130
        $uri = parse_url($uri, PHP_URL_PATH);
58 130
        $this->uri = rtrim($uri, ' /');       //We always ignore the last /
59 130
        $this->method = strtoupper($method);  //normalizing the HTTP method
60 130
    }
61
62
    /**
63
     * Converting this request to string dispatch
64
     */
65 7
    public function __toString()
66
    {
67 7
        return $this->response();
68
    }
69
70
    /**
71
     * Declares an error handler for a single Router::errorRoute instance on the
72
     * fly before dispatching the request, so the application can capture the
73
     * errors. These are cleaned after dispatching by forwardErrors()
74
     *
75
     * @see    Respect\Rest\Request::forwardErrors
76
     * @see    http://php.net/set_error_handler
77
     *
78
     * @return mixed The previous error handler
79
     */
80 111
    protected function prepareForErrorForwards()
81
    {
82 111
       foreach ($this->route->sideRoutes as $sideRoute) {
83
            if ($sideRoute instanceof Routes\Error) {
84
                return set_error_handler(
85
                    function () use ($sideRoute) {
86
                        $sideRoute->errors[] = func_get_args();
87
                    }
88
                );
89
            }
90
        }
91 111
    }
92
93
    /**
94
     * Iterates over routines to find instances of
95
     * Respect\Rest\Routines\ProxyableBy and call them, forwarding if
96
     * necessary
97
     *
98
     * @see    Respect\Rest\Routines\ProxyableBy
99
     * @see    Respect\Rest\Request::routineCall
100
     *
101
     * @return mixed A route forwarding or false
102
     */
103 111
    protected function processPreRoutines()
104
    {
105 111
        foreach ($this->route->routines as $routine) {
106 48
            if (!$routine instanceof ProxyableBy) {
107 9
                continue;
108
            } else {
109 40
                $result = $this->routineCall(
110 40
                    'by',
111 40
                    $this->method,
112 40
                    $routine,
0 ignored issues
show
Documentation introduced by
$routine is of type object<Respect\Rest\Routines\ProxyableBy>, but the function expects a object<Respect\Rest\Routines\Routinable>.

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...
113 40
                    $this->params
114
                );
115
116
                //Routine returned an instance, let's forward it
117 40
                if ($result instanceof AbstractRoute) {
118 2
                    return $this->forward($result);
119 38
                } elseif (false === $result) {
120 4
                    return false;
121
                }
122
            }
123
        }
124 107
    }
125
126
    /**
127
     * Iterates over routines to find instances of
128
     * Respect\Rest\Routines\ProxyableThrough and call them, forwarding if
129
     * necessary
130
     *
131
     * @see    Respect\Rest\Routines\ProxyableThrough
132
     * @see    Respect\Rest\Request::routineCall
133
     *
134
     * @return mixed A route forwarding or false
135
     */
136 105
    protected function processPosRoutines($response)
137
    {
138 105
        $proxyResults = array();
139
140 105
        foreach ($this->route->routines as $routine) {
141 42
            if ($routine instanceof ProxyableThrough) {
142 30
                $proxyResults[] = $this->routineCall(
143 30
                    'through',
144 30
                    $this->method,
145 30
                    $routine,
0 ignored issues
show
Documentation introduced by
$routine is of type object<Respect\Rest\Routines\ProxyableThrough>, but the function expects a object<Respect\Rest\Routines\Routinable>.

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...
146 30
                    $this->params
147
                );
148
            }
149
        }
150
151
        //Some routine returned a callback as its result. Let's run
152
        //these callbacks on our actual response.
153
        //This is mainly used by accept() and similar ones.
154 105
        foreach ($proxyResults as $proxyCallback) {
155 30
            if (is_callable($proxyCallback)) {
156 28
                $response = call_user_func_array(
157 28
                    $proxyCallback,
158 28
                    array($response)
159
                );
160
            }
161
        }
162
163 105
        return $response;
164
    }
165
166
    /**
167
     * Restores the previous error handler if present then check error routes
168
     * for logged errors, forwarding them or returning null silently
169
     *
170
     * @param mixed $errorHandler Some error handler (internal or external to
171
     *                            Respect)
172
     *
173
     * @return mixed A route forwarding or a silent null
174
     */
175 105
    protected function forwardErrors($errorHandler)
176
    {
177 105
        if (isset($errorHandler)) {
178
            if (!$errorHandler) {
179
                restore_error_handler();
180
            } else {
181
                set_error_handler($errorHandler);
182
            }
183
        }
184
185 105
        foreach ($this->route->sideRoutes as $sideRoute) {
186
            if ($sideRoute instanceof Routes\Error && $sideRoute->errors) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $sideRoute->errors of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
187
                return $this->forward($sideRoute);
188
            }
189
        }
190 105
    }
191
192
    /**
193
     * Does a catch-like operation on an exception based on previously
194
     * declared instances from Router::exceptionRoute
195
     *
196
     * @param Exception $e Any exception
197
     *
198
     * @return mixed A route forwarding or a silent null
199
     */
200 2
    protected function catchExceptions($e)
201
    {
202 2
        foreach ($this->route->sideRoutes as $sideRoute) {
203
            $exceptionClass = get_class($e);
204
            if (
205
                $exceptionClass      === $sideRoute->class
206
                || $sideRoute->class === 'Exception'
207
                || $sideRoute->class === '\Exception'
208
            ) {
209
                $sideRoute->exception = $e;
210
211
                return $this->forward($sideRoute);
212
            }
213
        }
214 2
    }
215
216
    /**
217
     * Does a catch-like operation on an error based on previously
218
     * declared instances from Router::exceptionRoute
219
     *
220
     * @param Error $e Any exception
221
     *
222
     * @return mixed A route forwarding or a silent null
223
     */
224
    protected function catchErrors($e)
225
    {
226
        foreach ($this->route->sideRoutes as $sideRoute) {
227
            $errorClass = get_class($e);
228
            if (
229
                $errorClass      === $sideRoute->class
230
                || $sideRoute->class === 'Error'
231
                || $sideRoute->class === '\Error'
232
            ) {
233
                $sideRoute->exception = $e;
234
235
                return $this->forward($sideRoute);
236
            }
237
        }
238
    }
239
240
    /**
241
     * Generates and returns the response from the current route
242
     *
243
     * @return string A response!
244
     */
245 115
    public function response()
246
    {
247
        try {
248
            //No routes, get out
249 115
            if (!$this->route instanceof AbstractRoute) {
250 4
                return;
251
            }
252
253 111
            $errorHandler = $this->prepareForErrorForwards();
254 111
            $preRoutineResult = $this->processPreRoutines();
0 ignored issues
show
Comprehensibility Best Practice introduced by
The expression $this->processPreRoutines(); of type string|false|null adds false to the return on line 257 which is incompatible with the return type documented by Respect\Rest\Request::response of type string. It seems like you forgot to handle an error condition.
Loading history...
255
256 111
            if (!is_null($preRoutineResult)) {
257 6
                return $preRoutineResult;
258
            }
259
260 107
            $response = $this->route->runTarget($this->method, $this->params);
261
262
            //The code returned another route, this is a forward
263 105
            if ($response instanceof AbstractRoute) {
264 2
                return $this->forward($response);
265
            }
266
267 105
            $possiblyModifiedResponse = $this->processPosRoutines($response);
268 105
            $errorResponse = $this->forwardErrors($errorHandler);
269
270 105
            if (!is_null($errorResponse)) {
271
                return $errorResponse;
272
            }
273
274 105
            return $possiblyModifiedResponse;
275 2
        } catch (\Exception $e) {
276
            //Tries to catch it using catchExceptions()
277 2
            if (!$exceptionResponse = $this->catchExceptions($e)) {
0 ignored issues
show
Documentation introduced by
$e is of type object<Exception>, but the function expects a object<Respect\Rest\Exception>.

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...
278 2
                throw $e;
279
            }
280
281
            //Returns whatever the exception routes returned
282
            return (string) $exceptionResponse;
283
        } catch (\Error $e) {
284
            //Tries to catch it using catchErrors()
285
            if (!$errorResponse = $this->catchErrors($e)) {
0 ignored issues
show
Documentation introduced by
$e is of type object<Error>, but the function expects a object<Respect\Rest\Error>.

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...
286
                throw $e;
287
            }
288
289
            //Returns whatever the exception routes returned
290
            return (string) $errorResponse;
291
        }
292
    }
293
294
    /**
295
     * Calls a routine on the current route and returns its result
296
     *
297
     * @param string     $type    The name of the routine (accept, when, etc)
298
     * @param string     $method  The method name (GET, HEAD, POST, etc)
299
     * @param Routinable $routine Some routine instance
300
     * @param array      $params  Params from the routine
301
     *
302
     * @return mixed Whatever the routine returns
303
     */
304 54
    public function routineCall($type, $method, Routinable $routine, &$params)
305
    {
306 54
        $reflection = $this->route->getReflection(
307
            //GET and HEAD are the same for routines
308 54
            $method == 'HEAD' ? 'GET' : $method
309
        );
310
311 54
        $callbackParameters = array();
312
313 54
        if (!$routine instanceof ParamSynced) {
314 36
            $callbackParameters = $params;
315
        } else {
316 18
            foreach ($routine->getParameters() as $parameter) {
317 10
                $callbackParameters[] = $this->extractRouteParam(
318 10
                    $reflection,
319 10
                    $parameter,
320 10
                    $params
321
                );
322
            }
323
        }
324
325 54
        return $routine->{$type}($this, $callbackParameters);
326
    }
327
328
    /**
329
     * Extracts a parameter value from the current route
330
     *
331
     * @param ReflectionFunctionAbstract $callback   Any function reflection
332
     * @param ReflectionParameter        $routeParam Any parameter reflection
333
     * @param array                      $params     Request URI params
334
     *
335
     * @return mixed a value from the reflected param
336
     */
337 10
    protected function extractRouteParam(
338
        ReflectionFunctionAbstract $callback,
339
        ReflectionParameter $routeParam,
340
        &$params
341
    ) {
342 10
        foreach ($callback->getParameters() as $callbackParamReflection) {
343
            //Check if parameters have same name and present (not filtered)
344
            if (
345 9
                $callbackParamReflection->getName() === $routeParam->getName()
346 9
                && isset($params[$callbackParamReflection->getPosition()])
347
            ) {
348 7
                return $params[$callbackParamReflection->getPosition()];
349
            }
350
        }
351
352 6
        if ($routeParam->isDefaultValueAvailable()) {
353 3
            return $routeParam->getDefaultValue();
354
        }
355
356 4
        return;
357
    }
358
359
    /**
360
     * Forwards a route
361
     *
362
     * @param AbstractRoute $route Any route
363
     *
364
     * @return Response from the forwarded route
365
     */
366 5
    public function forward(AbstractRoute $route)
367
    {
368 5
        $this->route = $route;
369
370 5
        return $this->response();
371
    }
372
}
373