Technote

by sizuhiko

PSR-17 HTTP Factories のライブラリ

令和最初の記事も、PHP。 この記事はPSR-15リクエストハンドラーのライブラリ比較の続編です。

前回のサンプルプログラムでは zendframework/zend-diactorosHTTP Message Factories を使って PSR-7のHTTPレスポンス(Psr\Http\Message\ResponseInterface)を作っていました。

今回は、他のライブラリを使っても大丈夫か考えてみたいと思います。

PHP-17 の HTTP Factories に対応したライブラリ

PackagistでPSR-17を検索してみました。 PSR-15ほどはないようです。ということで独断と偏見(ダウンロード数が多いもの)を選んで比較してみたいと思います。 なお「フレームワーク」と書いてあるものや、特定のPSR-7ライブラリ だけ に依存したもの(たとえばhttp-factory-guzzleなど)は除外しています。

それぞれのライブラリの説明を読んで気づくでしょう。

PSR-17 HTTP-Factory with auto-discovery support

そう先ほど「特定のPSR-7ライブラリ だけ に依存したもの」と書きましたが、実は特定のライブラリに依存します。 それはそうですよね。PSR-7のHTTPレスポンスインターフェースの実装インスタンスを 生成 しないといけないので、何かしらに依存します。

ただ、こういった auto-discovery してくれるライブラリを使っておくと、PSR-7のライブラリを変更したときも影響が少ないですね。 でもそれ、さっきのサンプルで DI してたから、それで良くない?というのは、まさにそのとおりです。

で、結局どうすると良いの?

  • PSR-7/PSR-11/PSR-15/PSR-17 を使うアーキテクチャにするなら、FactoryInterfaceをDIすれば影響範囲は設定箇所だけになります
  • PSR-7/PSR-15/PSR-17 を使う(PSR-11を使わない)アーキテクチャにするなら、上記のような PSR-7 を自動判定してくれるライブラリを使うと良い

ということになると思います。

それならPSR-7のライブラリも使い分けたサンプルが見たかった!とかあるかもしれませんが、それはまた今度!(機会があれば)

おまけ

さて、PSR-7/PSR-15/PSR-17 の話は面白かったですか? これに加えて、フロントエンドも含めてフレームワークに依存しないアプリケーションとは? みたいな話をPHPカンファレンス福岡2019でします。 午後一のセッション。また @soudai1025 の裏になりましたが、私の方がマニアックな話なのかな。

では6月に福岡でお会いしましょう!

PSR-15リクエストハンドラーのライブラリ比較

平成最後の記事は、PHP。 久々のPHP記事ですが、PSR-15に準拠したリクエストハンドラーライブラリの実装を比較してみようと思います。

PSR-15とは?

HTTPメッセージを使用するリクエストハンドラーと、ミドルウェアコンポーネントの共通インターフェースです。

それPSR-7なのでは?という鋭い人は、 @tanakahisateruさんのスライド PHP-FIGのHTTP処理標準の設計はなぜPSR-7/15/17になったのかを参照ください。

PSR-7については、だいぶ知れ渡っていると思うので、PSR-15のリクエストハンドラーに着目していきます。

PHP-15のリクエストハンドラーに対応したライブラリ

PackagistでPSR-15を検索してみました。 たくさんありますね。ということで独断と偏見(ダウンロード数が多いもの)を選んで比較してみたいと思います。 なお「フレームワーク」と書いてあるものは除外しています。 例えば zend-expressiveminimalist PSR-7 middleware framework for PHP と書いてあるとおりなので。 Slimなどのマイクロフレームワークも同様です。

の4つを使ってサンプルプログラム集を作ってみました。

sizuhiko/psr15-requesthandler-examples

PSR-7

まず前提事項として、PSR-7対応のライブラリが必要です。 本サンプルでは、主に zendframework/zend-diactoros を使っています。 sunrise/http-routerの例ついては、同系統にsunrise/http-messagesunrise-php/http-server-requestがあるので、それを利用しています。 PSR-7のライブラリが違うと、どのような記述の違いがあるのかわかりやすいでしょう。

サンプルの内容

サンプルプログラム集clone するか、ダウンロードして、READMEの手順に従ってください。

ドキュメントルート(/)にアクセスすると、Hello, World! 出すだけの簡単なものです。

サンプルコードの流れ

で、結局PSR-15とは?みたいな話になるわけですが、どの例も以下のような流れで動いています

  • PSR-7 でHTTPリクエスト(Psr\Http\Message\RequestInterface)を受け取る
  • PSR-15の Psr\Http\Server\MiddlewareInterfacePsr\Http\Server\RequestHandlerInterface でいい感じに PSR-7のHTTPレスポンス(Psr\Http\Message\ResponseInterface) を生成する。
  • PSR-7 のHTTPレスポンスをエミットする

つまり、真ん中の部分を処理することになります。

リクエストハンドラ

サンプルのsrcフォルダの下には

  • Middlewares
  • RequestHandlers

があり、それぞれ Psr\Http\Server\MiddlewareInterfacePsr\Http\Server\RequestHandlerInterface を実装したクラスが置いてあります。 で、それぞれのリクエストハンドラーからは、適したハンドラが呼び出されるようになっています。

