2014年はこんな年でした
気になったこと
phpspec、behat
- 知らないうちにphpspecがすごいことになっていた件 - iakioの日記
- 1つのSubjectに集中せよ - phpspecのコンセプト - iakioの日記
- "Design How Your Objects Talk Through Mocking"を見た - iakioの日記
- Introducing Modelling by Example - BDDの新しいアプローチ - iakioの日記
phpspecには本当に感銘をうけた。他のxUnit Frameworkとはかなり考え方が違うので合う合わないはあると思うけど、 「単純で小さな部品を組み合わせてソフトウェアを作る」という発想は自分の理想に近いなと思った。
今のところQiitaにphpspecについて書いているのは私だけのようだけど。
TypeScript
「あれなんかわかんねーな、ここだけJavaScriptでいーや」みたいな撤退がしやすいところがTypeScriptの良いところだと思う。 変数は型を持たないけど型アノテーションは付けられるよというのが流行りつつあるようだ。 phpではタイプヒント実行時に見てDIとかMockを生成するみたいなのが流行っているけれども、 TypeScriptでも型情報を何か別の用途に使えないかなと思っている。
Brackets
ここ数年、デザイナーとプログラマーがどうやって一緒に仕事をしていくかみたいな話がよくあるけれど、 Extract for Bracketsがその間をいい具合に埋めるツールにならないかなと思っている。 あと、TypeScriptでコード書くときも結構使える。
もっと前はこんな年でした
そもそも本当に個人情報をデータベースに保存していいのか
Kazuho's Weblog: Heartbleed脆弱性と、その背後にあるWebアプリケーションアーキテクチャの一般的欠陥について
今年最も気になった記事です。
にもかかわらず、ウェブ関連のソフトウェアにおいては同原則を用いずに、安全性の根拠をプログラムにバグがない点に求めるという悪しき慣習が続いています。特に、機能別の権限分離はまだしも、アクセスユーザー別の権限分離については系統だった実施例が非常に少ないという印象をもっています。
たとえば、SQL Injectionに代表されるSQL関連の情報漏洩も、アクセス制御にRDBMSのアクセス制御機構を用いず、アプリケーションプログラム内のSQL(とそのエスケープ)が正しく記述されている点に、安全性の根拠を求めているが故に発生しているわけです注1。
DJBがWebアプリケーションを作ったらどんなアーキテクチャになるんだろうと妄想する。まずリクエスト毎に別のユーザーIDでchrootだろうが、DB周りは難しいな / “Kazuho's Weblog: Heartbleed脆弱性…” http://t.co/FUaKT2a1gI
— ISHIDA Akio (@iakio) April 11, 2014
ここでDJBの名前を出したのは、ずっと前から気になっていたこの記事を思い出したからです(もしかしたら若い人はDJBとかqmailとか知らないかもしれないけど)。
セキュリティに関するいくつかの考察 - qmail 1.0 から十年 (Some thoughts on security after ten years of qmail 1.0)
この中でDJBは、セキュリティホールを無くするための取り組みの一つとして、「信頼されたコードをなくす」というのを挙げています。
「信頼されていない」というのは、 これらの檻の中のコードは — 何をしようと、どんなに悪いふるまいをしようと、 どれほど多くのバグがあろうと — ユーザのセキュリティ要求を 侵犯できないという意味である。
一般的なWebアプリケーションでは、全てが「信頼されたコード」として作られているように思います。
SQL Injectionされないように実装することは当然重要ですが、 SQL Injectionが可能になったくらいですべてのデータが丸見えになってしまうようなアーキテクチャというのは、 Webサーバーをroot権限で動かしているのと同じような危険を冒しているのかもしれません。
例えば原始的なWikiのように、誰でも同じデータを参照できて編集できるようなWebアプリケーションであればこれで問題はありませんが、 現在の多くのWebアプリケーションは、そうではなくなってきています。 にもかかわらず、アーキテクチャは変わっていません。
ではどうするべきなのか。今すぐには解決策は思いつきません。 データベース側で何かをすることが難しければ、レイヤを一つあげて、ORMあたりが常にユーザーの権限を意識するような作りは可能かもしれません。あと詳しく見てませんが最近Googleに買収されたFirebaseなんかは面白そうだなと思っています。
StackPHPを使ってみる(Middlewareを作る)
自分でMiddlewareを作る
ひな形はこんな感じ。
<?php // 何もしないミドルウェア class MyMiddleware implements HttpKernelInterface { private $app; public function __construct(HttpKernelInterface $app, array $options = []) { $this->app = $app; } public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQUEST, $catch = true) { return $this->app->handle($request, $type, $catch); } }
テンプレートエンジンをMiddlewareとして実装してみる
Mustacheを使ってみる。一番単純に実装するとこんな感じ。
<?php class Mustache implements HttpKernelInterface { private $app; private $engine; public function __construct(HttpKernelInterface $app, array $options = []) { $this->app = $app; $this->engine = new Mustache_Engine($options); } public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQUEST, $catch = true) { $request->attributes->set('mustache', $this->engine); return $this->app->handle($request, $type, $catch); } }
使う側はこんな感じ。
<?php $app = new CallableHttpKernel(function (Request $request) { $token = $request->attributes->get('oauth.token'); if (!$token) { return new RedirectResponse('/auth'); } $params = $token->getExtraParams(); return new Response($request->attributes->get('mustache')->render('Hello, {{ name }}', ['name' => $params['screen_name']])); }); // ... $stack = (new Stack\Builder()) ->push('Stack\\Session') // ... ->push('Mustache');
returnがかっこ悪いのでヘルパーメソッドにするとかTraitを使うのが良いかもしれない。その前にそろそろCallableHttpKernelから卒業しよう。
<?php class MyApplication implements HttpKernelInterface { public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQUEST, $catch = true) { $token = $request->attributes->get('oauth.token'); if (!$token) { return new RedirectResponse('/auth'); } $params = $token->getExtraParams(); return new Response($request->attributes->get('mustache')->render('Hello, {{ name }}', ['name' => $params['screen_name']])); } }
さてrenderメソッドを実装してみようと思ったんだけど、これこうなっちゃうな。
public function render($template, $params, $request) { return new Response($request->attributes->get('mustache')->render($template, $params)); } // 又は public function render($template, $params, $engine) { return new Response($engine->render($template, $params)); }
MiddlewareはRequestを受け取ってResponseを返すだけなので、Middlewareからアプリケーションに何かを渡すには$request->attributes経由ということになるんだけど、そうするとrenderメソッドの引数が1つ増えることになってイマイチ。 この辺がStackPHPを使う上でのキモかもしれない。
あるいはこう書けるようにした方がいいか。
return $request->attributes->get('mustache')->render('Hello, {{ name }}', ['name' => $params['screen_name']]);
Mustache_Engine#render()は文字列を返すけどHttpKernelInterface#handle()はResponseを返さなければならないので、単にMustache_Engineのインスタンスを返すのではなく、render()メソッドの生えた何かを返すようにしよう。本来は別のクラスを作った方が良さそうだけど単純にMiddlewareにrenderメソッドを実装してしまおう。
<?php class Mustache implements HttpKernelInterface { // ... public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQUEST, $catch = true) { $request->attributes->set('mustache', $this); $request->attributes->set('mustache.engine', $this->engine); return $this->app->handle($request, $type, $catch); } public function render($template, $params) { return new Response($this->engine->render($template, $params)); } }
あと、renderの引数はファイル名にしよう。
$stack = (new Stack\Builder()) ->push('Stack\\Session') // ... ->push('Mustache', [ 'loader' => new Mustache_Loader_FilesystemLoader(__DIR__ . '/views'), ]);
ここでMustache_Loader_FilesystemLoaderをnewするのは好きじゃない人もいるかもしれない。stack-sessionなんかは内部にPimpleのインスタンスを持っていて実行時に評価されるようにしているようだ。
テンプレートを作る。Mustache_Loader_FilesystemLoaderのデフォルトでは拡張子".mustache"を要求する。
<!-- views/index.html.mustache --> <h1>Hello, {{ name }}</h1>
使う方はこうしておこう。
$mustache = $request->attributes->get('mustache'); return $mustache->render('index.html', ['name' => $params['screen_name']]);
StackPHPを使ってみる(Twitter認証)
StackPHPというのが正式名称なのかわからないけど。
ひな形
- HttpKernelInterfaceを実装した$appを準備する。
- スタックを作る。
- $stack->resolve($app);
- $appを実行する
<?php $app = ...; // 1. $stack = (new Stack\Builder()) // 2. ->push(...) ->push(...); $app = $stack->resolve($app); // 3. // 4. $request = Request::createFromGlobals(); $response = $app->handle($request)->send(); $app->terminate($request, $response);
Hello, world
1のHttpKernelInterfaceを実装は、SymfonyやLaravelのアプリケーションでも良いのだけれど、
より簡単に作るためにstack/callable-http-kernel
というのが用意されている。
また、4の部分を簡単に書くために、stack/run
というのが用意されているので一緒にインストールしておく。
$ php composer.phar require stack/builder stack/callable-http-kernel stack/run
ちなみに最近、composer requireでバージョンを指定しなくても良くなったらしい。
<?php // index.php require "vendor/autoload.php"; use Stack\CallableHttpKernel; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; $app = new CallableHttpKernel(function (Request $request) { return new Response("Hello, world"); }); $stack = new Stack\Builder(); Stack\run($stack->resolve($app));
何もstackにpushしてないけどとりあえずこれで動く。
$ php -S localhost:9000
Twitter認証してみる
stack/sessionとigorw/stack-oauthをインストールする。
$ php composer.phar require stack/session:dev-master igorw/stack-oauth:dev-master
<?php require "vendor/autoload.php"; use Stack\CallableHttpKernel; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\RedirectResponse; $app = new CallableHttpKernel(function (Request $request) { $token = $request->attributes->get('oauth.token'); if (!$token) { return new RedirectResponse('/auth'); } $params = $token->getExtraParams(); return new Response("Hello, " . $params['screen_name']); }); $stack = (new Stack\Builder()) ->push('Stack\\Session') ->push('Igorw\\Stack\\OAuth', [ 'key' => getenv('OAUTH_KEY'), 'secret' => getenv('OAUTH_SECRET'), 'callback_url' => 'http://localhost:9000/auth/verify', 'success_url' => '/', 'failure_url' => '/auth', ]); Stack\run($stack->resolve($app));
一応動くけど多分このigorw/stack-oauthバグってるな。
続くかも。
Introducing Modelling by Example - BDDの新しいアプローチ
@everzet氏のブログ記事より。例によって翻訳は無理なので気になったところだけ要約。
http://everzet.com/post/99045129766/introducing-modelling-by-example
- Cucumber、Behat、SpecFlowなどのGherkin-based BDD toolのシナリオをユビキタス言語で 書くといいんじゃない?
つまり、
Scenario: Showing delivery cost for a product on the basket page Given there is a product: | name | White Marker | | price | £5 | And I am on the "/catalogue" page When I click "Buy" in the "White Marker" product block And I go to the "/basket" page Then I should see a list with 1 product And the overall price should be shown as £9
ではなく、
Scenario: Getting the delivery cost for a single product under £10 Given a product named "White Marker" and priced £5 was added to the catalogue When I add the "White Marker" product from the catalogue to the picked up basket Then the overall basket price should be £9
こう書く。
そしてUIやインフラストラクチャー層を除く、ドメインモデルだけのstep definitionから実装を始める。 一部のシナリオをピックアップして、同じフィーチャーに対してエンドツーエンド用のstep definitionを書く。 つまり、1つのシナリオに対して2種類のstep definitionを書くことになる (このフィーチャーに対してはこのstep definitionを使ってね、ということを定義する"suites"という 機能をBehat v3に実装した。CucumberやSpecFlowを使っている人は中の人におねだりしよう)。
こうすることで、インフラストラクチャ層がコアドメインをするのを素早く発見できるようになる。
BDDとDDDは共にTranslation Costを排除しようとしているがレイヤーが異なっている。 BDDは対話、DDDはコードにフォーカスしている。二度翻訳するのは無駄だし間違うかもしれないよね。
インフラストラクチャー層はシナリオをパスするための手段だ。 全てのアプリケーションがMySQLへの接続を必要としているからではなく、 永続化レイヤーが欠けていることによってシナリオがパスしなくなったから永続化レイヤーをアプリケーションに追加するんだ
"Design How Your Objects Talk Through Mocking"を見た
Konstantin Kudryashov - Design How Your Objects Talk Through Mocking at Laracon EU 2014 - YouTube
BehatやPhpSpecの作者である@everzetによるLaracon EU 2014での講演です。 英語が苦手なので翻訳することはできませんが印象的かつ聞き取れた部分を要約して紹介します。
モックはデータベース接続等の遅い部分をテストから分離して速くするためのものだと思われているが、それは誤解だ(そのような目的ではFake Implementationを使う)。
モックは、結果や状態に注目したクラシカルなTDDでは隠されていたオブジェクト間のメッセージングをさらけだし、設計の問題を明らかにするデザインツールだ。
メッセージングではなく、結果をさらけ出そうとするのなら、コードにさらにgetterを追加することを強いられるだろう
存在しないメソッド、インタフェース、クラスをモックしたことを知らせてくれないような壊れたMocking Frameworkを使わない
自分が所有していないオブジェクトをモックしない
(QAより)
Q:例えばS3のようなサードパーティー製のAPIを使う場合はどうすべきか。
A:インターフェースを作り、それを実装したS3と通信するクラスを作る。そのクラスのインテグレーションテストを書き、インターフェースをモックする。
インテグレーションテストはPHPUnitや他のテスティングフレームワークで書き、実際にS3にファイルが保存されるかをテストする。
感想。
サンプルコードはPHPUnit+Prophecyという構成で書かれています。説明のためにより広く知られている記法を選んだのではないかと思いますが、PhpSpecで書けばより簡潔なコードになるでしょう。
Mockを使った開発の問題として、Mockと実装が食い違ってしまうという点が挙げられる場合があります。しかしそれはMocking Frameworkがチェックすべきだと@everzetは指摘しています。多くのMocking Frameworkでは存在しないクラスやメソッドをMockすることができますが、PhpSpec/Prophecyではエラーとなります。
個人的な経験としては、PhpSpecを学ぶことで、Mock、システム境界、Hexagonal Architecture が一つの線で結ばれ、The GOOS bookに書かれていることが初めて理解できたように思います。
最初にThe GOOS Bookを読んだときは、「モックするのは自分の持っている型だけ」の意味が理解できなかったのですが、PhpSpecでコードを書いてみると、そもそもモックを書くのは次に実装しようとしているコラボレーターの場合が多く、モックは設計なんだ、既に存在しているものをモックする意味は無いんだということが自然に理解できたように思います。
The GOOS Bookって良い本だけどちょっと難しいので、同じようなテーマでPhpSpecで書かれたものがあればいいなあ。
実践テスト駆動開発 (Object Oriented SELECTION)
- 作者: Steve Freeman,Nat Pryce,和智右桂,高木正弘
- 出版社/メーカー: 翔泳社
- 発売日: 2012/09/14
- メディア: 大型本
- 購入: 4人 クリック: 262回
- この商品を含むブログ (30件) を見る
学習のためにGithubを徘徊する
ちょっと間が空きましたが前回の補足。
プログラミング初心者が中・上級者になるためには、GithubのリポジトリをWatchすればいいんじゃないかな - iakioの日記
- 自分で問題を解決しようとするだけだと、自分の発想にとらわれがちなので、他人のコードも参考にした方が良い
- だけど、コードを0から読むのは大変なので、興味のあるところからつまみ食いするのが良い
- そこで、GithubのPull RequestをWatchするのはどうか。興味の持てそうな話題以外は読み飛ばしても良い(数十件に1つくらいを真面目に見てみるくらいの感覚で良いと思う)
という話でした。
さて実際、僕がGithubを徘徊していてなるほどなと思った体験を1つ挙げてみます。
Gitlistは、PHPで実装されたGitのリポジトリビュワーで、Silexというフレームワークを使って実装されています。SilexはSymfonyのコンポーネントを使った軽量Webフレームワークで、Symfonyの開発者でもあるfabpotことFabien Potencier氏が開発しています。
で、このGitlistを見ていたら、fabpotからのPull Requestがありました。
Refactoring by fabpot · Pull Request #74 · klaussilveira/gitlist · GitHub
例えて言うなら、RailsのアプリケーションをGithubで公開していたらDHHからPull Requestが来たみたいな話です。まさに「Silexを使うならこう使え」と言わんばかりの内容でした。
特に今まで何のためにあるのかわからなかったPimpleのextendメソッドの使い方はなるほどなと思いました、という話は以前にも書いたので詳しくはこちらをご覧ください。
Pimple 2.0がリリースされたのでPimpleについて復習してみる - iakioの日記
さて、そういったことを思い返してみると、人に注目してみるのはGithubを徘徊する1つの方法かもしれません。あのライブラリを書いた作者は他にはどんなものを作っているのだろう。どんな他のプロジェクトに注目しているのだろう、といった具合に。
あるいは、Most active GitHub users (by contributions). http://twitter.com/paulmillrで、お気に入りの言語からアクティブなユーザーを見つけて、そこからたどってみるのも良いかもしれません。