Passed
Branch dev (473855)
by Alex
02:25
created

Route   C

Complexity

Total Complexity 57

Size/Duplication

Total Lines 613
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 4

Test Coverage

Coverage 63.16%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 57
c 1
b 0
f 0
lcom 1
cbo 4
dl 0
loc 613
ccs 96
cts 152
cp 0.6316
rs 6.295

42 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 15 5
A parseCallableController() 0 8 2
A parseCallablePlaceholders() 0 12 4
A callWithStrategy() 0 14 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 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 string|array|\Closure
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 string|array|\Closure
106
     */
107
108
    protected $controllerCreationFunction;
109
110
    /**
111
     * @param Collector $collector
112
     * @param string $method
113
     * @param string $pattern
114
     * @param string|array|\Closure $action
115
     */
116
117 35
    public function __construct(Collector $collector, $method, $pattern, $action)
118
    {
119 35
        $this->collector = $collector;
120 35
        $this->method    = $method;
121 35
        $this->pattern   = $pattern;
122 35
        $this->action    = $action;
123 35
    }
124
125
    /**
126
     * Clone this route and set it into the collector.
127
     *
128
     * @return Route
129
     */
130
131 3
    public function reset()
132
    {
133 3
        return $this->collector->set($this->method, $this->pattern, $this->action)->nth(0)
134 3
                               ->setStrategy($this->strategy)->setParams($this->params)
135 3
                               ->setDefaults($this->defaults)->setMetadataArray($this->metadata);
136
    }
137
138
    /**
139
     * Remove this route from the collector.
140
     *
141
     * @return self
142
     */
143
144 5
    public function forget()