このサンプルでは PSR-17 (HTTP Factories) を先行導入し、PSR-11 (Container Interface) に対応したライブラリでDIしてますが、 そのあたりは、次回以降で解説します。

ミドルウエアもリクエストハンドラも同じようにHello, World!をレスポンスボディに入れるだけです。

このサンプルをとおして言いたかったこと

このサンプルでは、ドキュメントルートだけの簡単な実装でしたが、ルーターの設定を追加するだけで、 実際のアプリケーションでも使えるようになることがわかると思います。

もちろん実際にはDBにアクセスする必要もあったりするわけですが、そこにはPHP Data Objects(PDO)があり、 実際のDBの差分を吸収してくれます。 そこで利用するSQLは標準であり、Webの標準よりも長い期間利用されています。

フレームワークを利用してアプリケーションを作ることは、速く簡単に構築できます。 しかしフレームワークに依存せずPHPの標準を使うことで、柔軟にライブラリを変えても、自分のビジネスロジックを流用することができるようになります。

もちろん今日のフレームワークの多くが PSR 対応を進めていますので、フレームワークを使っていても、より互換性が高いアプリケーションを作ることができるようになるかもしれません。

次回はPSR-17について、いくつかのライブラリを紹介します。

アスカン2019を開催しました

明日の開発カンファレンス2019の中の人として運営参加しました。

