Kensington Expert Mouse 5 USBをX11で使う方法

トラックボールの名機と呼ばれるKensington Expert Mouse 5(EM5)をX11で使おうとしたが問題が発生した。

まず、EM5には4つのボタンがあり、それぞれのボタンはデフォルトでは以下のように割り当てられている:

.---------. .---------.
| Middle  | | Back    |
`---------' `---------'
.---------. .---------.
| Left    | | Right   |
`---------' `---------'

これら4つのボタンとホイールのX、Y軸それぞれの正方向、負方向のスクロールを合わせて8ボタンのマウスの扱いになる。

この割り当てを以下のように変更した上で、左クリックと右クリック同時押しで中央クリック、進むボタンを押しながらカーソル移動をスクロールに割り当てたい。

.---------. .---------.
| Middle  | | Forward |
`---------' `---------'
.---------. .---------.
| Left    | | Right   |
`---------' `---------'

これを実現するためにxorg.confでevdevの設定を以下のようにした:

Section "InputClass"
    Identifier "Kensington USB/PS2 Expert Mouse"
    MatchIsPointer "on"
    MatchProduct "Kensington USB/PS2 Expert Mouse"
    Driver "evdev"
    Option "ButtonMapping" "1 8 3 4 5 6 7 9 2"
    Option "Emulate3Buttons" "on"
    Option "Emulate3Button" "9"
    Option "EmulateWheel" "on"
    Option "EmulateWheelButton" "8"
    Option "EmulateWheelInertia" "10"
    Option "XAxisMapping" "6 7"
    Option "YAxisMapping" "4 5"
EndSection

しかし 8ボタンマウス として認識しているため、ButtonMapping の設定値で最後の 2 が無視されて 1 8 3 4 5 6 7 9 になってしまう。 そのため中央クリックのエミュレーションでボタン2(中央ボタン)ではなく、ボタン9(進むボタン)のイベントが送信されてしまう。

マッピングxinput コマンドで確認できる:

$ xinput get-button-map "Kensington USB/PS2 Expert Mouse"
1 8 3 4 5 6 7 9

今回これを回避するためにソースを編集してボタン数を btnmap の長さ(32)で決め打ちして回避した。

パッチを当てるとマッピングは以下のようになった:

$ xinput get-button-map "Kensington USB/PS2 Expert Mouse"                                 
1 8 3 4 5 6 7 9 2 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 0 

また、以前の記事でも、中央クリックのエミュレーションをマッピングの変更とともに使用するためのパッチを書いたが、これは中央ボタンの物理ボタン番号を指定する Emulate3Button の設定が実装されて不要になった。

なお、シリアルポート版のEM5をPS/2で使う場合はパッチは必要なく、以下の設定で同じ動作が可能:

Section "InputClass"
    Identifier "ThinkPS/2 Kensington ThinkingMouse"
    MatchIsPointer "on"
    MatchProduct "ThinkPS/2 Kensington ThinkingMouse"
    Driver "evdev"
    Option "ButtonMapping" "1 8 3 4 5 6 7 2 9"
    Option "Emulate3Buttons" "on"
    Option "Emulate3Button" "8"
    Option "EmulateWheel" "on"
    Option "EmulateWheelButton" "9"
    Option "EmulateWheelInertia" "10"
    Option "XAxisMapping" "6 7"
    Option "YAxisMapping" "4 5"
EndSection

ただの声優オタクが選ぶ2017年注目の女性声優8人

先日人気声優が選ぶ「声優総選挙」ベスト200という番組が放送されていたみたいです。 自分は見ていないのですが、便乗して2017年注目の女性声優8人を勝手に選びます。

