请求派发模式:Dispatcher

大部分场景下,我们希望能够根据 URL,将请求分配给不同的“控制器”进行处理,即比较常见的路由模式Dispatcher 组件为这种模式提供了支持。

Dispatcher 组件是一个特殊的 Request Handler,它接受一个 Definition 作为每次请求的调用策略,然后你可以通过设置路由的方式,为不同请求绑定处理程序:

use ConstanzeStandard\Fluff\RequestHandler\Vargs;
use ConstanzeStandard\Fluff\RequestHandler\Dispatcher;
use Nyholm\Psr7\Response;

$core = new Dispatcher(Vargs::getDefinition());
$router = $core->getRouter();

$router->get('/user/{name}', function($request, $name) {
    return new Response(200, [], 'Hello '. $name);
});

你也可以将路由作为独立组件进行初始化,然后传入 Dispatcher 组件(Dispatcher 预留了路由接口,让你可以对路由组件进行替换)。

use ConstanzeStandard\Fluff\RequestHandler\Vargs;
use ConstanzeStandard\Fluff\RequestHandler\Dispatcher;
use ConstanzeStandard\Fluff\Routing\Router;
use Nyholm\Psr7\Response;

$router = new Router();
$router->get('/user/{name}', function($request, $name) {
    return new Response(200, [], 'Hello '. $name);
});

$core = new Dispatcher(Vargs::getDefinition(), $router);

现在我们已经构建了一个基于请求派发器(Dispatcher)的核心,接下来的操作与其他场景无异,只需要把核心组件传入 Application,然后分配 Request 实例即可。

use ConstanzeStandard\Fluff\Application;
use ConstanzeStandard\Fluff\Middleware\EndOutputBuffer;
use Nyholm\Psr7\ServerRequest;

$app = new Application($core);
$app->addMiddleware(new EndOutputBuffer());

$request = new ServerRequest('GET', '/user/Alex');
$app->handle($request);

路由组件

路由组件负责绑定请求与处理程序,然后向 Dispatcher 提供这些绑定关系,你可以通过调用 Dispatcher::getRouter 方法直接获取默认的路由组件。

use ConstanzeStandard\Fluff\Interfaces\RouterInterface;
use ConstanzeStandard\Fluff\RequestHandler\Vargs;
use ConstanzeStandard\Fluff\RequestHandler\Dispatcher;

$dispatcher = new Dispatcher(Vargs::getDefinition());
/** @var RouterInterface $router  */
$router = $dispatcher->getRouter();

添加一条路由信息

Router 提供了一个 add 方法和5个针对 HTTP Method 的辅助方法,用于添加路由信息:

/**
 * @param array|string          $methods 请求的 HTTP Method,可以指定一个或用数组指定多个
 * @param string                $pattern 请求的 URL 的匹配模式
 * @param \Closure|array|string $handler 请求的处理程序
 * @param MiddlewareInterface[] $middlewares 绑定的中间件
 * @param string|null           $name 路由名称
 * 
 * @return \ConstanzeStandard\Fluff\Interfaces\RouteInterface
 */
$router->add($methods, $pattern, $handler, $middlewares, $name);
$router->get($pattern, $handler, $middlewares, $name);
$router->post($pattern, $handler, $middlewares, $name);
$router->put($pattern, $handler, $middlewares, $name);
$router->delete($pattern, $handler, $middlewares, $name);
$router->options($pattern, $handler, $middlewares, $name);

其中 Router::add 方法的参数 $methods 可以使用字符串指定一个 HTTP Method,也可以用数组指定多个。

参数 $pattern 是 URL 的匹配模式,支持用花括号 {}标记一个 URL 参数,并传递给 $handler,并且可以对参数进行正则过滤,下面是一个示例,本例使用 Vargs 作为调用策略:

$router->get('/user/{id:\d+}', function($request, $id) {
    ...
});

上例中,我们指定了一个 URL 模式,在模式中,我们用花括号标记了 URL 参数 id,然后用冒号 (:)分隔,在后面指定了参数的验证规则 \d+,也就是,id 必须为数字。当比配成功后,参数将会被传入处理程序 $handler 中,由于我们本次选用了 Vargs 作为调用策略,所以 $handler 会接收到一个 Request 实例,和 URL 参数 id

如果你仔细阅读前面的章节,会发现,路由组件实际上是把需要手动传递给 Request Handler 的参数列表,通过 URL 参数的形式传递了

参数 $handlerRequest Handler 的处理程序是一样的,它的表现形式与你选用的 Definition 有关,也就是说,$handler 会继承绑定到 Dispatcher 上的 Request Handler 的特性。我们从上面的例子中已经看到,当绑定了 Vargs 的 Definition 时,$handler 也变成了顺序传参模式

同理,如果绑定的是 Delay 的 Definition,$handler 也可以像 Delay 组件一样支持 class@method 形式的参数了:

use ConstanzeStandard\Fluff\Application;
use ConstanzeStandard\Fluff\Middleware\EndOutputBuffer;
use ConstanzeStandard\Fluff\RequestHandler\Delay;
use ConstanzeStandard\Fluff\RequestHandler\Vargs;
use ConstanzeStandard\Fluff\RequestHandler\Dispatcher;
use Nyholm\Psr7\Response;
use Nyholm\Psr7\ServerRequest;

class Target
{
    public function index($request, $name)
    {
        return new Response(200, [], 'Hello '. $name);
    }
}

$strategy = function($className, $method) {
    return [new $className, $method];
};

$definition = Delay::getDefinition($strategy, Vargs::getDefinition());
$core = new Dispatcher($definition);

$router = $core->getRouter();
$router->get('/user/{id:\d+}', 'Target@index');  // 继承了 Delay 组件的特性

$app = new Application($core);
$app->addMiddleware(new EndOutputBuffer());

$app->handle(new ServerRequest('GET', '/user/12'));

参数 $middlewares 是针对一个路由的中间件列表,与全局中间件处于不同的层面,添加一个中间件也可以使用连贯方法 addMiddleware.

$router->get('/user/{id:\d+}', 'Target@index')->addMiddleware($middleware);

参数 $name 是本条路由的名称,你可以利用路由名称获取该路由所对应的 URL, 也可以使用连贯方法 setName 设置路由名称:

$router->get('/user/{id:\d+}', 'Target@index')->setName('user.id');

$routeService = $router->getRouteService();
$url = $routeService->urlFor('user.id', ['id' => 10], ['age' => 18]);
echo $url;  // /user/10?age=18

Delay 与 Dispatcher 的组合比较常见

Dispatcher 需要绑定多个处理程序(Handler),如果你使用类方法作为 Handler,那么,多个 PHP 类的初始化就会造成一定的资源浪费,这时,Delay 组件的作用就体现了出来,它可以将对象初始化的工作推迟到匹配成功以后进行,这在很大程度上缓解了系统压力,所以,Delay 与 Dispatcher 的组合是比较常见的,也符合我们熟知的大部分传统框架的做法。但你也许会选择 在路由中嵌入闭包 的形式,这在某些场景下也是比较实用的设计。

Fluff 微框架由 Alex 创建并维护 / Github home page

Copyright © 2019 Constanze Standard.