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

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

Df4978f14401325586e9e286b140ac4c?s=47 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/

Df4978f14401325586e9e286b140ac4c?s=128

n1215

October 30, 2018
Tweet

Transcript

  1. For PHP Meetup Osaka 2018.10 PSR‑15 Request Handlerから理解するMiddlewareの仕組み 2018/10/30 株式会社

    Nextat 中榮健二
  2. 自己紹介 中榮健二 (なかえけんじ) 株式会社Nextat 取締役 twitter: @n_1215 普段はLaravelを使ったソシャゲ開発などをやっています baserCMS コアコミッター

    最近ext‑asyncが気になる
  3. 今日は 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の議論の歴史 の話
  4. 1. PSRの話

  5. PSR PSR = PHP Standards Recommendations PHPを使う上でオススメの規約やインターフェースの集まり "Standards"とあるがPHPの公式ではない PHP‑FIG PHP‑FIG

    = PHP Framework Interoperability Group PSRを作っているグループ PHP製のフレームワーク・CMS・ツールの開発者が集まっている 共通の規約や仕様を定めて相互運用性を高める目的
  6. None
  7. PSRのメリット(個人の感想です) プロジェクトのコーディング規約を作るための雛形として有用 今後PSRに準拠するライブラリやFWが増えそうなので、ユーザーとして知っておいて損はない 自分で似たような仕組みを作るときにも参考になる PHP‑FIGのGoogle Groupの議論が興味深い

  8. PSRの分類 (1) オートローディング PSR‑4: Autoloader 名前空間とディレクトリ構造を対応させる規約 Composerを使っていれば恩恵に預かっていることも多いはず Deprecated PSR‑0: Autoloading

    Standard (Deprecated) PSR‑4の前身
  9. (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の補完。タグの一覧を提供
  10. (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
  11. (3) インターフェース (HTTP系/今回のお題) PSR‑7: HTTP message Interfaces PSR‑15: HTTP Server

    Request Handlers PSR‑17: HTTP Factories 他のインターフェースは個々で完結しているものが多いが、この3つはシリーズ物
  12. 2. PSR‑7 HTTP messageの話

  13. HTTPサーバアプリケーション

  14. 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
  15. 最近のブラウザは簡単にHTTPメッセージの中身が見られる

  16. PHPでのHTTPメッセージの処理 <?php echo 'Hello ' . htmlspecialchars($_GET["name"]) . '!'; スーパーグローバル($_GET,

    $_POST...)に入った値からHTTPリクエストの値が取れる echoやheader()関数でHTTPレスポンスを組み立てる HTTPに詳しくなくても使えてとてもお手軽 グローバル変数……
  17. HTTPメッセージ実装乱立の時代 HTTPリクエストとHTTPレスポンスのクラスを作って抽象化したい HTTPメッセージの実装がFW・プロジェクトごとに乱立していた

  18. PSR‑7: HTTPメッセージの標準化

  19. 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
  20. PSR‑7の採用例 Guzzle Slim3 CakePHP3 zend‑expressive ReactPHP

  21. Symfony HTTP Foundation 一番勢力の強い[要出典] HTTPメッセージ実装 Symfonyのコンポーネント Laravelなどで利用(illuminate/http) 今後はPSR‑7と二分する感じになりそう? PSR‑7互換のためのブリッジもある https://symfony.com/doc/current/components/psr7.html

    以降の話は、HTTP Foundationや他の実装に置き換えても事情は同じ
  22. 3. PSR‑15 HTTP Server Request Handlers の話

  23. HTTPサーバアプリケーション 入力:HTTPリクエスト 出力:HTTPレスポンス

  24. HTTPサーバアプリケーションの抽象化 interface RequestHandlerInterface { public function handle(ServerRequestInterface $request): ResponseInterface; }

    利用 $request = new PSR7Request(); $handler = new MyRequestHandler(); $response = $handler->handle($request);
  25. なるほどいまいちわからん

  26. 身近な例 Server Request Handler ≒ MVC FWのControllerのアクション class MyController {

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

    array { return ['hello' => $name]; } } (リクエスト →) ルートパラメータ → 配列 (→ JSONレスポンス) 暗黙的な変換でRequest Handler相当の処理が導出されている
  28. 4. PSR‑15 HTTP Server Middleware の話

  29. HTTP Server Middleware インターフェース interface MiddlewareInterface { public function process(

    ServerRequestInterface $request, RequestHandlerInterface $handler ): ResponseInterface; } Request Handlerを横断する共通処理を行う
  30. PSR‑15より前の実装はこういうシグネチャが多かった class MyMiddleware { public function handle( Request $request, callable

    $next ): Response; }
  31. なるほど全然わからん

  32. 具体例をそれっぽいコードで

  33. 1)メンテナンスモード public function process( ServerRequestInterface $request, RequestHandlerInterface $handler ): ResponseInterface

    { // 前処理 if ($this->isMaintenanceMode()) { return new Response(' メンテナンス中です', 503); } return $handler->handle($request); }
  34. 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; }
  35. 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; }
  36. 他にもいろいろ 認証 Cookie CSRFチェック 参考: middlwares/psr15‑middleares

  37. class MyMiddleware implements MiddlewareInterface { public function process( SeverRequestInterface $request,

    RequestHandlerInterface $handler ): ResponseInterface { // 前処理 // ... // ハンドラがリクエストを処理 $response = $handler->handle($request); // 後処理 // ... return $response; } }
  38. イメージ図

  39. 早期リターンの場合

  40. Interceptorパターン 処理が呼び出される前後に割り込み、制御を奪って別の処理を行うパターン https://en.wikipedia.org/wiki/Interceptor_pattern Aspected Oriented Programming MiddlewareはServer Request Handlerの処理に対して限定的なAOPの機能を提供

  41. 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; } }
  42. よく似ている class MyMiddleware implements MiddlewareInterface { public function process( SeverRequestInterface

    $request, RequestHandlerInterface $handler ): ResponseInterface { // 前処理 // ... // ハンドラがリクエストを処理 $response = $handler->handle($request); // 後処理 // ... return $response; } }
  43. 入出力に注目する

  44. コード 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);
  45. 合成が容易 Server Request Hadler + Middleware * N → Server

    Request Handler Middlewareを複数追加できる玉ねぎ型の構造
  46. 注: クリーンアーキテクチャとは別です 図は玉ねぎ型ですが

  47. ここまでのまとめ HTTPサーバアプリケーションやControllerアクションの抽象がServer Request Handler MiddlewareはServer Request Handler限定のInterceptor Server Request Hadler

    と Middleware を組み合わせてServer Request Handlerを再構成できる Request Handlerに影響を与えずに複数の処理を追加できる
  48. 5. PSR‑15の議論の歴史の話

  49. Middlewareありき ここまでの説明と、PSR‑15の議論の経緯は逆になる もともとMiddlewareの互換性を目指してPSR‑15が立ち上がった 各プロジェクトのMiddleware実装が先行していた Request Handlerという概念はまだ見出されていなかった

  50. 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
  51. Server Request Handlersという名付け 第二引数のcallableや\Closureの入出力の型がわかりづらい callable $next → DelegateInterface $delegate Delegateは処理を任せるものという意味合い

    Middlewareを第一に考えた命名 そもそもDelegateInterfaceの命名が悪いのでは? MiddlewareがなくてもDelegateだけを扱える 単体でも存在できるのにDelegateという名前はふさわしくない DelegateがなければMiddlewareは存在しない 主従が逆では? → \Psr\Http\Server\RequestHandlerInterface $handler
  52. パッケージが2つに分割された psr/http‑server‑handler psr/http‑server‑middleware 依存関係 psr/http‑server‑handlerはpsr/http‑messageに依存 psr/http‑server‑middlewareはpsr/http‑server‑handlerに依存

  53. タイトルも変更 PSR‑15 HTTP Middleware ↓ PSR‑15 Server Request Handlers

  54. おまけ: PSR‑15 フレームワークを作ろう Middleware / Request Handler Dispatcher MiddlewareとRequest Handlerを組み合わせてRequest

    Handlerを作る Router Requestの内容に応じたRequest Handlerを呼び出す DI Container Request Handlerの組み立て補助 どれも既存の実装があるので、組み合わせるだけでも可 特にMiddleware Dispatcherを一度作ればPSR‑15が理解できる
  55. None
  56. → 簡単なの作った。 https://github.com/n1215/tsukuyomi https://github.com/n1215/jugoya (Middlewareディスパッチャ)

  57. まとめ PSR‑15の歴史はMiddlewareの話から始まった PSR‑15の真の主役 Server Request Handlerは遅れて登場した 新規向けにはServer Request Handlerからのほうが理解しやすい 実用上はMiddlewareとServer

    Request Handlerの合成がキモ PSR‑15フレームワークを作るのは楽しいですよ
  58. ご静聴ありがとうございました ご質問があればどうぞ PR 毎週水曜1830~ 京都でもくもく会を開催中 https://nextat.connpass.com/