Skip to content

Commit

Permalink
feat: can add route handler as callable
Browse files Browse the repository at this point in the history
E.g.:
$routes->add('home', [Hello::class, 'index']);
$routes->add('product/(:num)/(:num)', [[Hello::class, 'index'], '$1/$2']);
  • Loading branch information
kenjis committed Feb 25, 2022
1 parent 200a04e commit 464ed03
Show file tree
Hide file tree
Showing 7 changed files with 139 additions and 0 deletions.
50 changes: 50 additions & 0 deletions system/Router/RouteCollection.php
Original file line number Diff line number Diff line change
Expand Up @@ -1137,6 +1137,10 @@ protected function create(string $verb, string $from, $to, ?array $options = nul
$from = trim($from, '/');
}

if (is_array($to)) {
$to = $this->processArrayCallableSyntax($from, $to);
}

$options = array_merge($this->currentOptions ?? [], $options ?? []);

// Route priority detect
Expand Down Expand Up @@ -1227,6 +1231,52 @@ protected function create(string $verb, string $from, $to, ?array $options = nul
}
}

/**
* @return string
*/
private function processArrayCallableSyntax(string $from, array $to)
{
// [classname, method]
// eg, [Home::class, 'index']
if (is_callable($to, true, $callableName)) {
// If the route has placeholders, add params automatically.
$params = $this->getMethodParams($from);

return '\\' . $callableName . $params;
}

// [[classname, method], params]
// eg, [[Home::class, 'index'], '$1/$2']
if (
isset($to[0], $to[1])
&& is_callable($to[0], true, $callableName)
&& is_string($to[1])
) {
$to = '\\' . $callableName . '/' . $to[1];
}

return $to;
}

/**
* Returns the method param string like `/$1/$2` for placeholders
*/
private function getMethodParams(string $from): string
{
preg_match_all('/\(.+?\)/', $from, $matches);
$count = is_countable($matches[0]) ? count($matches[0]) : 0;

$params = '';

if ($count > 0) {
for ($i = 1; $i <= $count; $i++) {
$params .= '/$' . $i;
}
}

return $params;
}

/**
* Compares the subdomain(s) passed in against the current subdomain
* on this page request.
Expand Down
40 changes: 40 additions & 0 deletions tests/system/Router/RouteCollectionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
use CodeIgniter\Router\Exceptions\RouterException;
use CodeIgniter\Test\CIUnitTestCase;
use Config\Modules;
use Tests\Support\Controllers\Hello;

/**
* @backupGlobals enabled
Expand Down Expand Up @@ -58,6 +59,45 @@ public function testBasicAdd()
$this->assertSame($expects, $routes);
}

public function testBasicAddCallable()
{
$routes = $this->getCollector();

$routes->add('home', [Hello::class, 'index']);

$routes = $routes->getRoutes();
$expects = [
'home' => '\Tests\Support\Controllers\Hello::index',
];
$this->assertSame($expects, $routes);
}

public function testBasicAddCallableWithParamsString()
{
$routes = $this->getCollector();

$routes->add('product/(:num)/(:num)', [[Hello::class, 'index'], '$2/$1']);

$routes = $routes->getRoutes();
$expects = [
'product/([0-9]+)/([0-9]+)' => '\Tests\Support\Controllers\Hello::index/$2/$1',
];
$this->assertSame($expects, $routes);
}

public function testBasicAddCallableWithParamsWithoutString()
{
$routes = $this->getCollector();

$routes->add('product/(:num)/(:num)', [Hello::class, 'index']);

$routes = $routes->getRoutes();
$expects = [
'product/([0-9]+)/([0-9]+)' => '\Tests\Support\Controllers\Hello::index/$1/$2',
];
$this->assertSame($expects, $routes);
}

public function testAddPrefixesDefaultNamespaceWhenNoneExist()
{
$routes = $this->getCollector();
Expand Down
25 changes: 25 additions & 0 deletions user_guide_src/source/incoming/routing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,31 @@ routes. With the examples URLs from above:

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


Array Callable Syntax
=====================

Since v4.2.0, you can use array callable syntax to specify the controller:

.. literalinclude:: routing/004_1.php
:lines: 2-

Or using ``use`` keyword:

.. literalinclude:: routing/004_2.php
:lines: 2-

If there are placeholders, it will automatically set the parameters in the specified order:

.. literalinclude:: routing/004_3.php
:lines: 2-

But the auto-configured parameters may not be correct if you use regular expressions in routes.
In such a case, you can specify the parameters manually:

.. literalinclude:: routing/004_4.php
:lines: 2-

Custom Placeholders
===================

Expand Down
3 changes: 3 additions & 0 deletions user_guide_src/source/incoming/routing/004_1.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<?php

$routes->get('/', [\App\Controllers\Home::class, 'index']);
5 changes: 5 additions & 0 deletions user_guide_src/source/incoming/routing/004_2.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?php

use App\Controllers\Home;

$routes->get('/', [Home::class, 'index']);
8 changes: 8 additions & 0 deletions user_guide_src/source/incoming/routing/004_3.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?php

use App\Controllers\Product;

$routes->get('product/(:num)/(:num)', [Product::class, 'index']);

// The above code is the same as the following:
$routes->get('product/(:num)/(:num)', 'Product::index/$1/$2');
8 changes: 8 additions & 0 deletions user_guide_src/source/incoming/routing/004_4.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?php

use App\Controllers\Product;

$routes->get('product/(:num)/(:num)', [[Product::class, 'index'], '$2/$1']);

// The above code is the same as the following:
$routes->get('product/(:num)/(:num)', 'Product::index/$2/$1');

0 comments on commit 464ed03

Please sign in to comment.