NEW: Variadic URL parameter matches for url_handlers (#9438)

* Add wildcard URL parameter matches for url_handlers

* Extra tests for wildcard parameters

* Add a PHP warning if more params appear after wildcard param
This commit is contained in:
Daniel Hensby 2020-03-24 20:16:13 +00:00 committed by GitHub
parent b6512edec8
commit 1fb574a5bd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 104 additions and 1 deletions

View File

@ -142,6 +142,51 @@ the `TeamController`.
Match an url starting with `/admin/help/`, but don't include `/help/` as part of the action (the shift point is set to
start parsing variables and the appropriate controller action AFTER the `//`).
### Wildcard URL Patterns
As of SilverStripe 4.6 there are two wildcard patterns that can be used. `$@` and `$*`. These parameters can only be used
at the end of a URL pattern, any further rules are ignored.
Inspired by bash variadic variable syntax there are two ways to capture all URL parameters without having to explicitly
specify them in the URL rule.
Using `$@` will split the URL into numbered parameters (`$1`, `$2`, ..., `$n`). For example:
```php
<?php
class StaffController extends \SilverStripe\Control\Controller
{
private static $url_handlers = [
'staff/$@' => 'index',
];
public function index($request)
{
// GET /staff/managers/bob
$request->latestParam('$1'); // managers
$request->latestParam('$2'); // bob
}
}
```
Alternatively, if access to the parameters is not required in this way then it is possible to use `$*` to match all
URL parameters but not collect them in the same way:
```php
<?php
class StaffController extends \SilverStripe\Control\Controller
{
private static $url_handlers = [
'staff/$*' => 'index',
];
public function index($request)
{
// GET /staff/managers/bob
$request->remaining(); // managers/bob
}
}
```
## URL Handlers

View File

@ -564,7 +564,28 @@ class HTTPRequest implements ArrayAccess
/** @skipUpgrade */
$key = "Controller";
$arguments[$varName] = isset($this->dirParts[$i]) ? $this->dirParts[$i] : null;
if ($varName === '*' || $varName === '@') {
if (isset($patternParts[$i + 1])) {
user_error(sprintf('All URL params after wildcard parameter $%s will be ignored', $varName), E_USER_WARNING);
}
if ($varName === '*') {
array_pop($patternParts);
$shiftCount = sizeof($patternParts);
$patternParts = array_merge($patternParts, array_slice($this->dirParts, $i));
break;
} else {
array_pop($patternParts);
$shiftCount = sizeof($patternParts);
$remaining = count($this->dirParts) - $i;
for ($j = 1; $j <= $remaining; $j++) {
$arguments["$${j}"] = $this->dirParts[$j + $i - 1];
}
$patternParts = array_merge($patternParts, array_keys($arguments));
break;
}
} else {
$arguments[$varName] = $this->dirParts[$i] ?? null;
}
if ($part == '$Controller'
&& (
!ClassInfo::exists($arguments[$key])

View File

@ -25,6 +25,43 @@ class HTTPRequestTest extends SapphireTest
$this->assertEquals(array("_matched" => true), $request->match('add', true));
}
/**
* @useDatabase false
*/
public function testWildCardMatch()
{
$request = new HTTPRequest('GET', 'admin/crm/test');
$this->assertEquals(['$1' => 'crm', '$2' => 'test'], $request->match('admin/$@', true));
$this->assertTrue($request->allParsed());
$request = new HTTPRequest('GET', 'admin/crm/test');
$this->assertEquals(['_matched' => true], $request->match('admin/$*', true));
$this->assertTrue($request->allParsed());
$this->assertEquals('crm/test', $request->remaining());
$request = new HTTPRequest('GET', 'admin/crm/test/part1/part2');
$this->assertEquals(['Action' => 'crm', '$1' => 'test', '$2' => 'part1', '$3' => 'part2'], $request->match('admin/$Action/$@', true));
$this->assertTrue($request->allParsed());
$request = new HTTPRequest('GET', 'admin/crm/test/part1/part2');
$this->assertEquals(['Action' => 'crm'], $request->match('admin/$Action/$*', true));
$this->assertTrue($request->allParsed());
$this->assertEquals('test/part1/part2', $request->remaining());
}
/**
* This test just asserts a warning is given if there is more than one wildcard parameter. Note that this isn't an
* enforcement of an API and we an add new behaviour in the future to allow many wildcard params if we want to
*
* @expectedException \PHPUnit_Framework_Error_Warning
*/
public function testWildCardWithFurtherParams()
{
$request = new HTTPRequest('GET', 'admin/crm/test');
// all parameters after the first wildcard parameter are ignored
$request->match('admin/$Action/$@/$Other/$*', true);
}
public function testHttpMethodOverrides()
{
$request = new HTTPRequest(