Completed
Push — master ( d34cec...faecd8 )
by Augusto
02:22
created

AbstractRoute::match()   C

Complexity

Conditions 8
Paths 12

Size

Total Lines 35
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 26
CRAP Score 8

Importance

Changes 3
Bugs 0 Features 0
Metric Value
c 3
b 0
f 0
dl 0
loc 35
ccs 26
cts 26
cp 1
rs 5.3846
cc 8
eloc 21
nc 12
nop 2
crap 8
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\Routes;
10
11
use ReflectionClass;
12
use Respect\Rest\Request;
13
use Respect\Rest\Routines\Routinable;
14
use Respect\Rest\Routines\ProxyableWhen;
15
use Respect\Rest\Routines\IgnorableFileExtension;
16
use Respect\Rest\Routines\Unique;
17
18
/**
19
 * Base class for all Routes
20
 *
21
 * @method \Respect\Rest\Routes\AbstractRoute abstractAccept()
22
 * @method \Respect\Rest\Routes\AbstractRoute abstractRoutine()
23
 * @method \Respect\Rest\Routes\AbstractRoute abstractSyncedRoutine()
24
 * @method \Respect\Rest\Routes\AbstractRoute acceptCharset()
25
 * @method \Respect\Rest\Routes\AbstractRoute acceptEncoding()
26
 * @method \Respect\Rest\Routes\AbstractRoute acceptLanguage()
27
 * @method \Respect\Rest\Routes\AbstractRoute accept()
28
 * @method \Respect\Rest\Routes\AbstractRoute authBasic()
29
 * @method \Respect\Rest\Routes\AbstractRoute by()
30
 * @method \Respect\Rest\Routes\AbstractRoute contentType()
31
 * @method \Respect\Rest\Routes\AbstractRoute ignorableFileExtension()
32
 * @method \Respect\Rest\Routes\AbstractRoute lastModified()
33
 * @method \Respect\Rest\Routes\AbstractRoute paramSynced()
34
 * @method \Respect\Rest\Routes\AbstractRoute proxyableBy()
35
 * @method \Respect\Rest\Routes\AbstractRoute proxyableThrough()
36
 * @method \Respect\Rest\Routes\AbstractRoute proxyableWhen()
37
 * @method \Respect\Rest\Routes\AbstractRoute routinable()
38
 * @method \Respect\Rest\Routes\AbstractRoute through()
39
 * @method \Respect\Rest\Routes\AbstractRoute unique()
40
 * @method \Respect\Rest\Routes\AbstractRoute userAgent()
41
 * @method \Respect\Rest\Routes\AbstractRoute when()
42
 * ...
43
 */
