Completed
Push — master ( 047943...ada7af )
by Lee
05:25
created

RouteMetaData   C

Complexity

Total Complexity 47

Size/Duplication

Total Lines 461
Duplicated Lines 0 %

Coupling/Cohesion

Components 7
Dependencies 6

Test Coverage

Coverage 87.25%

Importance

Changes 29
Bugs 4 Features 2
Metric Value
wmc 47
c 29
b 4
f 2
lcom 7
cbo 6
dl 0
loc 461
ccs 130
cts 149
cp 0.8725
rs 6.5283

29 Methods

Rating   Name   Duplication   Size   Complexity  
A setClassMetaData() 0 4 1
A getClassMetaData() 0 4 1
A getRoutePattern() 0 4 1
A setRoutePattern() 0 4 1
A getRouteConditions() 0 4 1
A getName() 0 4 1
A setName() 0 4 1
A getNamedRoute() 0 4 1
A getVerbs() 0 4 1
A getRouteParams() 0 4 2
A setExpose() 0 4 1
A getExpose() 0 4 1
A getHandleCall() 0 4 1
A hasHandleCall() 0 4 1
A isAllowedOptionRequest() 0 4 1
A usesHttpVerbs() 0 4 1
A setRouteConditions() 0 6 2
A setVerbs() 0 10 3
A setRouteParams() 0 11 1
A setUnmappedRouteParams() 0 11 1
A setHandleCall() 0 6 2
B needsHandleCall() 0 13 5
A setAllowedOptionRequest() 0 11 3
B getOriginLocation() 0 19 5
B serialize() 0 24 3
A unserialize() 0 17 1
A setCollection() 0 4 1
A isCollection() 0 4 1
A matches() 0 16 2

How to fix   Complexity   

Complex Class

Complex classes like RouteMetaData 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 RouteMetaData, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * This file is part of the Drest 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
 * @author Lee Davis
9
 * @copyright Copyright (c) Lee Davis <@leedavis81>
10
 * @link https://github.com/leedavis81/drest/blob/master/LICENSE
11
 * @license http://opensource.org/licenses/MIT The MIT X License (MIT)
12
 */
13
namespace Drest\Mapping;
14
15
use Doctrine\ORM\EntityManager;
16
use Drest\DrestException;
17
use Drest\Helper\ObjectToArray;
18
use Drest\Route\Matcher as RouteMatcher;
19
use DrestCommon\Request\Request;
20
21
/**
22
 *
23
 * A class metadata instance that holds all the information for a Drest entity
24
 * @author Lee
25
 *
26
 */