私はプログラム枠1つを決めたり、事前準備をお手伝いしたり、当日雑用したりと、まぁ大したことはしてません(汗

私の渾身のオススメ枠、ということで今回は弊社岡島から「総売り上げ:35,400円 ~ 受託エンジニアが自社サービスのPOをやって学んだこと。」という発表をしてもらいました。 直前のセッションが、ヴェルク株式会社 田向 祐介さまの「受託開発の会社が自社サービスを立ち上げて軌道に乗せるまでの取り組み」だったこともあり、受託開発会社のサービス開発成功事例からの、失敗事例ということで、プログラムの流れも良かったと思います。

また今回のアスカンでは、はじめての2トラックということで、たくさんの事例をご紹介することができたと思います。

当日の様子は、Togetterにまとまっています。

@akiko_pusuさん、@NEKOGETさんが、グラレコを投稿してくれていますので、よりトーク内容含めてふりかえってもらえると思います。

昨年は秋にも開催したアスカンですが、今年はどうなるでしょうか?

そうそう、最後にアンケートもありますので、参加したみなさま、スピーカーやスタッフのためにも回答をお願いしたします。

Toilet EvolutionのフロントエンドをPolymer3対応する(4)

この記事はToilet EvolutionのフロントエンドをPolymer3対応する(3)の続編です。

前回までの対応で、ページをロードしたときに、ブラウザのコンソールにエラーが出ることが少なくなりました。 ただし多くのページで表示が崩れていたり、うまく表示できないエレメントがあります。 これは今回の ES Modules に対応してなかったエレメントに関連するところだったのですが、これらの対応を解説していきます。

dom-module has style outside template というワーニングが出る

ブラウザのコンソールを確認していると dom-module has style outside template といったワーニングが表示されます。 これは <dom-module> の中で <template><style> が並列に記述されている場合に発生します。

なので、すべて <template> の子要素に <style> を移動しました(コミット)。

動かすようにするための微調整

続いて1つずつの修正はそれぞれ大きくないものの、動作するまでの調整をたくさん実施しました(コミット)。

クリック処理が動かない

これは1から2の段階で廃止になった listeners を使っていたためで、利用箇所を addEventListener や、 on-tap, on-click に変更しました。

moment でロケールを利用しようとするとエラーになる

Toilet Evolution では、トイレの利用状況を表示するときに 10分前 のような表示をしているのですが、これに moment.js の機能(toDate())を使っています。 moment だけ(ロケールを使わない)なら npm でインストールして

import moment from 'moment';

と記述すればよかった(動いた)のですが、どうしてもロケールの解決がうまくいかない(Webpackするとエラーになる)ので、今回はあきらめてHTML側でグローバルに利用できる形にしました。

  <script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.24.0/moment.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.24.0/locale/ja.js"></script>

いくつかWebpackとmomentのissueも見つけて解決策もありそうだったのですが、とりあえず困らないのでこの方法で。

style や slot が解決されない

modulizerで自動変換された <google-map-marker> エレメントは以下のようになっていました。

const $_documentContainer = document.createElement('template');

$_documentContainer.innerHTML = `<dom-module id="google-map-marker">
  <style>
    :host {
      display: none;
    }
  </style>
  <template><slot></slot></template>
</dom-module>`;

document.head.appendChild($_documentContainer.content);

もとのHTMLでの記述は以下のようになっていました。

<dom-module id="google-map-marker">
  <template>
    <style>
      :host {
        display: none;
      }
    </style>

    <slot></slot>
  </template>
  <script>

自分が作ったカスタムエレメントでは、 Polymer_templatehtml を使うように変換されていたのですが、bowerでインストールしたコンポーネントの多くは上記のように変換されていました。これを html を使うようにしてみたところ解決しました。

Polymer({

  _template: html`
  <style>
    :host {
      display: none;
    }
  </style>
  <slot></slot>
`,

<google-map> エレメントも同様にうまく動作しなかったので、 html をつけて _template で返却するように対応しました。

さいごに

4回にわたって、Polymer1のプロジェクトをPolymer3に移行した解説をしてきました。 この連載がPolymer1からアップグレードする他の方の手助けになれば幸いです。

基本的な部分は、ほとんどmodulizerで変換できるので、これは便利だなーと思うのでした。 Polymer3対応した依存エレメントは、どこかのタイミングでPRを出してフィードバックしたいなと思っています。

では良いWeb Componentsライフを!

Toilet EvolutionのフロントエンドをPolymer3対応する(3)

この記事はToilet EvolutionのフロントエンドをPolymer3対応する(2)の続編です。

今回もmozlier適用だけでは動かないので、ローカルで動作するように修正というコミットにおける試行錯誤を書いていきます。

前回は多くのアプリケーションや、カスタムエレメントで実施しなければならない共通的な修正箇所について解説しましたが、ここからはアプリケーションで使っている機能やエレメントについて、修正が必要だった箇所について解説していきます。

ES Module対応になっていないエレメントを使う

前回の記事で「bower_components を使わないようにする」について解説しました。 PolymerチームやVaadinチームなどが作っているコンポーネントについては、ほとんど ES Module に対応しているのですが、場合によっては対応してないモジュールを使っていることがあるかもしれません。

Toilet Evolutionでは以下のエレメントが ES Module に対応していませんでした。iron-signalstoast-er の依存に関連しています。

そこで、これらのエレメントは modulizer で変換されたものを、そのまま利用することにしました。 また変換されたエレメントについては、前回の記事同様に共通的な修正箇所が必要になります。 つまり自分で作ったエレメントと同様に、そのエレメントが依存している(bower.jsonに含まれている)エレメントやライブラリについても、 npm install で取得するように変更する必要があります。

またPolymer1用のライブラリだったりする場合は、Polymer1から2へのアップグレードガイドに書いてある slot の対応なども必要だったので、そのライブラリが最新版だとPolymerのどのバージョンに対応したものなのかを確認しておくことが重要です。

そして、これらの作業が結構やっかいです(自分で作ったものでないので、ソースを理解する必要があります)。

以前のPolymerのビヘイビアは個別にインストールする必要がある

HTMLインポートで利用していた、Polymerチームが作っていたようなカスタムエレメントのビヘイビアは、個別に import するか、場合によっては npm install で導入する必要があります。 自分で作ったエレメントでは、ビヘイビアを使っていなかったので、この段階で初めて知りました(これは意外にもドキュメントに書いていません)。

例えば <gold-password-input> では、PaperInputAddonBehavior という paper-input に含まれていたビヘイビアを使っています。 これは以下のように、import を追加してあげる必要があります。

import {PaperInputAddonBehavior} from '@polymer/paper-input/paper-input-addon-behavior.js';

ES Module対応しているエレメントであれば、webcomponents.orgのビヘイビアを参照すると解説が書いてあります。 上記の PaperInputAddonBehavior であれば、こちらです。

Polymer.dom を変更する

これも自分のエレメントでは使っていなかったので気付かなかったのですが、使っているエレメントがあったので修正の必要がありました。 まず dom をインポートします。

import {dom} from '@polymer/polymer/lib/legacy/polymer.dom.js'

で、Polymer.dom を使っている箇所を dom に変更します。例えば以下のような感じです。

return dom(this).parentNode;

bowerでカスタムエレメント以外のライブラリに依存している

<gold-password-input>zxcvbnというパスワードストレングスエミュレータを使っています。 Node.jsなどにも対応しているのですが、今回はbowerでインストールしたフォルダをそのままコピーして、リポジトリに追加しました。 このようにカスタムエレメント以外のライブラリを使っている場合は、npmでインストールしてビルドする方法以外の検討も必要となります。 特に<gold-password-input>では、以下のように script タグを動的に追加してライブラリをインポートしていたため、npmを利用するのに適していなかったという理由があります。

  ready: function() {
    isZxcvbnLoaded = typeof zxcvbn !== "undefined";
    if (!isZxcvbnLoaded) {
      isZxcvbnLoaded = true;
      var oScript = document.createElement("script");
      oScript.type = "text\/javascript";
      oScript.onerror = function(err) {
        isZxcvbnLoaded = false;
        throw new URIError("The script " + err.target.src + " is not accessible.");
      };
      this.parentNode.insertBefore(oScript, this);
      oScript.src = this.resolveUrl("../zxcvbn/dist/zxcvbn.js");
    }
  },

このぐらいまで対応が進むと、ページをロードしたときに、ブラウザのコンソールにエラーが出ることが少なくなります。 ただし多くのページで表示が崩れていたり、うまく表示できないエレメントがあります。 これは今回の ES Modules に対応してなかったエレメントに関連するところなのですが、 これらの対応方法については次回解説していきます。