44
abstract class AbstractRoute
45
{
46
    /** @const string Identifier for catch-all parameters in a route path */
47
    const CATCHALL_IDENTIFIER = '/**';
48
    /** @const string Identifier for normal parameters in a route path */
49
    const PARAM_IDENTIFIER = '/*';
50
    /** @const string Quoted version of the normal parameter identifier */
51
    const QUOTED_PARAM_IDENTIFIER = '/\*';
52
    /** @const string A regular expression that cathes from a / to the end */
53
    const REGEX_CATCHALL = '(/.*)?';
54
    /** @const string A regular expression that cathes one parameter */
55
    const REGEX_SINGLE_PARAM = '/([^/]+)';
56
    /** @const string A regular expression that cathes one ending parameter */
57
    const REGEX_ENDING_PARAM = '#/\(\[\^/\]\+\)#';
58
    /** @const string A regular expression that cathes one optional parameter */
59
    const REGEX_OPTIONAL_PARAM = '(?:/([^/]+))?';
60
    /** @const string A regular expression that identifies invalid parameters */
61
    const REGEX_INVALID_OPTIONAL_PARAM = '#\(\?\:/\(\[\^/\]\+\)\)\?/#';
62
63
    /** @var string The HTTP method for this route (GET, POST, ANY, etc) */
64
    public $method = '';
65
    /** @var string The pattern for this route (like /users/*) */
66
    public $pattern = '';
67
    /** @var string The generated regex to match the route pattern */
68
    public $regexForMatch = '';
69
    /**
70
     * @var string The generated regex for creating URIs from parameters
71
     * @see Respect\Rest\AbstractRoute::createURI
72
     */
73
    public $regexForReplace = '';
74
    /**
75
     * @var array A list of routines appended to this route
76
     * @see Respect\Rest\Routines\AbstractRoutine
77
     */
78
    public $routines = array();
79
    /**
80
     * @var array A list of side routes to be used
81
     * @see Respect\Rest\Routes\AbstractRoute
82
     */
83
    public $sideRoutes = array();
84
85
    /** @var array A virtualhost applied to this route (deprecated) */
86
    public $virtualHost = null;
87
88
    /**
89
     * Returns the RelfectionFunctionAbstract object for the passed method
90
     *
91
     * @param string $method The HTTP method (GET, POST, etc)
92
     */
93
    abstract public function getReflection($method);
94
95
    /**
96
     * Runs the target method/params into this route
97
     *
98
     * @param string $method The HTTP method (GET, POST, etc)
99
     * @param array  $params A list of params to pass to the target
100
     */
101
    abstract public function runTarget($method, &$params);
102
103
    /**
104
     * @param string $method  The HTTP method (GET, POST, etc)
105
     * @param string $pattern The pattern for this route path
106
     */
107 130
    public function __construct($method, $pattern)
108
    {
109 130
        $this->pattern = $pattern;
110 130
        $this->method = strtoupper($method);
111
112 130
        list($this->regexForMatch, $this->regexForReplace)
113 130
            = $this->createRegexPatterns($pattern);
114 130
    }
115
116
    /**
117
     * A magic routine builder and composite appender
118
     *
119
     * @param string $method    The HTTP method (GET, POST, etc)
120
     * @param array  $arguments Arguments to pass to this routine constructor
121
     * @see   Respect\Rest\Routes\AbstractRoute::appendRoutine
122
     *
123
     * @return AbstractRoute The route itselt
124
     */
125 54
    public function __call($method, $arguments)
126
    {
127 54
        $reflection = new ReflectionClass(
128 54
            'Respect\\Rest\\Routines\\'.ucfirst($method)
129 54
        );
130
131 54
        return $this->appendRoutine($reflection->newInstanceArgs($arguments));
132
    }
133
134
    /**
135
     * Appends a pre-built routine to this route
136
     *
137
     * @param Routinable $routine A routine to be appended
138
     * @see   Respect\Rest\Routes\AbstractRoute::__call
139
     *
140
     * @return AbstractRoute The route itselt
141
     */
142 56
    public function appendRoutine(Routinable $routine)
143
    {
144
        $key = $routine instanceof Unique
145 56
            ? get_class($routine)
146 56
            : spl_object_hash($routine);
147
148 56
        $this->routines[$key] = $routine;
149
150 56
        return $this;
151
    }
152
153
    /**
154
     * Creates an URI for this route with the passed parameters, replacing
155
     * them in the declared pattern. /hello/* with ['tom'] returns /hello/tom
156
     *
157
     * @param mixed $param1 Some parameter
158
     * @param mixed $etc    This route accepts as many parameters you can pass
159
     *
160
     * @see Respect\Rest\Request::$params
161
     *
162
     * @return string the created URI
163
     */
164 1
    public function createUri($param1 = null, $etc = null)
0 ignored issues
show
Unused Code introduced by
The parameter $param1 is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $etc is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
165
    {
166 1
        $params = func_get_args();
167 1
        array_unshift($params, $this->regexForReplace);
168
169 1
        $params = preg_replace('#(?<!^)/? *$#', '', $params);
170
171 1
        return rtrim($this->virtualHost, ' /').call_user_func_array('sprintf', $params);
172
    }
173
174
    /**
175
     * Passes a request through all this routes ProxyableWhen routines
176
     *
177
     * @param Request $request The request you want to process
178
     * @param array   $params  Parameters for the processed request
179
     * @see   Respect\Rest\Routines\ProxyableWhen
180
     *
181
     * @see Respect\Rest\Request::$params
182
     *
183
     * @return bool always true \,,/
184
     */
185 114
    public function matchRoutines(Request $request, $params = array())
186
    {
187 114
        foreach ($this->routines as $routine) {
188
            if ($routine instanceof ProxyableWhen
189 55
                    && !$request->routineCall(
190 42
                        'when',
191 42
                        $request->method,
192 42
                        $routine,
0 ignored issues
show
Documentation introduced by
$routine is of type object<Respect\Rest\Routines\ProxyableWhen>, 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...
193
                        $params
194 55
                    )) {
195 2
                return false;
196
            }
197 112
        }
198
199 112
        return true;
200
    }
201
202
    /**
203
     * Checks if a request passes for this route
204
     *
205
     * @param Request $request The request you want to process
206
     * @param array   $params  Parameters for the processed request
207
     *
208
     * @see Respect\Rest\Request::$params
209
     *
210
     * @return bool as true as xkcd (always true)
211
     */
212 121
    public function match(Request $request, &$params = array())
213
    {
214 121
        $params = array();
215 121
        $matchUri = $request->uri;
216
217 121
        foreach ($this->routines as $routine) {
218 56
            if ($routine instanceof IgnorableFileExtension) {
219 37
                $matchUri = preg_replace(
220 37
                    '#(\.[\w\d-_.~\+]+)*$#',
221 37
                    '',
222 37
                    $request->uri
223 37
                );
224 37
            }
225 121
        }
226
227 121
        if (!preg_match($this->regexForMatch, $matchUri, $params)) {
228 19
            return false;
229
        }
230
231 119
        array_shift($params);
232
233
        if (
234 119
            false !== stripos($this->pattern, '/**')
235 119
            && false !== stripos(end($params), '/')
236 119
        ) {
237 5
            $lastParam = array_pop($params);
238 5
            $params[] = explode('/', ltrim($lastParam, '/'));
239 5
        } elseif (
240 115
            false !== stripos($this->pattern, '/**') && !isset($params[0])
241 115
        ) {
242 2
            $params[] = array(); // callback expects a parameter give it
243 2
        }
244
245 119
        return true;
246
    }
247
248
    /**
249
     * This creates a regular expression that matches a route pattern and
250
     * extracts it's parameters
251
     *
252
     * @param string $pattern The pattern for the regex creation
253
     *
254
     * @return array A matcher regex and a replacer regex for createUri()
255
     */
256 130
    protected function createRegexPatterns($pattern)
257
    {
258 130
        $extra = $this->extractCatchAllPattern($pattern);
259
260 130
        $matchPattern = str_replace(
261 130
            static::QUOTED_PARAM_IDENTIFIER,
262 130
            static::REGEX_SINGLE_PARAM,
263 130
            preg_quote(rtrim($pattern, ' /')),
264
            $paramCount
265 130
        );
266
267 130
        $pattern = rtrim($pattern);
268
269 130
        $replacePattern = str_replace(
270 130
            static::PARAM_IDENTIFIER,
271 130
            '/%s',
272
            $pattern
273 130
        );
274 130
        $matchPattern = $this->fixOptionalParams($matchPattern);
275 130
        $matchRegex = "#^{$matchPattern}{$extra}$#";
276
277 130
        return array($matchRegex, $replacePattern);
278
    }
279
280
    /**
281
     * Extracts the catch-all parameter from a pattern and modifies the passed
282
     * parameter to remove that. Yes, we're modifying by reference.
283
     *
284
     * @param string $pattern The pattern for the regex creation
285
     *
286
     * @return string The catch-all parameter or empty string
287
     */
288 130
    protected function extractCatchAllPattern(&$pattern)
289
    {
290 130
        $extra = static::REGEX_CATCHALL;
291
292
        if (
293 130
            (strlen($pattern) - strlen(static::CATCHALL_IDENTIFIER))
294 130
                === strripos($pattern, static::CATCHALL_IDENTIFIER)
295 130
        ) {
296 7
            $pattern = substr($pattern, 0, -3);
297 7
        } else {
298 124
            $extra = '';
299
        }
300
301 130
        $pattern = str_replace(
302 130
            static::CATCHALL_IDENTIFIER,
303 130
            static::PARAM_IDENTIFIER,
304
            $pattern
305 130
        );
306
307 130
        return $extra;
308
    }
309
310
    /**
311
     * Identifies using regular expressions a sequence of parameters in the end
312
     * of a pattern and make the latest ones optional for the matcher regex
313
     *
314
     * @param string $quotedPattern a preg_quoted route pattern
315
     */
316 130
    protected function fixOptionalParams($quotedPattern)
317
    {
318
        if (
319 130
            strlen($quotedPattern) - strlen(static::REGEX_SINGLE_PARAM)
320 130
            === strripos($quotedPattern, static::REGEX_SINGLE_PARAM)
321 130
        ) {
322 79
            $quotedPattern = preg_replace(
323 79
                static::REGEX_ENDING_PARAM,
324 79
                static::REGEX_OPTIONAL_PARAM,
325
                $quotedPattern
326 79
            );
327 79
        }
328
329 130
        $quotedPattern = preg_replace(
330 130
            static::REGEX_INVALID_OPTIONAL_PARAM,
331 130
            static::REGEX_SINGLE_PARAM.'/',
332
            $quotedPattern
333 130
        );
334
335 130
        return $quotedPattern;
336
    }
337
}
338