Skip to content

Commit 5340ec6

Browse files
committed
feat: add AutoRouterImproved/AutoRouteCollector
1 parent acab083 commit 5340ec6

2 files changed

Lines changed: 242 additions & 0 deletions

File tree

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
<?php
2+
3+
/**
4+
* This file is part of CodeIgniter 4 framework.
5+
*
6+
* (c) CodeIgniter Foundation <admin@codeigniter.com>
7+
*
8+
* For the full copyright and license information, please view
9+
* the LICENSE file that was distributed with this source code.
10+
*/
11+
12+
namespace CodeIgniter\Commands\Utilities\Routes\AutoRouterImproved;
13+
14+
use CodeIgniter\Commands\Utilities\Routes\ControllerFinder;
15+
use CodeIgniter\Commands\Utilities\Routes\FilterCollector;
16+
17+
/**
18+
* Collects data for Auto Routing Improved.
19+
*/
20+
final class AutoRouteCollector
21+
{
22+
/**
23+
* @var string namespace to search
24+
*/
25+
private string $namespace;
26+
27+
private string $defaultController;
28+
private string $defaultMethod;
29+
private array $httpMethods;
30+
31+
/**
32+
* List of controllers in Defined Routes that should not be accessed via Auto-Routing.
33+
*
34+
* @var class-string[]
35+
*/
36+
private array $protectedControllers;
37+
38+
/**
39+
* @param string $namespace namespace to search
40+
*/
41+
public function __construct(
42+
string $namespace,
43+
string $defaultController,
44+
string $defaultMethod,
45+
array $httpMethods,
46+
array $protectedControllers
47+
) {
48+
$this->namespace = $namespace;
49+
$this->defaultController = $defaultController;
50+
$this->defaultMethod = $defaultMethod;
51+
$this->httpMethods = $httpMethods;
52+
$this->protectedControllers = $protectedControllers;
53+
}
54+
55+
/**
56+
* @return array<int, array<int, string>>
57+
* @phpstan-return list<list<string>>
58+
*/
59+
public function get(): array
60+
{
61+
$finder = new ControllerFinder($this->namespace);
62+
$reader = new ControllerMethodReader($this->namespace, $this->httpMethods);
63+
64+
$tbody = [];
65+
66+
foreach ($finder->find() as $class) {
67+
// Exclude controllers in Defined Routes.
68+
if (in_array($class, $this->protectedControllers, true)) {
69+
continue;
70+
}
71+
72+
$routes = $reader->read(
73+
$class,
74+
$this->defaultController,
75+
$this->defaultMethod
76+
);
77+
78+
if ($routes === []) {
79+
continue;
80+
}
81+
82+
$routes = $this->addFilters($routes);
83+
84+
foreach ($routes as $item) {
85+
$tbody[] = [
86+
strtoupper($item['method']) . '(auto)',
87+
$item['route'] . $item['route_params'],
88+
$item['handler'],
89+
$item['before'],
90+
$item['after'],
91+
];
92+
}
93+
}
94+
95+
return $tbody;
96+
}
97+
98+
private function addFilters($routes)
99+
{
100+
$filterCollector = new FilterCollector(true);
101+
102+
foreach ($routes as &$route) {
103+
// Search filters for the URI with all params
104+
$sampleUri = $this->generateSampleUri($route);
105+
$filtersLongest = $filterCollector->get($route['method'], $route['route'] . $sampleUri);
106+
107+
// Search filters for the URI without optional params
108+
$sampleUri = $this->generateSampleUri($route, false);
109+
$filtersShortest = $filterCollector->get($route['method'], $route['route'] . $sampleUri);
110+
111+
// Get common array elements
112+
$filters['before'] = array_intersect($filtersLongest['before'], $filtersShortest['before']);
113+
$filters['after'] = array_intersect($filtersLongest['after'], $filtersShortest['after']);
114+
115+
$route['before'] = implode(' ', array_map('class_basename', $filters['before']));
116+
$route['after'] = implode(' ', array_map('class_basename', $filters['after']));
117+
}
118+
119+
return $routes;
120+
}
121+
122+
private function generateSampleUri(array $route, bool $longest = true): string
123+
{
124+
$sampleUri = '';
125+
126+
if (isset($route['params'])) {
127+
$i = 1;
128+
129+
foreach ($route['params'] as $required) {
130+
if ($longest && ! $required) {
131+
$sampleUri .= '/' . $i++;
132+
}
133+
}
134+
}
135+
136+
return $sampleUri;
137+
}
138+
}
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
<?php
2+
3+
/**
4+
* This file is part of CodeIgniter 4 framework.
5+
*
6+
* (c) CodeIgniter Foundation <admin@codeigniter.com>
7+
*
8+
* For the full copyright and license information, please view
9+
* the LICENSE file that was distributed with this source code.
10+
*/
11+
12+
namespace CodeIgniter\Commands\Utilities\Routes\AutoRouterImproved;
13+
14+
use CodeIgniter\Test\CIUnitTestCase;
15+
use Config\Filters;
16+
use Config\Services;
17+
18+
/**
19+
* @internal
20+
*/
21+
final class AutoRouteCollectorTest extends CIUnitTestCase
22+
{
23+
protected function setUp(): void
24+
{
25+
parent::setUp();
26+
27+
$this->resetServices(true);
28+
}
29+
30+
private function createAutoRouteCollector(array $filterConfigFilters): AutoRouteCollector
31+
{
32+
$routes = Services::routes();
33+
$routes->resetRoutes();
34+
$routes->setAutoRoute(true);
35+
config('Feature')->autoRoutesImproved = true;
36+
$namespace = 'Tests\Support\Controllers';
37+
$routes->setDefaultNamespace($namespace);
38+
39+
/** @var Filters $filterConfig */
40+
$filterConfig = config('Filters');
41+
$filterConfig->filters = $filterConfigFilters;
42+
Services::filters($filterConfig);
43+
44+
return new AutoRouteCollector(
45+
$namespace,
46+
'Home',
47+
'index',
48+
['get', 'post'],
49+
[],
50+
);
51+
}
52+
53+
public function testGetFilterMatches()
54+
{
55+
$filterConfigFilters = ['honeypot' => ['before' => ['newautorouting/save*']]];
56+
$collector = $this->createAutoRouteCollector($filterConfigFilters);
57+
58+
$routes = $collector->get();
59+
60+
$expected = [
61+
0 => [
62+
0 => 'GET(auto)',
63+
1 => 'newautorouting',
64+
2 => '\\Tests\\Support\\Controllers\\Newautorouting::getIndex',
65+
3 => '',
66+
4 => 'toolbar',
67+
],
68+
1 => [
69+
0 => 'POST(auto)',
70+
1 => 'newautorouting/save/../..[/..]',
71+
2 => '\\Tests\\Support\\Controllers\\Newautorouting::postSave',
72+
3 => 'honeypot',
73+
4 => 'toolbar',
74+
],
75+
];
76+
$this->assertSame($expected, $routes);
77+
}
78+
79+
public function testGetFilterDoesNotMatch()
80+
{
81+
$filterConfigFilters = ['honeypot' => ['before' => ['newautorouting/save/*/*']]];
82+
$collector = $this->createAutoRouteCollector($filterConfigFilters);
83+
84+
$routes = $collector->get();
85+
86+
$expected = [
87+
0 => [
88+
0 => 'GET(auto)',
89+
1 => 'newautorouting',
90+
2 => '\\Tests\\Support\\Controllers\\Newautorouting::getIndex',
91+
3 => '',
92+
4 => 'toolbar',
93+
],
94+
1 => [
95+
0 => 'POST(auto)',
96+
1 => 'newautorouting/save/../..[/..]',
97+
2 => '\\Tests\\Support\\Controllers\\Newautorouting::postSave',
98+
3 => '',
99+
4 => 'toolbar',
100+
],
101+
];
102+
$this->assertSame($expected, $routes);
103+
}
104+
}

0 commit comments

Comments
 (0)