[PSR-15] RequestHandler内でMiddlewareを使う
こんにちは、ナカエです。
最近、趣味の開発ではコントローラのアクション相当のクラスとして、PSR-15のRequestHandlerInterface実装を使うことが多いです。
<?php
namespace Psr\Http\Server;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
interface RequestHandlerInterface
{
/**
* Handle the request and return a response.
*/
public function handle(ServerRequestInterface $request): ResponseInterface;
}
そんな中で、特定のRequestHandlerに対してだけMiddlewareを適用したいケースがありました。
多くのRouterではルーティング時に特定のルートだけに個別にMiddlewareを設定することも可能です。 しかし、今回は設定が散らばることを嫌い、RequestHandler内でそのMiddlewareを使っていることがすぐ分かるようにしたいという要求があります。
実装
RequestHandler内でMiddlewareをDispatchすることにしました。
<??php
namespace Psr\Http\Server;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
/**
* An HTTP middleware component participates in processing an HTTP message,
* either by acting on the request or the response. This interface defines the
* methods required to use the middleware.
*/
interface MiddlewareInterface
{
/**
* Process an incoming server request and return a response, optionally delegating
* response creation to a handler.
*/
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface;
}
PSR-15 Middlewareのインターフェースは上記の通りなので、内部で匿名クラスを使い、もう一つRequestHandlerを作っています。 「RequestHandlerとMiddlewareを合わせると再びRequestHandlerになる」というMiddlewareシステムの玉ねぎ的な構造を利用しただけとも言えます。
<??php
declare(strict_types=1);
namespace App\Handlers;
use App\Middleware\MyMiddleware;
use Psr\Http\Server\RequestHandlerInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
/**
* Class MiddlewareWrappedHandler
* @package App\Handlers
*/
class MiddlewareWrappedHandler implements RequestHandlerInterface
{
/**
* @var MyMiddleware (MiddlewareInterfaceの実装)
*/
private $middleware;
public function __construct(MyMiddleware $middleware)
{
$this->middleware = $middleware;
}
public function handle(ServerRequestInterface $request): ResponseInterface
{
$callable = function (ServerRequestInterface $request): ResponseInterface {
return $this->innerHandle($request);
};
return $this->middleware->process($request, new class($callable) implements RequestHandlerInterface {
private $callable;
public function __construct(callable $callable)
{
$this->callable = $callable;
}
public function handle(ServerRequestInterface $request): ResponseInterface
{
return \call_user_func($this->callable, $request);
}
});
}
private function innerHandle(ServerRequestInterface $request): ResponseInterface
{
// 本来の処理
}
}
若干コードが長いので、abstractクラスにして継承して使う方が良いかもしれません。