選考基準としては「今」人気の人よりも、「これから」より人気になるであろう人を選んでいます。

  • 上田麗奈

    「うえしゃま」こと上田麗奈さんです。

    昨年はアニメーションとしては珍しいバイクを題材にした「ばくおん!!」で主人公「佐倉羽音」役を演じられて話題になりました。 さらに今年は自転車を題材にしたアニメ「南鎌倉高校女子自転車部」で主人公「舞春ひろみ」役を演じられており、乗り物アニメといえば彼女か、東山奈央さんという感じになりつつあります。

    いわゆる天然系のアホキャラを演じるとピカイチで、「ばくおん」の「佐倉羽音」役はこの上ないくらいマッチしていたと思います。

    ラジオでは、小野大輔さんとパーソナリティを務めていた「ディメラジ〜Dimension W Radio〜」ではパーソナリティが2人とも「ボケ」で面白いと話題になりました。

    また、昨年末にはミニアルバム「RefRain」が発売され、アーティスト活動も開始されています。

  • 夏川椎菜

    TriSeilのメンバーとしてもお馴染の「ナンス」こと夏川椎菜さんです。

    昨年は「はいふり」こと「ハイスクール・フリート」の主人公「岬明乃」役で注目を集めました。 癖のない正統派の主人公・ヒロイン声という印象で、これからも色々な作品で活躍されるのではないかと思います。

    ラジオは、新人声優の登竜門的な番組「ラジオどっとあい」で「夏川椎菜のナンスの角に小指ぶつけて痛い」という番組をやられていました。 「TrySailのTRYangle harmony」では個性の強い雨宮天さん、麻倉ももさんに押されてどちらかといえば地味な印象でしたが、個人の番組も独特なトークで中々に面白いです。

  • 大森日雅

    「にちかちゅ」こと大森日雅さんです。

    2014年に「六畳間の侵略者!?」の「虹野ゆりか」役で衝撃のデビューを飾ったことが印象的です。 当時、「脳がとろける」とも言われた独特の甘い声質が話題になりました。

    六畳間の侵略者!?」では大森日雅さん、鈴木絵理さん、田澤茉純さん、長縄まりあさんの4人で「ハート♡インベーダー」というユニットも結成されました。 個人的に注目していたユニットで、再始動されないかなと密かに期待しています。

    昨年は、「魔法少女なんてもういいですから」で「坂上ちや」役を演じられ、彼女の甘い声質とドSキャラのギャップが良かったです。 まだあまりアニメ出演数は多くありませんが、今年さらに活動の幅が広がることを期待しています。

    ラジオでは、鈴木絵理さんと2人でパーソナリティを務める「大森日雅鈴木絵理 今夜もスパで待ち合わせ♪」が印象に残っています。 ここでも、彼女のあの声質でドS発言をするというギャップが印象的です。

  • 本渡楓

    アイム期待の新人「えーで」こと本渡楓さんです。

    去年の下半期は「レガリア The Three Sacred Stars」の主人公「ユインシエル・アステリア」役、「ガーリッシュナンバー」の「久我山八重」役、「競女!!!!!!!!」の「青葉風音」役、とメイン級の役を立て続けに3つ担当されました。 今年は現時点で「うらら迷路帖」の「巽紺」役、「亜人ちゃんは語りたい」の「小鳥遊ひかり」役を演じられており、今一番勢いのある新人声優と言っても過言ではないと思います。

    声優としての演技に定評あるのはもちろんのこと、ラジオもかなり面白いです。 特に、芸人の天津向さんと2人でパーソナリティを務める「本渡楓と天津向の『本渡上陸作戦』」が強く印象に残っています。

    番組は基本的に本渡楓さんのボケに天津向さんがツッコむという形で進行しますが、その応酬が秀逸で、さながらプロのお笑いコンビのようなやり取りです。 お笑い芸人と、声優の組み合わせも新鮮で、今までにない新しい声優ラジオが聞きたい人は是非聞いてみて欲しいです。

  • 水瀬いのり

    「いのりん」こと水瀬いのりです。

    2014年頃から途切れることなくメイン級の役を多数演じられている、みなさんご存知の大人気声優です。 昨年特に印象的だったのは「魔法少女育成計画」の「スイムスイム」役です。 「スイムスイム」は今までになかったような難しい役柄だったと思うのですが、とても見事に演じられていたと思います。

    その他のアニメの出演も多数あるのですが、アーティスト活動についても触れておかなければならないでしょう。 まずデビューシングルの「夢のつぼみ」はとにかく衝撃的でした。 まず、歌声が別人の様で驚き、その声量に驚き、その歌唱力の高さに驚きました。 若手の声優で彼女の歌唱力に匹敵するの人はそういないのではないかと感じました。

    3rdシングルの「Starry Wish」は「魔法少女リリカルなのはシリーズ」の第5期となる「ViVid Strike!」のEDテーマになっています。 ちなみに、OPテーマは小倉唯さんが歌う「Future Strike」です。 「魔法少女リリカルなのはシリーズ」のOP&EDと言えば代々、水樹奈々さん、田村ゆかりさんが担当されていましたが、いよいよこの2人に続く声優アーティストが出てきたと感じました。 今後の活躍も楽しみです。

  • 花守ゆみり

    「ゆみりん」こと花守ゆみりさんです。

    昨年は「リルリルフェアリル」の主人公「りっぷ」役、「あんハピ♪」の「花小泉杏」役、「灼熱の卓球娘」の「旋風こより」役、「魔法少女育成計画」の「ねむりん」役、とメイン級の役が続きました。 「リルリルフェアリル」は女児向けアニメですが、今後プリキュア声優になる日も近いのではないでしょうか。

    まだ19歳でとても若いので今後のさらなる活躍を期待してます。

  • 長縄まりあ

    「なーなーまりあ」こと長縄まりあさんです。

    彼女はとにかく声が特徴的で、「長縄まりあ」でGoogle検索すると、本当にあの声が地声なのかと「長縄まりあ 地声」とサジェストされる程です。

    昨年はやはり「ステラのまほう」で主人公の「本田珠輝」役を演じられたのが印象的です。 彼女の可愛らしい癒し系の声がキャラクターにマッチしてとても良かったと思います。

    ラジオは、「小澤亜李・長縄まりあのおざなり」を取り上げない訳にはいかないでしょう。 内容としては小澤亜李さんと、長縄まりあさんがイチャイチャしているだけと言えばそうなのですが、2人のトークの端々に出る毒が強烈で面白いです。 声優ラジオとしては唯一無二と言ってもいい、非常に個性の際だったラジオだと思います。

  • 麻倉もも

    TriSeilのメンバーとしてもお馴染の「もちょ」こと麻倉ももさんです。

    麻倉ももを、ほっとけない。」これは「B.L.T. VOICE GIRLS」の表紙に彼女が載った時のコピーです。 これはまさに彼女の印象を的確に表したコピーだと思います。 彼女はとにかく可愛いらしくて、同時にどこか危なっかしくてほっとけないのです。

    昨年は、「クロムクロ」の「白羽小春」役が印象に残りました。 さらに一昨年になりますが「Charlotte」の「乙坂歩未」役も印象に残っています。 この2つはどちらも妹役で、妹といえば麻倉ももさんというのはもはや過言ではないでしょう。 まだアニメの出演数は多くはないですが、今後のより一層の活躍に期待しています。

    ラジオについては一昨年になりますが「ラジオどっとあい 麻倉もものもちょやまばなし」が印象的です。 とにかくボケの応酬でツッコミ不在の番組だったのですが、可愛いしそれがまた面白かったです。 現在は「TrySailのTRYangle harmony」でTrySailのメンバーとしてラジオをやっていますが、まったく別の人との組み合わせの番組も聞いてみたいです。

