自定义路由

参考Demo: Router.php

EasySwoole支持自定义路由,其路由利用fastRoute实现,因此其路由规则与其保持一致,该组件的详细文档请参考 GitHub文档

示例代码:

新建文件App\HttpController\Router.php:

  1. <?php
  2. /**
  3. * Created by PhpStorm.
  4. * User: yf
  5. * Date: 2018/8/15
  6. * Time: 上午10:39
  7. */
  8. namespace App\HttpController;
  9. use EasySwoole\Http\AbstractInterface\AbstractRouter;
  10. use FastRoute\RouteCollector;
  11. use EasySwoole\Http\Request;
  12. use EasySwoole\Http\Response;
  13. class Router extends AbstractRouter
  14. {
  15. function initialize(RouteCollector $routeCollector)
  16. {
  17. // TODO: Implement initialize() method.
  18. $routeCollector->get('/user', '/index.html');
  19. $routeCollector->get('/rpc', '/Rpc/index');
  20. $routeCollector->get('/', function (Request $request, Response $response) {
  21. $response->write('this router index');
  22. });
  23. $routeCollector->get('/test', function (Request $request, Response $response) {
  24. $response->write('this router test');
  25. return '/a';//重新定位到/a方法
  26. });
  27. $routeCollector->get('/user/{id:\d+}', function (Request $request, Response $response) {
  28. $response->write("this is router user ,your id is {$request->getQueryParam('id')}");//获取到路由匹配的id
  29. return false;//不再往下请求,结束此次响应
  30. });
  31. }
  32. }

访问127.0.0.1:9501/rpc,对应为App\HttpController\Rpc.php->index()

如果使用回调函数方式处理路由,return false 代表着不在继续往下请求,并且不能触发afterAction,gc等方法

实现代码:

  1. <?php
  2. /*
  3. * 进行一次初始化判定
  4. */
  5. if($this->router === null){
  6. $class = $this->controllerNameSpacePrefix.'\\Router';
  7. try{
  8. if(class_exists($class)){//如果存在router类
  9. $ref = new \ReflectionClass($class);
  10. if($ref->isSubclassOf(AbstractRouter::class)){
  11. $this->routerRegister = $ref->newInstance();
  12. $this->router = new GroupCountBased($this->routerRegister->getRouteCollector()->getData());//将router类的配置数据经过fastroute处理后取出
  13. }else{
  14. $this->router = false;
  15. throw new RouterError("class : {$class} not AbstractRouter class");
  16. }
  17. }else{
  18. $this->router = false;
  19. }
  20. }catch (\Throwable $throwable){
  21. $this->router = false;
  22. throw new RouterError($throwable->getMessage());
  23. }
  24. }
  25. $path = UrlParser::pathInfo($request->getUri()->getPath());
  26. if($this->router instanceof GroupCountBased){
  27. $handler = null;
  28. $routeInfo = $this->router->dispatch($request->getMethod(),$path);
  29. if($routeInfo !== false){
  30. switch ($routeInfo[0]) {//未找到路由匹配
  31. case \FastRoute\Dispatcher::NOT_FOUND:{
  32. $handler = $this->routerRegister->getRouterNotFoundCallBack();
  33. break;
  34. }
  35. case \FastRoute\Dispatcher::METHOD_NOT_ALLOWED:{//未找到处理方法
  36. $handler = $this->routerRegister->getMethodNotAllowCallBack();
  37. break;
  38. }
  39. case \FastRoute\Dispatcher::FOUND:{
  40. $handler = $routeInfo[1];
  41. //合并解析出来的数据
  42. $vars = $routeInfo[2];
  43. $data = $request->getQueryParams();
  44. $request->withQueryParams($vars+$data);//将数据插入到get数据中
  45. break;
  46. }
  47. default:{
  48. $handler = $this->routerRegister->getRouterNotFoundCallBack();
  49. break;
  50. }
  51. }
  52. }
  53. //如果handler不为null,那么说明,非为 \FastRoute\Dispatcher::FOUND ,因此执行
  54. if(is_callable($handler)){
  55. try{
  56. //若直接返回一个url path
  57. $ret = call_user_func($handler,$request,$response);
  58. if(is_string($ret)){
  59. $path = UrlParser::pathInfo($ret);
  60. }else if($ret == false){
  61. return;
  62. }else{
  63. //可能在回调中重写了URL PATH
  64. $path = UrlParser::pathInfo($request->getUri()->getPath());
  65. }
  66. $request->getUri()->withPath($path);
  67. }catch (\Throwable $throwable){
  68. $this->hookThrowable($throwable,$request,$response);
  69. //出现异常的时候,不在往下dispatch
  70. return;
  71. }
  72. }else if(is_string($handler)){
  73. $path = UrlParser::pathInfo($handler);
  74. $request->getUri()->withPath($path);
  75. goto response;
  76. }
  77. /*
  78. * 全局模式的时候,都拦截。非全局模式,否则继续往下
  79. */
  80. if($this->routerRegister->isGlobalMode()){
  81. return;
  82. }
  83. }

