Passed
Branch master (60a40e)
by Alex
03:08
created

Route   C

Complexity

Total Complexity 58

Size/Duplication

Total Lines 628
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 4

Test Coverage

Coverage 83.55%

Importance

Changes 6
Bugs 1 Features 0
Metric Value
wmc 58
c 6
b 1
f 0
lcom 1
cbo 4
dl 0
loc 628
ccs 127
cts 152
cp 0.8355
rs 6.0099

43 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 7 1
A reset() 0 6 1
A forget() 0 5 1
A call() 0 14 3
B parseCallable() 0 16 5
A parseCallableController() 0 8 2
A parseCallablePlaceholders() 0 12 4
A callWithStrategy() 0 12 3
A getCollector() 0 4 1
A getMethod() 0 4 1
A getPattern() 0 4 1
A getSegments() 0 4 1
A getAction() 0 4 1
A getNamespace() 0 4 1
A getParams() 0 4 1
A getParam() 0 4 1
A getMergedParams() 0 4 1
A getDefaults() 0 4 1
A getDefault() 0 4 1
A getMetadataArray() 0 4 1
A getMetadata() 0 4 1
A getStrategy() 0 8 2
A getMatcher() 0 4 1
A getBlock() 0 4 1
A setBlock() 0 5 1
A setMethod() 0 6 1
A setPattern() 0 6 1
A setPatternWithoutReset() 0 5 1
A setAction() 0 5 1
A setNamespace() 0 5 1
A setParams() 0 5 1
A setParam() 0 5 1
A setDefaults() 0 5 1
A setDefault() 0 5 1
A setMetadataArray() 0 5 1
A setMetadata() 0 5 1
A setStrategy() 0 5 1
A setMatcher() 0 5 1
A setConstraint() 0 14 2
A setControllerCreationFunction() 0 9 2
A hasParam() 0 4 1
A hasDefault() 0 4 1
A hasMetadata() 0 4 1

How to fix   Complexity   

Complex Class

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

1
<?php
2
3
/**
4
 * Codeburner Framework.
5
 *
6
 * @author Alex Rohleder <[email protected]>
7
 * @copyright 2016 Alex Rohleder
8
 * @license http://opensource.org/licenses/MIT
9
 */
10
11
namespace Codeburner\Router;
12
13
use Codeburner\Router\Exceptions\BadRouteException;
14
use Codeburner\Router\Strategies\MatcherAwareInterface;
15
use Codeburner\Router\Strategies\StrategyInterface;
16
17
/**
18
 * Route representation, a route must be able to chang and execute itself.
19
 *
20
 * @author Alex Rohleder <[email protected]>
21
 */
