Passed
Branch master (e894a3)
by Melech
15:39 queued 01:19
created

Route::addMiddleware()   A

Complexity

Conditions 6
Paths 32

Size

Total Lines 33
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 6
eloc 16
c 1
b 0
f 0
nc 32
nop 1
dl 0
loc 33
rs 9.1111
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the Valkyrja Framework package.
7
 *
8
 * (c) Melech Mizrachi <[email protected]>
9
 *
10
 * For the full copyright and license information, please view the LICENSE
11
 * file that was distributed with this source code.
12
 */
13
14
namespace Valkyrja\Http\Routing\Model;
15
16
use Valkyrja\Dispatcher\Model\Dispatch;
17
use Valkyrja\Http\Message\Enum\RequestMethod;
18
use Valkyrja\Http\Message\Enum\StatusCode;
19
use Valkyrja\Http\Middleware\Contract\RouteDispatchedMiddleware;
20
use Valkyrja\Http\Middleware\Contract\RouteMatchedMiddleware;
21
use Valkyrja\Http\Middleware\Contract\SendingResponseMiddleware;
22
use Valkyrja\Http\Middleware\Contract\TerminatedMiddleware;
23
use Valkyrja\Http\Middleware\Contract\ThrowableCaughtMiddleware;
24
use Valkyrja\Http\Routing\Constant\Regex;
25
use Valkyrja\Http\Routing\Exception\InvalidRoutePathException;
26
use Valkyrja\Http\Routing\Middleware\RedirectRouteMiddleware;
27
use Valkyrja\Http\Routing\Middleware\SecureRouteMiddleware;
28
use Valkyrja\Http\Routing\Model\Contract\Route as Contract;
29
use Valkyrja\Http\Routing\Model\Parameter\Parameter;
30
use Valkyrja\Http\Struct\Request\Contract\RequestStruct;
31
use Valkyrja\Http\Struct\Response\Contract\ResponseStruct;
32
use Valkyrja\Type\BuiltIn\Support\Cls;
33
use Valkyrja\Type\Data\Cast;
34
35
use function array_map;
36
use function array_merge;
37
use function assert;
38
use function is_a;
39
use function is_array;
40
use function is_int;
41
use function is_string;
42
43
/**
44
 * Class Route.
45
 *
46
 * @author Melech Mizrachi
47
 */
