Skip to content

Commit

Permalink
Add support for optionals in RouteParser
Browse files Browse the repository at this point in the history
Implementation rather hacky...
  • Loading branch information
nikic committed Jun 17, 2015
1 parent 7fdf09e commit ebf979b
Show file tree
Hide file tree
Showing 2 changed files with 121 additions and 4 deletions.
68 changes: 64 additions & 4 deletions src/RouteParser/Std.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@

namespace FastRoute\RouteParser;

use FastRoute\BadRouteException;
use FastRoute\RouteParser;

/**
* Parses routes of the following form:
* Parses route strings of the following form:
*
* "/user/{name}/{id:[0-9]+}"
* "/user/{name}[/{id:[0-9]+}]"
*/
class Std implements RouteParser {
const VARIABLE_REGEX = <<<'REGEX'
Expand All @@ -21,10 +22,69 @@ class Std implements RouteParser {
const DEFAULT_DISPATCH_REGEX = '[^/]+';

public function parse($route) {
$routeWithoutClosingOptionals = rtrim($route, ']');
$numOptionals = strlen($route) - strlen($routeWithoutClosingOptionals);
$routeParts = $this->parsePlaceholders($routeWithoutClosingOptionals);
if ($numOptionals === 0) {
return [$routeParts];
}
return $this->handleOptionals($routeParts, $numOptionals);
}

private function handleOptionals($routeParts, $numOptionals) {
$routeDatas = [];
$currentRouteData = [];
foreach ($routeParts as $part) {
// skip placeholders
if (!is_string($part)) {
$currentRouteData[] = $part;
continue;
}

$segments = explode('[', $part);
$currentNumOptionals = count($segments) - 1;
$numOptionals -= $currentNumOptionals;
if ($numOptionals < 0) {
throw new BadRouteException("Found more opening '[' than closing ']'");
}

$currentPart = '';
foreach ($segments as $i => $addPart) {
if ($addPart === '') {
if ($currentPart !== '') {
throw new BadRouteException("Empty optional part");
}
$routeDatas[] = $currentRouteData;
continue;
}

$currentPart .= $addPart;
if ($i !== $currentNumOptionals) {
$routeData = $currentRouteData;
$routeData[] = $currentPart;
$routeDatas[] = $routeData;
} else {
$currentRouteData[] = $currentPart;
}
}
}

$routeDatas[] = $currentRouteData;
if ($numOptionals > 0) {
throw new BadRouteException("Found more closing ']' than opening '['");
}

return $routeDatas;
}

/**
* Parses a route string only considering {placeholders}, but ignoring [optionals].
*/
private function parsePlaceholders($route) {
if (!preg_match_all(
self::VARIABLE_REGEX, $route, $matches, PREG_OFFSET_CAPTURE | PREG_SET_ORDER
)) {
return [[$route]];
return [$route];
}

$offset = 0;
Expand All @@ -44,6 +104,6 @@ public function parse($route) {
$routeData[] = substr($route, $offset);
}

return [$routeData];
return $routeData;
}
}
57 changes: 57 additions & 0 deletions test/RouteParser/StdTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,13 @@ public function testParse($routeString, $expectedRouteDatas) {
$this->assertSame($expectedRouteDatas, $routeDatas);
}

/** @dataProvider provideTestParseError */
public function testParseError($routeString, $expectedExceptionMessage) {
$parser = new Std();
$this->setExpectedException('FastRoute\\BadRouteException', $expectedExceptionMessage);
$parser->parse($routeString);
}

public function provideTestParse() {
return [
[
Expand Down Expand Up @@ -47,7 +54,57 @@ public function provideTestParse() {
[
['/test/', ['param', '\d{1,9}']]
]
],
[
'/test[opt]',
[
['/test'],
['/testopt'],
]
],
[
'/test[/{param}]',
[
['/test'],
['/test/', ['param', '[^/]+']],
]
],
[
'/{param}[opt]',
[
['/', ['param', '[^/]+']],
['/', ['param', '[^/]+'], 'opt']
]
],
[
'/test[/{name}[/{id:[0-9]+}]]',
[
['/test'],
['/test/', ['name', '[^/]+']],
['/test/', ['name', '[^/]+'], '/', ['id', '[0-9]+']],
]
]
];
}

public function provideTestParseError() {
return [
[
'/test[opt[opt2]',
"Found more opening '[' than closing ']'"
],
[
'/testopt]',
"Found more closing ']' than opening '['"
],
[
'/test[]',
"Empty optional part"
],
[
'/test[[opt]]',
"Empty optional part"
],
];
}
}

0 comments on commit ebf979b

Please sign in to comment.