Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/master'
Browse files Browse the repository at this point in the history
Conflicts:
	src/Dispatcher/GroupCountBased.php
	src/Dispatcher/GroupPosBased.php
  • Loading branch information
Ryan Gordon committed Dec 11, 2014
2 parents c9226c6 + f866cda commit 46c1ab0
Show file tree
Hide file tree
Showing 17 changed files with 340 additions and 150 deletions.
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ php:
- 5.4
- 5.5
- 5.6
- hhvm

4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ appropriately) is your job - this library is not bound to the PHP web SAPIs.
The `dispatch()` method returns an array those first element contains a status code. It is one
of `Dispatcher::NOT_FOUND`, `Dispatcher::METHOD_NOT_ALLOWED` and `Dispatcher::FOUND`. For the
method not allowed status the second array element contains a list of HTTP methods allowed for
this method. For example:
the supplied URI. For example:

[FastRoute\Dispatcher::METHOD_NOT_ALLOWED, ['GET', 'POST']]

Expand Down Expand Up @@ -130,7 +130,7 @@ array has a certain structure, best understood using an example:
'/user/',
['name', '[^/]+'],
'/',
['id', [0-9]+'],
['id', '[0-9]+'],
]

This array can then be passed to the `addRoute()` method of a data generator. After all routes have
Expand Down
5 changes: 4 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@
"php": ">=5.4.0"
},
"autoload": {
"files": ["src/bootstrap.php"]
"psr-4": {
"FastRoute\\": "src/"
},
"files": ["src/functions.php"]
}
}
28 changes: 28 additions & 0 deletions src/DataGenerator/CharCountBased.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

namespace FastRoute\DataGenerator;

class CharCountBased extends RegexBasedAbstract {
protected function getApproxChunkSize() {
return 30;
}

protected function processChunk($regexToRoutesMap) {
$routeMap = [];
$regexes = [];

$suffixLen = 0;
$suffix = '';
$count = count($regexToRoutesMap);
foreach ($regexToRoutesMap as $regex => $route) {
$suffixLen++;
$suffix .= "\t";

$regexes[] = '(?:' . $regex . '/(\t{' . $suffixLen . '})\t{' . ($count - $suffixLen) . '})';
$routeMap[$suffix] = [$route->handler, $route->variables];
}

$regex = '~^(?|' . implode('|', $regexes) . ')$~';
return ['regex' => $regex, 'suffix' => '/' . $suffix, 'routeMap' => $routeMap];
}
}
33 changes: 6 additions & 27 deletions src/DataGenerator/GroupCountBased.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,41 +3,20 @@
namespace FastRoute\DataGenerator;

class GroupCountBased extends RegexBasedAbstract {
const APPROX_CHUNK_SIZE = 10;

public function getData() {
if (empty($this->regexToRoutesMap)) {
return [$this->staticRoutes, []];
}

return [$this->staticRoutes, $this->generateVariableRouteData()];
}

private function generateVariableRouteData() {
$chunkSize = $this->computeChunkSize(count($this->regexToRoutesMap));
$chunks = array_chunk($this->regexToRoutesMap, $chunkSize, true);
return array_map([$this, 'processChunk'], $chunks);
}

private function computeChunkSize($count) {
$numParts = max(1, round($count / self::APPROX_CHUNK_SIZE));
return ceil($count / $numParts);
protected function getApproxChunkSize() {
return 10;
}

private function processChunk($regexToRoutesMap) {
protected function processChunk($regexToRoutesMap) {
$routeMap = [];
$regexes = [];
$numGroups = 0;
foreach ($regexToRoutesMap as $regex => $routes) {
$numVariables = count(reset($routes)->variables);
foreach ($regexToRoutesMap as $regex => $route) {
$numVariables = count($route->variables);
$numGroups = max($numGroups, $numVariables);

$regexes[] = $regex . str_repeat('()', $numGroups - $numVariables);

foreach ($routes as $route) {
$routeMap[$numGroups + 1][$route->httpMethod]
= [$route->handler, $route->variables];
}
$routeMap[$numGroups + 1] = [$route->handler, $route->variables];

++$numGroups;
}
Expand Down
34 changes: 7 additions & 27 deletions src/DataGenerator/GroupPosBased.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,39 +3,19 @@
namespace FastRoute\DataGenerator;

class GroupPosBased extends RegexBasedAbstract {
const APPROX_CHUNK_SIZE = 10;

public function getData() {
if (empty($this->regexToRoutesMap)) {
return [$this->staticRoutes, []];
}

return [$this->staticRoutes, $this->generateVariableRouteData()];
}

private function generateVariableRouteData() {
$chunkSize = $this->computeChunkSize(count($this->regexToRoutesMap));
$chunks = array_chunk($this->regexToRoutesMap, $chunkSize, true);
return array_map([$this, 'processChunk'], $chunks);
protected function getApproxChunkSize() {
return 10;
}

private function computeChunkSize($count) {
$numParts = max(1, round($count / self::APPROX_CHUNK_SIZE));
return ceil($count / $numParts);
}

private function processChunk($regexToRoutesMap) {
protected function processChunk($regexToRoutesMap) {
$routeMap = [];
$regexes = [];
$offset = 1;
foreach ($regexToRoutesMap as $regex => $routes) {
foreach ($routes as $route) {
$routeMap[$offset][$route->httpMethod]
= [$route->handler, $route->variables];
}

foreach ($regexToRoutesMap as $regex => $route) {
$regexes[] = $regex;
$offset += count(reset($routes)->variables);
$routeMap[$offset] = [$route->handler, $route->variables];

$offset += count($route->variables);
}

$regex = '~^(?:' . implode('|', $regexes) . ')$~';
Expand Down
25 changes: 25 additions & 0 deletions src/DataGenerator/MarkBased.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

namespace FastRoute\DataGenerator;

class MarkBased extends RegexBasedAbstract {
protected function getApproxChunkSize() {
return 30;
}

protected function processChunk($regexToRoutesMap) {
$routeMap = [];
$regexes = [];
$markName = 'a';
foreach ($regexToRoutesMap as $regex => $route) {
$regexes[] = $regex . '(*MARK:' . $markName . ')';
$routeMap[$markName] = [$route->handler, $route->variables];

++$markName;
}

$regex = '~^(?|' . implode('|', $regexes) . ')$~';
return ['regex' => $regex, 'routeMap' => $routeMap];
}
}

51 changes: 38 additions & 13 deletions src/DataGenerator/RegexBasedAbstract.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@

abstract class RegexBasedAbstract implements DataGenerator {
protected $staticRoutes = [];
protected $regexToRoutesMap = [];
protected $methodToRegexToRoutesMap = [];

protected abstract function getApproxChunkSize();
protected abstract function processChunk($regexToRoutesMap);

public function addRoute($httpMethod, $routeData, $handler) {
if ($this->isStaticRoute($routeData)) {
Expand All @@ -18,6 +21,29 @@ public function addRoute($httpMethod, $routeData, $handler) {
}
}

public function getData() {
if (empty($this->methodToRegexToRoutesMap)) {
return [$this->staticRoutes, []];
}

return [$this->staticRoutes, $this->generateVariableRouteData()];
}

private function generateVariableRouteData() {
$data = [];
foreach ($this->methodToRegexToRoutesMap as $method => $regexToRoutesMap) {
$chunkSize = $this->computeChunkSize(count($regexToRoutesMap));
$chunks = array_chunk($regexToRoutesMap, $chunkSize, true);
$data[$method] = array_map([$this, 'processChunk'], $chunks);
}
return $data;
}

private function computeChunkSize($count) {
$numParts = max(1, round($count / $this->getApproxChunkSize()));
return ceil($count / $numParts);
}

private function isStaticRoute($routeData) {
return count($routeData) == 1 && is_string($routeData[0]);
}
Expand All @@ -32,15 +58,14 @@ private function addStaticRoute($httpMethod, $routeData, $handler) {
));
}

foreach ($this->regexToRoutesMap as $routes) {
if (!isset($routes[$httpMethod])) continue;

$route = $routes[$httpMethod];
if ($route->matches($routeStr)) {
throw new BadRouteException(sprintf(
'Static route "%s" is shadowed by previously defined variable route "%s" for method "%s"',
$routeStr, $route->regex, $httpMethod
));
if (isset($this->methodToRegexToRoutesMap[$httpMethod])) {
foreach ($this->methodToRegexToRoutesMap[$httpMethod] as $route) {
if ($route->matches($routeStr)) {
throw new BadRouteException(sprintf(
'Static route "%s" is shadowed by previously defined variable route "%s" for method "%s"',
$routeStr, $route->regex, $httpMethod
));
}
}
}

Expand All @@ -50,14 +75,14 @@ private function addStaticRoute($httpMethod, $routeData, $handler) {
private function addVariableRoute($httpMethod, $routeData, $handler) {
list($regex, $variables) = $this->buildRegexForRoute($routeData);

if (isset($this->regexToRoutesMap[$regex][$httpMethod])) {
if (isset($this->methodToRegexToRoutesMap[$httpMethod][$regex])) {
throw new BadRouteException(sprintf(
'Cannot register two routes matching "%s" for method "%s"',
$regex, $httpMethod
));
}

$this->regexToRoutesMap[$regex][$httpMethod] = new Route(
$this->methodToRegexToRoutesMap[$httpMethod][$regex] = new Route(
$httpMethod, $handler, $regex, $variables
);
}
Expand Down Expand Up @@ -85,4 +110,4 @@ private function buildRegexForRoute($routeData) {

return [$regex, $variables];
}
}
}
28 changes: 28 additions & 0 deletions src/Dispatcher/CharCountBased.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

namespace FastRoute\Dispatcher;

class CharCountBased extends RegexBasedAbstract {
public function __construct($data) {
list($this->staticRouteMap, $this->variableRouteData) = $data;
}

protected function dispatchVariableRoute($routeData, $uri) {
foreach ($routeData as $data) {
if (!preg_match($data['regex'], $uri . $data['suffix'], $matches)) {
continue;
}

list($handler, $varNames) = $data['routeMap'][end($matches)];

$vars = [];
$i = 0;
foreach ($varNames as $varName) {
$vars[$varName] = $matches[++$i];
}
return [self::FOUND, $handler, $vars];
}

return [self::NOT_FOUND];
}
}
44 changes: 5 additions & 39 deletions src/Dispatcher/GroupCountBased.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,59 +2,25 @@

namespace FastRoute\Dispatcher;

use FastRoute\Dispatcher;

class GroupCountBased implements Dispatcher {
private $staticRouteMap;
private $variableRouteData;

class GroupCountBased extends RegexBasedAbstract {
public function __construct($data) {
list($this->staticRouteMap, $this->variableRouteData) = $data;
}

public function dispatch($httpMethod, $uri) {
if (isset($this->staticRouteMap[$uri])) {
return $this->dispatchStaticRoute($httpMethod, $uri);
} else {
return $this->dispatchVariableRoute($httpMethod, $uri);
}
}

private function dispatchStaticRoute($httpMethod, $uri) {
$routes = $this->staticRouteMap[$uri];

if (isset($routes[$httpMethod])) {
return [self::FOUND, $routes[$httpMethod], []];
} elseif ($httpMethod === 'HEAD' && isset($routes['GET'])) {
return [self::FOUND, $routes['GET'], []];
} else {
return [self::METHOD_NOT_ALLOWED, array_keys($routes), $uri];
}
}

private function dispatchVariableRoute($httpMethod, $uri) {
foreach ($this->variableRouteData as $data) {
protected function dispatchVariableRoute($routeData, $uri) {
foreach ($routeData as $data) {
if (!preg_match($data['regex'], $uri, $matches)) {
continue;
}

$routes = $data['routeMap'][count($matches)];
if (!isset($routes[$httpMethod])) {
if ($httpMethod === 'HEAD' && isset($routes['GET'])) {
$httpMethod = 'GET';
} else {
return [self::METHOD_NOT_ALLOWED, array_keys($routes), $uri];
}
}

list($handler, $varNames) = $routes[$httpMethod];
list($handler, $varNames) = $data['routeMap'][count($matches)];

$vars = [];
$i = 0;
foreach ($varNames as $varName) {
$vars[$varName] = $matches[++$i];
}
return [self::FOUND, $handler, $vars];
return [self::FOUND, $handler, $vars, $uri];
}

return [self::NOT_FOUND];
Expand Down
Loading

0 comments on commit 46c1ab0

Please sign in to comment.