weew/router

简单路由器。

v2.8.0 2016-07-27 05:08 UTC

README

Build Status Code Quality Test Coverage Version Licence

目录

安装

composer require weew/router

简介

路由器基本的功能是将 URL 与已注册的路由列表进行匹配,并在匹配成功时返回一个路由。如果没有匹配,将返回 null。路由可以包含任何你想要的内容,因为创建响应是基于路由的,这取决于你。这使你可以灵活地将路由器与任何其他现有的依赖注入容器或组件一起使用。路由器只做匹配 URL 与路由的工作。

注册路由

下面将看到用于路由注册的基本方法。路由路径可能包含一些用于预期值的占位符,例如 {id}。如果占位符以 ? 结尾,例如这里的 {alias?},则认为是可选的。作为第二个参数,你可以传递任何你想要的东西。稍后你将能够访问这个值,因此你可以创建一个响应或类似的内容。

$router = new Router();
$router->get('/', 'home')
    ->post('login', 'SomeController::handleLogin')
    ->put('users/{id}', function() {})
    ->patch('users/{id}', '')
    ->update('some/path/{optional?}', '')
    ->delete('users/{id}/{alias?}', '')
    ->options('you/wont/need/it', '');

你将主要使用这种定义格式来定义路由。

$router = new Router();
$router
    ->get('/', [SampleController::class, 'getHome'])
    ->get('about', [SampleController::class, 'getAbout'])
    ->get('contact', [SampleController::class, 'getContact']);

正如你在这个示例中看到的,你必须反复编写 SampleController 类。你可以通过在路由器本身上设置控制器类来避免这种情况。这样做将创建一个新的 嵌套路由器。下面的示例与上面的示例完全相同,只是你少了样板代码。

$router = new Router();
$router
    ->setController(SampleController::class)
        ->get('/', 'getHome')
        ->get('about', 'getAbout')
        ->get('contact', 'getContact');

路由参数

假设你定义了一些期望在 URL 中设置参数的路由,这里将展示如何访问它们。

$router = new Router();
$router->get('home/{greeting?}', 'home');

$route = $router->match(HttpRequestMethod::GET, new Url('home/welcome'));
echo $route->getParameter('greeting');
// welcome

匹配路由

到目前为止,你已经注册了所有路由,现在想要找到匹配指定 URL 的路由。

$router = new Router();
$router->get('home/{greeting?}', 'home');
$route = $router->match(HttpRequestMethod::GET, new Url('home/hello-there'));

if ( ! $route === null) {
    echo $route->getAction();
    // home
    echo $route->getParameter('greeting');
    // hello-there
} else {
    // no route found, thow a 404?
}

自定义模式

在某些情况下,你可能想要为路由参数指定自定义模式。例如,假设你希望你的 ID 仅由数字字符组成。

$router = new Router();
$router
    ->addPattern('id', '[0-9]+')
    ->get('users/{id}', '');

防火墙

使用自定义过滤器保护路由非常简单。一般来说,如果单个过滤器失败(返回 false 或抛出异常),则不会调用其他过滤器。同样,反之亦然。如果过滤器明确表示一切正常(返回 true),则不会调用其他过滤器。

$router = new Router();
$router->addFilter('auth', function(IRoute $route) {
    // returning false indicates that filter has failed, no other filters will be called
    return false;
});

$router->addFilter('guest', function(IRoute $route) {
    // explicitly returning true indicates that this route is ok, no other filters will be called
    return true;
});

$router->enableFilter('auth');
$router->enableFilter('guest');

// or

 $router->enableFilter(['auth', 'guest']);

过滤器必须返回一个布尔值来指示受影响的路由是否可以继续或应被忽略。过滤器与组一起使用效果最佳,请参见以下内容。