27
class RouteMetaData implements \Serializable
28
{
29
    /**
30
     * This route objects parent
31
     * @var ClassMetaData $classMetaData
32
     */
33
    protected $class_metadata;
34
35
    /**
36
     * A string route pattern to be matched on. eg /user/:id
37
     * @var string $route_pattern
38
     */
39
    protected $route_pattern;
40
41
    /**
42
     * Any custom regex conditions that are needed when matching a route param component.
43
     * Eg array('year' => '(19|20)\d\d')
44
     * @var array $route_conditions
45
     */
46
    protected $route_conditions = [];
47
48
    /**
49
     * Key-value array of URL parameter names
50
     * @var array $param_names
51
     */
52
    protected $param_names = [];
53
54
    /**
55
     * Key-value array of URL parameters with + at the end
56
     * @var array $param_names_path
57
     */
58
    protected $param_names_path = [];
59
60
    /**
61
     * Key-value array of URL parameters populated after a match has been successful
62
     * - or directly by using available setter
63
     * @var array $route_params
64
     */
65
    protected $route_params;
66
67
    /**
68
     * An index array of URL parameters that exist but didn't match a route pattern parameter
69
     * Eg: pattern: /user/:id+  with url: /user/1/some/additional/params.
70
     * The value id => 1 will go into $route_params
71
     * All the rest will go in here.
72
     * @var array $unmapped_route_params
73
     */
74
    protected $unmapped_route_params;
75
76
    /**
77
     * The route name (must be unique)
78
     * @var string $name
79
     */
80
    protected $name;
81
82
    /**
83
     * Any array of verbs allowed on this route.
84
     * They match the constant values defined in DrestCommon\Request\Request eg array('GET', 'POST')
85
     * @var array $verbs
86
     */
87
    protected $verbs;
88
89
    /**
90
     * Whether get requests to this route should be exposed / handled as collections
91
     * @var boolean $collection
92
     */
93
    protected $collection = false;
94
95
    /**
96
     * A handle function call for this route (if one is configured)
97
     * @var string $handle_call
98
     */
99
    protected $handle_call;
100
101
    /**
102
     * An array of fields to be exposed to the end client
103
     * @var array $expose
104
     */
105
    protected $expose = [];
106
107
    /**
108
     * Whether this route is open to allow OPTION requests to detail available $verbs
109
     * -1 = not set
110
     * 0  = not allowed
111
     * 1  = allowed
112
     * @var integer $allowed_option_request ;
113
     */
114
    protected $allowed_option_request = -1;
115
116
117
    /**
118
     * Set this objects parent metadata class
119
     * @param ClassMetaData $class_metadata
120
     */
121 47
    public function setClassMetaData(ClassMetaData $class_metadata)
122
    {
123 47
        $this->class_metadata = $class_metadata;
124 47
    }
125
126
    /**
127
     * Get this classes metadata object
128
     * @return ClassMetaData $class_metadata
129
     */
130 32
    public function getClassMetaData()
131
    {
132 32
        return $this->class_metadata;
133
    }
134
135
    /**
136
     * Get this routes route pattern
137
     * @return string $route_pattern
138
     */
139 30
    public function getRoutePattern()
140
    {
141 30
        return $this->route_pattern;
142
    }
143
144
    /**
145
     * Add the route path. eg '/users/:id'
146
     * @param string $route_pattern
147
     */
148 44
    public function setRoutePattern($route_pattern)
149
    {
150 44
        $this->route_pattern = $route_pattern;
151 44
    }
152
153
    /**
154
     * Add an array of route conditions
155
     * @param array $route_conditions
156
     */
157 36
    public function setRouteConditions(array $route_conditions)
158
    {
159 36
        foreach ($route_conditions as $field => $condition) {
160 36
            $this->route_conditions[preg_replace("/[^a-zA-Z0-9_]+/", "", $field)] = $condition;
161 36
        }
162 36
    }
163
164
    /**
165
     * Get the route conditions
166
     * @return array|null
167
     */
168 27
    public function getRouteConditions()
169
    {
170 27
        return $this->route_conditions;
171
    }
172
173
    /**
174
     * Get the name of this route
175
     * @return string $name
176
     */
177 50
    public function getName()
178
    {
179 50
        return $this->name;
180
    }
181
182
    /**
183
     * Sets a unique reference name for the resource.
184
     * If other resources are created with this name an exception is thrown (must be unique)
185
     * @param string $name
186
     */
187 50
    public function setName($name)
188
    {
189 50
        $this->name = $name;
190 50
    }
191
192
    /**
193
     * The unique named route to reference this route by
194
     * Note that is should use the entities fully qualified class names
195
     * @return string
196
     */
197 30
    public function getNamedRoute()
198
    {
199 30
        return ltrim($this->getClassMetaData()->getClassName(), '\\') . '::' . $this->getName();
200
    }
201
202
    /**
203
     * Whether requests to this route should be handled as collections
204
     * @param boolean $value
205
     */
206 34
    public function setCollection($value = true)
207
    {
208 34
        $this->collection = (bool) $value;
209 34
    }
210
211
    /**
212
     * Should this route be handled as a collection
213
     */
214 22
    public function isCollection()
215
    {
216 22
        return (bool) $this->collection;
217
    }
218
219
    /**
220
     * Get an array of verbs that are allowed on this route
221
     * @return array
222
     */
223 38
    public function getVerbs()
224
    {
225 38
        return $this->verbs;
226
    }
227
228
    /**
229
     * Add verbs that are to be allowed on this route.
230
     * @param  mixed          $verbs = a single or array of verbs valid for this route. eg array('GET', 'PUT')
231
     * @throws DrestException if verb is invalid
232
     */
233 44
    public function setVerbs($verbs)
234
    {
235 44
        foreach ((array) $verbs as $verb) {
236 44
            $verb = strtoupper($verb);
237 44
            if (!defined('DrestCommon\Request\Request::METHOD_' . $verb)) {
238 2
                throw DrestException::invalidHttpVerbUsed($verb);
239
            }
240 42
            $this->verbs[] = $verb;
241 42
        }
242 42
    }
243
244
    /**
245
     * Inject route params onto this object without performing a match. Useful when calling a named route directly
246
     * @param array $params - should be an associative array. keyed values are ignored
247
     */
248 2
    public function setRouteParams(array $params = [])
249
    {
250 2
        $this->route_params = array_flip(
251 2
            array_filter(
252 2
                array_flip($params),
253
                function ($entry) {
254 1
                    return !is_int($entry);
255
                }
256 2
            )
257 2
        );
258 2
    }
259
260
    /**
261
     * Get any params that were set after a successful match
262
     * @return array $params
263
     */
264 25
    public function getRouteParams()
265
    {
266 25
        return (!empty($this->route_params)) ? $this->route_params : [];
267
    }
268
269
    /**
270
     * Inject unmapped route params onto this object without performing a match.
271
     * Useful when calling a named route directly
272
     * @param array $params - should be a keyed array. associative values are ignored
273
     */
274
    public function setUnmappedRouteParams(array $params = [])
275
    {
276
        $this->unmapped_route_params = array_flip(
277
            array_filter(
278
                array_flip($params),
279
                function ($entry) {
280
                    return !is_string($entry);
281
                }
282
            )
283
        );
284
    }
285
286
    /**
287
     * An array of fields we're allowed to expose to the client
288
     * @param array $expose
289
     */
290 36
    public function setExpose(array $expose)
291
    {
292 36
        $this->expose = $expose;
293 36
    }
294
295
    /**
296
     * Get the field exposure on this route
297
     * @return array $expose
298
     */
299 34
    public function getExpose()
300
    {
301 34
        return $this->expose;
302
    }
303
304
    /**
305
     * Set the handle function call
306
     * @param string $handle_call
307
     */
308 36
    public function setHandleCall($handle_call)
309
    {
310 36
        if (is_string($handle_call)) {
311 36
            $this->handle_call = $handle_call;
312 36
        }
313 36
    }
314
315
    /**
316
     * Get the handle call function
317
     * @return string $handle_call
318
     */
319 3
    public function getHandleCall()
320
    {
321 3
        return $this->handle_call;
322
    }
323
324
    /**
325
     *
326
     * Does this route have an annotated handle call
327
     */
328 36
    public function hasHandleCall()
329
    {
330 36
        return isset($this->handle_call);
331
    }
332
333
    /**
334
     * Does this route need a handle call? Required for POST/PUT/PATCH verbs
335
     * @return boolean $response
336
     */
337 38
    public function needsHandleCall()
338
    {
339 38
        foreach ($this->getVerbs() as $verb) {
340
            switch ($verb) {
341 38
                case Request::METHOD_POST:
342 38
                case Request::METHOD_PUT:
343 38
                case Request::METHOD_PATCH:
344 35
                    return true;
345
            }
346 38
        }
347
348 38
        return false;
349
    }
350
351
    /**
352
     * Set whether we would like to expose this route (and its verbs) to OPTIONS requests
353
     * @param  integer|boolean $value - if using integer -1 to unset, 0 for no and 1 if yes
354
     * @throws DrestException
355
     */
356
    public function setAllowedOptionRequest($value = true)
357
    {
358
        if (is_bool($value)) {
359
            $this->allowed_option_request = ((bool) $value) ? 1 : 0;
360
        }
361
362
        // No need to test for -1 value, it cannot be anything else at this point.
363
364
        // Value is converted and saved as an int.
365
        $this->allowed_option_request = (int) $value;
366
    }
367
368
    /**
369
     * Is this route allowed to expose its verbs to OPTIONS requests
370
     * @return integer $result -1 if not set, 0 if no and 1 if yes
371
     */
372 2
    public function isAllowedOptionRequest()
373
    {
374 2
        return $this->allowed_option_request;
375
    }
376
377
    /**
378
     * Generate the location string from the provided object
379
     * @param  object        $object
380
     * @param  string        $url    - the Url to be prepended to the location
381
     * @param  EntityManager $em     - Optionally pass the entity manager to assist in determining a GET origin location
382
     * @return string|false
383
     */
384 3
    public function getOriginLocation($object, $url, EntityManager $em = null)
385
    {
386 3
        $exposedObjectArray = ObjectToArray::execute($object);
387 3
        if (($route = $this->class_metadata->getOriginRoute($em)) !== null) {
388 3
            if (!is_null($em)) {
389 3
                $pattern = $route->getRoutePattern();
390 3
                $ormClassMetadata = $em->getClassMetadata($this->getClassMetaData()->getClassName());
391 3
                foreach ($ormClassMetadata->getIdentifierFieldNames() as $identifier) {
392 3
                    if (isset($exposedObjectArray[$identifier])) {
393 3
                        $pattern = str_replace(':' . $identifier, $exposedObjectArray[$identifier], $pattern);
394 3
                    }
395 3
                }
396
397 3
                return $url . '/' . ltrim($pattern, '/');
398
            }
399
        }
400
401
        return false;
402
    }
403
404
    /**
405
     * Does this request match the route pattern
406
     * @param  Request $request
407
     * @param  boolean $matchVerb - Whether you want to match the route using the request HTTP verb
408
     *                            - useful for OPTIONS requests to provide route info
409
     * @param  string  $basePath  - add a base path to the route pattern
410
     * @return boolean $result
411
     */
412 30
    public function matches(Request $request, $matchVerb = true, $basePath = null)
413
    {
414 30
        $matcher = new RouteMatcher($this);
415 30
        if (!$matcher->matches($request, $matchVerb, $basePath))
416 30
        {
417 30
            return false;
418
        }
419
420
        // set determined parameters from running the match
421 27
        $this->route_params = $matcher->getRouteParams();
422 27
        $this->unmapped_route_params = $matcher->getUnmappedRouteParams();
423 27
        $this->param_names = $matcher->getParamNames();
424 27
        $this->param_names_path = $matcher->getParamNamesPath();
425
426 27
        return true;
427
    }
428
429
    /**
430
     * Is this route specific to defined HTTP verbs
431
     */
432 30
    public function usesHttpVerbs()
433
    {
434 30
        return !empty($this->verbs);
435
    }
436
437
    /**
438
     * Serialise this object
439
     * @return string
440
     */
441 1
    public function serialize()
442
    {
443 1
        $trace = debug_backtrace();
444 1
        if (!isset($trace[2]) || $trace[2]['class'] != 'Drest\Mapping\ClassMetaData') {
445
            trigger_error('RouteMetaData can only be serialized from a parent instance of ClassMetaData', E_USER_ERROR);
446
        }
447
448 1
        return serialize(
449
            [
450 1
                $this->route_pattern,
451 1
                $this->route_conditions,
452 1
                $this->param_names,
453 1
                $this->param_names_path,
454 1
                $this->route_params,
455 1
                $this->unmapped_route_params,
456 1
                $this->name,
457 1
                $this->verbs,
458 1
                $this->collection,
459 1
                $this->handle_call,
460 1
                $this->expose,
461 1
                $this->allowed_option_request
462 1
            ]
463 1
        );
464
    }
465
466
    /**
467
     * Un-serialise this object and reestablish it's state
468
     * @param string $string
469
     */
470 1
    public function unserialize($string)
471
    {
472
        list(
473 1
            $this->route_pattern,
474 1
            $this->route_conditions,
475 1
            $this->param_names,
476 1
            $this->param_names_path,
477 1
            $this->route_params,
478 1
            $this->unmapped_route_params,
479 1
            $this->name,
480 1
            $this->verbs,
481 1
            $this->collection,
482 1
            $this->handle_call,
483 1
            $this->expose,
484 1
            $this->allowed_option_request
485 1
            ) = unserialize($string);
486 1
    }
487
}
488