rxvt-unicodeのフォントレンダリングを改善する

問題

最近はNoto Fontという高品質な日本語フォントが無料で手に入るいい時代になりました。 しかし、rxvt-unicodeでこのフォントを指定すると、とても残念な表示になってしまいます。

フォントの設定は以下のものです:

Rxvt.font: \
  xft:Consolas:pixelsize=13, \
  xft:Noto Sans CJK JP:pixelsize=13:style=DemiLight
Rxvt.lineSpace: 6

f:id:emonkak:20161209173120p:plain

サンプルテキストにはロレム・イプサムを利用させて頂きました。

これはrxvt-unicodeのバージョン9.22で表示した画像ですが、いつくかの表示上の問題があります。

  1. 欧文の文字間隔が異常に広い
  2. lineSpaceで指定した分の数値が行の高さとして単純に足されるので文字が上によってしまう
  3. アンダーラインの表示がベースライン上に引かれるのでベースラインの下にはみ出す 'g' や 'y' が読みづらい(これは好み)
  4. 日本語フォントが著しく小さい

解決

1の文字間隔については既にパッチが出まわっていて、gentooのパッケージでは alt-fontwidth のuseオプション有効にすることで改善します。

f:id:emonkak:20161209173115p:plain

他の問題については1つづつ自分でパッチを書いて改善していきます。

まずはlineSpaceの問題についてですが、これ単純に文字の描画位置を調整するだけでいいので簡単です:

