Code Coverage
 
Lines
Branches
Paths
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
57 / 57
93.65% covered (success)
93.65%
59 / 63
50.68% covered (warning)
50.68%
37 / 73
92.00% covered (success)
92.00%
23 / 25
CRAP
0.00% covered (danger)
0.00%
0 / 1
Route
100.00% covered (success)
100.00%
57 / 57
93.65% covered (success)
93.65%
59 / 63
50.68% covered (warning)
50.68%
37 / 73
100.00% covered (success)
100.00%
25 / 25
211.18
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 any
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 map
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 get
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 post
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 put
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 patch
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 delete
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 head
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 options
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 withMethods
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 methods
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 prefix
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
5 / 5
75.00% covered (warning)
75.00%
3 / 4
100.00% covered (success)
100.00%
1 / 1
3.14
 controller
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 replaceMiddleware
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setBeforeHandlers
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setAfterHandlers
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 name
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 url
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 view
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 pattern
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 match
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 routePattern
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 pathWithoutPrefix
100.00% covered (success)
100.00%
12 / 12
85.00% covered (warning)
85.00%
17 / 20
15.00% covered (danger)
15.00%
6 / 40
100.00% covered (success)
100.00%
1 / 1
37.09
 normalizePrefix
100.00% covered (success)
100.00%
1 / 1
85.71% covered (warning)
85.71%
6 / 7
66.67% covered (warning)
66.67%
2 / 3
100.00% covered (success)
100.00%
1 / 1
2.15
1<?php
2
3declare(strict_types=1);
4
5namespace Celema\Router;
6
7use Celema\Router\Exception\ValueError;
8use Closure;
9use Psr\Http\Server\MiddlewareInterface as Middleware;
10
11/** @psalm-api */
12class Route
13{
14    use AddsBeforeAfter;
15    use AddsMiddleware;
16
17    /** @var null|list<string> */
18    protected ?array $methods = null;
19
20    /** @var Closure|list{string, string}|string */
21    protected Closure|array|string $view;
22
23    protected ?RoutePattern $routePattern = null;
24
25    /**
26     * @param string $pattern The URL pattern of the route
27     *
28     * @param callable|list{string, string}|non-empty-string $view The callable view. Can be a closure, an invokable object or any other callable
29     *
30     * @param string $name The name of the route, used to generate its URL. Routes without a name cannot be looked up.
31     */
32    public function __construct(
33        protected string $pattern,
34        callable|array|string $view,
35        protected string $name = '',
36    ) {
37        if (is_callable($view)) {
38            $this->view = Closure::fromCallable($view);
39        } else {
40            $this->view = $view;
41        }
42    }
43
44    /** @param callable|list{string, string}|non-empty-string $view */
45    public static function any(string $pattern, callable|array|string $view, string $name = ''): self
46    {
47        return new self($pattern, $view, $name);
48    }
49
50    /**
51     * @param array<array-key, string> $methods
52     * @param callable|list{string, string}|non-empty-string $view
53     */
54    public static function map(
55        array $methods,
56        string $pattern,
57        callable|array|string $view,
58        string $name = '',
59    ): self {
60        if ($methods === []) {
61            throw new ValueError('Route method list cannot be empty.');
62        }
63
64        return new self($pattern, $view, $name)->withMethods(array_values($methods));
65    }
66
67    /** @param callable|list{string, string}|non-empty-string $view */
68    public static function get(string $pattern, callable|array|string $view, string $name = ''): self
69    {
70        return new self($pattern, $view, $name)->withMethods(['GET']);
71    }
72
73    /** @param callable|list{string, string}|non-empty-string $view */
74    public static function post(string $pattern, callable|array|string $view, string $name = ''): self
75    {
76        return new self($pattern, $view, $name)->withMethods(['POST']);
77    }
78
79    /** @param callable|list{string, string}|non-empty-string $view */
80    public static function put(string $pattern, callable|array|string $view, string $name = ''): self
81    {
82        return new self($pattern, $view, $name)->withMethods(['PUT']);
83    }
84
85    /** @param callable|list{string, string}|non-empty-string $view */
86    public static function patch(string $pattern, callable|array|string $view, string $name = ''): self
87    {
88        return new self($pattern, $view, $name)->withMethods(['PATCH']);
89    }
90
91    /** @param callable|list{string, string}|non-empty-string $view */
92    public static function delete(
93        string $pattern,
94        callable|array|string $view,
95        string $name = '',
96    ): self {
97        return new self($pattern, $view, $name)->withMethods(['DELETE']);
98    }
99
100    /** @param callable|list{string, string}|non-empty-string $view */
101    public static function head(string $pattern, callable|array|string $view, string $name = ''): self
102    {
103        return new self($pattern, $view, $name)->withMethods(['HEAD']);
104    }
105
106    /** @param callable|list{string, string}|non-empty-string $view */
107    public static function options(
108        string $pattern,
109        callable|array|string $view,
110        string $name = '',
111    ): self {
112        return new self($pattern, $view, $name)->withMethods(['OPTIONS']);
113    }
114
115    /** @param list<string> $methods */
116    private function withMethods(array $methods): self
117    {
118        $this->methods = array_map(static fn(string $method): string => strtoupper($method), $methods);
119
120        return $this;
121    }
122
123    /** @return list<string> */
124    public function methods(): array
125    {
126        return $this->methods ?? [];
127    }
128
129    /** @internal */
130    public function prefix(string $pattern = '', string $name = ''): static
131    {
132        if ($pattern !== '') {
133            $this->pattern = $pattern . $this->pattern;
134            $this->routePattern = null;
135        }
136
137        if ($name !== '') {
138            $this->name = $name . $this->name;
139        }
140
141        return $this;
142    }
143
144    /** @internal */
145    public function controller(string $controller): static
146    {
147        if (is_string($this->view)) {
148            $this->view = [$controller, $this->view];
149
150            return $this;
151        }
152
153        throw new ValueError(
154            'Cannot add controller to route action. Controller groups require bare method names.',
155        );
156    }
157
158    /**
159     * @internal
160     * @param list<Middleware> $middleware
161     */
162    public function replaceMiddleware(array $middleware): static
163    {
164        $this->middleware = $middleware;
165
166        return $this;
167    }
168
169    /**
170     * @internal
171     * @param list<Before> $beforeHandlers
172     */
173    public function setBeforeHandlers(array $beforeHandlers): static
174    {
175        $this->beforeHandlers = $beforeHandlers;
176
177        return $this;
178    }
179
180    /**
181     * @internal
182     * @param list<After> $afterHandlers
183     */
184    public function setAfterHandlers(array $afterHandlers): static
185    {
186        $this->afterHandlers = $afterHandlers;
187
188        return $this;
189    }
190
191    public function name(): string
192    {
193        return $this->name;
194    }
195
196    /** @param array<array-key, mixed> $params */
197    public function url(array $params = []): string
198    {
199        return $this->routePattern()->generate($params);
200    }
201
202    /** @return Closure|list{string, string}|string */
203    public function view(): Closure|array|string
204    {
205        return $this->view;
206    }
207
208    public function pattern(): string
209    {
210        return $this->pattern;
211    }
212
213    /** @return null|array<string, string> */
214    public function match(string $url, string $prefix = ''): ?array
215    {
216        $path = $this->pathWithoutPrefix($url, $prefix);
217
218        if ($path === null) {
219            return null;
220        }
221
222        return $this->routePattern()->match($path);
223    }
224
225    private function routePattern(): RoutePattern
226    {
227        return $this->routePattern ??= new RoutePattern($this->pattern);
228    }
229
230    private function pathWithoutPrefix(string $url, string $prefix): ?string
231    {
232        $path = $url === '' ? '/' : $url;
233        $prefix = self::normalizePrefix($prefix);
234
235        if ($prefix === '') {
236            return $path;
237        }
238
239        if ($path === $prefix) {
240            return '/';
241        }
242
243        if (!str_starts_with($path, $prefix . '/')) {
244            return null;
245        }
246
247        $path = substr($path, strlen($prefix));
248
249        if ($path === '/' && $this->routePattern()->pattern() === '/') {
250            return null;
251        }
252
253        return $path;
254    }
255
256    private static function normalizePrefix(string $prefix): string
257    {
258        return $prefix === '' ? '' : '/' . trim($prefix, '/');
259    }
260}

