Router

Testing Is Documentation

tests/Router/RouterTest.phpRouter - 图1

路由是整个框架一个非常重要的调度组件,完成从请求到响应的完整过程,通常我们使用代理 \Leevel\Router\Proxy\Router 类进行静态调用。

路有服务提供者

路由服务是系统核心服务,会在系统初始化时通过路由服务提供者注册。

  1. namespace Common\Infra\Provider;
  2. use Admin\App\Middleware\Auth as AdminAuth;
  3. use Admin\App\Middleware\Cors;
  4. use Leevel\Auth\Middleware\Auth;
  5. use Leevel\Debug\Middleware\Debug;
  6. use Leevel\Di\IContainer;
  7. use Leevel\Log\Middleware\Log;
  8. use Leevel\Router\RouterProvider;
  9. use Leevel\Session\Middleware\Session;
  10. use Leevel\Throttler\Middleware\Throttler;
  11. class Router extends RouterProvider
  12. {
  13. /**
  14. * 控制器相对目录.
  15. *
  16. * @var string
  17. */
  18. protected string $controllerDir = 'App\\Controller';
  19. /**
  20. * 中间件分组.
  21. *
  22. * - 分组可以很方便地批量调用组件.
  23. *
  24. * @var array
  25. */
  26. protected array $middlewareGroups = [
  27. // web 请求中间件
  28. 'web' => [
  29. 'session',
  30. ],
  31. // api 请求中间件
  32. 'api' => [
  33. // API 限流,可以通过网关来做限流更高效,如果需要去掉注释即可
  34. // 'throttler:60,60',
  35. ],
  36. // 公共请求中间件
  37. 'common' => [
  38. 'log',
  39. ],
  40. ];
  41. /**
  42. * 中间件别名.
  43. *
  44. * - HTTP 中间件提供一个方便的机制来过滤进入应用程序的 HTTP 请求
  45. * - 例外在应用执行结束后响应环节也会调用 HTTP 中间件.
  46. *
  47. * @var array
  48. */
  49. protected array $middlewareAlias = [
  50. 'auth' => Auth::class,
  51. 'cors' => Cors::class,
  52. 'admin_auth' => AdminAuth::class,
  53. 'debug' => Debug::class,
  54. 'log' => Log::class,
  55. 'session' => Session::class,
  56. 'throttler' => Throttler::class,
  57. ];
  58. /**
  59. * 基础路径.
  60. *
  61. * @var array
  62. */
  63. protected array $basePaths = [
  64. '*' => [
  65. 'middlewares' => 'common',
  66. ],
  67. 'foo/*world' => [
  68. ],
  69. 'api/test' => [
  70. 'middlewares' => 'api',
  71. ],
  72. ':admin/*' => [
  73. 'middlewares' => 'admin_auth,cors',
  74. ],
  75. 'options/index' => [
  76. 'middlewares' => 'cors',
  77. ],
  78. 'admin/show' => [
  79. 'middlewares' => 'auth',
  80. ],
  81. ];
  82. /**
  83. * 分组.
  84. *
  85. * @var array
  86. */
  87. protected array $groups = [
  88. 'pet' => [],
  89. 'store' => [],
  90. 'user' => [],
  91. '/api/v1' => [
  92. 'middlewares' => 'api',
  93. ],
  94. 'api/v2' => [
  95. 'middlewares' => 'api',
  96. ],
  97. '/web/v1' => [
  98. 'middlewares' => 'web',
  99. ],
  100. 'web/v2' => [
  101. 'middlewares' => 'web',
  102. ],
  103. ];
  104. /**
  105. * 创建一个服务容器提供者实例.
  106. */
  107. public function __construct(IContainer $container)
  108. {
  109. parent::__construct($container);
  110. if ($container->make('app')->isDebug()) {
  111. $this->middlewareGroups['common'][] = 'debug';
  112. }
  113. }
  114. /**
  115. * bootstrap.
  116. */
  117. public function bootstrap(): void
  118. {
  119. parent::bootstrap();
  120. }
  121. /**
  122. * 返回路由.
  123. */
  124. public function getRouters(): array
  125. {
  126. return parent::getRouters();
  127. }
  128. }

