TL;DR

  • User Agent 判定器 WootheePHP 実装を作った
  • Packagist には登録できてなかったりするので少々お待ちください (2014-12-18 追記 Packagist に woothee/woothee として登録しました)
  • Woothee は便利プロジェクトなので盛り上がってほしい

Woothee とは

Woothee というのは @tagomoris さんが作った User Agent 判定器プロジェクトです。
Perl, Java, JavaScript, Python と、複数の言語に実装が用意されていて、元になるデータセットや、テスト用のデータセットは YAML 形式で公開されています。

詳しくは以下のブログ記事を読むのが良いでしょう。

Woothee PHP とは

PHP 実装がなかったようなので、勝手に作りました。

yuya-takeyama/woothee-php

ほぼ woothee-java を移植しただけです。
ただし、判定処理を行うファサードとなる Classifier クラスは、woothee-java が静的メソッドを呼び出して使うのに対して、woothee-php はインスタンスメソッドを呼び出して使うようにしてます。
その方が DI できていいよね、という感じです。

とりあえずテスト用のデータセットは全てパスしてるつもりです。
ユニットテスト通しただけで実際のプロジェクトへの投入とかはできてないので、ものすごいポカミスとかあるかもしれません。

Packagist への登録について

PHP ライブラリ作ったら 4 秒で Packagist に登録するのが PHP モダニストの流儀だとは思うのですが、せっかくならベンダー名として woothee 使いたいところで、そうなると一応 @tagomoris さんにも一言聞いておいたほうがいいだろうということで、後回しにしてます。 (まだ聞いてない)
(2014-02-18 追記 前述のとおり Packagist 登録済み)

あとせっかくなので、GitHub の woothee organization 入りもできるといいですね。
(2014-02-18 追記 woothee organization 入りし、woothee/woothee-php に移動しました)

Woothee のいいと思うところ

似たような機能のライブラリは無くはないと思うのですが、(ちょうど今 @chobie さんにも教えていただきました) Woothee のいいところはもっと別のところにあると思うんです。

  • 多言語で実装が用意されており、同一の判定を行うことが (少なくともユニットテストにより) 保証されている
  • 元データのデータセットが YAML で GitHub に公開されている (プルリクでデータの修正・追加が可能)
  • テスト用のデータセットも YAML で GitHub に公開されている (これもプルリクできる)

コミュニティとしてメンテナンスを続けていければ、極論 @tagomoris さん自身が飽きてしまっても問題ないわけです。

まとめ

とりあえず作りましたので、興味のある方はお試しください。
前述の通り、こちらでは本番投入できておりませんので、その辺はお察しください。

久々のブログです。

夕方に3DS LL とポケモンのセットが届いたのですが、電源がついてなくてあんまり遊べてません。

今週末は @kuzuha さん主催での Spika Hackathon というイベントに参加してきました。

Spika がどういうものなのかはこの辺の記事を見ればいいと思います。

TechWave に記事が紹介されて直後から、コードの品質がヤバいと話題になっていました。

僕自身も以下のような印象を持ちました。
(アッ、メッセンジャーの綴りが違う…)

という具合に割と強めに Dis ったりはしたものの、ちょうど同じ頃に以下のようなツイートを見ていたこともあって、せっかくだからいろいろなおしてみるかという気になったのでした。
(これはまた別の会社のゲームフレームワークについての文脈だったと思うけど)

この記事では送ったプルリクの詳細について残しておこうと思います。

#6 added CI base

これは参加者 3 名による複数の修正が含まれていて、以下のような内容を含みます。

Travis CI はそろそろ説明不要の感もありますが、日本 (というか PHP コミュニティ) ではあんまり流行ってないのかな、と思われる Coveralls について簡単に説明すると、コードカバレッジレポートを残すための PaaS です。
Coveralls に GitHub の OAuth を使ってサインインし、計測対象とするリポジトリを登録しておき、適宜設定を行うと Travis CI のビルド後にレポートが送られるようになります。
Ruby だと Gemfile に coveralls の gem を追加するぐらいで使えて便利です。
PHP だと @satooshi_jp さんによる php-coveralls というライブラリにより利用できますし、その他の言語でも大体使えるようになってきていることと思います。

このプルリクにより、ユニットテスト・継続的インテグレーションの基盤がとりあえずできました。

プルリクを送ると自動的にカバレッジレポートが送られるようになる

テストが実行されている場所・されていない場所が一目でわかる

WebTestCase を使ったユニットテストについては、前提として Spika の中の人が develop ブランチで Silex への移行を予めやっていた、というのが大きかったです。

#7 Change directory structure

PSR-0 の規約に従ってディレクトリ構造の変更を行い、 composer.json にもオートローディングの設定を行いました。

規約に沿った命名にすることで require_once が不要になる

これは地味ですがやっぱり重要だと思います。
require_once を書かなくてよくなる、というだけでも QOL が向上しますね。

