资讯专栏INFORMATION COLUMN

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

tomato / 3526人阅读

改进

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

效率不高原因

对于以下合并的正则

</>复制代码

  1. ~^(?:
  2. /user/([^/]+)/(d+)
  3. | /user/(d+)
  4. | /user/([^/]+)
  5. )$~x

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

</>复制代码

  1. preg_match($regex, "/user/nikic", $matches);
  2. => [
  3. "/user/nikic", # 完全匹配
  4. "", "", # 第一个(空)
  5. "", # 第二个(空)
  6. "nikic", # 第三个(被使用)
  7. ]

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

解决思路

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

</>复制代码

  1. preg_match("~(?:(Sat)ur|(Sun))day~", "Saturday", $matches)
  2. => ["Saturday", "Sat", ""] # 最后一个""其实是不存在的,写在这里是为了阐释概念
  3. preg_match("~(?:(Sat)ur|(Sun))day~", "Sunday", $matches)
  4. => ["Sunday", "", "Sun"]
  5. preg_match("~(?|(Sat)ur|(Sun))day~", "Saturday", $matches)
  6. => ["Saturday", "Sat"]
  7. preg_match("~(?|(Sat)ur|(Sun))day~", "Sunday", $matches)
  8. => ["Sunday", "Sun"]

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

</>复制代码

  1. ~^(?|
  2. /user/([^/]+)/(d+)
  3. | /user/(d+)()()
  4. | /user/([^/]+)()()()
  5. )$~x
实现 dispatcher.php

</>复制代码

  1. 1) {
  2. preg_match_all("~{([a-zA-Z0-9_]+?)}~", $route, $matchesVariables);
  3. return [
  4. preg_replace("~{[a-zA-Z0-9_]+?}~", "([a-zA-Z0-9_]+)", $route),
  5. $matchesVariables[1],
  6. ];
  7. } else {
  8. return [
  9. $route,
  10. [],
  11. ];
  12. }
  13. }
  14. throw new LogicException("register route failed, pattern is illegal");
  15. }
  16. /**
  17. * 注册路由
  18. * @param $httpMethod string | string[]
  19. * @param $route
  20. * @param $handler
  21. */
  22. public function addRoute($httpMethod, $route, $handler) {
  23. $routeData = $this->parse($route);
  24. foreach ((array) $httpMethod as $method) {
  25. if ($this->isStaticRoute($routeData)) {
  26. $this->addStaticRoute($method, $routeData, $handler);
  27. } else {
  28. $this->addVariableRoute($method, $routeData, $handler);
  29. }
  30. }
  31. }
  32. private function isStaticRoute($routeData) {
  33. return count($routeData[1]) === 0;
  34. }
  35. private function addStaticRoute($httpMethod, $routeData, $handler) {
  36. $routeStr = $routeData[0];
  37. if (isset($this->staticRoutes[$httpMethod][$routeStr])) {
  38. throw new LogicException(sprintf(
  39. "Cannot register two routes matching "%s" for method "%s"",
  40. $routeStr, $httpMethod
  41. ));
  42. }
  43. if (isset($this->methodToRegexToRoutesMap[$httpMethod])) {
  44. foreach ($this->methodToRegexToRoutesMap[$httpMethod] as $route) {
  45. if ($route->matches($routeStr)) {
  46. throw new LogicException(sprintf(
  47. "Static route "%s" is shadowed by previously defined variable route "%s" for method "%s"",
  48. $routeStr, $route->regex, $httpMethod
  49. ));
  50. }
  51. }
  52. }
  53. $this->staticRoutes[$httpMethod][$routeStr] = $handler;
  54. }
  55. private function addVariableRoute($httpMethod, $routeData, $handler) {
  56. list($regex, $variables) = $routeData;
  57. if (isset($this->methodToRegexToRoutesMap[$httpMethod][$regex])) {
  58. throw new LogicException(sprintf(
  59. "Cannot register two routes matching "%s" for method "%s"",
  60. $regex, $httpMethod
  61. ));
  62. }
  63. $this->methodToRegexToRoutesMap[$httpMethod][$regex] = new Route(
  64. $httpMethod, $handler, $regex, $variables
  65. );
  66. }
  67. public function get($route, $handler) {
  68. $this->addRoute("GET", $route, $handler);
  69. }
  70. public function post($route, $handler) {
  71. $this->addRoute("POST", $route, $handler);
  72. }
  73. public function put($route, $handler) {
  74. $this->addRoute("PUT", $route, $handler);
  75. }
  76. public function delete($route, $handler) {
  77. $this->addRoute("DELETE", $route, $handler);
  78. }
  79. public function patch($route, $handler) {
  80. $this->addRoute("PATCH", $route, $handler);
  81. }
  82. public function head($route, $handler) {
  83. $this->addRoute("HEAD", $route, $handler);
  84. }
  85. /**
  86. * 分发
  87. * @param $httpMethod
  88. * @param $uri
  89. */
  90. public function dispatch($httpMethod, $uri) {
  91. $staticRoutes = array_keys($this->staticRoutes[$httpMethod]);
  92. foreach ($staticRoutes as $staticRoute) {
  93. if($staticRoute === $uri) {
  94. return [self::FOUND, $this->staticRoutes[$httpMethod][$staticRoute], []];
  95. }
  96. }
  97. $routeLookup = [];
  98. $regexes = [];
  99. foreach ($this->methodToRegexToRoutesMap[$httpMethod] as $regex => $route) {
  100. $index = count($route->variables);
  101. if(array_key_exists($index, $routeLookup)) {
  102. $indexNear = $this->getArrNearEmptyEntry($routeLookup, $index);
  103. array_push($regexes, $regex . str_repeat("()", $indexNear - $index));
  104. $routeLookup[$indexNear] = [
  105. $this->methodToRegexToRoutesMap[$httpMethod][$regex]->handler,
  106. $this->methodToRegexToRoutesMap[$httpMethod][$regex]->variables,
  107. ];
  108. } else {
  109. $routeLookup[$index] = [
  110. $this->methodToRegexToRoutesMap[$httpMethod][$regex]->handler,
  111. $this->methodToRegexToRoutesMap[$httpMethod][$regex]->variables,
  112. ];
  113. array_push($regexes, $regex);
  114. }
  115. }
  116. $regexCombined = "~^(?|" . implode("|", $regexes) . ")$~";
  117. if(!preg_match($regexCombined, $uri, $matches)) {
  118. return [self::NOT_FOUND];
  119. }
  120. list($handler, $varNames) = $routeLookup[count($matches) - 1];
  121. $vars = [];
  122. $i = 0;
  123. foreach ($varNames as $varName) {
  124. $vars[$varName] = $matches[++$i];
  125. }
  126. return [self::FOUND, $handler, $vars];
  127. }
  128. private function getArrNearEmptyEntry(&$arr, $index) {
  129. while (array_key_exists(++$index, $arr));
  130. return $index;
  131. }
  132. }

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/26125.html