Uses

  1. <?php
  2. use Leevel\Di\Container;
  3. use Leevel\Http\Request;
  4. use Leevel\Router\IRouter;
  5. use Leevel\Router\Router;
  6. use Leevel\Router\RouterNotFoundException;
  7. use Symfony\Component\HttpFoundation\ParameterBag;
  8. use Symfony\Component\HttpFoundation\Response;
  9. use Tests\Router\Middlewares\Demo1;
  10. use Tests\Router\Middlewares\Demo2;
  11. use Tests\Router\Middlewares\Demo3;
  12. use Tests\Router\Middlewares\DemoForGroup;

基本使用

fixture 定义

Tests\Router\Controllers\Home

  1. namespace Tests\Router\Controllers;
  2. class Home
  3. {
  4. public function index(): string
  5. {
  6. return 'hello my home';
  7. }
  8. }
  1. public function testBaseUse(): void
  2. {
  3. $pathInfo = '/:tests';
  4. $attributes = [];
  5. $method = 'GET';
  6. $controllerDir = 'Router\\Controllers';
  7. $request = $this->createRequest($pathInfo, $attributes, $method);
  8. $router = $this->createRouter();
  9. $router->setControllerDir($controllerDir);
  10. $result = $router->dispatch($request);
  11. $this->assertInstanceof(Response::class, $result);
  12. $this->assertSame('hello my home', $result->getContent());
  13. }

控制器方法单独成为类

方法类的方法固定为 handle,返回响应结果。

fixture 定义

Tests\Router\Controllers\Hello\ActionClass

  1. namespace Tests\Router\Controllers\Hello;
  2. class ActionClass
  3. {
  4. public function handle(): string
  5. {
  6. return 'hello action class';
  7. }
  8. }
  1. public function testActionAsClass(): void
  2. {
  3. $pathInfo = '/:tests/hello/actionClass';
  4. $attributes = [];
  5. $method = 'GET';
  6. $controllerDir = 'Router\\Controllers';
  7. $request = $this->createRequest($pathInfo, $attributes, $method);
  8. $router = $this->createRouter();
  9. $router->setControllerDir($controllerDir);
  10. $result = $router->dispatch($request);
  11. $this->assertInstanceof(Response::class, $result);
  12. $this->assertSame('hello action class', $result->getContent());
  13. }

控制器方法支持短横线和下换线转换为驼峰规则

fixture 定义

Tests\Router\Controllers\Hello\ActionConvertFooBar

  1. namespace Tests\Router\Controllers\Hello;
  2. class ActionConvertFooBar
  3. {
  4. public function handle(): string
  5. {
  6. return 'hello action convert foo bar';
  7. }
  8. }
  1. public function testActionConvert(): void
  2. {
  3. $pathInfo = '/:tests/hello/action_convert-foo_bar';
  4. $attributes = [];
  5. $method = 'GET';
  6. $controllerDir = 'Router\\Controllers';
  7. $request = $this->createRequest($pathInfo, $attributes, $method);
  8. $router = $this->createRouter();
  9. $router->setControllerDir($controllerDir);
  10. $result = $router->dispatch($request);
  11. $this->assertInstanceof(Response::class, $result);
  12. $this->assertSame('hello action convert foo bar', $result->getContent());
  13. }

控制器支持短横线和下换线转换为驼峰规则

fixture 定义

Tests\Router\Controllers\ControllerConvertFooBar

  1. namespace Tests\Router\Controllers;
  2. class ControllerConvertFooBar
  3. {
  4. public function bar(): string
  5. {
  6. return 'hello controller convert';
  7. }
  8. }
  1. public function testControllerConvert(): void
  2. {
  3. $pathInfo = '/:tests/controller_convert-foo_bar/bar';
  4. $attributes = [];
  5. $method = 'GET';
  6. $controllerDir = 'Router\\Controllers';
  7. $request = $this->createRequest($pathInfo, $attributes, $method);
  8. $router = $this->createRouter();
  9. $router->setControllerDir($controllerDir);
  10. $result = $router->dispatch($request);
  11. $this->assertInstanceof(Response::class, $result);
  12. $this->assertSame('hello controller convert', $result->getContent());
  13. }

控制器支持子目录

控制器子目录支持无限层级。

fixture 定义