Paths

Below are the source code lines that represent each code path as identified by Xdebug. Please note a path is not necessarily coterminous with a line, a line may contain multiple paths and therefore show up more than once. Please also be aware that some paths may include implicit rather than explicit branches, e.g. an if statement always has an else as part of its logical flow even if you didn't write one.

Route->__construct
33        protected string $pattern,
34        callable|array|string $view,
35        protected string $name = '',
36    ) {
37        if (is_callable($view)) {
 
37        if (is_callable($view)) {
38            $this->view = Closure::fromCallable($view);
 
42    }
33        protected string $pattern,
34        callable|array|string $view,
35        protected string $name = '',
36    ) {
37        if (is_callable($view)) {
 
40            $this->view = $view;
41        }
42    }
 
42    }
Route->any
45    public static function any(string $pattern, callable|array|string $view, string $name = ''): self
46    {
47        return new self($pattern, $view, $name);
48    }
Route->controller
145    public function controller(string $controller): static
146    {
147        if (is_string($this->view)) {
 
148            $this->view = [$controller, $this->view];
149
150            return $this;
145    public function controller(string $controller): static
146    {
147        if (is_string($this->view)) {
 
153        throw new ValueError(
154            'Cannot add controller to route action. Controller groups require bare method names.',
155        );
156    }
Route->delete
93        string $pattern,
94        callable|array|string $view,
95        string $name = '',
96    ): self {
97        return new self($pattern, $view, $name)->withMethods(['DELETE']);
98    }
Route->get
68    public static function get(string $pattern, callable|array|string $view, string $name = ''): self
69    {
70        return new self($pattern, $view, $name)->withMethods(['GET']);
71    }
Route->head
101    public static function head(string $pattern, callable|array|string $view, string $name = ''): self
102    {
103        return new self($pattern, $view, $name)->withMethods(['HEAD']);
104    }
Route->map
55        array $methods,
56        string $pattern,
57        callable|array|string $view,
58        string $name = '',
59    ): self {
60        if ($methods === []) {
 
61            throw new ValueError('Route method list cannot be empty.');
55        array $methods,
56        string $pattern,
57        callable|array|string $view,
58        string $name = '',
59    ): self {
60        if ($methods === []) {
 
64        return new self($pattern, $view, $name)->withMethods(array_values($methods));
65    }
Route->match
214    public function match(string $url, string $prefix = ''): ?array
215    {
216        $path = $this->pathWithoutPrefix($url, $prefix);
217
218        if ($path === null) {
 
219            return null;
214    public function match(string $url, string $prefix = ''): ?array
215    {
216        $path = $this->pathWithoutPrefix($url, $prefix);
217
218        if ($path === null) {
 
222        return $this->routePattern()->match($path);
223    }
Route->methods
126        return $this->methods ?? [];
127    }
Route->name
193        return $this->name;
194    }
Route->normalizePrefix
256    private static function normalizePrefix(string $prefix): string
257    {
258        return $prefix === '' ? '' : '/' . trim($prefix, '/');
 
258        return $prefix === '' ? '' : '/' . trim($prefix, '/');
 
258        return $prefix === '' ? '' : '/' . trim($prefix, '/');
259    }
256    private static function normalizePrefix(string $prefix): string
257    {
258        return $prefix === '' ? '' : '/' . trim($prefix, '/');
 
258        return $prefix === '' ? '' : '/' . trim($prefix, '/');
 
258        return $prefix === '' ? '' : '/' . trim($prefix, '/');
 
258        return $prefix === '' ? '' : '/' . trim($prefix, '/');
 
258        return $prefix === '' ? '' : '/' . trim($prefix, '/');
259    }
256    private static function normalizePrefix(string $prefix): string
257    {
258        return $prefix === '' ? '' : '/' . trim($prefix, '/');
 
258        return $prefix === '' ? '' : '/' . trim($prefix, '/');
 
258        return $prefix === '' ? '' : '/' . trim($prefix, '/');
 
258        return $prefix === '' ? '' : '/' . trim($prefix, '/');
 
258        return $prefix === '' ? '' : '/' . trim($prefix, '/');
259    }
Route->options
108        string $pattern,
109        callable|array|string $view,
110        string $name = '',
111    ): self {
112        return new self($pattern, $view, $name)->withMethods(['OPTIONS']);
113    }
Route->patch
86    public static function patch(string $pattern, callable|array|string $view, string $name = ''): self
87    {
88        return new self($pattern, $view, $name)->withMethods(['PATCH']);
89    }
Route->pathWithoutPrefix
230    private function pathWithoutPrefix(string $url, string $prefix): ?string
231    {
232        $path = $url === '' ? '/' : $url;
 
232        $path = $url === '' ? '/' : $url;
 
232        $path = $url === '' ? '/' : $url;
233        $prefix = self::normalizePrefix($prefix);
234
235        if ($prefix === '') {
 
236            return $path;
230    private function pathWithoutPrefix(string $url, string $prefix): ?string
231    {
232        $path = $url === '' ? '/' : $url;
 
232        $path = $url === '' ? '/' : $url;
 
232        $path = $url === '' ? '/' : $url;
233        $prefix = self::normalizePrefix($prefix);
234
235        if ($prefix === '') {
 
239        if ($path === $prefix) {
 
240            return '/';
230    private function pathWithoutPrefix(string $url, string $prefix): ?string
231    {
232        $path = $url === '' ? '/' : $url;
 
232        $path = $url === '' ? '/' : $url;
 
232        $path = $url === '' ? '/' : $url;
233        $prefix = self::normalizePrefix($prefix);
234
235        if ($prefix === '') {
 
239        if ($path === $prefix) {
 
243        if (!str_starts_with($path, $prefix . '/')) {
 
243        if (!str_starts_with($path, $prefix . '/')) {
 
243        if (!str_starts_with($path, $prefix . '/')) {
 
244            return null;
230    private function pathWithoutPrefix(string $url, string $prefix): ?string
231    {
232        $path = $url === '' ? '/' : $url;
 
232        $path = $url === '' ? '/' : $url;
 
232        $path = $url === '' ? '/' : $url;
233        $prefix = self::normalizePrefix($prefix);
234
235        if ($prefix === '') {
 
239        if ($path === $prefix) {
 
243        if (!str_starts_with($path, $prefix . '/')) {
 
243        if (!str_starts_with($path, $prefix . '/')) {
 
243        if (!str_starts_with($path, $prefix . '/')) {
 
247        $path = substr($path, strlen($prefix));
 
247        $path = substr($path, strlen($prefix));
 
247        $path = substr($path, strlen($prefix));
248
249        if ($path === '/' && $this->routePattern()->pattern() === '/') {
 
249        if ($path === '/' && $this->routePattern()->pattern() === '/') {
 
249        if ($path === '/' && $this->routePattern()->pattern() === '/') {
 
250            return null;
230    private function pathWithoutPrefix(string $url, string $prefix): ?string
231    {
232        $path = $url === '' ? '/' : $url;
 
232        $path = $url === '' ? '/' : $url;
 
232        $path = $url === '' ? '/' : $url;
233        $prefix = self::normalizePrefix($prefix);
234
235        if ($prefix === '') {
 
239        if ($path === $prefix) {
 
243        if (!str_starts_with($path, $prefix . '/')) {
 
243        if (!str_starts_with($path, $prefix . '/')) {
 
243        if (!str_starts_with($path, $prefix . '/')) {
 
247        $path = substr($path, strlen($prefix));
 
247        $path = substr($path, strlen($prefix));
 
247        $path = substr($path, strlen($prefix));
248
249        if ($path === '/' && $this->routePattern()->pattern() === '/') {
 
249        if ($path === '/' && $this->routePattern()->pattern() === '/') {
 
249        if ($path === '/' && $this->routePattern()->pattern() === '/') {
 
253        return $path;
254    }
230    private function pathWithoutPrefix(string $url, string $prefix): ?string
231    {
232        $path = $url === '' ? '/' : $url;
 
232        $path = $url === '' ? '/' : $url;
 
232        $path = $url === '' ? '/' : $url;
233        $prefix = self::normalizePrefix($prefix);
234
235        if ($prefix === '') {
 
239        if ($path === $prefix) {
 
243        if (!str_starts_with($path, $prefix . '/')) {
 
243        if (!str_starts_with($path, $prefix . '/')) {
 
243        if (!str_starts_with($path, $prefix . '/')) {
 
247        $path = substr($path, strlen($prefix));
 
247        $path = substr($path, strlen($prefix));
 
247        $path = substr($path, strlen($prefix));
248
249        if ($path === '/' && $this->routePattern()->pattern() === '/') {
 
249        if ($path === '/' && $this->routePattern()->pattern() === '/') {
 
250            return null;
230    private function pathWithoutPrefix(string $url, string $prefix): ?string
231    {
232        $path = $url === '' ? '/' : $url;
 
232        $path = $url === '' ? '/' : $url;
 
232        $path = $url === '' ? '/' : $url;
233        $prefix = self::normalizePrefix($prefix);
234
235        if ($prefix === '') {
 
239        if ($path === $prefix) {
 
243        if (!str_starts_with($path, $prefix . '/')) {
 
243        if (!str_starts_with($path, $prefix . '/')) {
 
243        if (!str_starts_with($path, $prefix . '/')) {
 
247        $path = substr($path, strlen($prefix));
 
247        $path = substr($path, strlen($prefix));
 
247        $path = substr($path, strlen($prefix));
248
249        if ($path === '/' && $this->routePattern()->pattern() === '/') {
 
249        if ($path === '/' && $this->routePattern()->pattern() === '/') {
 
253        return $path;
254    }
230    private function pathWithoutPrefix(string $url, string $prefix): ?string
231    {
232        $path = $url === '' ? '/' : $url;
 
232        $path = $url === '' ? '/' : $url;
 
232        $path = $url === '' ? '/' : $url;
233        $prefix = self::normalizePrefix($prefix);
234
235        if ($prefix === '') {
 
239        if ($path === $prefix) {
 
243        if (!str_starts_with($path, $prefix . '/')) {
 
243        if (!str_starts_with($path, $prefix . '/')) {
 
243        if (!str_starts_with($path, $prefix . '/')) {
 
247        $path = substr($path, strlen($prefix));
 
247        $path = substr($path, strlen($prefix));
 
247        $path = substr($path, strlen($prefix));
248
249        if ($path === '/' && $this->routePattern()->pattern() === '/') {
 
249        if ($path === '/' && $this->routePattern()->pattern() === '/') {
 
249        if ($path === '/' && $this->routePattern()->pattern() === '/') {
 
250            return null;
230    private function pathWithoutPrefix(string $url, string $prefix): ?string
231    {
232        $path = $url === '' ? '/' : $url;
 
232        $path = $url === '' ? '/' : $url;
 
232        $path = $url === '' ? '/' : $url;
233        $prefix = self::normalizePrefix($prefix);
234
235        if ($prefix === '') {
 
239        if ($path === $prefix) {
 
243        if (!str_starts_with($path, $prefix . '/')) {
 
243        if (!str_starts_with($path, $prefix . '/')) {
 
243        if (!str_starts_with($path, $prefix . '/')) {
 
247        $path = substr($path, strlen($prefix));
 
247        $path = substr($path, strlen($prefix));
 
247        $path = substr($path, strlen($prefix));
248
249        if ($path === '/' && $this->routePattern()->pattern() === '/') {
 
249        if ($path === '/' && $this->routePattern()->pattern() === '/') {
 
249        if ($path === '/' && $this->routePattern()->pattern() === '/') {
 
253        return $path;
254    }
230    private function pathWithoutPrefix(string $url, string $prefix): ?string
231    {
232        $path = $url === '' ? '/' : $url;
 
232        $path = $url === '' ? '/' : $url;
 
232        $path = $url === '' ? '/' : $url;
233        $prefix = self::normalizePrefix($prefix);
234
235        if ($prefix === '') {
 
239        if ($path === $prefix) {
 
243        if (!str_starts_with($path, $prefix . '/')) {
 
243        if (!str_starts_with($path, $prefix . '/')) {
 
243        if (!str_starts_with($path, $prefix . '/')) {
 
247        $path = substr($path, strlen($prefix));
 
247        $path = substr($path, strlen($prefix));
 
247        $path = substr($path, strlen($prefix));
248
249        if ($path === '/' && $this->routePattern()->pattern() === '/') {
 
249        if ($path === '/' && $this->routePattern()->pattern() === '/') {
 
250            return null;
230    private function pathWithoutPrefix(string $url, string $prefix): ?string
231    {
232        $path = $url === '' ? '/' : $url;
 
232        $path = $url === '' ? '/' : $url;
 
232        $path = $url === '' ? '/' : $url;
233        $prefix = self::normalizePrefix($prefix);
234
235        if ($prefix === '') {
 
239        if ($path === $prefix) {
 
243        if (!str_starts_with($path, $prefix . '/')) {
 
243        if (!str_starts_with($path, $prefix . '/')) {
 
243        if (!str_starts_with($path, $prefix . '/')) {
 
247        $path = substr($path, strlen($prefix));
 
247        $path = substr($path, strlen($prefix));
 
247        $path = substr($path, strlen($prefix));
248
249        if ($path === '/' && $this->routePattern()->pattern() === '/') {
 
249        if ($path === '/' && $this->routePattern()->pattern() === '/') {
 
253        return $path;
254    }
230    private function pathWithoutPrefix(string $url, string $prefix): ?string
231    {
232        $path = $url === '' ? '/' : $url;
 
232        $path = $url === '' ? '/' : $url;
 
232        $path = $url === '' ? '/' : $url;
233        $prefix = self::normalizePrefix($prefix);
234
235        if ($prefix === '') {
 
239        if ($path === $prefix) {
 
243        if (!str_starts_with($path, $prefix . '/')) {
 
243        if (!str_starts_with($path, $prefix . '/')) {
 
243        if (!str_starts_with($path, $prefix . '/')) {
 
244            return null;
230    private function pathWithoutPrefix(string $url, string $prefix): ?string
231    {
232        $path = $url === '' ? '/' : $url;
 
232        $path = $url === '' ? '/' : $url;
 
232        $path = $url === '' ? '/' : $url;
233        $prefix = self::normalizePrefix($prefix);
234
235        if ($prefix === '') {
 
239        if ($path === $prefix) {
 
243        if (!str_starts_with($path, $prefix . '/')) {
 
243        if (!str_starts_with($path, $prefix . '/')) {
 
243        if (!str_starts_with($path, $prefix . '/')) {
 
247        $path = substr($path, strlen($prefix));
 
247        $path = substr($path, strlen($prefix));
 
247        $path = substr($path, strlen($prefix));
248
249        if ($path === '/' && $this->routePattern()->pattern() === '/') {
 
249        if ($path === '/' && $this->routePattern()->pattern() === '/') {
 
249        if ($path === '/' && $this->routePattern()->pattern() === '/') {
 
250            return null;
230    private function pathWithoutPrefix(string $url, string $prefix): ?string
231    {
232        $path = $url === '' ? '/' : $url;
 
232        $path = $url === '' ? '/' : $url;
 
232        $path = $url === '' ? '/' : $url;
233        $prefix = self::normalizePrefix($prefix);
234
235        if ($prefix === '') {
 
239        if ($path === $prefix) {
 
243        if (!str_starts_with($path, $prefix . '/')) {
 
243        if (!str_starts_with($path, $prefix . '/')) {
 
243        if (!str_starts_with($path, $prefix . '/')) {
 
247        $path = substr($path, strlen($prefix));
 
247        $path = substr($path, strlen($prefix));
 
247        $path = substr($path, strlen($prefix));
248
249        if ($path === '/' && $this->routePattern()->pattern() === '/') {
 
249        if ($path === '/' && $this->routePattern()->pattern() === '/') {
 
249        if ($path === '/' && $this->routePattern()->pattern() === '/') {
 
253        return $path;
254    }
230    private function pathWithoutPrefix(string $url, string $prefix): ?string
231    {
232        $path = $url === '' ? '/' : $url;
 
232        $path = $url === '' ? '/' : $url;
 
232        $path = $url === '' ? '/' : $url;
233        $prefix = self::normalizePrefix($prefix);
234
235        if ($prefix === '') {
 
239        if ($path === $prefix) {
 
243        if (!str_starts_with($path, $prefix . '/')) {
 
243        if (!str_starts_with($path, $prefix . '/')) {
 
243        if (!str_starts_with($path, $prefix . '/')) {
 
247        $path = substr($path, strlen($prefix));
 
247        $path = substr($path, strlen($prefix));
 
247        $path = substr($path, strlen($prefix));
248
249        if ($path === '/' && $this->routePattern()->pattern() === '/') {
 
249        if ($path === '/' && $this->routePattern()->pattern() === '/') {
 
250            return null;
230    private function pathWithoutPrefix(string $url, string $prefix): ?string
231    {
232        $path = $url === '' ? '/' : $url;
 
232        $path = $url === '' ? '/' : $url;
 
232        $path = $url === '' ? '/' : $url;
233        $prefix = self::normalizePrefix($prefix);
234
235        if ($prefix === '') {
 
239        if ($path === $prefix) {
 
243        if (!str_starts_with($path, $prefix . '/')) {
 
243        if (!str_starts_with($path, $prefix . '/')) {
 
243        if (!str_starts_with($path, $prefix . '/')) {
 
247        $path = substr($path, strlen($prefix));
 
247        $path = substr($path, strlen($prefix));
 
247        $path = substr($path, strlen($prefix));
248
249        if ($path === '/' && $this->routePattern()->pattern() === '/') {
 
249        if ($path === '/' && $this->routePattern()->pattern() === '/') {
 
253        return $path;
254    }
230    private function pathWithoutPrefix(string $url, string $prefix): ?string
231    {
232        $path = $url === '' ? '/' : $url;
 
232        $path = $url === '' ? '/' : $url;
 
232        $path = $url === '' ? '/' : $url;
233        $prefix = self::normalizePrefix($prefix);
234
235        if ($prefix === '') {
 
239        if ($path === $prefix) {
 
243        if (!str_starts_with($path, $prefix . '/')) {
 
243        if (!str_starts_with($path, $prefix . '/')) {
 
243        if (!str_starts_with($path, $prefix . '/')) {
 
247        $path = substr($path, strlen($prefix));
 
247        $path = substr($path, strlen($prefix));
 
247        $path = substr($path, strlen($prefix));
248
249        if ($path === '/' && $this->routePattern()->pattern() === '/') {
 
249        if ($path === '/' && $this->routePattern()->pattern() === '/') {
 
249        if ($path === '/' && $this->routePattern()->pattern() === '/') {
 
250            return null;
230    private function pathWithoutPrefix(string $url, string $prefix): ?string
231    {
232        $path = $url === '' ? '/' : $url;
 
232        $path = $url === '' ? '/' : $url;
 
232        $path = $url === '' ? '/' : $url;
233        $prefix = self::normalizePrefix($prefix);
234
235        if ($prefix === '') {
 
239        if ($path === $prefix) {
 
243        if (!str_starts_with($path, $prefix . '/')) {
 
243        if (!str_starts_with($path, $prefix . '/')) {
 
243        if (!str_starts_with($path, $prefix . '/')) {
 
247        $path = substr($path, strlen($prefix));
 
247        $path = substr($path, strlen($prefix));
 
247        $path = substr($path, strlen($prefix));
248
249        if ($path === '/' && $this->routePattern()->pattern() === '/') {
 
249        if ($path === '/' && $this->routePattern()->pattern() === '/') {
 
249        if ($path === '/' && $this->routePattern()->pattern() === '/') {
 
253        return $path;
254    }
230    private function pathWithoutPrefix(string $url, string $prefix): ?string
231    {
232        $path = $url === '' ? '/' : $url;
 
232        $path = $url === '' ? '/' : $url;
 
232        $path = $url === '' ? '/' : $url;
233        $prefix = self::normalizePrefix($prefix);
234
235        if ($prefix === '') {
 
239        if ($path === $prefix) {
 
243        if (!str_starts_with($path, $prefix . '/')) {
 
243        if (!str_starts_with($path, $prefix . '/')) {
 
243        if (!str_starts_with($path, $prefix . '/')) {
 
247        $path = substr($path, strlen($prefix));
 
247        $path = substr($path, strlen($prefix));
 
247        $path = substr($path, strlen($prefix));
248
249        if ($path === '/' && $this->routePattern()->pattern() === '/') {
 
249        if ($path === '/' && $this->routePattern()->pattern() === '/') {
 
250            return null;
230    private function pathWithoutPrefix(string $url, string $prefix): ?string
231    {
232        $path = $url === '' ? '/' : $url;
 
232        $path = $url === '' ? '/' : $url;
 
232        $path = $url === '' ? '/' : $url;
233        $prefix = self::normalizePrefix($prefix);
234
235        if ($prefix === '') {
 
239        if ($path === $prefix) {
 
243        if (!str_starts_with($path, $prefix . '/')) {
 
243        if (!str_starts_with($path, $prefix . '/')) {
 
243        if (!str_starts_with($path, $prefix . '/')) {
 
247        $path = substr($path, strlen($prefix));
 
247        $path = substr($path, strlen($prefix));
 
247        $path = substr($path, strlen($prefix));
248
249        if ($path === '/' && $this->routePattern()->pattern() === '/') {
 
249        if ($path === '/' && $this->routePattern()->pattern() === '/') {
 
253        return $path;
254    }
230    private function pathWithoutPrefix(string $url, string $prefix): ?string
231    {
232        $path = $url === '' ? '/' : $url;
 
232        $path = $url === '' ? '/' : $url;
 
232        $path = $url === '' ? '/' : $url;
233        $prefix = self::normalizePrefix($prefix);
234
235        if ($prefix === '') {
 
236            return $path;
230    private function pathWithoutPrefix(string $url, string $prefix): ?string
231    {
232        $path = $url === '' ? '/' : $url;
 
232        $path = $url === '' ? '/' : $url;
 
232        $path = $url === '' ? '/' : $url;
233        $prefix = self::normalizePrefix($prefix);
234
235        if ($prefix === '') {
 
239        if ($path === $prefix) {
 
240            return '/';
230    private function pathWithoutPrefix(string $url, string $prefix): ?string
231    {
232        $path = $url === '' ? '/' : $url;
 
232        $path = $url === '' ? '/' : $url;
 
232        $path = $url === '' ? '/' : $url;
233        $prefix = self::normalizePrefix($prefix);
234
235        if ($prefix === '') {
 
239        if ($path === $prefix) {
 
243        if (!str_starts_with($path, $prefix . '/')) {
 
243        if (!str_starts_with($path, $prefix . '/')) {
 
243        if (!str_starts_with($path, $prefix . '/')) {
 
244            return null;
230    private function pathWithoutPrefix(string $url, string $prefix): ?string
231    {
232        $path = $url === '' ? '/' : $url;
 
232        $path = $url === '' ? '/' : $url;
 
232        $path = $url === '' ? '/' : $url;
233        $prefix = self::normalizePrefix($prefix);
234
235        if ($prefix === '') {
 
239        if ($path === $prefix) {
 
243        if (!str_starts_with($path, $prefix . '/')) {
 
243        if (!str_starts_with($path, $prefix . '/')) {
 
243        if (!str_starts_with($path, $prefix . '/')) {
 
247        $path = substr($path, strlen($prefix));
 
247        $path = substr($path, strlen($prefix));
 
247        $path = substr($path, strlen($prefix));
248
249        if ($path === '/' && $this->routePattern()->pattern() === '/') {
 
249        if ($path === '/' && $this->routePattern()->pattern() === '/') {
 
249        if ($path === '/' && $this->routePattern()->pattern() === '/') {
 
250            return null;
230    private function pathWithoutPrefix(string $url, string $prefix): ?string
231    {
232        $path = $url === '' ? '/' : $url;
 
232        $path = $url === '' ? '/' : $url;
 
232        $path = $url === '' ? '/' : $url;
233        $prefix = self::normalizePrefix($prefix);
234
235        if ($prefix === '') {
 
239        if ($path === $prefix) {
 
243        if (!str_starts_with($path, $prefix . '/')) {
 
243        if (!str_starts_with($path, $prefix . '/')) {
 
243        if (!str_starts_with($path, $prefix . '/')) {
 
247        $path = substr($path, strlen($prefix));
 
247        $path = substr($path, strlen($prefix));
 
247        $path = substr($path, strlen($prefix));
248
249        if ($path === '/' && $this->routePattern()->pattern() === '/') {
 
249        if ($path === '/' && $this->routePattern()->pattern() === '/') {
 
249        if ($path === '/' && $this->routePattern()->pattern() === '/') {
 
253        return $path;
254    }
230    private function pathWithoutPrefix(string $url, string $prefix): ?string
231    {
232        $path = $url === '' ? '/' : $url;
 
232        $path = $url === '' ? '/' : $url;
 
232        $path = $url === '' ? '/' : $url;
233        $prefix = self::normalizePrefix($prefix);
234
235        if ($prefix === '') {
 
239        if ($path === $prefix) {
 
243        if (!str_starts_with($path, $prefix . '/')) {
 
243        if (!str_starts_with($path, $prefix . '/')) {
 
243        if (!str_starts_with($path, $prefix . '/')) {
 
247        $path = substr($path, strlen($prefix));
 
247        $path = substr($path, strlen($prefix));
 
247        $path = substr($path, strlen($prefix));
248
249        if ($path === '/' && $this->routePattern()->pattern() === '/') {
 
249        if ($path === '/' && $this->routePattern()->pattern() === '/') {
 
250            return null;
230    private function pathWithoutPrefix(string $url, string $prefix): ?string
231    {
232        $path = $url === '' ? '/' : $url;
 
232        $path = $url === '' ? '/' : $url;
 
232        $path = $url === '' ? '/' : $url;
233        $prefix = self::normalizePrefix($prefix);
234
235        if ($prefix === '') {
 
239        if ($path === $prefix) {
 
243        if (!str_starts_with($path, $prefix . '/')) {
 
243        if (!str_starts_with($path, $prefix . '/')) {
 
243        if (!str_starts_with($path, $prefix . '/')) {
 
247        $path = substr($path, strlen($prefix));
 
247        $path = substr($path, strlen($prefix));
 
247        $path = substr($path, strlen($prefix));
248
249        if ($path === '/' && $this->routePattern()->pattern() === '/') {
 
249        if ($path === '/' && $this->routePattern()->pattern() === '/') {
 
253        return $path;
254    }
230    private function pathWithoutPrefix(string $url, string $prefix): ?string
231    {
232        $path = $url === '' ? '/' : $url;
 
232        $path = $url === '' ? '/' : $url;
 
232        $path = $url === '' ? '/' : $url;
233        $prefix = self::normalizePrefix($prefix);
234
235        if ($prefix === '') {
 
239        if ($path === $prefix) {
 
243        if (!str_starts_with($path, $prefix . '/')) {
 
243        if (!str_starts_with($path, $prefix . '/')) {
 
243        if (!str_starts_with($path, $prefix . '/')) {
 
247        $path = substr($path, strlen($prefix));
 
247        $path = substr($path, strlen($prefix));
 
247        $path = substr($path, strlen($prefix));
248
249        if ($path === '/' && $this->routePattern()->pattern() === '/') {
 
249        if ($path === '/' && $this->routePattern()->pattern() === '/') {
 
249        if ($path === '/' && $this->routePattern()->pattern() === '/') {
 
250            return null;
230    private function pathWithoutPrefix(string $url, string $prefix): ?string
231    {
232        $path = $url === '' ? '/' : $url;
 
232        $path = $url === '' ? '/' : $url;
 
232        $path = $url === '' ? '/' : $url;
233        $prefix = self::normalizePrefix($prefix);
234
235        if ($prefix === '') {
 
239        if ($path === $prefix) {
 
243        if (!str_starts_with($path, $prefix . '/')) {
 
243        if (!str_starts_with($path, $prefix . '/')) {
 
243        if (!str_starts_with($path, $prefix . '/')) {
 
247        $path = substr($path, strlen($prefix));
 
247        $path = substr($path, strlen($prefix));
 
247        $path = substr($path, strlen($prefix));
248
249        if ($path === '/' && $this->routePattern()->pattern() === '/') {
 
249        if ($path === '/' && $this->routePattern()->pattern() === '/') {
 
249        if ($path === '/' && $this->routePattern()->pattern() === '/') {
 
253        return $path;
254    }
230    private function pathWithoutPrefix(string $url, string $prefix): ?string
231    {
232        $path = $url === '' ? '/' : $url;
 
232        $path = $url === '' ? '/' : $url;
 
232        $path = $url === '' ? '/' : $url;
233        $prefix = self::normalizePrefix($prefix);
234
235        if ($prefix === '') {
 
239        if ($path === $prefix) {
 
243        if (!str_starts_with($path, $prefix . '/')) {
 
243        if (!str_starts_with($path, $prefix . '/')) {
 
243        if (!str_starts_with($path, $prefix . '/')) {
 
247        $path = substr($path, strlen($prefix));
 
247        $path = substr($path, strlen($prefix));
 
247        $path = substr($path, strlen($prefix));
248
249        if ($path === '/' && $this->routePattern()->pattern() === '/') {
 
249        if ($path === '/' && $this->routePattern()->pattern() === '/') {
 
250            return null;
230    private function pathWithoutPrefix(string $url, string $prefix): ?string
231    {
232        $path = $url === '' ? '/' : $url;
 
232        $path = $url === '' ? '/' : $url;
 
232        $path = $url === '' ? '/' : $url;
233        $prefix = self::normalizePrefix($prefix);
234
235        if ($prefix === '') {
 
239        if ($path === $prefix) {
 
243        if (!str_starts_with($path, $prefix . '/')) {
 
243        if (!str_starts_with($path, $prefix . '/')) {
 
243        if (!str_starts_with($path, $prefix . '/')) {
 
247        $path = substr($path, strlen($prefix));
 
247        $path = substr($path, strlen($prefix));
 
247        $path = substr($path, strlen($prefix));
248
249        if ($path === '/' && $this->routePattern()->pattern() === '/') {
 
249        if ($path === '/' && $this->routePattern()->pattern() === '/') {
 
253        return $path;
254    }
230    private function pathWithoutPrefix(string $url, string $prefix): ?string
231    {
232        $path = $url === '' ? '/' : $url;
 
232        $path = $url === '' ? '/' : $url;
 
232        $path = $url === '' ? '/' : $url;
233        $prefix = self::normalizePrefix($prefix);
234
235        if ($prefix === '') {
 
239        if ($path === $prefix) {
 
243        if (!str_starts_with($path, $prefix . '/')) {
 
243        if (!str_starts_with($path, $prefix . '/')) {
 
243        if (!str_starts_with($path, $prefix . '/')) {
 
244            return null;
230    private function pathWithoutPrefix(string $url, string $prefix): ?string
231    {
232        $path = $url === '' ? '/' : $url;
 
232        $path = $url === '' ? '/' : $url;
 
232        $path = $url === '' ? '/' : $url;
233        $prefix = self::normalizePrefix($prefix);
234
235        if ($prefix === '') {
 
239        if ($path === $prefix) {
 
243        if (!str_starts_with($path, $prefix . '/')) {
 
243        if (!str_starts_with($path, $prefix . '/')) {
 
243        if (!str_starts_with($path, $prefix . '/')) {
 
247        $path = substr($path, strlen($prefix));
 
247        $path = substr($path, strlen($prefix));
 
247        $path = substr($path, strlen($prefix));
248
249        if ($path === '/' && $this->routePattern()->pattern() === '/') {
 
249        if ($path === '/' && $this->routePattern()->pattern() === '/') {
 
249        if ($path === '/' && $this->routePattern()->pattern() === '/') {
 
250            return null;
230    private function pathWithoutPrefix(string $url, string $prefix): ?string
231    {
232        $path = $url === '' ? '/' : $url;
 
232        $path = $url === '' ? '/' : $url;
 
232        $path = $url === '' ? '/' : $url;
233        $prefix = self::normalizePrefix($prefix);
234
235        if ($prefix === '') {
 
239        if ($path === $prefix) {
 
243        if (!str_starts_with($path, $prefix . '/')) {
 
243        if (!str_starts_with($path, $prefix . '/')) {
 
243        if (!str_starts_with($path, $prefix . '/')) {
 
247        $path = substr($path, strlen($prefix));
 
247        $path = substr($path, strlen($prefix));
 
247        $path = substr($path, strlen($prefix));
248
249        if ($path === '/' && $this->routePattern()->pattern() === '/') {
 
249        if ($path === '/' && $this->routePattern()->pattern() === '/') {
 
249        if ($path === '/' && $this->routePattern()->pattern() === '/') {
 
253        return $path;
254    }
230    private function pathWithoutPrefix(string $url, string $prefix): ?string
231    {
232        $path = $url === '' ? '/' : $url;
 
232        $path = $url === '' ? '/' : $url;
 
232        $path = $url === '' ? '/' : $url;
233        $prefix = self::normalizePrefix($prefix);
234
235        if ($prefix === '') {
 
239        if ($path === $prefix) {
 
243        if (!str_starts_with($path, $prefix . '/')) {
 
243        if (!str_starts_with($path, $prefix . '/')) {
 
243        if (!str_starts_with($path, $prefix . '/')) {
 
247        $path = substr($path, strlen($prefix));
 
247        $path = substr($path, strlen($prefix));
 
247        $path = substr($path, strlen($prefix));
248
249        if ($path === '/' && $this->routePattern()->pattern() === '/') {
 
249        if ($path === '/' && $this->routePattern()->pattern() === '/') {
 
250            return null;
230    private function pathWithoutPrefix(string $url, string $prefix): ?string
231    {
232        $path = $url === '' ? '/' : $url;
 
232        $path = $url === '' ? '/' : $url;
 
232        $path = $url === '' ? '/' : $url;
233        $prefix = self::normalizePrefix($prefix);
234
235        if ($prefix === '') {
 
239        if ($path === $prefix) {
 
243        if (!str_starts_with($path, $prefix . '/')) {
 
243        if (!str_starts_with($path, $prefix . '/')) {
 
243        if (!str_starts_with($path, $prefix . '/')) {
 
247        $path = substr($path, strlen($prefix));
 
247        $path = substr($path, strlen($prefix));
 
247        $path = substr($path, strlen($prefix));
248
249        if ($path === '/' && $this->routePattern()->pattern() === '/') {
 
249        if ($path === '/' && $this->routePattern()->pattern() === '/') {
 
253        return $path;
254    }
230    private function pathWithoutPrefix(string $url, string $prefix): ?string
231    {
232        $path = $url === '' ? '/' : $url;
 
232        $path = $url === '' ? '/' : $url;
 
232        $path = $url === '' ? '/' : $url;
233        $prefix = self::normalizePrefix($prefix);
234
235        if ($prefix === '') {
 
239        if ($path === $prefix) {
 
243        if (!str_starts_with($path, $prefix . '/')) {
 
243        if (!str_starts_with($path, $prefix . '/')) {
 
243        if (!str_starts_with($path, $prefix . '/')) {
 
247        $path = substr($path, strlen($prefix));
 
247        $path = substr($path, strlen($prefix));
 
247        $path = substr($path, strlen($prefix));
248
249        if ($path === '/' && $this->routePattern()->pattern() === '/') {
 
249        if ($path === '/' && $this->routePattern()->pattern() === '/') {
 
249        if ($path === '/' && $this->routePattern()->pattern() === '/') {
 
250            return null;
230    private function pathWithoutPrefix(string $url, string $prefix): ?string
231    {
232        $path = $url === '' ? '/' : $url;
 
232        $path = $url === '' ? '/' : $url;
 
232        $path = $url === '' ? '/' : $url;
233        $prefix = self::normalizePrefix($prefix);
234
235        if ($prefix === '') {
 
239        if ($path === $prefix) {
 
243        if (!str_starts_with($path, $prefix . '/')) {
 
243        if (!str_starts_with($path, $prefix . '/')) {
 
243        if (!str_starts_with($path, $prefix . '/')) {
 
247        $path = substr($path, strlen($prefix));
 
247        $path = substr($path, strlen($prefix));
 
247        $path = substr($path, strlen($prefix));
248
249        if ($path === '/' && $this->routePattern()->pattern() === '/') {
 
249        if ($path === '/' && $this->routePattern()->pattern() === '/') {
 
249        if ($path === '/' && $this->routePattern()->pattern() === '/') {
 
253        return $path;
254    }
230    private function pathWithoutPrefix(string $url, string $prefix): ?string
231    {
232        $path = $url === '' ? '/' : $url;
 
232        $path = $url === '' ? '/' : $url;
 
232        $path = $url === '' ? '/' : $url;
233        $prefix = self::normalizePrefix($prefix);
234
235        if ($prefix === '') {
 
239        if ($path === $prefix) {
 
243        if (!str_starts_with($path, $prefix . '/')) {
 
243        if (!str_starts_with($path, $prefix . '/')) {
 
243        if (!str_starts_with($path, $prefix . '/')) {
 
247        $path = substr($path, strlen($prefix));
 
247        $path = substr($path, strlen($prefix));
 
247        $path = substr($path, strlen($prefix));
248
249        if ($path === '/' && $this->routePattern()->pattern() === '/') {
 
249        if ($path === '/' && $this->routePattern()->pattern() === '/') {
 
250            return null;
230    private function pathWithoutPrefix(string $url, string $prefix): ?string
231    {
232        $path = $url === '' ? '/' : $url;
 
232        $path = $url === '' ? '/' : $url;
 
232        $path = $url === '' ? '/' : $url;
233        $prefix = self::normalizePrefix($prefix);
234
235        if ($prefix === '') {
 
239        if ($path === $prefix) {
 
243        if (!str_starts_with($path, $prefix . '/')) {
 
243        if (!str_starts_with($path, $prefix . '/')) {
 
243        if (!str_starts_with($path, $prefix . '/')) {
 
247        $path = substr($path, strlen($prefix));
 
247        $path = substr($path, strlen($prefix));
 
247        $path = substr($path, strlen($prefix));
248
249        if ($path === '/' && $this->routePattern()->pattern() === '/') {
 
249        if ($path === '/' && $this->routePattern()->pattern() === '/') {
 
253        return $path;
254    }
Route->pattern
210        return $this->pattern;
211    }
Route->post
74    public static function post(string $pattern, callable|array|string $view, string $name = ''): self
75    {
76        return new self($pattern, $view, $name)->withMethods(['POST']);
77    }
Route->prefix
130    public function prefix(string $pattern = '', string $name = ''): static
131    {
132        if ($pattern !== '') {
 
133            $this->pattern = $pattern . $this->pattern;
134            $this->routePattern = null;
135        }
136
137        if ($name !== '') {
 
137        if ($name !== '') {
 
138            $this->name = $name . $this->name;
139        }
140
141        return $this;
 
141        return $this;
142    }
130    public function prefix(string $pattern = '', string $name = ''): static
131    {
132        if ($pattern !== '') {
 
133            $this->pattern = $pattern . $this->pattern;
134            $this->routePattern = null;
135        }
136
137        if ($name !== '') {
 
137        if ($name !== '') {
 
141        return $this;
142    }
130    public function prefix(string $pattern = '', string $name = ''): static
131    {
132        if ($pattern !== '') {
 
137        if ($name !== '') {
 
138            $this->name = $name . $this->name;
139        }
140
141        return $this;
 
141        return $this;
142    }
130    public function prefix(string $pattern = '', string $name = ''): static
131    {
132        if ($pattern !== '') {
 
137        if ($name !== '') {
 
141        return $this;
142    }
Route->put
80    public static function put(string $pattern, callable|array|string $view, string $name = ''): self
81    {
82        return new self($pattern, $view, $name)->withMethods(['PUT']);
83    }
Route->replaceMiddleware
162    public function replaceMiddleware(array $middleware): static
163    {
164        $this->middleware = $middleware;
165
166        return $this;
167    }
Route->routePattern
227        return $this->routePattern ??= new RoutePattern($this->pattern);
228    }
Route->setAfterHandlers
184    public function setAfterHandlers(array $afterHandlers): static
185    {
186        $this->afterHandlers = $afterHandlers;
187
188        return $this;
189    }
Route->setBeforeHandlers
173    public function setBeforeHandlers(array $beforeHandlers): static
174    {
175        $this->beforeHandlers = $beforeHandlers;
176
177        return $this;
178    }
Route->url
197    public function url(array $params = []): string
198    {
199        return $this->routePattern()->generate($params);
200    }
Route->view
205        return $this->view;
206    }
Route->withMethods
116    private function withMethods(array $methods): self
117    {
118        $this->methods = array_map(static fn(string $method): string => strtoupper($method), $methods);
119
120        return $this;
121    }
{closure:/workspace/celema/router/src/Route.php:118-118}
118        $this->methods = array_map(static fn(string $method): string => strtoupper($method), $methods);