diff --git a/src/rxvtfont.C b/src/rxvtfont.C
index 1914539..20a77af 100644
--- a/src/rxvtfont.C
+++ b/src/rxvtfont.C
@@ -1395,7 +1395,7 @@ rxvt_font_xft::draw (rxvt_drawable &d, int x, int y,

           ep->glyph = glyph;
           ep->x = x_ + (cwidth - extents.xOff >> 1);
-          ep->y = y_ + ascent;
+          ep->y = y_ + ascent + (term->lineSpace >> 1);

           if (extents.xOff == 0)
             ep->x = x_ + cwidth;

次のアンダーラインの問題も同様です:

diff --git a/src/screen.C b/src/screen.C
index 9eb375a..7b3cdbf 100644
--- a/src/screen.C
+++ b/src/screen.C
@@ -2432,8 +2432,8 @@ rxvt_term::scr_refresh () NOTHROW
                 XSetForeground (dpy, gc, pix_colors[fore]);

               XDrawLine (dpy, vt, gc,
-                         xpixel, ypixel + font->ascent + 1,
-                         xpixel + Width2Pixel (count) - 1, ypixel + font->ascent + 1);
+                         xpixel, ypixel + Height2Pixel (1) - 1,
+                         xpixel + Width2Pixel (count) - 1, ypixel + Height2Pixel (1) - 1);
             }
         }                     /* for (col....) */
     }                         /* for (row....) */

最後の日本語の文字が著しく小さくなる問題ですが、これは中々苦労しました。

条件としては組み合わせるフォントのアセンダ・ディセンダの値が大きく異なると発生します。 ですので、FontForgeなどでアセンダ・ディセンダを調整することで回避することもできます(やってました)。

しかし、これはあんまりなのでソース側で対応することにしました。 どうやら、欧文フォントに比べて日本語フォントのアセンダ・ディセンダが大きすぎるので、枠内に収まるように縮小しているようです。 これはもう余計なお世話という他ないので、この処理はばっさりと消してしまいます。

すると、文字が正しい大きさで描画されるようになりますが、欧文フォントと日本語フォントのベースラインが揃わずにガタガタになってしまいます。 これは以下のことをして調整しました:

  • フォントのアセンダ・ディセンダ、高さの値を xterm で使っている値と同じものを使うようにするパッチを適用
  • 描画する行の高さを取得し、アセンダ・ディセンダの比率から描画位置を決定する
  • ベースラインから描画位置を決定する

これで以下のように綺麗に描画されるようになりました:

f:id:emonkak:20161209173122p:plain

ここまでやったすべての修正を含むソースは以下にあります。

https://github.com/emonkak/rxvt-unicode/tree/9.22-patched

パッチは以下になります:

おわりに

rxvt-unicode はフォントレンダリングにはいくつか問題がありますが、他にいい代替がないので困りものでした。 今回これを綺麗に描画するように修正できて今はrxvt-unicodeに満足しています。

本当は本家が修正されるのがいいんでしょうけど、様々なディストリのパッケージが独自にパッチを適用しているのを見ると厳しいのかもしれません。

追記:描画位置の修正が不十分だったのでさらに修正しました

PSR-7とPSR-15を使ったWebアプリケーション開発

はじめに

PSR-7(HTTP Message)が承認されてからしばらく経ちますが、現在はこれを使った様々なライブラリ・フレームワークが登場しています。 これによって特定のライブラリ・フレームワークにロックインされずに、Webアプリケーションを実装できる道程が見えてきました。

しかし、PSR-7はあくまでHTTPメッセージのインターフェイスを提供するもので、リクエストを受け取ってレスポンスを返す流れを抽象化するものではありません。 これはHTTPミドルウェアと呼称されますが、そのインターフェイスはそれぞれの実装でまちまちです。 そこで、これを抽象化するPSR-15(HTTP Middleware)が提案されています。

ミドルウェアは大まかにダブルパスのミドルウェアと、シングパスのミドルウェアに分けることができます。 PSR-15は現在の所シングルパスのシグネチャを採用しています。

シングルパスのミドルウェアシグネチャ

function (ServerRequestInterface $request, callable $next): ResponseInterface;

ダブルパスのミドルウェアシグネチャ

function (ServerRequestInterface $request, ResponseInterface $response, callable $next): ResponseInterface;