22
23
class Route
24
{
25
26
    /**
27
     * @var Collector
28
     */
29
30
    protected $collector;
31
    
32
    /**
33
     * @var string
34
     */
35
36
    protected $method;
37
    
38
    /**
39
     * @var string
40
     */
41
42
    protected $pattern;
43
    
44
    /**
45
     * @var callable
46
     */
47
48
    protected $action;
49
    
50
    /**
51
     * @var string
52
     */
53
54
    protected $namespace = "";
55
56
    /**
57
     * @var string[]
58
     */
59
60
    protected $params = [];
61
62
    /**
63
     * Defaults are parameters set by the user, and don't
64
     * appear on the pattern.
65
     *
66
     * @var array
67
     */
68
69
    protected $defaults = [];
70
71
    /**
72
     * Metadata can be set to be used on filters, dispatch strategies
73
     * or anywhere the route object is used.
74
     *
75
     * @var array
76
     */
77
78
    protected $metadata = [];
79
80
    /**
81
     * @var string|StrategyInterface
82
     */
83
84
    protected $strategy;
85
86
    /**
87
     * Blocked routes are dynamic routes selected to pass by the matcher.
88
     *
89
     * @var boolean
90
     */
91
92
    protected $blocked = false;
93
94
    /**
95
     * The matcher that dispatched this route.
96
     *
97
     * @var Matcher $matcher
98
     */
99
100
    protected $matcher;
101
102
    /**
103
     * The function used to create controllers from name.
104
     *
105
     * @var callable
106
     */
107
108
    protected $controllerCreationFunction;
109
110
    /**
111
     * @param Collector $collector
112
     * @param string $method
113
     * @param string $pattern
114
     * @param callable $action
115
     */
116
117 47
    public function __construct(Collector $collector, $method, $pattern, $action)
118
    {
119 47
        $this->collector = $collector;
120 47
        $this->method    = $method;
121 47
        $this->pattern   = $pattern;
122 47
        $this->action    = $action;
123 47
    }
124
125
    /**
126
     * Clone this route and set it into the collector.
127
     *
128
     * @return Route
129
     */
130
131 4
    public function reset()
132
    {
133 4
        return $this->collector->set($this->method, $this->pattern, $this->action)->nth(0)
134 4
                               ->setStrategy($this->strategy)->setParams($this->params)
135 4
                               ->setDefaults($this->defaults)->setMetadataArray($this->metadata);
136
    }
137
138
    /**
139
     * Remove this route from the collector.
140
     *
141
     * @return self
142
     */
143
144 6
    public function forget()
145
    {
146 6
        $this->collector->forget($this->method, $this->pattern);
147 6
        return $this;
148
    }
149
150
    /**
151
     * Execute the route action, if no strategy was provided the action
152
     * will be executed by the call_user_func PHP function.
153
     *
154
     * @throws BadRouteException
155
     * @return mixed
156
     */
157
158 15
    public function call()
159
    {
160 15
        $this->action = $this->parseCallable($this->action);
161
162 15
        if ($this->strategy === null) {
163 10
            return call_user_func_array($this->action, array_merge($this->defaults, $this->params));
164
        }
165
166 5
        if (!is_object($this->strategy)) {
167 2
            $this->strategy = new $this->strategy;
168 2
        }
169
170 5
        return $this->callWithStrategy();
171
    }
172
173
    /**
174
     * Seek for dynamic content in one callable. This allow to use parameters defined on pattern on callable
175
     * definition, eg. "get" "/{resource:string+}/{slug:slug+}" "{resource}::find".
176
     *
177
     * This will snakecase the resource parameter and deal with as a controller, then call the find method.
178
     * A request for "/articles/my-first-article" will execute find method of Articles controller with only
179
     * "my-first-article" as parameter.
180
     *
181
     * @param callable $callable
182
     * @return callable
183
     */
184
185 15
    private function parseCallable($callable)
186
    {
187 15
        if (is_string($callable) && strpos($callable, "::")) {
188 9
            $callable = explode("::", $callable);
189 9
        }
190
191 15
        if (is_array($callable)) {
192 9
            if (is_string($callable[0])) {
193 9
                   $callable[0] = $this->parseCallableController($callable[0]);
194 9
            }
195
196 9
            $callable[1] = $this->parseCallablePlaceholders($callable[1]);
197 9
        }
198
199 15
        return $callable;
200
    }
201
202
    /**
203
     * Get the controller object.
204
     *
205
     * @param string $controller
206
     * @return Object
207
     */
208
209 9
    private function parseCallableController($controller)
210
    {
211 9
        $controller  = rtrim($this->namespace, "\\") . "\\" . $this->parseCallablePlaceholders($controller);
212
213 9
        if ($this->controllerCreationFunction === null) {
214 8
               return new $controller;
215 1
        } else return call_user_func($this->controllerCreationFunction, $controller);
216
    }
217
218
    /**
219
     * Parse and replace dynamic content on route action.
220
     *
221
     * @param string $fragment Part of callable
222
     * @return string
223
     */
224
225 9
    private function parseCallablePlaceholders($fragment)
226
    {
227 9
        if (strpos($fragment, "{") !== false) {
228 1
            foreach ($this->params as $placeholder => $value) {
229 1
                if (strpos($fragment, "{" . $placeholder . "}") !== false) {
230 1
                    $fragment = str_replace("{" . $placeholder . "}", ucwords(str_replace("-", " ", $value)), $fragment);
231 1
                }
232 1
            }
233 1
        }
234
235 9
        return $fragment;
236
    }
237
238
    /**
239
     * Execute the route action with the given strategy.
240
     *
241
     * @throws BadRouteException
242
     * @return mixed
243
     */
244
245 5
    private function callWithStrategy()
246
    {
247 5
        if ($this->strategy instanceof StrategyInterface) {
248 4
            if ($this->strategy instanceof MatcherAwareInterface) {
249 1
                $this->strategy->setMatcher($this->matcher);
250 1
            }
251
252 4
            return $this->strategy->call($this);
253
        }
254
255 1
        throw new BadRouteException(str_replace("%s", get_class($this->strategy), BadRouteException::BAD_STRATEGY));
256
    }
257
258
    /**
259
     * @return Collector
260
     */
261
262
    public function getCollector()
263
    {
264
        return $this->collector;
265
    }
266
267
    /**
268
     * @return string
269
     */
270
271
    public function getMethod()
272
    {
273
        return $this->method;
274
    }
275
276
    /**
277
     * @return string
278
     */
279
280 28
    public function getPattern()
281
    {
282 28
        return $this->pattern;
283
    }
284
285
    /**
286
     * @return string[]
287
     */
288
289
    public function getSegments()
290
    {
291
        return explode("/", $this->pattern);
292
    }
293
294
    /**
295
     * @return callable
296
     */
297
298 9
    public function getAction()
299
    {
300 9
        return $this->action;
301
    }
302
303
    /**
304
     * @return string
305
     */
306
307
    public function getNamespace()
308
    {
309
        return $this->namespace;
310
    }
311
312
    /**
313
     * @return string[]
314
     */
315
316 28
    public function getParams()
317
    {
318 28
        return $this->params;
319
    }
320
321
    /**
322
     * @param string $key
323
     * @return string
324
     */
325
326
    public function getParam($key)
327
    {
328
        return $this->params[$key];
329
    }
330
331
    /**
332
     * Return defaults and params merged in one array.
333
     *
334
     * @return array
335
     */
336
337 3
    public function getMergedParams()
338
    {
339 3
        return array_merge($this->defaults, $this->params);
340
    }
341
342
    /**
343
     * @return array
344
     */
345
346 1
    public function getDefaults()
347
    {
348 1
        return $this->defaults;
349
    }
350
351
    /**
352
     * @param string $key
353
     * @return mixed
354
     */
355
356 1
    public function getDefault($key)
357
    {
358 1
        return $this->defaults[$key];
359
    }
360
361
    /**
362
     * @return array
363
     */
364
365 1
    public function getMetadataArray()
366
    {
367 1
        return $this->metadata;
368
    }
369
370
    /**
371
     * @param string $key
372
     * @return mixed
373
     */
374
375 1
    public function getMetadata($key)
376
    {
377 1
        return $this->metadata[$key];
378
    }
379
380
    /**
381
     * @return string|null
382
     */
383
384
    public function getStrategy()
385
    {
386
        if ($this->strategy instanceof StrategyInterface) {
387
            return get_class($this->strategy);
388
        }
389
390
        return $this->strategy;
391
    }
392
393
    /**
394
     * @return Matcher
395
     */
396
397
    public function getMatcher()
398
    {
399
        return $this->matcher;
400
    }
401
402
    /**
403
     * Verify if a Route have already been blocked.
404
     *
405
     * @return boolean
406
     */
407
408 28
    public function getBlock()
409
    {
410 28
        return $this->blocked;
411
    }
412
413
    /**
414
     * Blocking a route indicate that that route have been selected and
415
     * parsed, now it will be given to the matcher.
416
     *
417
     * @param bool $blocked
418
     * @return self
419
     */
420
421 28
    public function setBlock($blocked)
422
    {
423 28
        $this->blocked = $blocked;
424 28
        return $this;
425
    }
426
427
    /**
428
     * @param string $method
429
     * @return Route
430
     */
431
432 1
    public function setMethod($method)
433
    {
434 1
        $this->forget();
435 1
        $this->method = $method;
436 1
        return $this->reset();
437
    }
438
439
    /**
440
     * @param string $pattern
441
     * @return Route
442
     */
443
444 3
    public function setPattern($pattern)
445
    {
446 3
        $this->forget();
447 3
        $this->pattern = $pattern;
448 3
        return $this->reset();
449
    }
450
451
    /**
452
     * @param string $pattern
453
     * @return self
454
     */
455
456 28
    public function setPatternWithoutReset($pattern)
457
    {
458 28
        $this->pattern = $pattern;
459 28
        return $this;
460
    }
461
462
    /**
463
     * @param string $action
464
     * @return self
465
     */
466
467 3
    public function setAction($action)
468
    {
469 3
        $this->action = $action;
470 3
        return $this;
471
    }
472
473
    /**
474
     * @param string $namespace
475
     * @return self
476
     */
477
478 1
    public function setNamespace($namespace)
479
    {
480 1
        $this->namespace = $namespace;
481 1
        return $this;
482
    }
483
484
    /**
485
     * @param string[] $params
486
     * @return self
487
     */
488
489 29
    public function setParams(array $params)
490
    {
491 29
        $this->params = $params;
492 29
        return $this;
493
    }
494
495
    /**
496
     * @param string $key
497
     * @param string $value
498
     *
499
     * @return self
500
     */
501
502
    public function setParam($key, $value)
503
    {
504
        $this->params[$key] = $value;
505
        return $this;
506
    }
507
508
    /**
509
     * @param mixed[] $defaults
510
     * @return self
511
     */
512
513 5
    public function setDefaults(array $defaults)
514
    {
515 5
        $this->defaults = $defaults;
516 5
        return $this;
517
    }
518
519
    /**
520
     * @param string $key
521
     * @param mixed $value
522
     *
523
     * @return self
524
     */
525
526 1
    public function setDefault($key, $value)
527
    {
528 1
        $this->defaults[$key] = $value;
529 1
        return $this;
530
    }
531
532
    /**
533
     * @param mixed[] $metadata
534
     * @return self
535
     */
536
537 5
    public function setMetadataArray(array $metadata)
538
    {
539 5
        $this->metadata = $metadata;
540 5
        return $this;
541
    }
542
543
    /**
544
     * @param string $key
545
     * @param mixed $value
546
     *
547
     * @return $this
548
     */
549
550 1
    public function setMetadata($key, $value)
551
    {
552 1
        $this->metadata[$key] = $value;
553 1
        return $this;
554
    }
555
556
    /**
557
     * @param null|string|StrategyInterface $strategy
558
     * @return self
559
     */
560
561 16
    public function setStrategy($strategy)
562
    {
563 16
        $this->strategy = $strategy;
564 16
        return $this;
565
    }
566
567
    /**
568
     * @param Matcher $matcher
569
     * @return self
570
     */
571
572 26
    public function setMatcher(Matcher $matcher)
573
    {
574 26
        $this->matcher = $matcher;
575 26
        return $this;
576
    }
577
578
    /**
579
     * Set a constraint to a token in the route pattern.
580
     *
581
     * @param string $token
582
     * @param string $regex
583
     *
584
     * @return self
585
     */
586
587 1
    public function setConstraint($token, $regex)
588
    {
589 1
        $initPos = strpos($this->pattern, "{" . $token);
590
591 1
        if ($initPos !== false) {
592 1
            $endPos = strpos($this->pattern, "}", $initPos);
593 1
            $newPattern = substr_replace($this->pattern, "{" . "$token:$regex" . "}", $initPos, $endPos - $initPos + 1);
594 1
            $wildcards = $this->collector->getWildcardTokens();
595 1
            $newPattern = str_replace(array_keys($wildcards), $wildcards, $newPattern);
596 1
            $this->setPatternWithoutReset($newPattern);
597 1
        }
598
599 1
        return $this;
600
    }
601
602
    /**
603
     * Set a function to create controllers.
604
     *
605
     * @param callable $callable
606
     * @throws BadRouteException
607
     * @return self
608
     */
609
610 1
    public function setControllerCreationFunction($callable)
611
    {
612 1
        if (!is_callable($callable)) {
613 1
            throw new BadRouteException(BadRouteException::WRONG_CONTROLLER_CREATION_FUNC);
614
        }
615
616 1
        $this->controllerCreationFunction = $this->parseCallable($callable);
617 1
        return $this;
618
    }
619
620
    /**
621
     * @param string $key
622
     * @return bool
623
     */
624
625
    public function hasParam($key)
626
    {
627
        return isset($this->params[$key]);
628
    }
629
630
    /**
631
     * @param string $key
632
     * @return bool
633
     */
634
635
    public function hasDefault($key)
636
    {
637
        return isset($this->defaults[$key]);
638
    }
639
640
    /**
641
     * @param string $key
642
     * @return bool
643
     */
644
645
    public function hasMetadata($key)
646
    {
647
        return isset($this->metadata[$key]);
648
    }
649
650
}
651