kuria / router
HTTP请求路由器
Requires
- php: >=7.1
- kuria/url: ^5.0
Requires (Dev)
- ext-json: *
- kuria/dev-meta: ^0.6
This package is auto-updated.
Last update: 2024-09-22 17:49:52 UTC
README
HTTP请求路由器。
内容
功能
- 使用OO构建器定义路由
- 匹配请求属性(方法、方案、主机、端口、路径)
- 正则表达式驱动的主机和路径模式
- 生成URL
要求
- PHP 7.1+
使用
路由入站请求
简单的PATH_INFO
路由
使用$_SERVER['PATH_INFO']
和硬编码的上下文信息进行简单路由。
示例URL:http://localhost/index.php/page/index
<?php use Kuria\Router\Context; use Kuria\Router\Result\Match; use Kuria\Router\Result\MethodNotAllowed; use Kuria\Router\Route\RouteCollector; use Kuria\Router\Router; // create router $router = new Router(); // define default context $router->setDefaultContext(new Context( 'http', // scheme 'localhost', // host 80, // port '/index.php' // base path )); // define routes $router->defineRoutes(function (RouteCollector $c) { $c->get('index')->path('/'); $c->get('page')->path('/page/{name}'); $c->addGroup('user_', '/user', function (RouteCollector $c) { $c->add('register')->methods(['GET', 'POST'])->path('/register'); $c->add('login')->methods(['GET', 'POST'])->path('/login'); $c->get('logout')->path('/logout'); $c->get('profile')->path('/profile/{username}'); }); }); // match current request $path = rawurldecode($_SERVER['PATH_INFO'] ?? '/'); $result = $router->matchPath($_SERVER['REQUEST_METHOD'], $path); // handle the result if ($result instanceof Match) { // success // do something with the matched route and parameters echo 'Matched path: ', $result->getSubject()->path, "\n"; echo 'Matched route: ', $result->getRoute()->getName(), "\n"; echo 'Parameters: ', print_r($result->getParameters(), true), "\n"; } elseif ($result instanceof MethodNotAllowed) { // method not allowed http_response_code(405); header('Allow: ' . implode(', ', $result->getAllowedMethods())); echo "Method not allowed :(\n"; } else { // not found http_response_code(404); echo "Not found :(\n"; }
使用kuria/request-info的动态路由
可以使用kuria/request-info库自动检测上下文和路径信息。
它支持简单的路径信息和重写URL,并可以从受信任的代理头中提取信息。
<?php use Kuria\RequestInfo\RequestInfo; use Kuria\Router\Context; use Kuria\Router\Result\Match; use Kuria\Router\Result\MethodNotAllowed; use Kuria\Router\Route\RouteCollector; use Kuria\Router\Router; // create router $router = new Router(); // define default context $router->setDefaultContext(new Context( RequestInfo::getScheme(), RequestInfo::getHost(), RequestInfo::getPort(), RequestInfo::getBasePath() )); // define routes $router->defineRoutes(function (RouteCollector $c) { $c->get('index')->path('/'); $c->get('page')->path('/page/{name}'); $c->addGroup('user_', '/user', function (RouteCollector $c) { $c->add('register')->methods(['GET', 'POST'])->path('/register'); $c->add('login')->methods(['GET', 'POST'])->path('/login'); $c->get('logout')->path('/logout'); $c->get('profile')->path('/profile/{username}'); }); }); // match current request $path = rawurldecode(RequestInfo::getPathInfo()); $result = $router->matchPath(RequestInfo::getMethod(), $path !== '' ? $path : '/'); // handle the result if ($result instanceof Match) { // success // do something with the matched route and parameters echo 'Matched path: ', $result->getSubject()->path, "\n"; echo 'Matched route: ', $result->getRoute()->getName(), "\n"; echo 'Parameters: ', print_r($result->getParameters(), true), "\n"; } elseif ($result instanceof MethodNotAllowed) { // method not allowed http_response_code(405); header('Allow: ' . implode(', ', $result->getAllowedMethods())); echo "Method not allowed :(\n"; } else { // not found http_response_code(404); echo "Not found :(\n"; }
定义路由
RouteCollector
提供了一个方便的接口来定义路由。
使用它最简单的方法是使用一个接受RouteCollector
实例的回调来调用Router->defineRoutes()
。然后路由器负责添加定义的路由。
<?php use Kuria\Router\Route\RouteCollector; use Kuria\Router\Router; $router = new Router(); $router->defineRoutes(function (RouteCollector $c) { $c->get('index')->path('/'); $c->post('login')->path('/login'); // ... });
路由收集器API
RouteCollector
提供了创建和组织路由构建器的方法。
返回的RouteBuilder
实例可以用于配置路由。请参阅路由构建器API。
add($routeName): RouteBuilder
- 添加路由get($routeName): RouteBuilder
- 添加匹配GET请求的路由head($routeName): RouteBuilder
- 添加匹配HEAD请求的路由post($routeName): RouteBuilder
- 添加匹配POST请求的路由put($routeName): RouteBuilder
- 添加匹配PUT请求的路由delete($routeName): RouteBuilder
- 添加匹配DELETE请求的路由options($routeName): RouteBuilder
- 添加匹配OPTIONS请求的路由patch($routeName): RouteBuilder
- 添加匹配PATCH请求的路由addVariant($existingRouteName, $newRouteName): RouteBuilder
- 添加现有路由的变体,请参阅路由变体addGroup($namePrefix, $pathPrefix, $callback): void
- 添加具有公共前缀的一组路由,请参阅路由组hasBuilder($routeName): bool
- 检查路由是否已定义getBuilder($routeName): RouteBuilder
- 获取给定路由的构建器removeBuilder($routeName): void
- 删除路由定义getBuilders(): RouteBuilder[]
- 获取所有配置的构建器getRoutes(): Route[]
- 构建路由clear(): void
- 删除所有定义的路由
路由变体
要添加多个相似的路线,您可以定义单个路由,然后通过调用addVariant()
使用该定义作为新路由的基础。
<?php use Kuria\Router\Route\RouteCollector; use Kuria\Router\Router; $router = new Router(); $router->defineRoutes(function (RouteCollector $c) { // define a base route $c->get('get_row') ->path('/{database}/{row}') ->defaults(['format' => 'json']); // define a variant of the base route $c->addVariant('get_row', 'get_row_with_format') ->appendPath('.{format}') ->requirements(['format' => 'json|xml']); }); // print defined routes foreach ($router->getRoutes() as $route) { echo $route->getName(), ' :: ', $route->dump(), "\n"; }
输出
get_row :: GET /{database}/{row} get_row_with_format :: GET /{database}/{row}.{format}
路由组
要定义具有公共路径和名称前缀的多个路由,请使用addGroup()
<?php use Kuria\Router\Route\RouteCollector; use Kuria\Router\Router; $router = new Router(); $router->defineRoutes(function (RouteCollector $c) { $c->addGroup('user_', '/user', function (RouteCollector $c) { $c->add('register')->methods(['GET', 'POST'])->path('/register'); $c->add('login')->methods(['GET', 'POST'])->path('/login'); $c->get('logout')->path('/logout'); $c->get('profile')->path('/profile/{username}'); }); }); // print defined routes foreach ($router->getRoutes() as $route) { echo $route->getName(), ' :: ', $route->dump(), "\n"; }
输出
user_register :: GET|POST /user/register user_login :: GET|POST /user/login user_logout :: GET /user/logout user_profile :: GET /user/profile/{username}
路由构建器API
RouteBuilder
提供了一个流畅的接口来配置单个路由。
methods($allowedMethods): self
- 匹配请求方法(必须为大写,例如GET
、POST
等)scheme($scheme): self
- 匹配协议(例如http
或https
)host($hostPattern): self
- 匹配主机名模式,见 路由模式prependHost($hostPatternPrefix): self
- 向主机名模式添加前缀appendHost($hostPatternPrefix): self
- 向主机名模式添加后缀port($port): self
- 匹配端口path($pathPattern): self
- 匹配路径模式,见 路由模式prependPath($pathPatternPrefix): self
- 向路径模式添加前缀appendPath($pathPatternPrefix): self
- 向路径模式添加后缀defaults($defaults): self
- 指定默认参数,见 路由默认值attributes($attributes): self
- 指定任意路由属性,见 路由属性requirements($requirements): self
- 指定参数要求,见 路由要求
示例调用
<?php $router->defineRoutes(function (RouteCollector $c) { // $c->add() returns a RouteBuilder $c->add('user_profile_page') ->methods(['GET', 'POST']) ->scheme('https') ->host('{username}.example.com') ->port(8080) ->path('/{page}') ->defaults(['page' => 'home']) ->requirements(['username' => '\w+', 'page' => '[\w.\-]+']); });
路由模式
路由的主机和路径可以包含任意数量的参数占位符。
占位符语法如下
{parameterName}
参数名称可以由任何字符组成,但不能是 }
。
这些参数将在匹配结果中可用。见 匹配路由。
注意
不支持可选模式参数。如果您需要匹配相同资源的不同结构的URL,请相应地定义多个路由。
见 路由变体。
路由默认值
路由可以包含默认参数值。
这些默认值在生成URL时使用(如果未指定一个或多个参数)。见 生成URL。
默认参数在定义指向同一资源的多个路由时也很有用(因此这些路由可以互换)。
路由属性
路由可以包含任意属性。
其用途完全取决于应用程序,但它是一个存储各种元数据的好地方,例如控制器名称或处理程序调用。
路由要求
路由要求是针对每个主机或路径模式参数的一组普通正则表达式。见 路由模式。
正则表达式不应有分隔符。它们也被自动锚定,因此不应包含 ^
或 $
。
默认要求
如果没有指定要求,将假定一个默认值,具体取决于模式类型
- 主机模式:
.+
- 任意类型的多个字符
- 路径模式:
[^/]+
- 不是斜杠的多个字符
缓存路由
构建和编译路由将向您的应用程序引入一些开销。幸运的是,定义的路由可以序列化并存储以供以后使用。
以下是一个使用 kuria/cache 库进行路由缓存示例,但您可以使用任何其他库或代码。
<?php use Kuria\Cache\Cache; use Kuria\Cache\Driver\Filesystem\FilesystemDriver; use Kuria\Router\Route\RouteCollector; use Kuria\Router\Router; // example cache $cache = new Cache(new FilesystemDriver(__DIR__ . '/cache')); // create router $router = new Router(); // attempt to load routes from the cache $routes = $cache->get('routes'); if ($routes === null) { // no routes found in cache, define them $router->defineRoutes(function (RouteCollector $c) { $c->get('index')->path('/'); $c->get('page')->path('/page/{name}'); }); // store defined routes in the cache $cache->set('routes', $router->getRoutes()); } else { // use routes from cache $router->setRoutes($routes); }
注意
包含不可序列化值的路由(如属性中的闭包)无法缓存。
匹配路由
定义路由后,可以使用路由器来路由请求。
见 路由传入请求 中的示例代码。
使用Router->match()/matchPath()
Router->match()
和 Router->matchPath()
都返回 Kuria\Router\Result\Result
的一个实例,该实例可能是以下之一
Kuria\Router\Result\Match
已成功匹配路由。使用Match
对象可以访问匹配的路由和参数。
应用程序需要自行处理这些信息。
<?php use Kuria\Router\Result\Match; use Kuria\Router\Router; $result = $router->matchPath('GET', '/user/profile/bob'); if ($result instanceof Match) { echo 'Matched route is ', $result->getRoute()->getName(), "\n"; echo 'Matched parameters are: ', json_encode($result->getParameters()), "\n"; }
输出
Matched route is user_profile Matched parameters are: {"username":"bob"}
提示
您可以在$result->getRoute()->getAttributes()
中访问路由属性。
见路由属性。
Kuria\Router\Result\MethodNotAllowed
没有匹配到路由,但如果方法不同,则存在匹配的路由。
在这种情况下,正确的响应是HTTP 405方法不允许,带有指定允许方法的Allow
头部。
<?php use Kuria\Router\Result\MethodNotAllowed; $result = $router->matchPath('POST', '/user/logout'); if ($result instanceof MethodNotAllowed) { http_response_code(405); header('Allow: ' . implode(', ', $result->getAllowedMethods())); }
Kuria\Router\Result\NotFound
没有匹配到任何路由。
在这种情况下,正确的响应是HTTP 404未找到。
<?php use Kuria\Router\Result\NotFound; $result = $router->matchPath('GET', '/nonexistent'); if ($result instanceof NotFound) { http_response_code(404); }
HEAD到GET回退
为了便于遵守HTTP规范,如果HEAD
请求没有匹配任何路由,则将尝试第二次匹配,假设使用GET
方法。
PHP本身支持HEAD
请求,并且只会响应头部,因此在大多数情况下不需要创建额外的路由来处理这些请求。
生成URL
定义路由后,可以使用路由器生成URL。
见路由传入请求,以查看配置的路由示例。
使用Router->generate()
Router->generate()
方法将为给定路由和参数生成URL,并返回一个Kuria\Url\Url
实例。
- 如果不存在此类路由或参数无效,将抛出异常(见路由要求)
- 如果缺少某些参数,则将使用配置的默认值(见路由默认值)
- 任何额外的参数(不在主机或路径模式中)将被添加为查询参数
- 如果方案、主机或端口与上下文不同,则URL的偏好格式为
Url::ABSOLUTE
;如果它们全部相同或未定义,则将使用Url::RELATIVE
(见路由上下文)
<?php var_dump( $router->generate('user_register')->build(), $router->generate('user_profile', ['username' => 'bob', 'extra' => 'example'])->build() );
输出
string(14) "/user/register" string(31) "/user/profile/bob?extra=example"
如果您希望始终获取绝对URL,请使用buildAbsolute()
<?php var_dump( $router->generate('index')->buildAbsolute(), $router->generate('page', ['name' => 'contact'])->buildAbsolute() );
输出
string(17) "http://localhost/" string(29) "http://localhost/page/contact"
路由器上下文
路由上下文用于在生成URL或匹配路径时填充缺失信息(方案、主机、端口等)。
它可以以两种方式指定
使用Router->setDefaultContext()
此方法定义了一个默认上下文,在未提供时使用。
<?php use Kuria\Router\Context; $router->setDefaultContext(new Context( 'https', // scheme 'example.com', // host 443, // port '' // basePath ));
使用$context
参数
Router->matchPath()
和Router->generate()
接受一个可选的$context
参数。
如果没有提供上下文,则将使用默认上下文。如果没有指定默认上下文,则将抛出异常。