145
    {
146 5
        $this->collector->forget($this->method, $this->pattern);
147 5
        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 7
    public function call()
159
    {
160 7
        $this->action = $this->parseCallable($this->action);
161
162 7
        if ($this->strategy === null) {
163 5
            return call_user_func_array($this->action, array_merge($this->defaults, $this->params));
164
        }
165
166 2
        if (!is_object($this->strategy)) {
167 2
            $this->strategy = new $this->strategy;
168 2
        }
169
170 2
        return $this->callWithStrategy();
171
    }
172
173
    /**
174
     * Seek for dynamic content on callables. eg. routes action controller#action
175
     * syntax allow to use the variables to build the string like: {controller}@{action}
176
     *
177
     * @param string|array|\Closure $callable
178
     * @return string|array|\Closure
179
     */
180
181 7
    private function parseCallable($callable)
182
    {
183 7
        if (is_string($callable) && strpos($callable, "@")) {
184 4
            $callable = explode("@", $callable);
185 4
        }
186
187 7
        if (is_array($callable)) {
188 4
            if (is_string($callable[0])) {
189 4
                   $callable[0] = $this->parseCallableController($callable[0]);
190 4
                   $callable[1] = $this->parseCallablePlaceholders($callable[1]);
191 4
            } else $callable[1] = $this->parseCallablePlaceholders($callable[1]);
192 4
        }
193
194 7
        return $callable;
195
    }
196
197
    /**
198
     * Get the controller object.
199
     *
200
     * @param string $controller
201
     * @return Object
202
     */
203
204 4
    private function parseCallableController($controller)
205
    {
206 4
        $controller  = rtrim($this->namespace, "\\") . "\\" . $this->parseCallablePlaceholders($controller);
207
208 4
        if ($this->controllerCreationFunction === null) {
209 3
               return new $controller;
210 1
        } else return call_user_func($this->controllerCreationFunction, $controller);
211
    }
212
213
    /**
214
     * Parse and replace dynamic content on route action.
215
     *
216
     * @param  string $fragment Part of callable
217
     * @return string
218
     */
219
220 4
    private function parseCallablePlaceholders($fragment)
221
    {
222 4
        if (strpos($fragment, "{") !== false) {
223
            foreach ($this->params as $placeholder => $value) {
224
                if (strpos($fragment, "{" . $placeholder . "}") !== false) {
225
                    $fragment = str_replace("{" . $placeholder . "}", ucwords(str_replace("-", " ", $value)), $fragment);
226
                }
227
            }
228
        }
229
230 4
        return $fragment;
231
    }
232
233
    /**
234
     * Execute the route action with the given strategy.
235
     *
236
     * @throws BadRouteException
237
     * @return mixed
238
     */
239
240 2
    private function callWithStrategy()
241
    {
242 2
        if ($this->strategy instanceof StrategyInterface) {
243 1
            if ($this->strategy instanceof MatcherAwareInterface) {
244 1
                $this->strategy->setMatcher($this->matcher);
245 1
            }
246
247 1
            return $this->strategy->call($this);
248
        }
249
250 1
        $strategy = get_class($this->strategy);
251 1
        throw new BadRouteException("`$strategy` is not a valid route dispatch strategy, ".
252 1
            "it must implement the `Codeburner\\Router\\Strategies\\StrategyInterface` interface.");
253
    }
254
255
    /**
256
     * @return Collector
257
     */
258
259
    public function getCollector()
260
    {
261
        return $this->collector;
262
    }
263
264
    /**
265
     * @return string
266
     */
267
268
    public function getMethod()
269
    {
270
        return $this->method;
271
    }
272
273
    /**
274
     * @return string
275
     */
276
277 22
    public function getPattern()
278
    {
279 22
        return $this->pattern;
280
    }
281
282
    /**
283
     * @return string[]
284
     */
285
286
    public function getSegments()
287
    {
288
        return explode("/", $this->pattern);
289
    }
290
291
    /**
292
     * @return string|array|\Closure
293
     */
294
295 6
    public function getAction()
296
    {
297 6
        return $this->action;
298
    }
299
300
    /**
301
     * @return string
302
     */
303
304
    public function getNamespace()
305
    {
306
        return $this->namespace;
307
    }
308
309
    /**
310
     * @return string[]
311
     */
312
313 22
    public function getParams()
314
    {
315 22
        return $this->params;
316
    }
317
318
    /**
319
     * @param string $key
320
     * @return string
321
     */
322
323
    public function getParam($key)
324
    {
325
        return $this->params[$key];
326
    }
327
328
    /**
329
     * @return array
330
     */
331
332
    public function getDefaults()
333
    {
334
        return $this->defaults;
335
    }
336
337
    /**
338
     * @param string $key
339
     * @return mixed
340
     */
341
342
    public function getDefault($key)
343
    {
344
        return $this->defaults[$key];
345
    }
346
347
    /**
348
     * @return array
349
     */
350
351
    public function getMetadataArray()
352
    {
353
        return $this->metadata;
354
    }
355
356
    /**
357
     * @param string $key
358
     * @return mixed
359
     */
360
361
    public function getMetadata($key)
362
    {
363
        return $this->metadata[$key];
364
    }
365
366
    /**
367
     * @return string|null
368
     */
369
370
    public function getStrategy()
371
    {
372
        if ($this->strategy instanceof StrategyInterface) {
373
            return get_class($this->strategy);
374
        }
375
376
        return $this->strategy;
377
    }
378
379
    /**
380
     * @inheritdoc
381
     */
382
383
    public function getMatcher()
384
    {
385
        return $this->matcher;
386
    }
387
388
    /**
389
     * Verify if a Route have already been blocked.
390
     *
391
     * @return boolean
392
     */
393
394 22
    public function getBlock()
395
    {
396 22
        return $this->blocked;
397
    }
398
399
    /**
400
     * Blocking a route indicate that that route have been selected and
401
     * parsed, now it will be given to the matcher.
402
     *
403
     * @param bool $blocked
404
     * @return self
405
     */
406
407 22
    public function setBlock($blocked)
408
    {
409 22
        $this->blocked = $blocked;
410 22
        return $this;
411
    }
412
413
    /**
414
     * @param string $method
415
     * @return Route
416
     */
417
418
    public function setMethod($method)
419
    {
420
        $this->forget();
421
        $this->method = $method;
422
        return $this->reset();
423
    }
424
425
    /**
426
     * @param string $pattern
427
     * @return Route
428
     */
429
430 3
    public function setPattern($pattern)
431
    {
432 3
        $this->forget();
433 3
        $this->pattern = $pattern;
434 3
        return $this->reset();
435
    }
436
437
    /**
438
     * @param string $pattern
439
     * @return self
440
     */
441
442 22
    public function setPatternWithoutReset($pattern)
443
    {
444 22
        $this->pattern = $pattern;
445 22
        return $this;
446
    }
447
448
    /**
449
     * @param string $action
450
     * @return self
451
     */
452
453
    public function setAction($action)
454
    {
455
        $this->action = $action;
456
        return $this;
457
    }
458
459
    /**
460
     * @param string $namespace
461
     * @return self
462
     */
463
464
    public function setNamespace($namespace)
465
    {
466
        $this->namespace = $namespace;
467
        return $this;
468
    }
469
470
    /**
471
     * @param string[] $params
472
     * @return self
473
     */
474
475 22
    public function setParams(array $params)
476
    {
477 22
        $this->params = $params;
478 22
        return $this;
479
    }
480
481
    /**
482
     * @param string $key
483
     * @param string $value
484
     *
485
     * @return self
486
     */
487
488
    public function setParam($key, $value)
489
    {
490
        $this->params[$key] = $value;
491
        return $this;
492
    }
493
494
    /**
495
     * @param mixed[] $defaults
496
     * @return self
497
     */
498
499 3
    public function setDefaults(array $defaults)
500
    {
501 3
        $this->defaults = $defaults;
502 3
        return $this;
503
    }
504
505
    /**
506
     * @param string $key
507
     * @param mixed $value
508
     *
509
     * @return self
510
     */
511
512
    public function setDefault($key, $value)
513
    {
514
        $this->defaults[$key] = $value;
515
        return $this;
516
    }
517
518
    /**
519
     * @param mixed[] $metadata
520
     * @return self
521
     */
522
523 3
    public function setMetadataArray(array $metadata)
524
    {
525 3
        $this->metadata = $metadata;
526 3
        return $this;
527
    }
528
529
    /**
530
     * @param string $key
531
     * @param mixed $value
532
     *
533
     * @return $this
534
     */
535
536
    public function setMetadata($key, $value)
537
    {
538
        $this->metadata[$key] = $value;
539
        return $this;
540
    }
541
542
    /**
543
     * @param null|string|StrategyInterface $strategy
544
     * @return self
545
     */
546
547 12
    public function setStrategy($strategy)
548
    {
549 12
        $this->strategy = $strategy;
550 12
        return $this;
551
    }
552
553
    /**
554
     * @inheritdoc
555
     */
556
557 20
    public function setMatcher(Matcher $matcher)
558
    {
559 20
        $this->matcher = $matcher;
560 20
        return $this;
561
    }
562
563
    /**
564
     * Set a constraint to a token in the route pattern.
565
     *
566
     * @param string $token
567
     * @param string $regex
568
     *
569
     * @return self
570
     */
571
572 1
    public function setConstraint($token, $regex)
573
    {
574 1
        $initPos = strpos($this->pattern, "{" . $token);
575
576 1
        if ($initPos !== false) {
577 1
            $endPos = strpos($this->pattern, "}", $initPos);
578 1
            $newPattern = substr_replace($this->pattern, "{" . "$token:$regex" . "}", $initPos, $endPos - $initPos + 1);
579 1
            $wildcards = $this->collector->getWildcardTokens();
580 1
            $newPattern = str_replace(array_keys($wildcards), $wildcards, $newPattern);
581 1
            $this->setPatternWithoutReset($newPattern);
582 1
        }
583
584 1
        return $this;
585
    }
586
587
    /**
588
     * Set a function to create controllers.
589
     *
590
     * @param string|array|\Closure $callable
591
     * @throws BadRouteException
592
     * @return self
593
     */
594
595 1
    public function setControllerCreationFunction($callable)
596
    {
597 1
        if (!is_callable($callable)) {
598
            throw new BadRouteException(BadRouteException::WRONG_CONTROLLER_CREATION_FUNC);
599
        }
600
601 1
        $this->controllerCreationFunction = $this->parseCallable($callable);
602 1
        return $this;
603
    }
604
605
    /**
606
     * @param string $key
607
     * @return bool
608
     */
609
610
    public function hasParam($key)
611
    {
612
        return isset($this->params[$key]);
613
    }
614
615
    /**
616
     * @param string $key
617
     * @return bool
618
     */
619
620
    public function hasDefault($key)
621
    {
622
        return isset($this->defaults[$key]);
623
    }
624
625
    /**
626
     * @param string $key
627
     * @return bool
628
     */
629
630
    public function hasMetadata($key)
631
    {
632
        return isset($this->metadata[$key]);
633
    }
634
635
}
636