dzentota/router

快速灵活的安全路由器。

dev-master 2023-12-08 21:27 UTC

This package is auto-updated.

Last update: 2024-09-08 23:10:07 UTC


README

用法

Router的用法非常简单

// Fetch method and URI from somewhere
$httpMethod = $_SERVER['REQUEST_METHOD'];
$uri = $_SERVER['REQUEST_URI'];

// Strip query string (?foo=bar) and decode URI
if (false !== $pos = strpos($uri, '?')) {
    $uri = substr($uri, 0, $pos);
}
$uri = rawurldecode($uri);

$route = (new Router())
    ->get('/user/{id}', 'UserController@show', ['id' => Id::class])
    ->findRoute($httpMethod, $uri);

解析后的$route将具有以下结构(对于GET /user/42

array(4) {
  ["route"]=>
  string(10) "/user/{id}"
  ["method"]=>
  string(3) "get"
  ["action"]=>
  string(19) "UserController@show"
  ["params"]=>
  array(1) {
    ["id"]=>
    object(Id)#4 (1) {
      ["value":protected]=>
      string(2) "42"
    }
  }
}

定义路由

通过在Router实例上调用addRoute()来添加路由

$r->addRoute($method, string $route, string $action, array $constraints = []);

$method是要匹配的特定路由的HTTP方法字符串。可以使用数组指定多个有效方法

// These two calls
$r->addRoute('GET', '/test', 'handler');
$r->addRoute('POST', '/test', 'handler');
// Are equivalent to this one call
$r->addRoute(['GET', 'POST'], '/test', 'handler');

Router使用语法,其中{foo}指定名为foo的占位符。路由中的每个占位符都必须是已定义的类型。从安全角度来看,在不正确验证您的领域数据的情况下接受用户数据(通过HTTP)是没有意义的。

假设我们有一个存储在数据库中的用户列表,并且可以通过自增正整数ID检索这些用户。在这种情况下,引入一个领域原语- ID是个好主意

<?php

use dzentota\TypedValue\Typed;
use dzentota\TypedValue\TypedValue;
use dzentota\TypedValue\ValidationResult;

class Id implements Typed
{
    use TypedValue;

    public static function validate($value): ValidationResult
    {
        $result = new ValidationResult();
        if (!is_numeric($value) || $value <= 0) {
            $result->addError('Bad ID');
        }
        return $result;
    }
}

现在您可以在路由的占位符中使用ID作为自定义类型

$r->get('/user/{id}', 'UserController@show', ['id' => Id::class])

{...?}中括起来的路由参数被视为可选的,因此/foo/{bar?}将匹配/foo/foo/bar

常见请求方法的快捷方式

对于GETPOSTPUTPATCHDELETEOPTIONSHEAD请求方法,都有快捷方式。例如

$r->get('/get-route', 'get_handler');
$r->post('/post-route', 'post_handler');

等同于

$r->addRoute('GET', '/get-route', 'get_handler');
$r->addRoute('POST', '/post-route', 'post_handler');

此外,还有一个虚拟的ANY方法,它匹配任何请求方法,因此

$r->addRoute('ANY', '/route', 'get_handler');

等同于

$r->addRoute(['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS', 'HEAD'], '/route', 'get_handler');

路由组

此外,您还可以在组内指定路由。组内定义的所有路由都将具有共同的前缀。

例如,将您的路由定义为

$r->addGroup('/admin', function (Router $r) {
    $r->addRoute('GET', '/do-something', 'handler');
    $r->addRoute('GET', '/do-another-thing', 'handler');
    $r->addRoute('GET', '/do-something-else', 'handler');
});

将产生与以下相同的结果

$r->addRoute('GET', '/admin/do-something', 'handler');
$r->addRoute('GET', '/admin/do-another-thing', 'handler');
$r->addRoute('GET', '/admin/do-something-else', 'handler');

缓存

您可以使用dump()导出并保存路由,以便以后可以使用load()加载它们 保存路由

file_put_contents('routes.php', sprintf('<?php return %s;',  var_export($r->dump(), true)));

恢复路由

$routes = require 'routes.php';
$r->load($routes);

关于HEAD请求的注意事项

HTTP规范要求服务器支持GET和HEAD方法

GET和HEAD方法必须由所有通用服务器支持

为了避免强制用户为每个资源手动注册HEAD路由,我们回退到匹配给定资源的可用GET路由。PHP Web SAPI透明地从HEAD响应中删除实体体,因此此行为对大多数用户没有影响。当然,您始终可以指定自定义处理程序来处理HEAD方法