Tests\Router\Controllers\Sub\World

  1. namespace Tests\Router\Controllers\Sub;
  2. class World
  3. {
  4. public function foo()
  5. {
  6. return 'hello sub world foo';
  7. }
  8. }
  1. public function testSubControllerDir(): void
  2. {
  3. $pathInfo = '/:tests/sub/world/foo';
  4. $attributes = [];
  5. $method = 'GET';
  6. $controllerDir = 'Router\\Controllers';
  7. $request = $this->createRequest($pathInfo, $attributes, $method);
  8. $router = $this->createRouter();
  9. $router->setControllerDir($controllerDir);
  10. $result = $router->dispatch($request);
  11. $this->assertInstanceof(Response::class, $result);
  12. $this->assertSame('hello sub world foo', $result->getContent());
  13. }

控制器子目录支持短横线和下换线转换为驼峰规则

fixture 定义

Tests\Router\Controllers\Sub\World

  1. namespace Tests\Router\Controllers\Sub;
  2. class World
  3. {
  4. public function foo()
  5. {
  6. return 'hello sub world foo';
  7. }
  8. }
  1. public function testConvertAll(): void
  2. {
  3. $this->expectException(\Leevel\Router\RouterNotFoundException::class);
  4. $this->expectExceptionMessage(
  5. 'The router Tests\\Router\\Controllers\\HeLloWor\\Bar\\Foo\\XYYAc\\ControllerXxYy::actionXxxYzs() was not found.'
  6. );
  7. $pathInfo = '/:tests/he_llo-wor/Bar/foo/xYY-ac/controller_xx-yy/action-xxx_Yzs';
  8. $attributes = [];
  9. $method = 'GET';
  10. $controllerDir = 'Router\\Controllers';
  11. $request = $this->createRequest($pathInfo, $attributes, $method);
  12. $router = $this->createRouter();
  13. $router->setControllerDir($controllerDir);
  14. $router->dispatch($request);
  15. }

可以转换为 JSON 的控制器响应

fixture 定义

Tests\Router\Controllers\ShouldJson

  1. namespace Tests\Router\Controllers;
  2. class ShouldJson
  3. {
  4. public function index(): array
  5. {
  6. return ['foo' => 'bar'];
  7. }
  8. }
  1. public function testShouldJson(): void
  2. {
  3. $pathInfo = '/:tests/should_json';
  4. $attributes = [];
  5. $method = 'GET';
  6. $controllerDir = 'Router\\Controllers';
  7. $request = $this->createRequest($pathInfo, $attributes, $method);
  8. $router = $this->createRouter();
  9. $router->setControllerDir($controllerDir);
  10. $result = $router->dispatch($request);
  11. $this->assertInstanceof(Response::class, $result);
  12. $this->assertSame('{"foo":"bar"}', $result->getContent());
  13. }

不可以转换为 JSON 的控制器响应强制转化为字符串

fixture 定义

Tests\Router\Controllers\Response\IntResponse

  1. namespace Tests\Router\Controllers\Response;
  2. class IntResponse
  3. {
  4. public function handle(): int
  5. {
  6. return 123456;
  7. }
  8. }
  1. public function testResponseIsInt(): void
  2. {
  3. $pathInfo = '/:tests/Response/IntResponse';
  4. $attributes = [];
  5. $method = 'GET';
  6. $controllerDir = 'Router\\Controllers';
  7. $request = $this->createRequest($pathInfo, $attributes, $method);
  8. $router = $this->createRouter();
  9. $router->setControllerDir($controllerDir);
  10. $result = $router->dispatch($request);
  11. $this->assertInstanceof(Response::class, $result);
  12. $this->assertSame('123456', $result->getContent());
  13. }

RESTFUL 控制器响应

fixture 定义

测试类型例子

  1. # Tests\Router\RouterTest::getRestfulData
  2. public function getRestfulData();

Tests\Router\Controllers\Restful\Show

  1. namespace Tests\Router\Controllers\Restful;
  2. use Leevel\Router\IRouter;
  3. class Show
  4. {
  5. public function handle()
  6. {
  7. return 'hello for restful '.IRouter::RESTFUL_SHOW;
  8. }
  9. }

Tests\Router\Controllers\Restful\Store

  1. namespace Tests\Router\Controllers\Restful;
  2. use Leevel\Router\IRouter;
  3. class Store
  4. {
  5. public function handle()
  6. {
  7. return 'hello for restful '.IRouter::RESTFUL_STORE;
  8. }
  9. }

