Upgrade to Pro — share decks privately, control downloads, hide ads and more …

PSR-15 Request Handlerから理解するMiddlewareの仕組み

n1215
October 30, 2018

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
Tweet

More Decks by n1215

Other Decks in Programming

Transcript

  1. 今日は 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の議論の歴史 の話
  2. PSR PSR = PHP Standards Recommendations PHPを使う上でオススメの規約やインターフェースの集まり "Standards"とあるがPHPの公式ではない PHP‑FIG PHP‑FIG

    = PHP Framework Interoperability Group PSRを作っているグループ PHP製のフレームワーク・CMS・ツールの開発者が集まっている 共通の規約や仕様を定めて相互運用性を高める目的
  3. (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の補完。タグの一覧を提供
  4. (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
  5. (3) インターフェース (HTTP系/今回のお題) PSR‑7: HTTP message Interfaces PSR‑15: HTTP Server

    Request Handlers PSR‑17: HTTP Factories 他のインターフェースは個々で完結しているものが多いが、この3つはシリーズ物
  6. PHPでのHTTPメッセージの処理 <?php echo 'Hello ' . htmlspecialchars($_GET["name"]) . '!'; スーパーグローバル($_GET,

    $_POST...)に入った値からHTTPリクエストの値が取れる echoやheader()関数でHTTPレスポンスを組み立てる HTTPに詳しくなくても使えてとてもお手軽 グローバル変数……
  7. 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
  8. HTTPサーバアプリケーションの抽象化 interface RequestHandlerInterface { public function handle(ServerRequestInterface $request): ResponseInterface; }

    利用 $request = new PSR7Request(); $handler = new MyRequestHandler(); $response = $handler->handle($request);
  9. 身近な例 Server Request Handler ≒ MVC FWのControllerのアクション class MyController {

    public function hello(ServerRequestInterface $request) : ResponseInterface { return new JsonResponse(['hello' => 'world']); } }
  10. 実際はControllerのアクションは利便性重視な場合が多い $router->get('/hello/{name}', 'MyController::hello'); class MyController { public function hello(string $name):

    array { return ['hello' => $name]; } } (リクエスト →) ルートパラメータ → 配列 (→ JSONレスポンス) 暗黙的な変換でRequest Handler相当の処理が導出されている
  11. HTTP Server Middleware インターフェース interface MiddlewareInterface { public function process(

    ServerRequestInterface $request, RequestHandlerInterface $handler ): ResponseInterface; } Request Handlerを横断する共通処理を行う
  12. 1)メンテナンスモード public function process( ServerRequestInterface $request, RequestHandlerInterface $handler ): ResponseInterface

    { // 前処理 if ($this->isMaintenanceMode()) { return new Response(' メンテナンス中です', 503); } return $handler->handle($request); }
  13. 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; }
  14. 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; }
  15. class MyMiddleware implements MiddlewareInterface { public function process( SeverRequestInterface $request,

    RequestHandlerInterface $handler ): ResponseInterface { // 前処理 // ... // ハンドラがリクエストを処理 $response = $handler->handle($request); // 後処理 // ... return $response; } }
  16. 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; } }
  17. よく似ている class MyMiddleware implements MiddlewareInterface { public function process( SeverRequestInterface

    $request, RequestHandlerInterface $handler ): ResponseInterface { // 前処理 // ... // ハンドラがリクエストを処理 $response = $handler->handle($request); // 後処理 // ... return $response; } }
  18. コード 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);
  19. 合成が容易 Server Request Hadler + Middleware * N → Server

    Request Handler Middlewareを複数追加できる玉ねぎ型の構造
  20. ここまでのまとめ HTTPサーバアプリケーションやControllerアクションの抽象がServer Request Handler MiddlewareはServer Request Handler限定のInterceptor Server Request Hadler

    と Middleware を組み合わせてServer Request Handlerを再構成できる Request Handlerに影響を与えずに複数の処理を追加できる
  21. 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
  22. Server Request Handlersという名付け 第二引数のcallableや\Closureの入出力の型がわかりづらい callable $next → DelegateInterface $delegate Delegateは処理を任せるものという意味合い

    Middlewareを第一に考えた命名 そもそもDelegateInterfaceの命名が悪いのでは? MiddlewareがなくてもDelegateだけを扱える 単体でも存在できるのにDelegateという名前はふさわしくない DelegateがなければMiddlewareは存在しない 主従が逆では? → \Psr\Http\Server\RequestHandlerInterface $handler
  23. おまけ: PSR‑15 フレームワークを作ろう Middleware / Request Handler Dispatcher MiddlewareとRequest Handlerを組み合わせてRequest

    Handlerを作る Router Requestの内容に応じたRequest Handlerを呼び出す DI Container Request Handlerの組み立て補助 どれも既存の実装があるので、組み合わせるだけでも可 特にMiddleware Dispatcherを一度作ればPSR‑15が理解できる