Skip to content

Commit 196d759

Browse files
authored
Merge pull request #5713 from kenjis/feat-route-add-callable
feat: can add route handler as callable
2 parents 28aed3f + 8ed1223 commit 196d759

File tree

7 files changed

+135
-0
lines changed

7 files changed

+135
-0
lines changed

system/Router/RouteCollection.php

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1137,6 +1137,11 @@ protected function create(string $verb, string $from, $to, ?array $options = nul
11371137
$from = trim($from, '/');
11381138
}
11391139

1140+
// When redirecting to named route, $to is an array like `['zombies' => '\Zombies::index']`.
1141+
if (is_array($to) && count($to) === 2) {
1142+
$to = $this->processArrayCallableSyntax($from, $to);
1143+
}
1144+
11401145
$options = array_merge($this->currentOptions ?? [], $options ?? []);
11411146

11421147
// Route priority detect
@@ -1227,6 +1232,47 @@ protected function create(string $verb, string $from, $to, ?array $options = nul
12271232
}
12281233
}
12291234

1235+
private function processArrayCallableSyntax(string $from, array $to): string
1236+
{
1237+
// [classname, method]
1238+
// eg, [Home::class, 'index']
1239+
if (is_callable($to, true, $callableName)) {
1240+
// If the route has placeholders, add params automatically.
1241+
$params = $this->getMethodParams($from);
1242+
1243+
return '\\' . $callableName . $params;
1244+
}
1245+
1246+
// [[classname, method], params]
1247+
// eg, [[Home::class, 'index'], '$1/$2']
1248+
if (
1249+
isset($to[0], $to[1])
1250+
&& is_callable($to[0], true, $callableName)
1251+
&& is_string($to[1])
1252+
) {
1253+
$to = '\\' . $callableName . '/' . $to[1];
1254+
}
1255+
1256+
return $to;
1257+
}
1258+
1259+
/**
1260+
* Returns the method param string like `/$1/$2` for placeholders
1261+
*/
1262+
private function getMethodParams(string $from): string
1263+
{
1264+
preg_match_all('/\(.+?\)/', $from, $matches);
1265+
$count = is_countable($matches[0]) ? count($matches[0]) : 0;
1266+
1267+
$params = '';
1268+
1269+
for ($i = 1; $i <= $count; $i++) {
1270+
$params .= '/$' . $i;
1271+
}
1272+
1273+
return $params;
1274+
}
1275+
12301276
/**
12311277
* Compares the subdomain(s) passed in against the current subdomain
12321278
* on this page request.

tests/system/Router/RouteCollectionTest.php

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use CodeIgniter\Router\Exceptions\RouterException;
1616
use CodeIgniter\Test\CIUnitTestCase;
1717
use Config\Modules;
18+
use Tests\Support\Controllers\Hello;
1819

1920
/**
2021
* @backupGlobals enabled
@@ -58,6 +59,45 @@ public function testBasicAdd()
5859
$this->assertSame($expects, $routes);
5960
}
6061

62+
public function testBasicAddCallable()
63+
{
64+
$routes = $this->getCollector();
65+
66+
$routes->add('home', [Hello::class, 'index']);
67+
68+
$routes = $routes->getRoutes();
69+
$expects = [
70+
'home' => '\Tests\Support\Controllers\Hello::index',
71+
];
72+
$this->assertSame($expects, $routes);
73+
}
74+
75+
public function testBasicAddCallableWithParamsString()
76+
{
77+
$routes = $this->getCollector();
78+
79+
$routes->add('product/(:num)/(:num)', [[Hello::class, 'index'], '$2/$1']);
80+
81+
$routes = $routes->getRoutes();
82+
$expects = [
83+
'product/([0-9]+)/([0-9]+)' => '\Tests\Support\Controllers\Hello::index/$2/$1',
84+
];
85+
$this->assertSame($expects, $routes);
86+
}
87+
88+
public function testBasicAddCallableWithParamsWithoutString()
89+
{
90+
$routes = $this->getCollector();
91+
92+
$routes->add('product/(:num)/(:num)', [Hello::class, 'index']);
93+
94+
$routes = $routes->getRoutes();
95+
$expects = [
96+
'product/([0-9]+)/([0-9]+)' => '\Tests\Support\Controllers\Hello::index/$1/$2',
97+
];
98+
$this->assertSame($expects, $routes);
99+
}
100+
61101
public function testAddPrefixesDefaultNamespaceWhenNoneExist()
62102
{
63103
$routes = $this->getCollector();

user_guide_src/source/incoming/routing.rst

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,31 @@ routes. With the examples URLs from above:
145145

146146
will only match **product/123** and generate 404 errors for other example.
147147

148+
149+
Array Callable Syntax
150+
=====================
151+
152+
Since v4.2.0, you can use array callable syntax to specify the controller:
153+
154+
.. literalinclude:: routing/004_1.php
155+
:lines: 2-
156+
157+
Or using ``use`` keyword:
158+
159+
.. literalinclude:: routing/004_2.php
160+
:lines: 2-
161+
162+
If there are placeholders, it will automatically set the parameters in the specified order:
163+
164+
.. literalinclude:: routing/004_3.php
165+
:lines: 2-
166+
167+
But the auto-configured parameters may not be correct if you use regular expressions in routes.
168+
In such a case, you can specify the parameters manually:
169+
170+
.. literalinclude:: routing/004_4.php
171+
:lines: 2-
172+
148173
Custom Placeholders
149174
===================
150175

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<?php
2+
3+
$routes->get('/', [\App\Controllers\Home::class, 'index']);
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<?php
2+
3+
use App\Controllers\Home;
4+
5+
$routes->get('/', [Home::class, 'index']);
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?php
2+
3+
use App\Controllers\Product;
4+
5+
$routes->get('product/(:num)/(:num)', [Product::class, 'index']);
6+
7+
// The above code is the same as the following:
8+
$routes->get('product/(:num)/(:num)', 'Product::index/$1/$2');
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?php
2+
3+
use App\Controllers\Product;
4+
5+
$routes->get('product/(:num)/(:num)', [[Product::class, 'index'], '$2/$1']);
6+
7+
// The above code is the same as the following:
8+
$routes->get('product/(:num)/(:num)', 'Product::index/$2/$1');

0 commit comments

Comments
 (0)