Tests\Router\Controllers\Restful\Update

  1. namespace Tests\Router\Controllers\Restful;
  2. use Leevel\Router\IRouter;
  3. class Update
  4. {
  5. public function handle()
  6. {
  7. return 'hello for restful '.IRouter::RESTFUL_UPDATE;
  8. }
  9. }

Tests\Router\Controllers\Restful\Destroy

  1. namespace Tests\Router\Controllers\Restful;
  2. use Leevel\Router\IRouter;
  3. class Destroy
  4. {
  5. public function handle()
  6. {
  7. return 'hello for restful '.IRouter::RESTFUL_DESTROY;
  8. }
  9. }
  1. public function testRestful(string $method, string $action): void
  2. {
  3. $pathInfo = '/:tests/restful/5';
  4. $attributes = [];
  5. $method = $method;
  6. $controllerDir = 'Router\\Controllers';
  7. $request = $this->createRequest($pathInfo, $attributes, $method);
  8. $router = $this->createRouter();
  9. $router->setControllerDir($controllerDir);
  10. $result = $router->dispatch($request);
  11. $this->assertInstanceof(Response::class, $result);
  12. $this->assertSame('hello for restful '.$action, $result->getContent());
  13. }

setPreRequestMatched 设置路由请求预解析结果

fixture 定义

Tests\Router\Controllers\PreRequestMatched\Prefix\Bar\Foo

  1. namespace Tests\Router\Controllers\PreRequestMatched\Prefix\Bar;
  2. class Foo
  3. {
  4. public function handle(): string
  5. {
  6. return 'hello preRequestMatched';
  7. }
  8. }
  1. public function testSetPreRequestMatched(): void
  2. {
  3. $pathInfo = '';
  4. $attributes = [];
  5. $method = 'GET';
  6. $controllerDir = 'Router\\Controllers';
  7. $request = $this->createRequest($pathInfo, $attributes, $method);
  8. $router = $this->createRouter();
  9. $router->setPreRequestMatched($request, [
  10. IRouter::APP => 'Tests',
  11. IRouter::CONTROLLER => 'Bar',
  12. IRouter::ACTION => 'foo',
  13. IRouter::PREFIX => 'PreRequestMatched\\Prefix',
  14. IRouter::ATTRIBUTES => null,
  15. IRouter::MIDDLEWARES => null,
  16. IRouter::VARS => null,
  17. ]);
  18. $router->setControllerDir($controllerDir);
  19. $result = $router->dispatch($request);
  20. $this->assertInstanceof(Response::class, $result);
  21. $this->assertSame('hello preRequestMatched', $result->getContent());
  22. }

穿越中间件

fixture 定义

Tests\Router\Controllers\Hello\ThroughMiddleware

  1. namespace Tests\Router\Controllers\Hello;
  2. class ThroughMiddleware
  3. {
  4. public function handle(): string
  5. {
  6. return 'hello throughMiddleware';
  7. }
  8. }

Tests\Router\Middlewares\Demo1

  1. namespace Tests\Router\Middlewares;
  2. use Closure;
  3. use Leevel\Http\Request;
  4. use Symfony\Component\HttpFoundation\Response;
  5. class Demo1
  6. {
  7. public function __construct()
  8. {
  9. }
  10. public function terminate(Closure $next, Request $request, Response $response)
  11. {
  12. $GLOBALS['demo_middlewares'][] = 'Demo1::terminate';
  13. $next($request, $response);
  14. }
  15. }

Tests\Router\Middlewares\Demo2

  1. namespace Tests\Router\Middlewares;
  2. use Closure;
  3. use Leevel\Http\Request;
  4. use Symfony\Component\HttpFoundation\Response;
  5. class Demo2
  6. {
  7. public function __construct()
  8. {
  9. }
  10. public function handle(Closure $next, Request $request)
  11. {
  12. $GLOBALS['demo_middlewares'][] = 'Demo2::handle';
  13. $next($request);
  14. }
  15. public function terminate(Closure $next, Request $request, Response $response)
  16. {
  17. $GLOBALS['demo_middlewares'][] = 'Demo2::terminate';
  18. $next($request, $response);
  19. }
  20. }