PSR-15のインターフェイスを提供するhttp-interop/http-middlewareにシングルパスを採用する理由があります。 ダブルパスのミドルウェアには事前にレスポンスを与える必要がありますが、そのレスポンスが利用可能なものかどうかを保証できないことが問題だと判断し、シングルパスを採用したようです。

The most severe is that passing an empty response has no guarantees that the response is in a usable state. This is further exacerbated by the fact that a middleware may modify the response before passing it for further dispatching.

残念ながら、この2つのインターフェイスには相互運用性がないので、アダプターを介してシグネチャを統一しても同じ動作を保証できません。 しかしこれは過渡期の問題で、恐らくはPSR-15の承認とともにどちらかに統一されることで解決に向かうはずです。

実装方針

本稿ではPSR-15を利用して簡単なサンプルとしてのWebアプリケーションを実装することを目的とします。 アプリケーションの設計については様々な手法がありますが、今回はDDD(Domain-driven design)を採用することを念頭に置いて進めていきます(サンプルなのでドメインと言っても実体があまりないですが)。

実装にあたっては既存のフレームワークを使うという選択肢もありますが、ここでは単機能なライブラリを組合わせて疎結合な独自のマイクロフレームワークを構築し、その上でアプリケーションを実装します。 これはあえてフルスタックフレームワークを避けたということでもあります。

フルスタックフレームワークは機能が豊富で便利なのは確かですが、注意深く設計をしないとドメインが特定のインフラストラクチャに依存する形になってしまいがちです。 さらには、複雑なドメインとアプリケーションの要件次第では、フレームワークの機能も十分に生かすことができず、かえって足枷になってしまうこともあります(フレームワークに汚いハックをして無理矢理要件を満たすなど)。 これはフレームワークが汎用のものである所以です。

ドメインは特定のインフラストラクチャから完全に分離されており、それ単体で動作するのが理想的です。 そうすると、ドメインへの影響は一切なしに、フレームワークを乗り換えることだってできます。 フレームワークもアプリケーションの要件に完全に合致したものがあればミスマッチもありません。

そこで、クリーンアーキテクチャを採用した、独自のマイクロフレームワークを作成するという道を選択しました。 幸いにして、そのための部品(ライブラリ)は揃いつつあります(揃えました)。 クリーンアーキテクチャではフレームワークに依存するレイヤーはこの図にある「Interface Adapters」と「Frameworks & Drivers」の部分だけです。

f:id:emonkak:20161209010829j:plain

https://8thlight.com/blog/uncle-bob/2012/08/13/the-clean-architecture.html

仕様

作成するのはアカウント登録、ログイン、ログアウトがあるだけのごく単純なアプリケーションです。

  • ユーザーはサービスを利用するためのアカウントを作成(サインアップ)しなければならない
  • アカウント作成にはメールアドレスとパスワードが必要
  • アカウントが作成されると自動的にログインされる
  • ログインに成功すると「Hello World!」と表示される
  • ログインしたアカウントはログアウトすることができる
  • ログインするにはアカウント作成時に入力したメールアドレスとパスワードが必要

実装

今回実装したアプリケーションのソースは以下にあります。

https://github.com/emonkak/php-http-app-skeleton

使用ライブラリ

いくつか自作のライブラリも含まれますが、それぞれ詳細については追って記事にしたいと思います。

ディレクトリ構造

Laravelの構成を参考にしています。

パッケージ構成

  • App\Adapters

    HTTP、コンソール(CLI)、データベースなどの外部インターフェイスへのアダプターが格納されます。 本来であればデータベースにアクセスするリポジトリの実装はこちらに配置するのが望ましいですが、今回はインターフェイス定義を省略して簡略化するために App\Domain に配置しています。

  • App\UseCases

    アプリケーションのユースケースが格納されます。 今回の場合はアカウントの認証と作成のためのサービスが格納されています。

  • App\Domain

    アプリケーションが対象とする問題領域であるドメインの実装が格納されます。 パッケージ直下には実装は格納せずに、集約ルート(ルートエンティティ)ごとにサブパッケージを定義します。 今回はアカウントのサブパッケージのみが格納されています。

  • App\Supports

    他の各レイヤーの実装を支援するためのユーティリティが格納されます。

エントリーポイント

まずはサーバーのエントリーポイントとなるスクリプトを見てみます。

最初に、リクエストからレスポンスを生成するための Application クラスのインスタンスを別ファイルから require しています。 この Applicationインスタンスにリクエストを与えて、生成されたレスポンスを送信するというのが全体の流れです。