有时你可能想要抛出一个包含过滤器未通过原因的异常。如果你简单地抛出一个常规异常,这将终止程序流程,即使你在某处捕获了这个异常,也会终止整个路由过程。即使某个特定的路由没有匹配,因为过滤器失败,可能还有另一个匹配。在抛出常规异常之后,没有其他路由可能匹配。为了解决这个问题,你可以简单地使用 FilterException 包装你的异常。这将确保路由过程按照预期完成,并给另一个路由匹配的机会。如果最终没有找到路由,你的原始异常将被抛出。

$router = new Router();
$router->addFilter('auth', function(IRoute $route) {
    throw new FilterException(
        new UnauthorizedException()
    );
});

现在,过滤器失败不会中断路由过程。如果所有路由都匹配失败,将不会抛出异常。但如果没有任何其他路由可以替代(取而代之),将会抛出UnauthorizedException

参数解析器

通常你可能想要处理路由参数,并用另一个参数替换它。例如,当你使用模型时。这个路由/users/{id}将始终包含请求用户的id。如果它包含用户模型而不是id,那岂不是很好吗?

$router = new Router();
$router->addResolver('user', function($parameter) {
    return new User(); // for the sake of the example lets just return a new model
});

$router->get('users/{user}', function(User $user) {
    // work with the user model
});

用户的id神奇地解析成了它的模型。现在你可以在你的路由处理程序中使用它。

规则

你可能还想根据当前URL指定额外的路由限制。例如,限制你的路由仅限于子域名或协议。

$router = new Router();
$router
    ->restrictSubdomain('api')
    ->get('users/{id}', '');

还有很多其他你可能发现有用的限制。

$router = new Router();
$router->restrictProtocol('https')
    ->restrictDomain(['domain1', 'domain2'])
    ->restrictTLD(['com', 'net'])
    ->restrictSubdomain('api')
    ->restrictHost(['domain1.com', 'domain2.net'])

分组路由

有时你的路由之间有一个明显的边界。比如说,你想让你的API路由只在api子域名和https下可用。其他所有路由都应该保持不变。

$router = new Router();
$router->get('/', 'home');
$router->group(function(IRouter $router) {
    $router->restrictProtocol('https')
        ->restrictSubdomain('api')
        ->get('users/{id}', '');
});
$router->get('about', 'about');

完整示例

这是使用路由器的一个完整示例。路由器本身非常灵活,最终它取决于你如何使用它。基本上它只是返回一个路由。之后,如何处理它就取决于你了。你可能动态解析控制器,或者甚至将它与依赖注入容器结合使用。

$router = new Router();

$router->get('/', 'home');
$router->get('about', 'about');
$router->post('login', 'login');

$router->addFilter('auth', function(IRoute $route) {
    return fasle; // not logged in
});

$router->addResolver('user', function($id) {
    return new User($id);
});

$router->group(function(IRouter $router) {
    $router->setPrefix('api/v1');
    $router->restrictProtocol('https');
    $router->restrictSubdomain('api');
    $router->enableFilter('auth');

    $router->get('users/{user}/{alias?}', function(IRoute $route) {
        $response = new JsonResponse(HttpRequestMethod::GET, [
            'id' => $route->getParameter('user')->id,
            'alias' => $route->getParameter('alias', 'no alias')
        ]);
        $response->send();
    });
});

$router->group(function(IRouter $router) {
    $router->setPrefix('api/v2');
    $router->addPattern('id', '[a-zA-Z]+');

    $router->get('users/{user}/{alias?}', function(IRoute $route) {
        $response = new JsonResponse(HttpRequestMethod::GET, [
            'id' => $route->getParameter('user')->id,
            'alias' => $route->getParameter('alias', 'no alias')
        ]);
        $response->send();
    });
});

// recommended way to work with the request
$request = new CurrentRequest();
$route = $router->match($request->getMethod(), $request->getUrl());

// create a response based on the route
if ($route instanceof IRoute) {
    $abstract = $route->getAction();

    if (is_callable($abstract)) {
        $abstract($route);
    } else {
        echo $abstract;
    }
} else {
    echo '404';
}

扩展

有几种可用的扩展