Tests\Router\Middlewares\Demo3

  1. namespace Tests\Router\Middlewares;
  2. use Closure;
  3. use Leevel\Http\Request;
  4. class Demo3
  5. {
  6. public function __construct()
  7. {
  8. }
  9. public function handle(Closure $next, Request $request, int $arg1 = 1, string $arg2 = 'hello')
  10. {
  11. $GLOBALS['demo_middlewares'][] = sprintf('Demo3::handle(arg1:%s,arg2:%s)', $arg1, $arg2);
  12. $next($request);
  13. }
  14. }

Tests\Router\Middlewares\DemoForGroup

  1. namespace Tests\Router\Middlewares;
  2. use Closure;
  3. use Leevel\Http\Request;
  4. use Symfony\Component\HttpFoundation\Response;
  5. class DemoForGroup
  6. {
  7. public function __construct()
  8. {
  9. }
  10. public function handle(Closure $next, Request $request)
  11. {
  12. $GLOBALS['demo_middlewares'][] = 'DemoForGroup::handle';
  13. $next($request);
  14. }
  15. public function terminate(Closure $next, Request $request, Response $response)
  16. {
  17. $GLOBALS['demo_middlewares'][] = 'DemoForGroup::terminate';
  18. $next($request, $response);
  19. }
  20. }
  1. public function testThroughMiddleware(): void
  2. {
  3. $pathInfo = '/:tests/hello/throughMiddleware';
  4. $attributes = [];
  5. $method = 'GET';
  6. $controllerDir = 'Router\\Controllers';
  7. $request = $this->createRequest($pathInfo, $attributes, $method);
  8. $router = $this->createRouter();
  9. $router->setMiddlewareGroups([
  10. 'group1' => [
  11. 'demo1',
  12. 'demo2',
  13. ],
  14. 'group2' => [
  15. 'demo1',
  16. 'demo3:10,world',
  17. ],
  18. 'group3' => [
  19. 'demo1',
  20. 'demo2',
  21. 'demo3:10,world',
  22. ],
  23. ]);
  24. $router->setMiddlewareAlias([
  25. 'demo1' => Demo1::class,
  26. 'demo2' => Demo2::class,
  27. 'demo3' => Demo3::class,
  28. 'demoForGroup' => DemoForGroup::class,
  29. ]);
  30. $router->setBasePaths([
  31. '*' => [
  32. 'middlewares' => [
  33. 'handle' => [
  34. Demo2::class.'@handle',
  35. ],
  36. 'terminate' => [
  37. Demo1::class.'@terminate',
  38. Demo2::class.'@terminate',
  39. ],
  40. ],
  41. ],
  42. '/^\\/:tests\/hello\/throughMiddleware\\/$/' => [
  43. 'middlewares' => [
  44. 'handle' => [
  45. Demo3::class.':10,hello@handle',
  46. ],
  47. ],
  48. ],
  49. '/^\\/:tests(\\S*)\\/$/' => [
  50. 'middlewares' => [
  51. 'handle' => [
  52. DemoForGroup::class.'@handle',
  53. ],
  54. 'terminate' => [
  55. DemoForGroup::class.'@terminate',
  56. ],
  57. ],
  58. ],
  59. ]);
  60. $router->setControllerDir($controllerDir);
  61. if (isset($GLOBALS['demo_middlewares'])) {
  62. unset($GLOBALS['demo_middlewares']);
  63. }
  64. $result = $router->dispatch($request);
  65. $router->throughMiddleware($request, [
  66. $result,
  67. ]);
  68. $this->assertInstanceof(Response::class, $result);
  69. $this->assertSame('hello throughMiddleware', $result->getContent());
  70. $data = <<<'eot'
  71. [
  72. "Demo2::handle",
  73. "Demo3::handle(arg1:10,arg2:hello@handle)",
  74. "DemoForGroup::handle",
  75. "Demo1::terminate",
  76. "Demo2::terminate",
  77. "DemoForGroup::terminate"
  78. ]
  79. eot;
  80. $this->assertSame(
  81. $data,
  82. $this->varJson(
  83. $GLOBALS['demo_middlewares']
  84. )
  85. );
  86. unset($GLOBALS['demo_middlewares']);
  87. }