<?php
// public/index.php

use Zend\Diactoros\Response\SapiEmitter;
use Zend\Diactoros\ServerRequestFactory;

// アプリケーションのインスタンスを読み込む
$app = require __DIR__ . '/../bootstrap/http.php';

// グローバル変数からリクエストを生成
$request = ServerRequestFactory::fromGlobals();

// リクエストからレスポンスを生成
$response = $app->handle($request);

// レスポンスを送信する
(new SapiEmitter())->emit($response);

アプリケーションの初期化

Application の初期化は bootstrap/http.php で行なわれます。 ここではオートローダーを読み込み環境変数の設定をした上で、各種ミドルウェアの登録を行っています。

以下は bootstrap/http.php の内容です:

<?php

require __DIR__ . '/../vendor/autoload.php';

// .envから環境変数を読み込み
(new Dotenv\Dotenv(__DIR__ . '/../'))->load();

if (getenv('APP_DEBUG')) {
    // デバッグモードならsymfony/debugによるエラー画面を表示する
    Symfony\Component\Debug\Debug::enable();
}

$app = new App\Adapters\Http\Application(realpath(__DIR__ . '/../'));

// 以降ミドルウェアの設定
// ミドルウェアは追加された順番通りに実行される

// POST時に_methodパラメータをPOSTすることでメソッドをオーバーライドする
$app->pipe(
    (new Middlewares\MethodOverride())
        ->post(['PATCH', 'PUT', 'DELETE'])
        ->parsedBodyParameter('_method')
);

// セッションを開始する
$app->register(App\Adapters\Http\Middlewares\SessionStarter::class);

// アカウントを認証する認証
$app->register(App\Adapters\Http\Middlewares\Authenticator::class);

// テンプレートに設定される共通の変数($request, $uri, $session, $flashes)を設定する
$app->register(App\Adapters\Http\Middlewares\ViewSharedVariables::class);

// ルーティングの結果から処理を設定されたハンドラに移譲する
$app->registerDispatcher();

// エラーのロギングを実行する
$app->registerErrorHandler();

if (!getenv('APP_DEBUG')) {
    // Dispatcherが処理できなかった時に最後に実行されるミドルウェア
    // 404ぺージが表示される
    $app->register(App\Adapters\Http\Middlewares\ErrorPage::class);

    // 同様の実装をエラー発生時にも実行されるように登録する
    // catchされない例外があればここでハンドリングされてエラーページが作成される
    $app->registerOnError(App\Adapters\Http\Middlewares\ErrorPage::class);
}

return $app;

DIコンテナの生成

Applicationインスタンスが生成される時に、コンストラクターでは bootstrap/container.phprequire してPSR-11の ContainerInterface の実装が生成されます。 要求されるのは ContainerInterfaceインスタンスなので、好きなDIコンテナの実装を利用することができます。 今回は自作の emonkak/di を使っています。

このコンテナにはアプリケーションに必要な各種インスタンスが登録されます。 環境の違いによって異なる設定を利用したい場合は、.env によって設定される環境変数を利用して切り替えます。

以下はDBのコネクションをコンテナに登録している部分のソースです:

<?php

$container->factory(PDOInterface::class, function() {
    return new PDO(
        sprintf(
            '%s:host=%s;port=%d;dbname=%s',
            getenv('DB_CONNECTION'),
            getenv('DB_HOST'),
            getenv('DB_PORT'),
            getenv('DB_DATABASE')
        ),
        getenv('DB_USERNAME'),
        getenv('DB_PASSWORD'),
        [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
        ]
    );
})->in(SingletonScope::getInstance());

本サンプルでは設定値を管理するための専用の仕組みは得に設けていませんが、ほとんどの場合この環境変数を使う方法で事足りるはずです。

仮に環境によって特別な設定をしたい場合についても、以下のように if で分岐してしまえば対応できます。 多くの設定は環境によらず共通のはずで(環境ごとの差異が大きすぎるのは問題)、異なる部分だけをif文で分岐するというのが重複も発生せずシンプルでいいと思ってます。

<?php

if (getenv('APP_ENV') === 'production') {
    // production settings
} else {
    // other settings
}

ルーターの生成

ルーターはURLのパスに応じて、リクエストをハンドリングするクラス(ハンドラー)を決定するためのものです。

