• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

codeigniter4 / CodeIgniter4 / 18327246865

07 Oct 2025 09:54PM UTC coverage: 84.318% (-0.007%) from 84.325%
18327246865

Pull #9749

github

web-flow
Merge e7c65af30 into 3473349b6
Pull Request #9749: feat(app): Allow turning off defined routes and/or auto routing

48 of 52 new or added lines in 5 files covered. (92.31%)

4 existing lines in 1 file now uncovered.

21125 of 25054 relevant lines covered (84.32%)

194.63 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

82.49
/system/Router/Router.php
1
<?php
2

3
declare(strict_types=1);
4

5
/**
6
 * This file is part of CodeIgniter 4 framework.
7
 *
8
 * (c) CodeIgniter Foundation <admin@codeigniter.com>
9
 *
10
 * For the full copyright and license information, please view
11
 * the LICENSE file that was distributed with this source code.
12
 */
13

14
namespace CodeIgniter\Router;
15

16
use Closure;
17
use CodeIgniter\Exceptions\PageNotFoundException;
18
use CodeIgniter\HTTP\Exceptions\BadRequestException;
19
use CodeIgniter\HTTP\Exceptions\RedirectException;
20
use CodeIgniter\HTTP\Method;
21
use CodeIgniter\HTTP\Request;
22
use CodeIgniter\HTTP\ResponseInterface;
23
use CodeIgniter\Router\Exceptions\RouterException;
24
use Config\App;
25
use Config\Feature;
26
use Config\Routing;
27

28
/**
29
 * Request router.
30
 *
31
 * @see \CodeIgniter\Router\RouterTest
32
 */