控制器支持冒号分隔为子目录

子目录支持无限层级。

fixture 定义

Tests\Router\Controllers\Colon\Hello

  1. namespace Tests\Router\Controllers\Colon;
  2. class Hello
  3. {
  4. public function index(): string
  5. {
  6. return 'hello colon with controller';
  7. }
  8. }
  1. public function testColonInController(): void
  2. {
  3. $pathInfo = '/:tests/colon:hello';
  4. $attributes = [];
  5. $method = 'GET';
  6. $controllerDir = 'Router\\Controllers';
  7. $request = $this->createRequest($pathInfo, $attributes, $method);
  8. $router = $this->createRouter();
  9. $router->setControllerDir($controllerDir);
  10. $result = $router->dispatch($request);
  11. $this->assertInstanceof(Response::class, $result);
  12. $this->assertSame('hello colon with controller', $result->getContent());
  13. }

控制器支持冒号分隔为子目录多层级例子

子目录支持无限层级。

fixture 定义

Tests\Router\Controllers\ColonActionSingle\Hello\World\Foo\Index

  1. namespace Tests\Router\Controllers\ColonActionSingle\Hello\World\Foo;
  2. class Index
  3. {
  4. public function handle(): string
  5. {
  6. return 'hello colon with more than one in controller and action is single';
  7. }
  8. }
  1. public function testColonInControllerWithMoreThanOne(): void
  2. {
  3. $pathInfo = '/:tests/colon:hello:world:foo';
  4. $attributes = [];
  5. $method = 'GET';
  6. $controllerDir = 'Router\\Controllers';
  7. $request = $this->createRequest($pathInfo, $attributes, $method);
  8. $router = $this->createRouter();
  9. $router->setControllerDir($controllerDir);
  10. $result = $router->dispatch($request);
  11. $this->assertInstanceof(Response::class, $result);
  12. $this->assertSame('hello colon with more than one in controller', $result->getContent());
  13. }

方法支持冒号分隔转为驼峰规则

冒号分隔方法,方法未独立成类,则将冒号转为驼峰规则。

下面例子中的方法为 fooBar

fixture 定义

Tests\Router\Controllers\Colon\Action

  1. namespace Tests\Router\Controllers\Colon;
  2. class Action
  3. {
  4. public function fooBar(): string
  5. {
  6. return 'hello colon with action and action is not single class';
  7. }
  8. public function moreFooBar(): string
  9. {
  10. return 'hello colon with action and action is not single class with more than one';
  11. }
  12. public function beforeButFirst(): string
  13. {
  14. return 'hello colon with action and action is not single class before but first';
  15. }
  16. }
  1. public function testColonInActionAndActionIsNotSingleClass(): void
  2. {
  3. $pathInfo = '/:tests/colon:action/foo:bar';
  4. $attributes = [];
  5. $method = 'GET';
  6. $controllerDir = 'Router\\Controllers';
  7. $request = $this->createRequest($pathInfo, $attributes, $method);
  8. $router = $this->createRouter();
  9. $router->setControllerDir($controllerDir);
  10. $result = $router->dispatch($request);
  11. $this->assertInstanceof(Response::class, $result);
  12. $this->assertSame('hello colon with action and action is not single class', $result->getContent());
  13. }

方法独立为类支持冒号分隔转为子目录

冒号分隔方法,方法独立成类,则将冒号转为子目录。

子目录支持无限层级。

fixture 定义

Tests\Router\Controllers\ColonActionSingle\Action\Foo\Bar

  1. namespace Tests\Router\Controllers\ColonActionSingle\Action\Foo;
  2. class Bar
  3. {
  4. public function handle(): string
  5. {
  6. return 'hello colon with action and action is not single class and action is single';
  7. }
  8. }
  1. public function testColonInActionAndActionIsSingleClass(): void
  2. {
  3. $pathInfo = '/:tests/colonActionSingle:action/foo:bar';
  4. $attributes = [];
  5. $method = 'GET';
  6. $controllerDir = 'Router\\Controllers';
  7. $request = $this->createRequest($pathInfo, $attributes, $method);
  8. $router = $this->createRouter();
  9. $router->setControllerDir($controllerDir);
  10. $result = $router->dispatch($request);
  11. $this->assertInstanceof(Response::class, $result);
  12. $this->assertSame('hello colon with action and action is not single class and action is single', $result->getContent());
  13. }