ルーターの生成は、パスに応じた処理を実行するための Dispatcher ミドルウェアの登録時に bootstrap/router.phprequire することで行われます。 ここで要求されるのは emonkak/router で定義されたインターフェイスである RouterInterface の実装です。

以下は bootstrap/router.php の内容です:

<?php

use App\Adapters\Http\Handlers;
use Emonkak\Router\TrieRouterBuilder;

return (new TrieRouterBuilder())
    ->get('/', Handlers\Index::class)
    ->get('/api/ping', Handlers\Api\Ping::class)
    ->get('/accounts/sign_up', Handlers\Accounts\SignUp::class)
    ->post('/accounts', Handlers\Accounts\Create::class)
    ->get('/sessions/login', Handlers\Sessions\Login::class)
    ->post('/sessions', Handlers\Sessions\Create::class)
    ->delete('/sessions', Handlers\Sessions\Delete::class)
    ->build();

これは単純にルーティングの内容を書き下しているだけですが、ハンドラーの命名に一定の法則を設けることで、クラスの一覧を取得して自動的にルーティングを定義するという方法も考えられます。 さらに生成したルーティングの内容をシリアライズした上でキャッシュすることで、性能的にも有利になります。 ルーティング数の多い大規模アプリケーションであれば、このような方法も検討もするといいかもしれません。

実装のイメージとしては以下のようなものです:

<?php

if (file_exsits(__DIR__ . '/../storage/router.cache.php')) {
    return unserialize(file_get_contents('/../storage/router.cache.php'));
}

$builder = new TrieRouterBuilder();

foreach (RoutingResolver::resolve(__DIR__ . '/../src/Adapters/Http/Handlers') as list($method, $path, $handler)) {
    $builder->route($method, $path, $handler);
}

file_put_contents(__DIR__ . '/../storage/router.cache.php', serialize($builder->build()));

return $router;

ハンドラーの実装

リクエストをハンドリングしてレスポンスを返すクラスをハンドラーと呼んでいます。 あらゆるPSR-15のミドルウェアはハンドラーとして利用できます。

その他、1つのクラスで複数のエンドポイントを取り扱う、コントローラースタイルのクラスをハンドラーとして登録することもできます。 コントローラーを登録する時はルーターにクラス名とメソッド名のペアを指定します。 指定されたクラスのメソッドは ServerMiddlewareInterfaceprocess() メソッドと同じシグネチャであることを期待されます。

コントローラーの例:

<?php

class ExampleController
{
    public function index(ServerRequestInterface $request, DelegateInterface $delegate)
    {
        ...
    }

    public function create(ServerRequestInterface $request, DelegateInterface $delegate)
    {
        ...
    }

    ...
}

http-interop/http-middleware に言及がありますが、PSR-15のシングルパスのミドルウェアはレスポンスの生成をミドルウェア自身が行うので、レスポンスの実装に対する依存が起きてしまいます。

Some have argued that passing the response helps ensure dependency inversion. While it is true that it helps avoid depending on a specific implementation of HTTP messages, the problem can also be resolved by injecting factories into the middleware to create HTTP message objects, or by injecting empty message instances. With the creation of HTTP Factories in PSR-17, a standard approach to handling dependency inversion is possible.

確かにPSR-17を使うことで特定の実装に依存することは回避できるのですが、ちょっと使い勝手が良くありません。 例えばJSONのレスポンスを返す時にContent-Typeを適切に設定してデータを json_encode() を設定しなければならないのはなかなかに面倒です。

そこでもっと簡単にレスポンスオブジェクトを生成できる Respondable トレイトを作成しました。

<?php

trait Respondable
{
    public function html($html, $statusCode = 200, array $headers = [])
    {
        return new HtmlResponse($html, $statusCode, $headers);
    }

    public function json($data, $statusCode = 200, array $headers = [])
    {
        return new JsonResponse($data, $statusCode, $headers);
    }

    public function redirect($uri, $statusCode = 302)
    {
        return new RedirectResponse($uri, $statusCode);
    }

    ...
}

これらのメソッドをハンドラーにミックスインして呼び出すことで、直接特定のレスポンスの実装に依存することはなくなりました。

おわりに

PSR-7とPSR-15を使ってマイクロフレームワークを作成して、簡単なサンプルアプリケーションという試みをしてみました。 機能としては不足している点はありますが、この成果は現実のアプリケーションに十分に適用できるものだと思います。