48
class Route extends Dispatch implements Contract
49
{
50
    /** @var string */
51
    protected const string DEFAULT_PATH = '/';
0 ignored issues
show
Bug introduced by
A parse error occurred: Syntax error, unexpected T_STRING, expecting '=' on line 51 at column 27
Loading history...
52
    /** @var string|null */
53
    protected const string|null DEFAULT_NAME = null;
54
    /** @var RequestMethod[] */
55
    protected const array DEFAULT_METHODS = [
56
        RequestMethod::GET,
57
        RequestMethod::HEAD,
58
    ];
59
    /** @var bool|null */
60
    protected const bool|null DEFAULT_SECURE = null;
61
    /** @var string|null */
62
    protected const string|null DEFAULT_TO = null;
63
    /** @var StatusCode|null */
64
    protected const StatusCode|null DEFAULT_CODE = null;
65
66
    /**
67
     * The path for this route.
68
     *
69
     * @var string
70
     */
71
    protected string $path = '/';
72
73
    /**
74
     * The redirect path for this route.
75
     *
76
     * @var string|null
77
     */
78
    protected string|null $to;
79
80
    /**
81
     * The redirect status code for this route.
82
     *
83
     * @var StatusCode|null
84
     */
85
    protected StatusCode|null $code;
86
87
    /**
88
     * The request methods for this route.
89
     *
90
     * @var RequestMethod[]
91
     */
92
    protected array $methods = [
93
        RequestMethod::GET,
94
        RequestMethod::HEAD,
95
    ];
96
97
    /**
98
     * The regex for dynamic routes.
99
     *
100
     * @var string|null
101
     */
102
    protected string|null $regex;
103
104
    /**
105
     * The dynamic parameters.
106
     *
107
     * @var array<array-key, Parameter>
108
     */
109
    protected array $parameters;
110
111
    /**
112
     * The middleware for this route.
113
     *
114
     * @var class-string<RouteMatchedMiddleware>[]|null
115
     */
116
    protected array|null $matchedMiddleware;
117
118
    /**
119
     * The middleware for this route.
120
     *
121
     * @var class-string<RouteDispatchedMiddleware>[]|null
122
     */
123
    protected array|null $dispatchedMiddleware;
124
125
    /**
126
     * The middleware for this route.
127
     *
128
     * @var class-string<ThrowableCaughtMiddleware>[]|null
129
     */
130
    protected array|null $exceptionMiddleware;
131
132
    /**
133
     * The middleware for this route.
134
     *
135
     * @var class-string<SendingResponseMiddleware>[]|null
136
     */
137
    protected array|null $sendingMiddleware;
138
139
    /**
140
     * The middleware for this route.
141
     *
142
     * @var class-string<TerminatedMiddleware>[]|null
143
     */
144
    protected array|null $terminatedMiddleware;
145
146
    /**
147
     * The request struct for this route.
148
     *
149
     * @var class-string<RequestStruct>|null
150
     */
151
    protected string|null $requestStruct;
152
153
    /**
154
     * The response struct for this route.
155
     *
156
     * @var class-string<ResponseStruct>|null
157
     */
158
    protected string|null $responseStruct;
159
160
    /**
161
     * Whether the route is dynamic.
162
     *
163
     * @var bool
164
     */
165
    protected bool $dynamic = false;
166
167
    /**
168
     * Whether the route is secure.
169
     *
170
     * @var bool
171
     */
172
    protected bool $secure = false;
173
174
    /**
175
     * Whether the route is a redirect.
176
     *
177
     * @var bool
178
     */
179
    protected bool $redirect = false;
180
181
    /**
182
     * @param non-empty-string|null                          $path                 [optional] The path
183
     * @param RequestMethod[]|null                           $methods              The request methods
184
     * @param Parameter[]|null                               $parameters           The parameters
185
     * @param class-string<RouteMatchedMiddleware>[]|null    $matchedMiddleware    The matched middleware
186
     * @param class-string<RouteDispatchedMiddleware>[]|null $dispatchedMiddleware The dispatched middleware
187
     * @param class-string<ThrowableCaughtMiddleware>[]|null $exceptionMiddleware  The exception middleware
188
     * @param class-string<SendingResponseMiddleware>[]|null $sendingMiddleware    The sending middleware
189
     * @param class-string<TerminatedMiddleware>[]|null      $terminatedMiddleware The terminated middleware
190
     * @param class-string<RequestStruct>|null               $requestStruct        The request struct
191
     * @param class-string<ResponseStruct>|null              $responseStruct       The response struct
192
     *
193
     * @throws InvalidRoutePathException
194
     */
195
    public function __construct(
196
        string|null $path = null,
197
        string|null $name = null,
198
        array|null $methods = null,
199
        array|null $parameters = null,
200
        array|null $matchedMiddleware = null,
201
        array|null $dispatchedMiddleware = null,
202
        array|null $exceptionMiddleware = null,
203
        array|null $sendingMiddleware = null,
204
        array|null $terminatedMiddleware = null,
205
        string|null $requestStruct = null,
206
        string|null $responseStruct = null,
207
        bool|null $secure = null,
208
        string|null $to = null,
209
        StatusCode|null $code = null,
210
    ) {
211
        $path    ??= static::DEFAULT_PATH;
212
        $name    ??= static::DEFAULT_NAME;
213
        $methods ??= static::DEFAULT_METHODS;
214
        $secure  ??= static::DEFAULT_SECURE;
215
        $to      ??= static::DEFAULT_TO;
216
        $code    ??= static::DEFAULT_CODE;
217
218
        $this->setPath($path);
219
        $this->setMethods($methods);
220
221
        if ($name !== null && $name !== '') {
222
            $this->name = $name;
223
        }
224
225
        if ($parameters !== null) {
226
            $this->setParameters($parameters);
227
        }
228
229
        if ($secure !== null) {
230
            $this->secure = $secure;
231
        }
232
233
        if ($to !== '') {
234
            $this->setTo($to);
235
        }
236
237
        $this->setMatchedMiddleware($matchedMiddleware);
238
        $this->setDispatchedMiddleware($dispatchedMiddleware);
239
        $this->setExceptionMiddleware($exceptionMiddleware);
240
        $this->setSendingMiddleware($sendingMiddleware);
241
        $this->setTerminatedMiddleware($terminatedMiddleware);
242
        $this->setCode($code);
243
        $this->setRequestStruct($requestStruct);
244
        $this->setResponseStruct($responseStruct);
245
    }
246
247
    /**
248
     * @inheritDoc
249
     */
250
    public function getPath(): string
251
    {
252
        return $this->path;
253
    }
254
255
    /**
256
     * @inheritDoc
257
     */
258
    public function setPath(string $path): static
259
    {
260
        if ($path === '') {
261
            throw new InvalidRoutePathException('Path must be a non-empty-string.');
262
        }
263
264
        $this->path = $path;
265
266
        return $this;
267
    }
268
269
    /**
270
     * @inheritDoc
271
     */
272
    public function withPath(string $path): static
273
    {
274
        $route = clone $this;
275
276
        $route->path .= $path;
277
278
        return $route;
279
    }
280
281
    /**
282
     * @inheritDoc
283
     */
284
    public function withName(string $name): static
285
    {
286
        $route = clone $this;
287
288
        $currentName = $this->name ?? '';
289
290
        if ($name) {
291
            $route->name = $currentName
292
                ? "$currentName.$name"
293
                : $name;
294
        }
295
296
        return $route;
297
    }
298
299
    /**
300
     * @inheritDoc
301
     */
302
    public function getTo(): string|null
303
    {
304
        return $this->to ?? null;
305
    }
306
307
    /**
308
     * @inheritDoc
309
     */
310
    public function setTo(string|null $to = null): static
311
    {
312
        if ($to !== null) {
313
            $this->setRedirect(true);
314
        }
315
316
        $this->to = $to;
317
318
        return $this;
319
    }
320
321
    /**
322
     * @inheritDoc
323
     */
324
    public function getCode(): StatusCode|null
325
    {
326
        return $this->code ?? null;
327
    }
328
329
    /**
330
     * @inheritDoc
331
     */
332
    public function setCode(StatusCode|int|null $code = null): static
333
    {
334
        $this->code = is_int($code)
335
            ? StatusCode::from($code)
336
            : $code;
337
338
        return $this;
339
    }
340
341
    /**
342
     * @inheritDoc
343
     */
344
    public function getMethods(): array
345
    {
346
        return $this->methods;
347
    }
348
349
    /**
350
     * @inheritDoc
351
     */
352
    public function setMethods(array $methods): static
353
    {
354
        $this->methods = array_map(
355
            callback: static fn (RequestMethod|string $method): RequestMethod => is_string($method)
356
                ? RequestMethod::from($method)
357
                : $method,
358
            array: $methods
359
        );
360
361
        return $this;
362
    }
363
364
    /**
365
     * @inheritDoc
366
     */
367
    public function getRegex(): string|null
368
    {
369
        return $this->regex ?? null;
370
    }
371
372
    /**
373
     * @inheritDoc
374
     */
375
    public function setRegex(string|null $regex = null): static
376
    {
377
        $this->regex = $regex;
378
379
        return $this;
380
    }
381
382
    /**
383
     * @inheritDoc
384
     */
385
    public function getParameters(): array
386
    {
387
        return $this->parameters ?? [];
388
    }
389
390
    /**
391
     * @inheritDoc
392
     */
393
    public function setParameters(array $parameters): static
394
    {
395
        $this->parameters = array_map(
396
            static fn (Parameter|array $parameter): Parameter => is_array($parameter)
397
                ? Parameter::fromArray($parameter)
398
                : $parameter,
399
            $parameters
400
        );
401
402
        return $this;
403
    }
404
405
    /**
406
     * @inheritDoc
407
     */
408
    public function setParameter(Parameter $parameter): static
409
    {
410
        $this->parameters ??= [];
411
412
        $this->parameters[] = $parameter;
413
414
        return $this;
415
    }
416
417
    /**
418
     * @inheritDoc
419
     */
420
    public function addParameter(
421
        string $name,
422
        string|null $regex = null,
423
        Cast|null $cast = null,
424
        bool $isOptional = false,
425
        bool $shouldCapture = true,
426
        mixed $default = null
427
    ): static {
428
        return $this->setParameter(
429
            new Parameter(
430
                name: $name,
431
                regex: $regex ?? Regex::ANY,
432
                cast: $cast,
433
                isOptional: $isOptional,
434
                shouldCapture: $shouldCapture,
435
                default: $default
436
            )
437
        );
438
    }
439
440
    /**
441
     * @inheritDoc
442
     */
443
    public function getMiddleware(): array|null
444
    {
445
        return array_merge(
446
            $this->matchedMiddleware ?? [],
447
            $this->dispatchedMiddleware ?? [],
448
            $this->exceptionMiddleware ?? [],
449
            $this->sendingMiddleware ?? [],
450
            $this->terminatedMiddleware ?? [],
451
        );
452
    }
453
454
    /**
455
     * @inheritDoc
456
     */
457
    public function setMiddleware(array|null $middleware = null): static
458
    {
459
        $this->matchedMiddleware    = [];
460
        $this->dispatchedMiddleware = [];
461
        $this->exceptionMiddleware  = [];
462
        $this->sendingMiddleware    = [];
463
        $this->terminatedMiddleware = [];
464
465
        $this->addMiddlewares($middleware ?? []);
466
467
        return $this;
468
    }
469
470
    /**
471
     * @inheritDoc
472
     */
473
    public function withMiddleware(array $middleware): static
474
    {
475
        $route = clone $this;
476
477
        $route->addMiddlewares($middleware);
478
479
        return $route;
480
    }
481
482
    /**
483
     * @inheritDoc
484
     */
485
    public function addMiddleware(string $middleware): static
486
    {
487
        if (Cls::inherits($middleware, RouteMatchedMiddleware::class)) {
488
            /** @var class-string<RouteMatchedMiddleware> $middleware */
489
            $this->matchedMiddleware   ??= [];
490
            $this->matchedMiddleware[] = $middleware;
491
        }
492
493
        if (Cls::inherits($middleware, RouteDispatchedMiddleware::class)) {
494
            /** @var class-string<RouteDispatchedMiddleware> $middleware */
495
            $this->dispatchedMiddleware   ??= [];
496
            $this->dispatchedMiddleware[] = $middleware;
497
        }
498
499
        if (Cls::inherits($middleware, ThrowableCaughtMiddleware::class)) {
500
            /** @var class-string<ThrowableCaughtMiddleware> $middleware */
501
            $this->exceptionMiddleware   ??= [];
502
            $this->exceptionMiddleware[] = $middleware;
503
        }
504
505
        if (Cls::inherits($middleware, SendingResponseMiddleware::class)) {
506
            /** @var class-string<SendingResponseMiddleware> $middleware */
507
            $this->sendingMiddleware   ??= [];
508
            $this->sendingMiddleware[] = $middleware;
509
        }
510
511
        if (Cls::inherits($middleware, TerminatedMiddleware::class)) {
512
            /** @var class-string<TerminatedMiddleware> $middleware */
513
            $this->terminatedMiddleware   ??= [];
514
            $this->terminatedMiddleware[] = $middleware;
515
        }
516
517
        return $this;
518
    }
519
520
    /**
521
     * @inheritDoc
522
     */
523
    public function addMiddlewares(array $middleware): static
524
    {
525
        array_map(fn (string $middlewareItem) => $this->addMiddleware($middlewareItem), $middleware);
526
527
        return $this;
528
    }
529
530
    /**
531
     * @inheritDoc
532
     *
533
     * @return class-string<RouteMatchedMiddleware>[]|null
534
     */
535
    public function getMatchedMiddleware(): array|null
536
    {
537
        return $this->matchedMiddleware ?? null;
538
    }
539
540
    /**
541
     * @inheritDoc
542
     */
543
    public function setMatchedMiddleware(array|null $middleware = null): static
544
    {
545
        $this->matchedMiddleware = $middleware;
546
547
        return $this;
548
    }
549
550
    /**
551
     * @inheritDoc
552
     */
553
    public function withMatchedMiddleware(array $middleware): static
554
    {
555
        $route = clone $this;
556
557
        $route->matchedMiddleware = array_merge($this->matchedMiddleware ?? [], $middleware);
558
559
        return $route;
560
    }
561
562
    /**
563
     * @inheritDoc
564
     *
565
     * @return class-string<RouteDispatchedMiddleware>[]|null
566
     */
567
    public function getDispatchedMiddleware(): array|null
568
    {
569
        return $this->dispatchedMiddleware ?? null;
570
    }
571
572
    /**
573
     * @inheritDoc
574
     */
575
    public function setDispatchedMiddleware(array|null $middleware = null): static
576
    {
577
        $this->dispatchedMiddleware = $middleware;
578
579
        return $this;
580
    }
581
582
    /**
583
     * @inheritDoc
584
     */
585
    public function withDispatchedMiddleware(array $middleware): static
586
    {
587
        $route = clone $this;
588
589
        $route->dispatchedMiddleware = array_merge($this->dispatchedMiddleware ?? [], $middleware);
590
591
        return $route;
592
    }
593
594
    /**
595
     * @inheritDoc
596
     *
597
     * @return class-string<ThrowableCaughtMiddleware>[]|null
598
     */
599
    public function getExceptionMiddleware(): array|null
600
    {
601
        return $this->exceptionMiddleware ?? null;
602
    }
603
604
    /**
605
     * @inheritDoc
606
     */
607
    public function setExceptionMiddleware(array|null $middleware = null): static
608
    {
609
        $this->exceptionMiddleware = $middleware;
610
611
        return $this;
612
    }
613
614
    /**
615
     * @inheritDoc
616
     */
617
    public function withExceptionMiddleware(array $middleware): static
618
    {
619
        $route = clone $this;
620
621
        $route->exceptionMiddleware = array_merge($this->exceptionMiddleware ?? [], $middleware);
622
623
        return $route;
624
    }
625
626
    /**
627
     * @inheritDoc
628
     *
629
     * @return class-string<SendingResponseMiddleware>[]|null
630
     */
631
    public function getSendingMiddleware(): array|null
632
    {
633
        return $this->sendingMiddleware ?? null;
634
    }
635
636
    /**
637
     * @inheritDoc
638
     */
639
    public function setSendingMiddleware(array|null $middleware = null): static
640
    {
641
        $this->sendingMiddleware = $middleware;
642
643
        return $this;
644
    }
645
646
    /**
647
     * @inheritDoc
648
     */
649
    public function withSendingMiddleware(array $middleware): static
650
    {
651
        $route = clone $this;
652
653
        $route->sendingMiddleware = array_merge($this->sendingMiddleware ?? [], $middleware);
654
655
        return $route;
656
    }
657
658
    /**
659
     * @inheritDoc
660
     *
661
     * @return class-string<TerminatedMiddleware>[]|null
662
     */
663
    public function getTerminatedMiddleware(): array|null
664
    {
665
        return $this->terminatedMiddleware ?? null;
666
    }
667
668
    /**
669
     * @inheritDoc
670
     */
671
    public function setTerminatedMiddleware(array|null $middleware = null): static
672
    {
673
        $this->terminatedMiddleware = $middleware;
674
675
        return $this;
676
    }
677
678
    /**
679
     * @inheritDoc
680
     */
681
    public function withTerminatedMiddleware(array $middleware): static
682
    {
683
        $route = clone $this;
684
685
        $route->terminatedMiddleware = array_merge($this->terminatedMiddleware ?? [], $middleware);
686
687
        return $route;
688
    }
689
690
    /**
691
     * @inheritDoc
692
     */
693
    public function getRequestStruct(): string|null
694
    {
695
        return $this->requestStruct ?? null;
696
    }
697
698
    /**
699
     * @inheritDoc
700
     */
701
    public function setRequestStruct(string|null $requestStruct = null): static
702
    {
703
        assert($requestStruct === null || is_a($requestStruct, RequestStruct::class, true));
704
705
        $this->requestStruct = $requestStruct;
706
707
        return $this;
708
    }
709
710
    /**
711
     * Get the response struct.
712
     *
713
     * @return class-string<ResponseStruct>|null
714
     */
715
    public function getResponseStruct(): string|null
716
    {
717
        return $this->responseStruct ?? null;
718
    }
719
720
    /**
721
     * Set the response struct.
722
     *
723
     * @param class-string<ResponseStruct>|null $responseStruct The response struct
724
     *
725
     * @return static
726
     */
727
    public function setResponseStruct(string|null $responseStruct = null): static
728
    {
729
        assert($responseStruct === null || is_a($responseStruct, ResponseStruct::class, true));
730
731
        $this->responseStruct = $responseStruct;
732
733
        return $this;
734
    }
735
736
    /**
737
     * @inheritDoc
738
     */
739
    public function isDynamic(): bool
740
    {
741
        return $this->dynamic;
742
    }
743
744
    /**
745
     * @inheritDoc
746
     */
747
    public function setDynamic(bool $dynamic = true): static
748
    {
749
        $this->dynamic = $dynamic;
750
751
        return $this;
752
    }
753
754
    /**
755
     * @inheritDoc
756
     */
757
    public function isSecure(): bool
758
    {
759
        return $this->secure;
760
    }
761
762
    /**
763
     * @inheritDoc
764
     */
765
    public function setSecure(bool $secure = true): static
766
    {
767
        if ($secure) {
768
            $this->matchedMiddleware[] = SecureRouteMiddleware::class;
769
        }
770
771
        $this->secure = $secure;
772
773
        return $this;
774
    }
775
776
    /**
777
     * @inheritDoc
778
     */
779
    public function isRedirect(): bool
780
    {
781
        return $this->redirect;
782
    }
783
784
    /**
785
     * @inheritDoc
786
     */
787
    public function setRedirect(bool $redirect): static
788
    {
789
        if ($redirect) {
790
            $this->matchedMiddleware[] = RedirectRouteMiddleware::class;
791
        }
792
793
        $this->redirect = $redirect;
794
795
        return $this;
796
    }
797
}
798