Share
Embed
PSR-15 Request Handlerから理解するMiddlewareの仕組み
PHP Meetup Osaka 2018.10の発表資料です。
PSR-15 Server Request HandlersとServer Middlewareについての解説。
PSR-15: https://www.php-fig.org/psr/psr-15/
n1215
October 30, 2018
Transcript
58 slides
-
今日は PSR‑15 HTTP Server Request Handlers の話をします。 内容 1. PSR
の話 2. PSR‑7 HTTP Message の話 3. PSR‑15 HTTP Server Request Handlers の話 4. PSR‑15 HTTP Server Middleware の話 5. PSR‑15の議論の歴史 の話 -
PSR PSR = PHP Standards Recommendations PHPを使う上でオススメの規約やインターフェースの集まり "Standards"とあるがPHPの公式ではない PHP‑FIG PHP‑FIG
= PHP Framework Interoperability Group PSRを作っているグループ PHP製のフレームワーク・CMS・ツールの開発者が集まっている 共通の規約や仕様を定めて相互運用性を高める目的 -
PSRの分類 (1) オートローディング PSR‑4: Autoloader 名前空間とディレクトリ構造を対応させる規約 Composerを使っていれば恩恵に預かっていることも多いはず Deprecated PSR‑0: Autoloading
Standard (Deprecated) PSR‑4の前身 -
(2) コーディングスタイル PSR‑1: Basic Coding Standard PSR‑2: Coding Style Guide
Draft PSR‑5: PHPDoc Standard PHPDocコメントの書き方の標準 PSR‑12: Extended Coding Style Guide PSR‑1/PSR‑2でカバーしきれない最近のPHPの機能に対応 PSR‑19: PHPDoc tags PSR‑5の補完。タグの一覧を提供 -
(3) インターフェース (今回は触れないもの) PSR‑3: Logger Interface PSR‑6: Caching Interface PSR‑11:
Container Interface PSR‑13: Link definition interfaces PSR‑16: Common Interface for Caching Libraries PSR‑18: HTTP Client Interfaces Draft PSR‑14: Event Dispatcher Interafaces -
(3) インターフェース (HTTP系/今回のお題) PSR‑7: HTTP message Interfaces PSR‑15: HTTP Server
Request Handlers PSR‑17: HTTP Factories 他のインターフェースは個々で完結しているものが多いが、この3つはシリーズ物 -
HTTPサーバアプリケーション 入力:HTTPリクエスト POST /path HTTP/1.1 Host: example.com this=is&request=body 出力:HTTPレスポンス HTTP/1.1
200 OK Content-Type: text/plain This is the response body -
PHPでのHTTPメッセージの処理 <?php echo 'Hello ' . htmlspecialchars($_GET["name"]) . '!'; スーパーグローバル($_GET,
$_POST...)に入った値からHTTPリクエストの値が取れる echoやheader()関数でHTTPレスポンスを組み立てる HTTPに詳しくなくても使えてとてもお手軽 グローバル変数…… -
PSR‑7の特徴 イミュータブルなオブジェクト (≒ 状態を変更するsetterを持たない) $request = $request ->withMethod('GET') ->withUri(new Uri('https://example.com/'));
bodyがStreamとして表現される public function withBody(StreamInterface $body); HTTPクライアント側とHTTPサーバ側に両対応 interface ServerRequestInterface extends RequestInterface -
HTTPサーバアプリケーションの抽象化 interface RequestHandlerInterface { public function handle(ServerRequestInterface $request): ResponseInterface; }
利用 $request = new PSR7Request(); $handler = new MyRequestHandler(); $response = $handler->handle($request); -
身近な例 Server Request Handler ≒ MVC FWのControllerのアクション class MyController {
public function hello(ServerRequestInterface $request) : ResponseInterface { return new JsonResponse(['hello' => 'world']); } } -
実際はControllerのアクションは利便性重視な場合が多い $router->get('/hello/{name}', 'MyController::hello'); class MyController { public function hello(string $name):
array { return ['hello' => $name]; } } (リクエスト →) ルートパラメータ → 配列 (→ JSONレスポンス) 暗黙的な変換でRequest Handler相当の処理が導出されている -
HTTP Server Middleware インターフェース interface MiddlewareInterface { public function process(
ServerRequestInterface $request, RequestHandlerInterface $handler ): ResponseInterface; } Request Handlerを横断する共通処理を行う -
1)メンテナンスモード public function process( ServerRequestInterface $request, RequestHandlerInterface $handler ): ResponseInterface
{ // 前処理 if ($this->isMaintenanceMode()) { return new Response(' メンテナンス中です', 503); } return $handler->handle($request); } -
2) レスポンスに共通ヘッダ付与 public function process( ServerRequestInterface $request, RequestHandlerInterface $handler ):
ResponseInterface { $response = $handler->handle($request); // 後処理 $modifiedResponse = $response ->withHeader('X-Powered-By: OreoreFramework/0.1') ->withHeader('X-XSS-Protection', '1; mode=block'); return $modifiedResponse; } -
3) ベンチマーカー RequestHandlerの処理時間をログに書き込む public function process( ServerRequestInterface $request, RequestHandlerInterface $handler
): ResponseInterface { // 前処理 $start = microtime(true); $response = $handler->handle($request); // 後処理 $time = microtime(true) - $start; $ms = number_format($time * 1000, 1); $message = "processed in {$ms} ms."; $this->logger->info($message); return $response; } -
class MyMiddleware implements MiddlewareInterface { public function process( SeverRequestInterface $request,
RequestHandlerInterface $handler ): ResponseInterface { // 前処理 // ... // ハンドラがリクエストを処理 $response = $handler->handle($request); // 後処理 // ... return $response; } } -
Interceptorの実装例 一般的なAOPで扱われる処理はより汎用的なもの 参考:Ray.Aop のメソッドインターセプター class MyInterceptor implements MethodInterceptor { public
function invoke(MethodInvocation $invocation) { // Before method invocation // ... // Method invocation $result = invocation->proceed(); // After method invocation // ... return $result; } } -
よく似ている class MyMiddleware implements MiddlewareInterface { public function process( SeverRequestInterface
$request, RequestHandlerInterface $handler ): ResponseInterface { // 前処理 // ... // ハンドラがリクエストを処理 $response = $handler->handle($request); // 後処理 // ... return $response; } } -
コード Server Request Hadler + Middleware → Server Request Handler
class WrappedHandler implements RequestHandlerInterface { public function __construct( MiddlewareInterface $middleware, RequestHandlerInterface $handler ) { $this->middleware = $middleware; $this->handler = $handler; } public function handle( ServerRequestInterface $request ): ResponseInterface { return $middleware->process($request, $this->handler); } } $newHandler = new WrappedHandler($middleware, $handler); $response = $newHandler->handle($request); -
合成が容易 Server Request Hadler + Middleware * N → Server
Request Handler Middlewareを複数追加できる玉ねぎ型の構造 -
ここまでのまとめ HTTPサーバアプリケーションやControllerアクションの抽象がServer Request Handler MiddlewareはServer Request Handler限定のInterceptor Server Request Hadler
と Middleware を組み合わせてServer Request Handlerを再構成できる Request Handlerに影響を与えずに複数の処理を追加できる -
interface SinglePassMiddleware { public function handle( Request $request, callable $next
): Response; } interface DoublePassMiddleware { public function handle( Request $request, Response $response, callable $next ): Response; } HTTPレスポンスを引数に取るシグネチャもある=ダブルパス方式 PSR‑15が採用したのはシングルパス方式 PSR‑15では、ファクトリを使うレスポンスの新規生成を推奨 → PSR‑17 HTTP Factories -
Server Request Handlersという名付け 第二引数のcallableや\Closureの入出力の型がわかりづらい callable $next → DelegateInterface $delegate Delegateは処理を任せるものという意味合い
Middlewareを第一に考えた命名 そもそもDelegateInterfaceの命名が悪いのでは? MiddlewareがなくてもDelegateだけを扱える 単体でも存在できるのにDelegateという名前はふさわしくない DelegateがなければMiddlewareは存在しない 主従が逆では? → \Psr\Http\Server\RequestHandlerInterface $handler -
おまけ: PSR‑15 フレームワークを作ろう Middleware / Request Handler Dispatcher MiddlewareとRequest Handlerを組み合わせてRequest
Handlerを作る Router Requestの内容に応じたRequest Handlerを呼び出す DI Container Request Handlerの組み立て補助 どれも既存の実装があるので、組み合わせるだけでも可 特にMiddleware Dispatcherを一度作ればPSR‑15が理解できる -
まとめ PSR‑15の歴史はMiddlewareの話から始まった PSR‑15の真の主役 Server Request Handlerは遅れて登場した 新規向けにはServer Request Handlerからのほうが理解しやすい 実用上はMiddlewareとServer
Request Handlerの合成がキモ PSR‑15フレームワークを作るのは楽しいですよ