全局模式拦截

在Router.php加入以下代码,即可开启全局模式拦截

  1. $this->setGlobalMode(true);

全局模式拦截下,路由将只匹配Router.php中的控制器方法响应,将不会执行框架的默认解析

异常错误处理

通过以下2个方法,可设置路由匹配错误以及未找到方法的回调:

  1. <?php
  2. $this->setMethodNotAllowCallBack(function (Request $request,Response $response){
  3. $response->write('未找到处理方法');
  4. return false;//结束此次响应
  5. });
  6. $this->setRouterNotFoundCallBack(function (Request $request,Response $response){
  7. $response->write('未找到路由匹配');
  8. return 'index';//重定向到index路由
  9. });

该回调函数只针对于fastRoute未匹配状况,如果回调里面不结束该请求响应,则该次请求将会继续进行Dispatch并尝试寻找对应的控制器进行响应处理。

fastRoute使用

addRoute方法

定义路由的addRoute方法原型如下,该方法需要三个参数,下面围绕这三个参数我们对路由组件进行更深一步的了解

  1. $routeCollector->addRoute($httpMethod, $routePattern, $handler)

httpMethod


该参数需要传入一个大写的HTTP方法字符串,指定路由可以拦截的方法,单个方法直接传入字符串,需要拦截多个方法可以传入一个一维数组,如下面的例子:

  1. // 拦截GET方法
  2. $routeCollector->addRoute('GET', '/router', '/Index');
  3. // 拦截POST方法
  4. $routeCollector->addRoute('POST', '/router', '/Index');
  5. // 拦截多个方法
  6. $routeCollector->addRoute(['GET', 'POST'], '/router', '/Index');

routePattern


传入一个路由匹配表达式,符合该表达式要求的路由才会被拦截并进行处理,表达式支持{参数名称:匹配规则}这样的占位符匹配,用于限定路由参数

基本匹配

下面的定义将会匹配 http://localhost:9501/users/info

  1. $routeCollector->addRoute('GET', '/users/info', 'handler');

绑定参数

下面的定义将/users/后面的部分作为参数,并且限定参数只能是数字[0-9]

  1. // 可以匹配: http://localhost:9501/users/12667
  2. // 不能匹配: http://localhost:9501/users/abcde
  3. $routeCollector->addRoute('GET', '/users/{id:\d+}', 'handler');

下面的定义不做任何限定,仅将匹配到的URL部分获取为参数

  1. // 可以匹配: http://localhost:9501/users/12667
  2. // 可以匹配: http://localhost:9501/users/abcde
  3. $routeCollector->addRoute('GET', '/users/{name}', 'handler');

有时候路由的部分位置是可选的,可以像下面这样定义

  1. // 可以匹配: http://localhost:9501/users/to
  2. // 可以匹配: http://localhost:9501/users/to/username
  3. $routeCollector->addRoute('GET', '/users/to[/{name}]', 'handler');

绑定的参数将由框架内部进行组装到get数据之中,调用方法:

  1. <?php
  2. $routeCollector->get('/user/{id:\d+}', function (Request $request, Response $response) {
  3. $response->write("this is router user ,your id is {$request->getQueryParam('id')}");
  4. return false;
  5. });

handler


指定路由匹配成功后需要处理的方法,可以传入一个闭包,当传入闭包时一定要注意处理完成之后要处理结束响应否则请求会继续Dispatch寻找对应的控制器来处理,当然如果利用这一点,也可以对某些请求进行处理后再交给控制器执行逻辑

  1. // 传入闭包的情况
  2. $routeCollector->addRoute('GET', '/router/{id:\d+}', function (Request $request, Response $response) {
  3. $id = $request->getQueryParam('id');
  4. $response->write('Userid : ' . $id);
  5. return false;
  6. });

也可以直接传入控制器路径

  1. $routeCollector->addRoute('GET', '/router2/{id:\d+}', '/Index');

更多使用详情请直接查看FastRouter。