実は、このような試みをしたのは2度目で、その時の成果として emonkak/wafというライブラリがあります。 これは前職で関わったとあるプロダクトで利用されています。 この時はアプリケーションの要件がかなり特殊で既存のフレームワークとは適合しなかったので、独自のフレームワークを作成する必要がありました。

本稿では説明不足な点も多々ありますが、ソースはシンプルで読み易いはずなので、興味があれば読んで頂ければと思います。

時間計算における丸め

本稿では時間計算についての丸め操作を考える。 最初に丸めの定義は以下とする。

丸め(まるめ)とは、与えられた数値を、ある一定の丸め幅の整数倍の数値に置き換えることである。 https://ja.wikipedia.org/wiki/%E7%AB%AF%E6%95%B0%E5%87%A6%E7%90%86

丸めの対象

時間計算における丸めの対象は瞬間(Instant)あるいは日時(LocalDateTime)とする。

瞬間 とはタイムゾーン(場所)によらず一意な時間である。瞬間は一般にUTC時間で表される。 一方、日時 は特定の瞬間を表すものではなく、タイムゾーンによってその日時を表す瞬間は異なる。 瞬間日時タイムゾーンが与えられれば相互に変換することができる。

丸めの単位

時間計算における丸めの単位は継続時間(Duration)とする。 継続時間とは、長さを表す時間の概念で秒で表されるものとする(実際の実装ではナノ秒)。

ここでまず単純な例を挙げると:

  • 2001/02/03 01:02:03 を 10分(600秒) を単位に切り捨てる → 2001/02/03 01:00:00
  • 2001/02/03 01:02:03 を 10分(600秒) を単位に切り上げる → 2001/02/03 01:10:00

まず丸め処理をするためには、丸めの対象となる日時を数値に変換する必要がある。 この例だと丸めの単位が一日の長さの約数なので、日を無視して時間だけに注力すれば良い。 SI単位系を用いて計算すると 01:02:03(1 * 60 * 60) + (2 * 60) + 3 = 3723(秒) となるので、ここから 3723 % 600 = 123(秒) のように余りを求めてそれを足し引きすればいい。

これは自明は結果だが、以下のケースだとどうなるか:

  • 2001/02/03 01:02:03 を 7分 を単位に切り捨てる → ?

この丸めは一般に定義できない。 なぜなら7分(420秒)というのは一日の長さの約数ではないからだ。 一日の長さははSI単位系24 * 60 * 60 = 86400(秒) なので 86400 / 420 = 205.714286... のように割り切ることはできない。 すると日を含めた日時を数値に変換して余りを求めなければならないが、それはどの瞬間を起点とするか、1年の長さが何秒なのかによって結果が異なってしまう。 これは 紀年法暦法を定義しないと数値に変換することはできない ことを意味する。

実装

型レベル数値リテラルを使って型レベルFizzBuzz

型レベルFizzBuzz(及び、type familyにおけるガードの書き方) - claustrophobiaを見て自分も型レベルFizzBuzz書いてみました。 GHC 7.8以降で動作します。

Natを受け取るとFBkindを持つ型を返すFizzBuzz型族と、FBに対するsingletonになるSFB型を定義して、 あとは型からsingletonを生成してそれをprintしています。

SNumberT 1
SNumberT 2
SFizzT
SNumberT 4
SBuzzT
SFizzT
SNumberT 7
SNumberT 8
SFizzT
SBuzzT
SNumberT 11
SFizzT
SNumberT 13
SNumberT 14
SFizzBuzzT
SNumberT 16

TypeScript 0.95でコンパイルできないジェネリックメソッドのコード

0.91ではコンパイルできた以下のコードが0.95ではコンパイルできなくなっている。バグ?

interface IFoo {
    f<T>(): T;
}   

class Foo implements IFoo {
    f<T>(): T {
        return null;
    }
}   

エラーメッセージ:

/Users/emon/Desktop/test.ts(5,7): error TS2137: Class Foo declares interface IFoo but does not implement it:
        Types of property 'f' of types 'Foo' and 'IFoo' are incompatible:
                Call signatures of types '<T>() => T' and '<T>() => T' are incompatible.

追記:Issuesに上がってた。

Interface declaration with generics not working in TypeScript 0.9.5