RESTFUL 控制器支持冒号分隔为子目录

子目录支持无限层级。

fixture 定义

Tests\Router\Controllers\ColonRestful\Hello\Show

  1. namespace Tests\Router\Controllers\ColonRestful\Hello;
  2. class Show
  3. {
  4. public function handle(): string
  5. {
  6. return 'hello colon restful with controller';
  7. }
  8. }
  1. public function testColonRestfulInControllerWithActionIsNotSingleClass(): void
  2. {
  3. $pathInfo = '/:tests/colonRestful:hello/5';
  4. $attributes = [];
  5. $method = 'GET';
  6. $controllerDir = 'Router\\Controllers';
  7. $request = $this->createRequest($pathInfo, $attributes, $method);
  8. $router = $this->createRouter();
  9. $router->setControllerDir($controllerDir);
  10. $result = $router->dispatch($request);
  11. $this->assertInstanceof(Response::class, $result);
  12. $this->assertSame('hello colon restful with controller', $result->getContent());
  13. }

RESTFUL 方法支持冒号分隔转为驼峰规则

冒号分隔方法,方法未独立成类,则将冒号转为驼峰规则。

下面例子中的方法为 fooBar

fixture 定义

Tests\Router\Controllers\ColonRestful\Hello

  1. namespace Tests\Router\Controllers\ColonRestful;
  2. class Hello
  3. {
  4. public function fooBar(): string
  5. {
  6. return 'hello colon restful with controller and action fooBar';
  7. }
  8. }
  1. public function testColonRestfulInActionWithActionIsNotSingleClass(): void
  2. {
  3. $pathInfo = '/:tests/colonRestful:hello/5/foo:bar';
  4. $attributes = [];
  5. $method = 'GET';
  6. $controllerDir = 'Router\\Controllers';
  7. $request = $this->createRequest($pathInfo, $attributes, $method);
  8. $router = $this->createRouter();
  9. $router->setControllerDir($controllerDir);
  10. $result = $router->dispatch($request);
  11. $this->assertInstanceof(Response::class, $result);
  12. $this->assertSame('hello colon restful with controller and action fooBar', $result->getContent());
  13. }

RESTFUL 方法支持冒号分隔为子目录

子目录支持无限层级。

fixture 定义

Tests\Router\Controllers\ColonRestfulActionSingle\Hello\Foo\Bar

  1. namespace Tests\Router\Controllers\ColonRestfulActionSingle\Hello\Foo;
  2. class Bar
  3. {
  4. public function handle(): string
  5. {
  6. return 'hello colon restful with action and action is single';
  7. }
  8. }
  1. public function testColonRestfulInActionWithActionIsSingleClass(): void
  2. {
  3. $pathInfo = '/:tests/colonRestfulActionSingle:hello/5/foo:bar';
  4. $attributes = [];
  5. $method = 'GET';
  6. $controllerDir = 'Router\\Controllers';
  7. $request = $this->createRequest($pathInfo, $attributes, $method);
  8. $router = $this->createRouter();
  9. $router->setControllerDir($controllerDir);
  10. $result = $router->dispatch($request);
  11. $this->assertInstanceof(Response::class, $result);
  12. $this->assertSame('hello colon restful with action and action is single', $result->getContent());
  13. }

应用支持冒号分隔为子目录

子目录支持无限层级。

fixture 定义

Tests\Router\SubAppController\Router\Controllers\Hello

  1. namespace Tests\Router\SubAppController\Router\Controllers;
  2. class Hello
  3. {
  4. public function index(): string
  5. {
  6. return 'hello sub app';
  7. }
  8. }
  1. public function testColonInApp(): void
  2. {
  3. $pathInfo = '/:tests:router:subAppController/hello';
  4. $attributes = [];
  5. $method = 'GET';
  6. $controllerDir = 'Router\\Controllers';
  7. $request = $this->createRequest($pathInfo, $attributes, $method);
  8. $router = $this->createRouter();
  9. $router->setControllerDir($controllerDir);
  10. $result = $router->dispatch($request);
  11. $this->assertInstanceof(Response::class, $result);
  12. $this->assertSame('hello sub app', $result->getContent());
  13. }