Passed
Branch dev (8e1e05)
by Alex
03:51
created

Route::setStrategy()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 5
ccs 3
cts 3
cp 1
rs 9.4285
cc 1
eloc 3
nc 1
nop 1
crap 1
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 50
    public function __construct(Collector $collector, $method, $pattern, $action)
118
    {
119 50
        $this->collector = $collector;
120 50
        $this->method    = $method;
121 50
        $this->pattern   = $pattern;
122 50
        $this->action    = $action;
123 50
    }
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
     * @param callable $container
155
     * @throws BadRouteException
156
     * @return mixed
157
     */
158
159 16
    public function call(callable $container = null)
160
    {
161 16
        $this->action = $this->buildCallable($this->action, $container);
0 ignored issues
show
Bug introduced by
It seems like $container defined by parameter $container on line 159 can also be of type null; however, Codeburner\Router\Route::buildCallable() does only seem to accept callable, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

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