调度器 (Dispatcher)
调度器 (Dispatcher)
一般与路由中间件(RouteMiddleware)
和 处理器(Handler)
配合使用。
在执行过程中,调度器会从 Request 中获取上层传递的调度信息 (dispatch data)
并构建由调度信息提供的动态绑定的中间件,然后将处理器作为内核进行最后的处理。下图展示了各组件与 Dispatcher
的关系。
*------------*
| Middleware | *-------------*
| Builder |<-----| Router |
*------------* | Middleware |
| *-------------*
V ^
*------------* [dispatch data]
| Dispatcher | ^
*------------* |
| *-----------------*
V | Sub Middlewares |
*------------* *-----------------*
| Sub | |
| Middleware |<------------*
| Builder |
*------------*
|
V
*----------*
| Sub |
| Handler |
*----------*
调度器是一个特殊的处理器,也可以直接传入 Application
中作为内核使用,但与 Handler
不同的是,它并不直接处理业务逻辑,而是先解析调度信息(dispatcher data)
,构建处理链条,最后引用子处理器进行处理。而调度信息一般由路由中间件(RouteMiddleware)
提供。
调度器初始化的第一个参数是处理器的定义(definition)
, 它是一个 callable
对象,负责创建处理器的实例。上一章展示的 Handler
, DelayHandler
和 DiHandler
都包含了静态方法 getDefinition
用来获取自身的 definition
.
Dispatcher
会继承子处理器的特性,并且 RouterMiddleware
所接受的调用类型也与子处理器保持一致。
与 Handler 的组合
use ConstanzeStandard\Fluff\Application;
use ConstanzeStandard\Fluff\Middleware\EndOutputBuffer;
use ConstanzeStandard\Fluff\Middleware\RouterMiddleware;
use ConstanzeStandard\Fluff\RequestHandler\Dispatcher;
use ConstanzeStandard\Fluff\RequestHandler\Handler;
use Nyholm\Psr7\Response;
use Nyholm\Psr7\ServerRequest;
use Psr\Http\Message\ServerRequestInterface;
$dispatcher = new Dispatcher(Handler::getDefinition());
$app = new Application($dispatcher);
/** @var RouterMiddleware $router */
$router = $app->addMiddleware(new RouterMiddleware());
$app->addMiddleware(new EndOutputBuffer());
$router->get('/user/{name}', function(ServerRequestInterface $request, $args) {
return new Response(200, [], 'Hello ' . $args['name']);
});
$request = new ServerRequest('GET', '/user/World');
$app->handle($request);
RouterMiddleware
为 FLuff 提供了路由功能,我们使用 RouterMiddleware::get
辅助方法,注册一个 GET
请求的路由,它的第一个参数是 URL 规则,你可以使用 {参数名称}
的形式标记一个 URL 参数,这个参数会传入处理器的额外参数中,额外参数在 Handler
中是以数组的形式传入 callable
对象的。
与 DelayHandler 的组合
use ConstanzeStandard\Fluff\Application;
use ConstanzeStandard\Fluff\Middleware\EndOutputBuffer;
use ConstanzeStandard\Fluff\Middleware\RouterMiddleware;
use ConstanzeStandard\Fluff\RequestHandler\Dispatcher;
use ConstanzeStandard\Fluff\RequestHandler\DelayHandler;
use Nyholm\Psr7\Response;
use Nyholm\Psr7\ServerRequest;
use Psr\Http\Message\ServerRequestInterface;
class Target
{
public function __construct(string $say)
{
$this->say = $say;
}
public function index(ServerRequestInterface $request, array $args)
{
return new Response(200, [], 'Hello '. $args['name']);
}
}
$dispatcher = new Dispatcher(DelayHandler::getDefinition('Hello '));
$app = new Application($dispatcher);
/** @var RouterMiddleware $router */
$router = $app->addMiddleware(new RouterMiddleware());
$app->addMiddleware(new EndOutputBuffer());
$router->get('/say/{name}', 'Target@index');
$request = new ServerRequest('GET', '/say/World');
$app->handle($request);
DelayHandler::getDefinition
的构造方法接受任意数量的参数,它们将作为callable
对象初始化的参数传入。
使用 DelayHandler
作为子处理器,可以避免 Target
对象在路由匹配之前初始化,如果你的项目中有大量的 controller 需要通过路由进行连接,那么应该考虑使用 DelayHandler
(或下面的 DiHandler),因为它可以避免无意义的初始化消耗。
与 DiHandler 的组合
use Beige\Psr11\Container;
use ConstanzeStandard\Fluff\Application;
use ConstanzeStandard\Fluff\Middleware\EndOutputBuffer;
use ConstanzeStandard\Fluff\Middleware\RouterMiddleware;
use ConstanzeStandard\Fluff\RequestHandler\Dispatcher;
use ConstanzeStandard\Fluff\RequestHandler\DiHandler;
use Nyholm\Psr7\Response;
use Nyholm\Psr7\ServerRequest;
use Psr\Http\Message\ServerRequestInterface;
class Service
{
public function sayHello($name)
{
return 'Hello ' . $name;
}
}
class Target
{
public function index(Service $service, $name)
{
return new Response(200, [], $service->sayHello($name));
}
}
$container = new Container();
$container->set(Service::class, new Service());
$dispatcher = new Dispatcher(DiHandler::getDefinition($container));
$app = new Application($dispatcher);
/** @var RouterMiddleware $router */
$router = $app->addMiddleware(new RouterMiddleware());
$app->addMiddleware(new EndOutputBuffer());
$router->get('/say/{name}', 'Target@index');
$request = new ServerRequest('GET', '/say/World');
$app->handle($request);
DiHandler::getDefinition
方法接受一个 Psr\Container\ContainerInterface
容器的实例,依赖注入系统将从这个容器做依赖查找,并将找到的依赖对象注入 Target
对象中。与 DelayHandler
相似,DiHandler
同样具有延迟初始化的特性。
ServerRequest
对象在中间件中改变了,但我无法将它传入 Target
的方法中。
我的 这个问题的解决方案也是利用中间件,RouterMiddleware::addMiddleware
方法允许你再路由层面添加一个中间件,你只需定义一个中间件,然后将 ServerRequest
对象载入容器即可:
use Beige\Psr11\Container;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
class RequestCatchingMiddleware implements MiddlewareInterface
{
public function __construct(Container $container)
{
$this->container = $container;
}
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
$this->container->set(ServerRequestInterface::class, $request);
$this->container->set(get_class($request), $request);
return $handler->handle($request);
}
}
...
$router->addMiddleware(new RequestCatchingMiddleware($container));
中间件采用“后进先出”的执行模式,所以,RequestCatchingMiddleware
应该在路由构建之前添加。然后 Target::index
就可以通过依赖注入获取 ServerRequestInterface
了。
class Target
{
public function index(ServerRequestInterface $request, Service $service, $name)
{
return new Response(200, [], $service->sayHello($name));
}
}