33
class Router implements RouterInterface
34
{
35
    /**
36
     * List of allowed HTTP methods (and CLI for command line use).
37
     */
38
    public const HTTP_METHODS = [
39
        Method::GET,
40
        Method::HEAD,
41
        Method::POST,
42
        Method::PATCH,
43
        Method::PUT,
44
        Method::DELETE,
45
        Method::OPTIONS,
46
        Method::TRACE,
47
        Method::CONNECT,
48
        'CLI',
49
    ];
50

51
    /**
52
     * A RouteCollection instance.
53
     *
54
     * @var RouteCollectionInterface
55
     */
56
    protected $collection;
57

58
    /**
59
     * Sub-directory that contains the requested controller class.
60
     * Primarily used by 'autoRoute'.
61
     *
62
     * @var string|null
63
     */
64
    protected $directory;
65

66
    /**
67
     * The name of the controller class.
68
     *
69
     * @var (Closure(mixed...): (ResponseInterface|string|void))|string
70
     */
71
    protected $controller;
72

73
    /**
74
     * The name of the method to use.
75
     *
76
     * @var string
77
     */
78
    protected $method;
79

80
    /**
81
     * An array of binds that were collected
82
     * so they can be sent to closure routes.
83
     *
84
     * @var array
85
     */
86
    protected $params = [];
87

88
    /**
89
     * The name of the front controller.
90
     *
91
     * @var string
92
     */
93
    protected $indexPage = 'index.php';
94

95
    /**
96
     * Whether dashes in URI's should be converted
97
     * to underscores when determining method names.
98
     *
99
     * @var bool
100
     */
101
    protected $translateURIDashes = false;
102

103
    /**
104
     * The route that was matched for this request.
105
     *
106
     * @var array|null
107
     */
108
    protected $matchedRoute;
109

110
    /**
111
     * The options set for the matched route.
112
     *
113
     * @var array|null
114
     */
115
    protected $matchedRouteOptions;
116

117
    /**
118
     * The locale that was detected in a route.
119
     *
120
     * @var string
121
     */
122
    protected $detectedLocale;
123

124
    /**
125
     * The filter info from Route Collection
126
     * if the matched route should be filtered.
127
     *
128
     * @var list<string>
129
     */
130
    protected $filtersInfo = [];
131

132
    protected ?AutoRouterInterface $autoRouter = null;
133

134
    /**
135
     * Permitted URI chars
136
     *
137
     * The default value is `''` (do not check) for backward compatibility.
138
     */
139
    protected string $permittedURIChars = '';
140

141
    /**
142
     * Stores a reference to the RouteCollection object.
143
     */
144
    public function __construct(RouteCollectionInterface $routes, ?Request $request = null)
145
    {
146
        $config = config(App::class);
192✔
147

148
        if (isset($config->permittedURIChars)) {
192✔
149
            $this->permittedURIChars = $config->permittedURIChars;
192✔
150
        }
151

152
        $this->collection = $routes;
192✔
153

154
        // These are only for auto-routing
155
        $this->controller = $this->collection->getDefaultController();
192✔
156
        $this->method     = $this->collection->getDefaultMethod();
192✔
157

158
        $this->collection->setHTTPVerb($request->getMethod() === '' ? $_SERVER['REQUEST_METHOD'] : $request->getMethod());
192✔
159

160
        $this->translateURIDashes = $this->collection->shouldTranslateURIDashes();
192✔
161
    }
162

163
    /**
164
     * Gets the AutoRouter instance
165
     */
166
    private function getAutoRouter(): AutoRouterInterface
167
    {
168
        if (! $this->autoRouter instanceof AutoRouterInterface) {
40✔
169
            $autoRoutesImproved = config(Feature::class)->autoRoutesImproved ?? false;
38✔
170
            if ($autoRoutesImproved) {
38✔
171
                assert($this->collection instanceof RouteCollection);
172

173
                // Only get protected controllers if we're using defined routes
174
                $protectedControllers = $this->collection->shouldUseDefinedRoutes()
7✔
175
                    ? $this->collection->getRegisteredControllers('*')
6✔
176
                    : [];
1✔
177

178
                $this->autoRouter = new AutoRouterImproved(
7✔
179
                    $protectedControllers,
7✔
180
                    $this->collection->getDefaultNamespace(),
7✔
181
                    $this->collection->getDefaultController(),
7✔
182
                    $this->collection->getDefaultMethod(),
7✔
183
                    $this->translateURIDashes,
7✔
184
                );
7✔
185
            } else {
186
                // Only get CLI routes if we're using defined routes
187
                $cliRoutes = $this->collection->shouldUseDefinedRoutes()
31✔
188
                    ? $this->collection->getRoutes('CLI', false)
30✔
189
                    : [];
1✔
190

191
                $this->autoRouter = new AutoRouter(
31✔
192
                    $cliRoutes,
31✔
193
                    $this->collection->getDefaultNamespace(),
31✔
194
                    $this->collection->getDefaultController(),
31✔
195
                    $this->collection->getDefaultMethod(),
31✔
196
                    $this->translateURIDashes,
31✔
197
                );
31✔
198
            }
199
        }
200

201
        return $this->autoRouter;
40✔
202
    }
203

204
    /**
205
     * Finds the controller corresponding to the URI.
206
     *
207
     * @param string|null $uri URI path relative to baseURL
208
     *
209
     * @return (Closure(mixed...): (ResponseInterface|string|void))|string Controller classname or Closure
210
     *
211
     * @throws BadRequestException
212
     * @throws PageNotFoundException
213
     * @throws RedirectException
214
     */
215
    public function handle(?string $uri = null)
216
    {
217
        // If we cannot find a URI to match against, then set it to root (`/`).
218
        if ($uri === null || $uri === '') {
174✔
219
            $uri = '/';
16✔
220
        }
221

222
        // Decode URL-encoded string
223
        $uri = urldecode($uri);
174✔
224

225
        $this->checkDisallowedChars($uri);
174✔
226

227
        // Restart filterInfo
228
        $this->filtersInfo = [];
173✔
229

230
        $useDefinedRoutes = $this->collection->shouldUseDefinedRoutes();
173✔
231
        $useAutoRoute     = $this->collection->shouldAutoRoute();
173✔
232

233
        // Let devs know if both are disabled
234
        if (! $useDefinedRoutes && ! $useAutoRoute) {
173✔
NEW
235
            throw RouterException::forNoRoutingAvailable();
×
236
        }
237

238
        // Fast path 1: Auto-routing ONLY (no defined routes to check)
239
        if ($useAutoRoute && ! $useDefinedRoutes) {
173✔
240
            $this->autoRoute($uri);
2✔
241

242
            return $this->controllerName();
2✔
243
        }
244

245
        // Fast path 2: Defined routes ONLY (no auto-routing fallback)
246
        if ($useDefinedRoutes && ! $useAutoRoute) {
171✔
247
            if ($this->checkRoutes($uri)) {
149✔
248
                if ($this->collection->isFiltered($this->matchedRoute[0])) {
123✔
249
                    $this->filtersInfo = $this->collection->getFiltersForRoute($this->matchedRoute[0]);
15✔
250
                }
251

252
                return $this->controller;
123✔
253
            }
254

255
            throw new PageNotFoundException(
10✔
256
                "Can't find a route for '{$this->collection->getHTTPVerb()}: {$uri}'.",
10✔
257
            );
10✔
258
        }
259

260
        // Original path: BOTH enabled (check defined routes first, then auto-route)
261
        // Checks defined routes
262
        if ($this->checkRoutes($uri)) {
22✔
263
            if ($this->collection->isFiltered($this->matchedRoute[0])) {
5✔
UNCOV
264
                $this->filtersInfo = $this->collection->getFiltersForRoute($this->matchedRoute[0]);
×
265
            }
266

267
            return $this->controller;
5✔
268
        }
269

270
        // Still here? Then we can try to match the URI against
271
        // Controllers/directories, but the application may not
272
        // want this, like in the case of API's.
273
        if (! $this->collection->shouldAutoRoute()) {
20✔
UNCOV
274
            throw new PageNotFoundException(
×
UNCOV
275
                "Can't find a route for '{$this->collection->getHTTPVerb()}: {$uri}'.",
×
UNCOV
276
            );
×
277
        }
278

279
        // Checks auto routes
280
        $this->autoRoute($uri);
20✔
281

282
        return $this->controllerName();
14✔
283
    }
284

285
    /**
286
     * Returns the filter info for the matched route, if any.
287
     *
288
     * @return list<string>
289
     */
290
    public function getFilters(): array
291
    {
292
        return $this->filtersInfo;
94✔
293
    }
294

295
    /**
296
     * Returns the name of the matched controller or closure.
297
     *
298
     * @return (Closure(mixed...): (ResponseInterface|string|void))|string Controller classname or Closure
299
     */
300
    public function controllerName()
301
    {
302
        return $this->translateURIDashes && ! $this->controller instanceof Closure
55✔
303
            ? str_replace('-', '_', $this->controller)
×
304
            : $this->controller;
55✔
305
    }
306

307
    /**
308
     * Returns the name of the method to run in the
309
     * chosen controller.
310
     */
311
    public function methodName(): string
312
    {
313
        return $this->translateURIDashes
117✔
314
            ? str_replace('-', '_', $this->method)
×
315
            : $this->method;
117✔
316
    }
317

318
    /**
319
     * Returns the 404 Override settings from the Collection.
320
     * If the override is a string, will split to controller/index array.
321
     *
322
     * @return array{string, string}|(Closure(string): (ResponseInterface|string|void))|null
323
     */
324
    public function get404Override()
325
    {
326
        $route = $this->collection->get404Override();
11✔
327

328
        if (is_string($route)) {
11✔
329
            $routeArray = explode('::', $route);
3✔
330

331
            return [
3✔
332
                $routeArray[0], // Controller
3✔
333
                $routeArray[1] ?? 'index',   // Method
3✔
334
            ];
3✔
335
        }
336

337
        if (is_callable($route)) {
8✔
338
            return $route;
1✔
339
        }
340

341
        return null;
7✔
342
    }
343

344
    /**
345
     * Returns the binds that have been matched and collected
346
     * during the parsing process as an array, ready to send to
347
     * instance->method(...$params).
348
     */
349
    public function params(): array
350
    {
351
        return $this->params;
83✔
352
    }
353

354
    /**
355
     * Returns the name of the sub-directory the controller is in,
356
     * if any. Relative to APPPATH.'Controllers'.
357
     *
358
     * Only used when auto-routing is turned on.
359
     */
360
    public function directory(): string
361
    {
362
        if ($this->autoRouter instanceof AutoRouter) {
8✔
363
            return $this->autoRouter->directory();
8✔
364
        }
365

366
        return '';
×
367
    }
368

369
    /**
370
     * Returns the routing information that was matched for this
371
     * request, if a route was defined.
372
     *
373
     * @return array|null
374
     */
375
    public function getMatchedRoute()
376
    {
377
        return $this->matchedRoute;
×
378
    }
379

380
    /**
381
     * Returns all options set for the matched route
382
     *
383
     * @return array|null
384
     */
385
    public function getMatchedRouteOptions()
386
    {
387
        return $this->matchedRouteOptions;
1✔
388
    }
389

390
    /**
391
     * Sets the value that should be used to match the index.php file. Defaults
392
     * to index.php but this allows you to modify it in case you are using
393
     * something like mod_rewrite to remove the page. This allows you to set
394
     * it a blank.
395
     *
396
     * @param string $page
397
     */
398
    public function setIndexPage($page): self
399
    {
400
        $this->indexPage = $page;
×
401

402
        return $this;
×
403
    }
404

405
    /**
406
     * Tells the system whether we should translate URI dashes or not
407
     * in the URI from a dash to an underscore.
408
     *
409
     * @deprecated This method should be removed.
410
     */
411
    public function setTranslateURIDashes(bool $val = false): self
412
    {
413
        // Need to get or create the AutoRouter instance
414
        $autoRouter = $this->collection->shouldAutoRoute() ? $this->getAutoRouter() : null;
14✔
415

416
        if ($autoRouter instanceof AutoRouter) {
14✔
417
            $autoRouter->setTranslateURIDashes($val);
11✔
418

419
            return $this;
11✔
420
        }
421

422
        return $this;
3✔
423
    }
424

425
    /**
426
     * Returns true/false based on whether the current route contained
427
     * a {locale} placeholder.
428
     *
429
     * @return bool
430
     */
431
    public function hasLocale()
432
    {
433
        return (bool) $this->detectedLocale;
72✔
434
    }
435

436
    /**
437
     * Returns the detected locale, if any, or null.
438
     *
439
     * @return string
440
     */
441
    public function getLocale()
442
    {
443
        return $this->detectedLocale;
1✔
444
    }
445

446
    /**
447
     * Checks Defined Routes.
448
     *
449
     * Compares the uri string against the routes that the
450
     * RouteCollection class defined for us, attempting to find a match.
451
     * This method will modify $this->controller, etal as needed.
452
     *
453
     * @param string $uri The URI path to compare against the routes
454
     *
455
     * @return bool Whether the route was matched or not.
456
     *
457
     * @throws RedirectException
458
     */
459
    protected function checkRoutes(string $uri): bool
460
    {
461
        $routes = $this->collection->getRoutes($this->collection->getHTTPVerb());
171✔
462

463
        // Don't waste any time
464
        if ($routes === []) {
171✔
465
            return false;
4✔
466
        }
467

468
        $uri = $uri === '/'
168✔
469
            ? $uri
36✔
470
            : trim($uri, '/ ');
140✔
471

472
        // Loop through the route array looking for wildcards
473
        foreach ($routes as $routeKey => $handler) {
168✔
474
            $routeKey = $routeKey === '/'
168✔
475
                ? $routeKey
88✔
476
                // $routeKey may be int, because it is an array key,
168✔
477
                // and the URI `/1` is valid. The leading `/` is removed.
168✔
478
                : ltrim((string) $routeKey, '/ ');
143✔
479

480
            $matchedKey = $routeKey;
168✔
481

482
            // Are we dealing with a locale?
483
            if (str_contains($routeKey, '{locale}')) {
168✔
484
                $routeKey = str_replace('{locale}', '[^/]+', $routeKey);
20✔
485
            }
486

487
            // Does the RegEx match?
488
            if (preg_match('#^' . $routeKey . '$#u', $uri, $matches)) {
168✔
489
                // Is this route supposed to redirect to another?
490
                if ($this->collection->isRedirect($routeKey)) {
144✔
491
                    // replacing matched route groups with references: post/([0-9]+) -> post/$1
492
                    $redirectTo = preg_replace_callback('/(\([^\(]+\))/', static function (): string {
13✔
493
                        static $i = 1;
2✔
494

495
                        return '$' . $i++;
2✔
496
                    }, is_array($handler) ? key($handler) : $handler);
13✔
497

498
                    throw new RedirectException(
13✔
499
                        preg_replace('#\A' . $routeKey . '\z#u', $redirectTo, $uri),
13✔
500
                        $this->collection->getRedirectCode($routeKey),
13✔
501
                    );
13✔
502
                }
503
                // Store our locale so CodeIgniter object can
504
                // assign it to the Request.
505
                if (str_contains($matchedKey, '{locale}')) {
131✔
506
                    preg_match(
2✔
507
                        '#^' . str_replace('{locale}', '(?<locale>[^/]+)', $matchedKey) . '$#u',
2✔
508
                        $uri,
2✔
509
                        $matched,
2✔
510
                    );
2✔
511

512
                    if ($this->collection->shouldUseSupportedLocalesOnly()
2✔
513
                        && ! in_array($matched['locale'], config(App::class)->supportedLocales, true)) {
2✔
514
                        // Throw exception to prevent the autorouter, if enabled,
515
                        // from trying to find a route
516
                        throw PageNotFoundException::forLocaleNotSupported($matched['locale']);
1✔
517
                    }
518

519
                    $this->detectedLocale = $matched['locale'];
1✔
520
                    unset($matched);
1✔
521
                }
522

523
                // Are we using Closures? If so, then we need
524
                // to collect the params into an array
525
                // so it can be passed to the controller method later.
526
                if (! is_string($handler) && is_callable($handler)) {
130✔
527
                    $this->controller = $handler;
40✔
528

529
                    // Remove the original string from the matches array
530
                    array_shift($matches);
40✔
531

532
                    $this->params = $matches;
40✔
533

534
                    $this->setMatchedRoute($matchedKey, $handler);
40✔
535

536
                    return true;
40✔
537
                }
538

539
                if (str_contains($handler, '::')) {
97✔
540
                    [$controller, $methodAndParams] = explode('::', $handler);
95✔
541
                } else {
542
                    $controller      = $handler;
2✔
543
                    $methodAndParams = '';
2✔
544
                }
545

546
                // Checks `/` in controller name
547
                if (str_contains($controller, '/')) {
97✔
548
                    throw RouterException::forInvalidControllerName($handler);
1✔
549
                }
550

551
                if (str_contains($handler, '$') && str_contains($routeKey, '(')) {
96✔
552
                    // Checks dynamic controller
553
                    if (str_contains($controller, '$')) {
23✔
554
                        throw RouterException::forDynamicController($handler);
1✔
555
                    }
556

557
                    if (config(Routing::class)->multipleSegmentsOneParam === false) {
22✔
558
                        // Using back-references
559
                        $segments = explode('/', preg_replace('#\A' . $routeKey . '\z#u', $handler, $uri));
21✔
560
                    } else {
561
                        if (str_contains($methodAndParams, '/')) {
1✔
562
                            [$method, $handlerParams] = explode('/', $methodAndParams, 2);
1✔
563
                            $params                   = explode('/', $handlerParams);
1✔
564
                            $handlerSegments          = array_merge([$controller . '::' . $method], $params);
1✔
565
                        } else {
566
                            $handlerSegments = [$handler];
×
567
                        }
568

569
                        $segments = [];
1✔
570

571
                        foreach ($handlerSegments as $segment) {
1✔
572
                            $segments[] = $this->replaceBackReferences($segment, $matches);
1✔
573
                        }
574
                    }
575
                } else {
576
                    $segments = explode('/', $handler);
75✔
577
                }
578

579
                $this->setRequest($segments);
95✔
580

581
                $this->setMatchedRoute($matchedKey, $handler);
95✔
582

583
                return true;
95✔
584
            }
585
        }
586

587
        return false;
26✔
588
    }
589

590
    /**
591
     * Replace string `$n` with `$matches[n]` value.
592
     */
593
    private function replaceBackReferences(string $input, array $matches): string
594
    {
595
        $pattern = '/\$([1-' . count($matches) . '])/u';
1✔
596

597
        return preg_replace_callback(
1✔
598
            $pattern,
1✔
599
            static function ($match) use ($matches) {
1✔
600
                $index = (int) $match[1];
1✔
601

602
                return $matches[$index] ?? '';
1✔
603
            },
1✔
604
            $input,
1✔
605
        );
1✔
606
    }
607

608
    /**
609
     * Checks Auto Routes.
610
     *
611
     * Attempts to match a URI path against Controllers and directories
612
     * found in APPPATH/Controllers, to find a matching route.
613
     *
614
     * @return void
615
     */
616
    public function autoRoute(string $uri)
617
    {
618
        [$this->directory, $this->controller, $this->method, $this->params]
38✔
619
             = $this->getAutoRouter()->getRoute($uri, $this->collection->getHTTPVerb());
38✔
620
    }
621

622
    /**
623
     * Scans the controller directory, attempting to locate a controller matching the supplied uri $segments
624
     *
625
     * @param array $segments URI segments
626
     *
627
     * @return array returns an array of remaining uri segments that don't map onto a directory
628
     *
629
     * @deprecated this function name does not properly describe its behavior so it has been deprecated
630
     *
631
     * @codeCoverageIgnore
632
     */
633
    protected function validateRequest(array $segments): array
634
    {
635
        return $this->scanControllers($segments);
×
636
    }
637

638
    /**
639
     * Scans the controller directory, attempting to locate a controller matching the supplied uri $segments
640
     *
641
     * @param array $segments URI segments
642
     *
643
     * @return array returns an array of remaining uri segments that don't map onto a directory
644
     *
645
     * @deprecated Not used. Moved to AutoRouter class.
646
     */
647
    protected function scanControllers(array $segments): array
648
    {
649
        $segments = array_filter($segments, static fn ($segment): bool => $segment !== '');
×
650
        // numerically reindex the array, removing gaps
651
        $segments = array_values($segments);
×
652

653
        // if a prior directory value has been set, just return segments and get out of here
654
        if (isset($this->directory)) {
×
655
            return $segments;
×
656
        }
657

658
        // Loop through our segments and return as soon as a controller
659
        // is found or when such a directory doesn't exist
660
        $c = count($segments);
×
661

662
        while ($c-- > 0) {
×
663
            $segmentConvert = ucfirst($this->translateURIDashes === true ? str_replace('-', '_', $segments[0]) : $segments[0]);
×
664
            // as soon as we encounter any segment that is not PSR-4 compliant, stop searching
665
            if (! $this->isValidSegment($segmentConvert)) {
×
666
                return $segments;
×
667
            }
668

669
            $test = APPPATH . 'Controllers/' . $this->directory . $segmentConvert;
×
670

671
            // as long as each segment is *not* a controller file but does match a directory, add it to $this->directory
672
            if (! is_file($test . '.php') && is_dir($test)) {
×
673
                $this->setDirectory($segmentConvert, true, false);
×
674
                array_shift($segments);
×
675

676
                continue;
×
677
            }
678

679
            return $segments;
×
680
        }
681

682
        // This means that all segments were actually directories
683
        return $segments;
×
684
    }
685

686
    /**
687
     * Sets the sub-directory that the controller is in.
688
     *
689
     * @param bool $validate if true, checks to make sure $dir consists of only PSR4 compliant segments
690
     *
691
     * @return void
692
     *
693
     * @deprecated This method should be removed.
694
     */
695
    public function setDirectory(?string $dir = null, bool $append = false, bool $validate = true)
696
    {
697
        if ($dir === null || $dir === '') {
35✔
698
            $this->directory = null;
32✔
699
        }
700

701
        // Need to get or create the AutoRouter instance
702
        $autoRouter = $this->collection->shouldAutoRoute() ? $this->getAutoRouter() : null;
35✔
703

704
        if ($autoRouter instanceof AutoRouter) {
35✔
705
            $autoRouter->setDirectory($dir, $append, $validate);
4✔
706
        }
707
    }
708

709
    /**
710
     * Returns true if the supplied $segment string represents a valid PSR-4 compliant namespace/directory segment
711
     *
712
     * regex comes from https://www.php.net/manual/en/language.variables.basics.php
713
     *
714
     * @deprecated Moved to AutoRouter class.
715
     */
716
    private function isValidSegment(string $segment): bool
717
    {
718
        return (bool) preg_match('/^[a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*$/', $segment);
×
719
    }
720

721
    /**
722
     * Set request route
723
     *
724
     * Takes an array of URI segments as input and sets the class/method
725
     * to be called.
726
     *
727
     * @param array $segments URI segments
728
     *
729
     * @return void
730
     */
731
    protected function setRequest(array $segments = [])
732
    {
733
        // If we don't have any segments - use the default controller;
734
        if ($segments === []) {
95✔
735
            return;
×
736
        }
737

738
        [$controller, $method] = array_pad(explode('::', $segments[0]), 2, null);
95✔
739

740
        $this->controller = $controller;
95✔
741

742
        // $this->method already contains the default method name,
743
        // so don't overwrite it with emptiness.
744
        if (! empty($method)) {
95✔
745
            $this->method = $method;
93✔
746
        }
747

748
        array_shift($segments);
95✔
749

750
        $this->params = $segments;
95✔
751
    }
752

753
    /**
754
     * Sets the default controller based on the info set in the RouteCollection.
755
     *
756
     * @deprecated This was an unnecessary method, so it is no longer used.
757
     *
758
     * @return void
759
     */
760
    protected function setDefaultController()
761
    {
762
        if (empty($this->controller)) {
×
763
            throw RouterException::forMissingDefaultRoute();
×
764
        }
765

766
        sscanf($this->controller, '%[^/]/%s', $class, $this->method);
×
767

768
        if (! is_file(APPPATH . 'Controllers/' . $this->directory . ucfirst($class) . '.php')) {
×
769
            return;
×
770
        }
771

772
        $this->controller = ucfirst($class);
×
773

774
        log_message('info', 'Used the default controller.');
×
775
    }
776

777
    /**
778
     * @param callable|string $handler
779
     */
780
    protected function setMatchedRoute(string $route, $handler): void
781
    {
782
        $this->matchedRoute = [$route, $handler];
128✔
783

784
        $this->matchedRouteOptions = $this->collection->getRoutesOptions($route);
128✔
785
    }
786

787
    /**
788
     * Checks disallowed characters
789
     */
790
    private function checkDisallowedChars(string $uri): void
791
    {
792
        foreach (explode('/', $uri) as $segment) {
174✔
793
            if ($segment !== '' && $this->permittedURIChars !== ''
174✔
794
                && preg_match('/\A[' . $this->permittedURIChars . ']+\z/iu', $segment) !== 1
174✔
795
            ) {
796
                throw new BadRequestException(
2✔
797
                    'The URI you submitted has disallowed characters: "' . $segment . '"',
2✔
798
                );
2✔
799
            }
800
        }
801
    }
802
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc