您的位置:首页 > 编程语言 > PHP开发

Just for fun——PHP框架之简单的路由器(2)

2017-11-20 00:00 218 查看

改进

紧接上一篇文章Just for fun——PHP框架之简单的路由器(1)
代码下载

效率不高原因

对于以下合并的正则

~^(?:
/user/([^/]+)/(\d+)
| /user/(\d+)
| /user/([^/]+)
)$~x

最终匹配的是分组中的某一个,我们需要的子匹配也是那个分组中的,然而从结果看

preg_match($regex, '/user/nikic', $matches);
=> [
"/user/nikic",   # 完全匹配
"", "",          # 第一个(空)
"",              # 第二个(空)
"nikic",         # 第三个(被使用)
]

这里是最后一个路由被匹配了,但是其他分组的子匹配也被填充了,这是多余的。

解决思路

PCRE正则里?|也是非捕获分组,那么?|?:有什么区别呢??
区别在于?|组号重置,看以下几个例子就懂了

preg_match('~(?:(Sat)ur|(Sun))day~', 'Saturday', $matches)
=> ["Saturday", "Sat", ""]   # 最后一个""其实是不存在的,写在这里是为了阐释概念

preg_match('~(?:(Sat)ur|(Sun))day~', 'Sunday', $matches)
=> ["Sunday", "", "Sun"]

preg_match('~(?|(Sat)ur|(Sun))day~', 'Saturday', $matches)
=> ["Saturday", "Sat"]

preg_match('~(?|(Sat)ur|(Sun))day~', 'Sunday', $matches)
=> ["Sunday", "Sun"]

所有我们可以用?|来代替?:来减少多余的子匹配填充,但是这样一来的话,如何判断哪个分组被匹配了呢??(因为之前的判断技巧就失效了)
我们可以这样,添加一些多余子匹配

~^(?|
/user/([^/]+)/(\d+)
| /user/(\d+)()()
| /user/([^/]+)()()()
)$~x

实现

dispatcher.php

<?php
/**
* User: salamander
* Date: 2017/11/12
* Time: 13:43
*/

namespace SalamanderRoute;

class Dispatcher {
/** @var mixed[][] */
protected $staticRoutes = [];

/** @var Route[][] */
private $methodToRegexToRoutesMap = [];

const NOT_FOUND = 0;
const FOUND = 1;
const METHOD_NOT_ALLOWED = 2;

/**
* 提取占位符
* @param $route
* @return array
*/
private function parse($route) {
$regex = '~^(?:/[a-zA-Z0-9_]*|/\{([a-zA-Z0-9_]+?)\})+/?$~';
if(preg_match($regex, $route, $matches)) {
// 区分静态路由和动态路由
if(count($matches) > 1) {
preg_match_all('~\{([a-zA-Z0-9_]+?)\}~', $route, $matchesVariables);
return [
preg_replace('~{[a-zA-Z0-9_]+?}~', '([a-zA-Z0-9_]+)', $route),
$matchesVariables[1],
];
} else {
return [
$route,
[],
];
}
}
throw new \LogicException('register route failed, pattern is illegal');
}

/**
* 注册路由
* @param $httpMethod string | string[]
* @param $route
* @param $handler
*/
public function addRoute($httpMethod, $route, $handler) {
$routeData = $this->parse($route);
foreach ((array) $httpMethod as $method) {
if ($this->isStaticRoute($routeData)) {
$this->addStaticRoute($method, $routeData, $handler);
} else {
$this->addVariableRoute($method, $routeData, $handler);
}
}
}

private function isStaticRoute($routeData) {
return count($routeData[1]) === 0;
}

private function addStaticRoute($httpMethod, $routeData, $handler) {
$routeStr = $routeData[0];

if (isset($this->staticRoutes[$httpMethod][$routeStr])) {
throw new \LogicException(sprintf(
'Cannot register two routes matching "%s" for method "%s"',
$routeStr, $httpMethod
));
}

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

$this->staticRoutes[$httpMethod][$routeStr] = $handler;
}

private function addVariableRoute($httpMethod, $routeData, $handler) {
list($regex, $variables) = $routeData;

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

$this->methodToRegexToRoutesMap[$httpMethod][$regex] = new Route(
$httpMethod, $handler, $regex, $variables
);
}

public function get($route, $handler) {
$this->addRoute('GET', $route, $handler);
}

public function post($route, $handler) {
$this->addRoute('POST', $route, $handler);
}

public function put($route, $handler) {
$this->addRoute('PUT', $route, $handler);
}

public function delete($route, $handler) {
$this->addRoute('DELETE', $route, $handler);
}

public function patch($route, $handler) {
$this->addRoute('PATCH', $route, $handler);
}

public function head($route, $handler) {
$this->addRoute('HEAD', $route, $handler);
}

/**
* 分发
* @param $httpMethod
* @param $uri
*/
public function dispatch($httpMethod, $uri) {
$staticRoutes = array_keys($this->staticRoutes[$httpMethod]);
foreach ($staticRoutes as $staticRoute) {
if($staticRoute === $uri) {
return [self::FOUND, $this->staticRoutes[$httpMethod][$staticRoute], []];
}
}

$routeLookup = [];
$regexes = [];
foreach ($this->methodToRegexToRoutesMap[$httpMethod] as $regex => $route) {
$index = count($route->variables);
if(array_key_exists($index, $routeLookup)) {
$indexNear = $this->getArrNearEmptyEntry($routeLookup, $index);
array_push($regexes, $regex . str_repeat('()', $indexNear - $index));
$routeLookup[$indexNear] = [
$this->methodToRegexToRoutesMap[$httpMethod][$regex]->handler,
$this->methodToRegexToRoutesMap[$httpMethod][$regex]->variables,
];
} else {
$routeLookup[$index] = [
$this->methodToRegexToRoutesMap[$httpMethod][$regex]->handler,
$this->methodToRegexToRoutesMap[$httpMethod][$regex]->variables,
];
array_push($regexes, $regex);
}
}
$regexCombined = '~^(?|' . implode('|', $regexes) . ')$~';
if(!preg_match($regexCombined, $uri, $matches)) {
return [self::NOT_FOUND];
}
list($handler, $varNames) = $routeLookup[count($matches) - 1];
$vars = [];
$i = 0;
foreach ($varNames as $varName) {
$vars[$varName] = $matches[++$i];
}
return [self::FOUND, $handler, $vars];
}

private function getArrNearEmptyEntry(&$arr, $index) {
while (array_key_exists(++$index, $arr));
return $index;
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  php7 php框架 php