#11 データベースのインターフェイスを追加

Spika はバックエンドのストレージとして CouchDB を使っていて、それを隠蔽した SpikaDBHandler クラス (現 CouchDb クラス) というのがあるんですが、その public メソッドだけを抜き出して DbInterface というインターフェイスを定義しました。

インターフェイスを定義することで、それに沿った実装さえ用意すればバックエンドのストレージを容易に置き換えられる、というのもありますが、とりあえずはユニットテストを書く上で便利、というのもあります。
データベースに依存したクラスであっても、インターフェイスを元にしたモックオブジェクトさえあればオンメモリで動作するユニットテストが書けるようになります。

#15 Dependency Lookup ではなく Dependency Injection を使用する

Silex による開発で割とやってしまいがちな間違いは Dependency Lookup により依存オブジェクトを取得するようなモジュールを書いてしまうことだと思います。

どういうことかというと、大体以下の記事に書かれているのでした。

Pimpleでシンプルに正しくDIを理解する

この記事では Service Locator という言葉が使われていますが、Service Locator による依存性の解決は Dependency Lookup です。

#19 $app['beforeTokenChecker'] のモジュール化 & ユニットテスト

いくつかの API では、事前処理としてアクセストークンによる認証を行っているのですが、その認証機構が無名関数により実装されていて、ユニットテストが全くかかれていない状態だったので、クラスとして再実装しました。

前提として、#11 においてデータアクセス部分をインターフェイス化していたからこそテストが書きやすかった、というのがあります。

ところで、Silex の Before/After Middleware という機構はちょっと使い辛いんじゃないかなーと…
その辺を解決する仕組みとして、WSGI/Rack/PSGI あたりを意識した Stack というフレームワークがあって、これを使えば大分いい感じになりそうなんですが、要求される PHP のバージョンが 5.3 から 5.4 に上がってしまうので、とりあえずやめました。

まとめ

レガシーな PHP アプリケーションをリファクタリングする上で、それなりに汎用性の高い知見が得られた気がするので、まとめておきます。

  • テスタビリティを考慮したフレームワークを使うといろいろ楽 (Silex の WebTestCase 便利)
  • パッケージの管理は Composer 一択
  • PSR-0 を守ると require_once とか書かなくてよくなって楽
  • Travis CI による継続的インてグレーションは基本
  • ついでに Coveralls によるカバレッジレポートの自動化もやっておくと便利だしカッコいい
  • モジュールを作るときはクラスではなくインターフェイスに依存するようにするユニットテストが楽
  • Dependency Lookup じゃなくて Dependency Injection を使った方がユニットテストが楽

人生楽して生きたいものですね。

関連記事

ちょっと CakePHP を使う機会があったのでソースコードを眺めていたところ、ちょいとしたバグが見つかりまして、すぐにパッチを書き Pull Request を行ったところ、ものの数時間でマージされました。

バグというのが、「alphaNumeric バリデータにおいて、改行で区切ってしまえばどんな文字列も通してしまっていた」ということです。

修正は以下の 1 コミットのみ。

Fix alphaNumeric validation · 14c81fe · cakephp/cakephp

preg_match や preg_replace をはじめ、PCRE を使った正規表現で、このような 1 行の文字列にバリデーションを行う際は、正規表現の修飾子として D (PCRE_DOLLAR_ENDONLY) を使用しないと、末尾の改行に対して検出漏れが発生してしまいます。

それどころか、m (PCRE_MULTILINE) なんてつけていると各行に対してパターンマッチングを行い、preg_match の場合はどれかひとつでもマッチすれば 1 を返すので、例えば以下のような文字列も OK、という恐ろしいことが起こってしまいます。

$result = preg_match('/^[\p{Ll}\p{Lm}\p{Lo}\p{Lt}\p{Lu}\p{Nd}]+$/mu', "abc\n<script>alert(1)</script>");
var_dump($result);
// => int(1)

あくまでユニットテストレベルでの検証ですが、このような恐ろしいことが起こっておりましたので、早いところ次のリリースが行われることを願うばかりです。
CakePHP 2.3.2 当たりで取り込まれることが予想されます。

こういう正規表現を書いていると徳丸先生方面から斧が飛んで来る可能性があります。
予め徳丸本等を読んで対策を行うことをオススメ致します。
あとは PHP のマニュアルとか PCRE 自体のマニュアルの PASSING MODIFIERS TO THE REGULAR EXPRESSION ENGINE 付近とか。
(なお、徳丸本の 81 ページによると ^ の代わりに ¥A を、$ の代わりに ¥z を使って、「行の」先頭・末尾ではなく「データの」先頭・末尾を示すようにせよ、とあります。)

なお、その他のバリデータも同様の問題をはらんでいる可能性もありそうですが、普段 CakePHP を使わない私としてはそこまで見切れないので、仕事等で CakePHP を利用している方は、是非とも contribute してみてはいかがでしょうか。

,