コンピュータやソフトウェアのあれこれ@道民(&元道民)
iakio
This user hasn't shared any biographical information
Homepage: http://d.hatena.ne.jp/iakio/
Posts by iakio
VagrantとChef-soloについて学んだいくつかのこと
5月 12th
ホストはWindows7、ゲストはCentOSです。
Vagrantのインストール
からダウンロードします。インストーラーが出来て便利になりましたね。
とりあえず起動
どのBoxがよいのかよくわからないけどとりえあず今はminimal-centos-56使っています。他は
から探しましょう。
> vagrant box add minimal-centos-56 http://dl.dropbox.com/u/9227672/centos-5.6-x86_64-netinstall-4.1.6.box > vagrant init minimal-centos-56 > vagrant up
Chef-soloを使う場合でも、とりあえずは何もせずに一度VMを起動して、VM内でknifeコマンドなどを使うのが良いと思います。
sshする
localhost:2222にsshします。鍵の設定をしてもいいけど、vagrant/vagrantでパスワードでログインできます。
Chef-soloを設定
ホスト側のカレントディレクトリ(Vagrantfileのあるディレクトリ)が/vagrantにVirtualBoxの共有フォルダがマウントされているので、ここで作業します。
$ cd /vagrant $ knife cookbook create myrecipe -o cookbooks WARNING: No knife configuration file found ** Creating cookbook myrecipe ** Creating README for cookbook: myrecipe ** Creating metadata for cookbook: myrecipe
何もしないcookbookができました。Vagrantfileの設定をします。前述の通りVagrantfileは共有フォルダにあるので、Windows側で編集してもLinxu側で編集してもOKです(ただしこの時点ではviしか入ってませんが)。
Vagrant::Config.run do |config|
config.vm.provision :chef_solo do |chef|
chef.cookbooks_path = "cookbooks"
chef.add_recipe "myrecipe"
end
end
この設定により、VirtualBoxの共有フォルダが新たに作られることになるので、再起動。
> vagrant reload
パッケージをインストール
では念願のvimをインストールします。yum installするのをグッとこらえて、cookbooks/myrecipe/recipes/default.rbに一行追加。
package "vim-enhanced"
で、vagrant provisionを実行。
> vagrant provision [default] Running provisioner: Vagrant::Provisioners::ChefSolo... [default] Generating chef JSON and uploading... [default] Running chef-solo... [Mon, 30 Apr 2012 03:56:27 -0400] INFO: *** Chef 0.10.4 *** [Mon, 30 Apr 2012 03:56:27 -0400] INFO: Setting the run_list to ["recipe[myrecip e]"] from JSON [Mon, 30 Apr 2012 03:56:27 -0400] INFO: Run List is [recipe[myrecipe]] [Mon, 30 Apr 2012 03:56:27 -0400] INFO: Run List expands to [myrecipe] [Mon, 30 Apr 2012 03:56:27 -0400] INFO: Starting Chef Run for localhost.localdom ain [Mon, 30 Apr 2012 03:56:27 -0400] INFO: Processing package[vim-enhanced] action install (myrecipe::default line 9) [Mon, 30 Apr 2012 03:57:40 -0400] INFO: package[vim-enhanced] installed version 7.0.109-7.el5 [Mon, 30 Apr 2012 03:57:40 -0400] INFO: Chef Run complete in 72.384425 seconds [Mon, 30 Apr 2012 03:57:40 -0400] INFO: Running report handlers [Mon, 30 Apr 2012 03:57:40 -0400] INFO: Report handlers complete
gitをインストールする(epelを使う)
http://community.opscode.com/cookbooks/yum
を使って、yumのリポジトリを追加します。knife cookbook site installというコマンドもあるのですがこれを使うには先にgitが必要なので、今回はdownloadコマンドを使います。
$ knife cookbook site download yum $ cd /vagrant/cookbooks/ $ tar zxf /path/to/yum-0.5.2.tar.gz
myrecipe/recipes/default.rbに追加。
include_recipe "yum::epel" package "git" package "vim-enhanced"
で、vagrant provisionすると、gitがインストールされます。
apacheをインストール、設定する
myrecipe/recipes/default.rbに追加。
package "httpd" service "httpd" do action [:enable, :start ] end
vagrant provisionでインストール、起動はOKです。Vagrantfileを編集してポートフォワードの設定をします。
config.vm.forward_port 80, 8080
今度はvagrant reloadが必要です。再起動後、ホストからlocalhost:8080でVMのapacheにアクセスできます。
DocumentRootを/vagrant/wwwに変更してみます。直接httpd.confを編集するのをグッとこらえて、
$ cp /etc/httpd/conf/httpd.conf /vagrant/cookbooks/myrecipe/templates/default/httpd.conf.erb $ vim cookbooks/myrecipe/templates/default/httpd.conf.erb
myrecipe/recipes/default.rbに追加。
template "/etc/httpd/conf/httpd.conf" do source "httpd.conf.erb" mode "0644" end
vagrant provisionでhttpd.confが書き換わります。反映するにはsudo /sbin/servecie httpd reloadするなど。
デバッグなど
VirtualBoxの画面を表示する
config.vm.boot_mode = :gui
vagrantのデバッグログ
> set VAGRANT_LOG=INFO > vagrant up
chefのデバッグログ
config.vm.provision :chef_solo do |chef|
chef.log_level = "debug"
その他、使ってみて思ったこと
こんな感じで環境を作って、/vagrantの下で作業をしていくのですが、VirtualBox共有フォルダは結構遅いので、git cloneのような大量にファイルを更新する場合はホスト側からやった方がいいかもしれません。
/vagrant の下に作ったファイルはパーミッションが0777になります。かつここでgit addすると常に0644でaddされます。なので0755にしたい場合は
$ git update-index --chmod=+x hoge.sh
とするようです。
cookbookの雛形を作ったり、レシピをダウンロードしてきたりなどするにはknifeコマンドが便利です(無くても出来なくないですが)。knifeコマンドはゲスト側にあってもホスト側にあってもいいと思いますが、今回の場合
- Windowsにknifeを入れるのは大変そう(試してないけど)
- Box側には最初からknifeコマンドは入っているが、gitがyumの標準のリポジトリに無い(knife site installコマンドを使うにはgitが必要)
と、なかなかやっかいな状況でした。
http://community.opscode.com/cookbooks/databaseを使ってMySQLのDBやユーザーを作る、みたいなことをやる場合は、knife site installであれば依存する他のレシピも自動的に持ってきてくれるので便利です。とはいえ、ちょっと大げさすぎる気がしたので、結局レシピ内でシェルスクリプトを書くことにしましたが。色々エレガントなやり方もあると思いますが、適当に落としどころを見つけるのも大事だと思います。
[PHP]PHPUnitのMockが生成するコード
5月 2nd
PHPUnitのMockはコードを生成してeval()しているわけだけれども、PHPUnit_Framework_TestCase#getMockBuilder()の引数に存在しないクラス名を渡すと、そのクラスを生成するコードも出力する。
どんなコードを生成しているかはこのように確認できる。
<?php require_once('Text/Template.php'); require_once('PHPUnit/Framework/MockObject/Generator.php'); $mock = PHPUnit_Framework_MockObject_Generator::generate('Foo'); echo $mock['code'];
実行結果:
class Foo
{
}
class Mock_Foo_ce23079e extends Foo implements PHPUnit_Framework_MockObject_MockObject
{
protected static $staticInvocationMocker;
protected $invocationMocker;
public function __clone()
{
$this->invocationMocker = clone $this->__phpunit_getInvocationMocker();
}
public function expects(PHPUnit_Framework_MockObject_Matcher_Invocation $matcher)
{
return $this->__phpunit_getInvocationMocker()->expects($matcher);
}
...
と、Fooを作ってからそのサブクラスを作って、そのオブジェクトをMockとして返している。
何でこんなことを調べたのかというと、Fooが存在しているつもりでモックを作ったのだが実は存在していなかった(requireされていなかった)、で、本当はFooのコンストラクタには引数が必要だったのだがそれを忘れていた。だがここでのFooはMockObject_Generatorが作った空のFooなので当然エラーにはならない。単体では。
しかしこれを他のテストケースと一緒に実行すると、別のところでFooがrequireされていたためにエラーが発覚する。というはまり方をしたためです。
[PHP]Symfony2/SilexのControllerResolverを読む2
4月 1st
ところで、Silex\ControllerResolverにはこんなコードがあるんですがこれは何をやってるんでしょうねえ。
protected function doGetArguments(Request $request, $controller, array $parameters)
{
foreach ($parameters as $param) {
if ($param->getClass() && $param->getClass()->isInstance($this->app)) {
$request->attributes->set($param->getName(), $this->app);
break;
}
}
return parent::doGetArguments($request, $controller, $parameters);
}
引数の$requestは当然リクエスト、$controllerは通常Silexでは無名関数、$parametersはReflectionFunction#getParameters()の戻り値で、ReflectionParameterの配列です。
http://jp2.php.net/manual/ja/reflectionfunctionabstract.getparameters.php
$param->getClass()->isInstance($this->app)
の部分ですが、ReflectionParameter#getClass()はReflectionClassのインスタンスを返すので、ReflectionClass#isInstance()が呼ばれています。これはこう書き換えることができます。
is_a($this->app, $param->getClass()->getName())
つまり、コントローラの仮引数がApplicationクラスであれば、$request->attributesにセットしている。で、最後にparent::doGetArguments()を呼び出しているということはもしかして、、、
コントローラの仮引数に"Application $app"って書いておけば勝手に渡ってくるってこと!?
つまり実は前回のコードはこれでいいってことか。
<?php require_once __DIR__.'/../vendor/.composer/autoload.php'; use Silex\Application; class LeapYearController { function indexAction(Application $app, $year) { //... } } $app = new Application(); $app->get('/is_leap_year/{year}', 'LeapYearController::indexAction'); return $app;
あーこれなら書いてみる気になりますね。よくできてるなーって関心しかけたけどいやいやいやいやちょっと待って。ってことは
$app->get('/hello/{name}', function($name) use($app) {
return 'Hello '.$app->escape($name);
});
じゃなくて
$app->get('/hello/{name}', function(Application $app, $name) {
return 'Hello '.$app->escape($name);
});
でも良かったってことじゃん!!
まだ続くかも。
Symfony2のControllerResolverを読む
3月 25th
"Create your own framework... on top of the Symfony2 Components"を読んで、Symfony2とSilexに興味がわいてきたので色々実験してみました。とりあえず"Create your own framework..."でやったCalendarをSilexでどう実装するかを考えてみます。
Silexといえば
$app->get('/hello/{name}', function ($name) use ($app) {
return 'Hello '.$app->escape($name);
});
$app->run();
こんな感じのコードというイメージがありますが、中身としてはpart6で出てきたSymfony\Component\HttpKernel\Controller\ControllerResolverの派生クラスであるSilex\ControllerResolverを使っていて、$app->get()の第二引数には無名関数以外のものも渡すことができます。
ではどのようなものを渡すことができるのか、ControllerResolver#getController()を見ると以下のようになっているようです。
1. コールバック配列。つまり array($class_name, $method_name) 又は array($obj, $method_name)
2. __invokeを実装するオブジェクト
3. __invokeを実装するクラスのクラス名
4. 関数名
5. クラス名::メソッド名
無名関数の場合は2.になります。"Create your own framework..."では
$routes->add('leap_year', new Routing\Route('/is_leap_year/{year}', array(
'year' => null,
'_controller' => 'LeapYearController::indexAction',
)));
のようにルートを定義していたので、同様に、Silexでも
$app->get('/hello/{name}', 'LeapYearController::indexAction');
と書くこともできます。しかしこれはあまり嬉しくありません。LeapYearController::indexAction()に$nameを渡すことはできますが、無名関数を使った時とは異なり、サービスロケーターである$appを参照することはできないからです。
ではSymfony2の場合はどうなっているのでしょうか。Symfony2のコントローラーはController#getContainer()でDIコンテナを参照することができます。これは、Symfony2のControllerResolverはSymfony\Bundle\FrameworkBundle\Controller\ControllerResolverであり、そこでControllerResolver#createController()をオーバーライドして、コントローラーをnewした後にController#setContainer()を呼び出しているためです。
Silexでも同様のControllerResolverを作ることも可能です。非常に雑に書くとこんな感じ。
<?php require_once __DIR__.'/../vendor/.composer/autoload.php'; use Silex\Application; use Silex\ControllerResolver; class MyControllerResolver extends ControllerResolver { function __construct($app) { $this->app = $app; } protected function createController($controller) { list ($obj, $method) = parent::createController($controller); $obj->app = $this->app; return array($obj, $method); } } class LeapYearController { function indexAction($year) { //... } } $app = new Application(); $app['resolver'] = $app->share(function () use ($app) { return new MyControllerResolver($app); }); $app->get('/is_leap_year/{year}', 'LeapYearController::indexAction'); return $app;
と、$app['resolver']に独自のControllerResolverのインスタンスを返す無名関数をセットしてやれば良いわけです。
とまあ長々と書きましたがこれはあくまで実験で、実際のところこういうやり方はSilexっぽくは無いと思いますw。ただ、これだけでもSilexやSymfony2がとても柔軟にできていることは実感できました。
続く予定。
[php]Symfony2のControllerResolverを読む
3月 25th
"Create your own framework... on top of the Symfony2 Components"を読んで、Symfony2とSilexに興味がわいてきたので色々実験してみました。とりあえず"Create your own framework..."でやったCalendarをSilexでどう実装するかを考えてみます。
Silexといえば
$app->get('/hello/{name}', function ($name) use ($app) {
return 'Hello '.$app->escape($name);
});
$app->run();
こんな感じのコードというイメージがありますが、中身としてはpart6で出てきたSymfony\Component\HttpKernel\Controller\ControllerResolverの派生クラスであるSilex\ControllerResolverを使っていて、$app->get()の第二引数には無名関数以外のものも渡すことができます。
ではどのようなものを渡すことができるのか、ControllerResolver#getController()を見ると以下のようになっているようです。
1. コールバック配列。つまり array($class_name, $method_name) 又は array($obj, $method_name)
2. __invokeを実装するオブジェクト
3. __invokeを実装するクラスのクラス名
4. 関数名
5. クラス名::メソッド名
無名関数の場合は2.になります。"Create your own framework..."では
$routes->add('leap_year', new Routing\Route('/is_leap_year/{year}', array(
'year' => null,
'_controller' => 'LeapYearController::indexAction',
)));
のようにルートを定義していたので、同様に、Silexでも
$app->get('/hello/{name}', 'LeapYearController::indexAction');
と書くこともできます。しかしこれはあまり嬉しくありません。LeapYearController::indexAction()に$nameを渡すことはできますが、無名関数を使った時とは異なり、サービスロケーターである$appを参照することはできないからです。
(2014/04/01追記:実は簡単にできました Symfony2/SilexのControllerResolverを読む2 - iakioの日記)
ではSymfony2の場合はどうなっているのでしょうか。Symfony2のコントローラーはController#getContainer()でDIコンテナを参照することができます。これは、Symfony2のControllerResolverはSymfony\Bundle\FrameworkBundle\Controller\ControllerResolverであり、そこでControllerResolver#createController()をオーバーライドして、コントローラーをnewした後にController#setContainer()を呼び出しているためです。
Silexでも同様のControllerResolverを作ることも可能です。非常に雑に書くとこんな感じ。
<?php require_once __DIR__.'/../vendor/.composer/autoload.php'; use Silex\Application; use Silex\ControllerResolver; class MyControllerResolver extends ControllerResolver { function __construct($app) { $this->app = $app; } protected function createController($controller) { list ($obj, $method) = parent::createController($controller); $obj->app = $this->app; return array($obj, $method); } } class LeapYearController { function indexAction($year) { //... } } $app = new Application(); $app['resolver'] = $app->share(function () use ($app) { return new MyControllerResolver($app); }); $app->get('/is_leap_year/{year}', 'LeapYearController::indexAction'); return $app;
と、$app['resolver']に独自のControllerResolverのインスタンスを返す無名関数をセットしてやれば良いわけです。
とまあ長々と書きましたがこれはあくまで実験で、実際のところこういうやり方はSilexっぽくは無いと思いますw。ただ、これだけでもSilexやSymfony2がとても柔軟にできていることは実感できました。
続く予定。
[PHP]Create your own framework… on top of the Symfony2 Componentsを読んだ
3月 20th
2ヶ月前にブックマークした”Create your own framework... on top of the Symfony2 Components”を写経しつつやっと読みました。面白かったです。
フレームワークがどのような問題を解決しようとしているか、という話もありますが、それよりSymfonyやSilexをやっている人が理解を深めるのに良いエントリなのではないでしょうか。
個人的に驚いたのはpart 6のControllerResolverのあたりでした。Requestから呼び出すべきコントローラーとその引数を決定するんですが、仮引数の名前とType Hintを使って、いい感じで引数を渡してくれるようになっています。
$routes->add('leap_year', new Routing\Route('/is_leap_year/{year}', array(
'year' => null,
'_controller' => 'LeapYearController::indexAction',
)));
// このようにルートが定義されていればコントローラ側では
...
public function indexAction($year) // 動く
public function indexAction($y) // エラー
public function indexAction(Request $request, $year) // 動く
public function indexAction($year, Request $request) // 動く
public function indexAction(Request $request) {
$year = $r->attributes->get('year'); // こう書けば動く
...
内部ではやはりリフレクションを使っているようですね。
https://github.com/symfony/HttpKernel/blob/master/Controller/ControllerResolver.php
あとComposerは便利ですね。nodeのnpm、RubyのbundlerのPHP版です。今後はスクリプト言語のパッケージ管理はこういう風になっていくんだろうなと思いました。
最後に「もっと知りたければ、Silexの、特にApplicationクラスを読んでみるといいよ」と言われたのでこれから読んでみたいと思います。
[その他]2011年はこんな年でした
12月 31st
- ほぼPHP。ちょっとJavaScriptとC#。お遊びでOCamlとCommon Lisp
- Android買った
- 実家より遠いところにはいかなった
- はじめてTDDBCに参加した
- はじめてえにしテックカフェに参加した
- 転職した
今年グっときた動画。
[Win32][PHP][tdd] Windowsでphpunit –colors
12月 16th
あるいはコマンドプロンプトでANSIカラーを表示させる方法。
Windowsのコマンドプロンプトでphpunit --colorsすると、
こんな感じになります。ゲンナリです。グリーンにならないならテストに対する意欲も半減です。コマンドプロンプトに色を付けるには、SetConsoleTextAttribute()とかいうWin32APIを使う必要があるらしいです。で、調べてたら、
WindowsのコマンドプロンプトでもANSIカラーを使いたい「wac」 - MOONGIFT|オープンソース・ソフトウェア紹介を軸としたITエンジニア、Webデザイナー向けブログ
こんなのを見つけました。これはANSIエスケープシーケンスを読みこんでSetConsoleTextAttribute()を呼び出してくれるもの。なんですが中を見てみたら結構手抜きの実装で、phpunit --colorsの出力では上手くいきません。
なんでこれを直してやろうかと色々試行錯誤していたら、別の方法を発見しました。
パイプでwacじゃなくて単にcatに渡してやればいい。このcatはmsysのcat(\msys\1.0\bin\cat.exe)です。msysGitを使ってればそっち(\Program Files\Git\bin\cat.exe)でも良いと思います。
どこのレイヤでそうなってるのかよくわからないような、当たり前といわれれば当たり前ような解決でした。
[php][tdd]TDD Boot Camp 札幌 2.3に参加してきた
12月 12th
お題はボーリングのスコア計算システム。PHPチームで参加してきました。
ドキュメントを見ようとしたら、PHPUnitのサイトに全然接続できなくてどうしようかと思いましたが、githubでドキュメントのソースのxmlを見ながらどうにか頑張りました。
@shuji_w6eからもお誉めの言葉をいただき、半日Emacsと格闘したかいがありました。
よかったこと
- PHPUnitで試してみたかったことが色々できた
テストメソッド名は日本語で書いてみました。--testdoxで出力する場合、英字の大文字小文字変換を行なうところで若干文字化けがあったため、先頭は英字という命名規則でやりました。testdoxを使わないのであれば問題無いと思います。
@testアノテーションも使ってみましたが、メソッド名にtest付けた方が速いかなという気もしましたw。@expectedException、@dataproviderアノテーション、markTestIncomplete()は普通に便利。assertThat()も使ってみたかったんですがドキュメント見るのしんどくて断念。
- 「1つ上のレイヤーからテスト駆動開発にアプローチする」というテーマを実践できた
- 簡単にテストを実行すること。見やすく書きやすいテストケースを書くことが重要ということを再認識できた
反省点
- 終了直前までボウリングのスコア計算のルールを勘違いしていた。まだまだ実装するつもりだったのに気付いたら機能テストが通っていた
- 機能テストが通った時点で満足したのと若干集中力が切れたのでニコ動とか見てた
他のチームの振り返りを聞いている途中でやっと何故テストが通ったのかきちんと頭が整理できました。振り返りの順番が最初だったら説明できなかったかもしれませんw
ちなみに見ていた動画はこちら
2008年の角谷さんの講演。2/3からボウリングスコア計算のライブコーディング。見所満載です。
その他
- Behatやってみたい
- JavaScriptやってみたい
- pgTAPやってみたい
- SmalltalkとTDDBCの相性は異常
- Perlのテストコードも見てみたい
- 関数型言語のテストコードも見てみたい
- SCMのイベントもできそうじゃない?
他にも色々ありましたが気が向いたら書きます。
[その他]deliciousで幸せになれる呪文
9月 14th
一時期は存続が危ぶまれたhttp://www.delicious.com/ですが、自分用のブックマークというよりは、何か調べごとがある時にはとても重宝しています。
例えばCakePHPについて調べようと思った場合、それとは別の軸のタグも使って検索するようにしています。
http://www.delicious.com/tag/cakephp+bestpractice
とか。
プログラミング関係で使えそうなタグはこんな感じ。
- bestpractice
- patterns
- tips
- hints
- tutorial
- reference
- hack
あとタイトルの元ネタ - もみあげチャ〜シュ〜 : xvideosで幸せになれる呪文 - ライブドアブログ