相关文章

  • Just for fun——PHP框架简单由器(1)

    摘要:路由路由的功能就是分发请求到不同的控制器,基于的原理就是正则匹配。 路由 路由的功能就是分发请求到不同的控制器,基于的原理就是正则匹配。接下来呢,我们实现一个简单的路由器,实现的能力是 对于静态的路由(没占位符的),正确调用callback 对于有占位符的路由,正确调用callback时传入占位符参数,譬如对于路由:/user/{id},当请求为/user/23时,传入参数$args...

    smallStone 评论0 收藏0
  • Just for fun——基于Swoole做个小框架

    摘要:使开发人员可以编写高性能的异步并发,服务。使用作为网络通信框架,可以使企业研发团队的效率大大提升,更加专注于开发创新产品。总之,这个库让可以常驻内存,并提供了,等功能。 swoole 使 PHP 开发人员可以编写高性能的异步并发 TCP、UDP、Unix Socket、HTTP,WebSocket 服务。Swoole 可以广泛应用于互联网、移动通信、企业软件、云计算、网络游戏、物联网(...

    CoreDump 评论0 收藏0
  • Just for fun——基于Swoole做个小框架

    摘要:使开发人员可以编写高性能的异步并发,服务。使用作为网络通信框架,可以使企业研发团队的效率大大提升,更加专注于开发创新产品。总之,这个库让可以常驻内存,并提供了,等功能。 swoole 使 PHP 开发人员可以编写高性能的异步并发 TCP、UDP、Unix Socket、HTTP,WebSocket 服务。Swoole 可以广泛应用于互联网、移动通信、企业软件、云计算、网络游戏、物联网(...

    fevin 评论0 收藏0
  • Just for fun——PHP框架简单模板引擎

    摘要:原理使用模板引擎的好处是数据和视图分离。对于循环语句怎么办呢这个的话,请看流程控制的替代语法 原理 使用模板引擎的好处是数据和视图分离。一个简单的PHP模板引擎原理是 extract数组($data),使key对应的变量可以在此作用域起效 打开输出控制缓冲(ob_start) include模板文件,include遇到html的内容会输出,但是因为打开了缓冲,内容输出到了缓冲中 ob...

    X1nFLY 评论0 收藏0
  • Just for fun——Slim借力Swoole

    摘要:的话,是一个遵循规范微型的框架,作者这样说大致意思的核心工作分发了请求,然后调用回调函数,返回一个对象。执行的方法时,我们从中取出的依赖,这时候,注册的回调函数被调用,返回实例。 Slim Slim的话,是一个遵循PSR (PSR-7)规范微型的框架,作者这样说: Slim is a PHP micro framework that helps you quickly write si...

    leejan97 评论0 收藏0

发表评论

0条评论

tomato

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<