ホーム > タグ > PHP

PHP

Pythonista見習いから、少しだけステップアップするための9クエスチョン from Quora

Python question

Pythonistaとしてステップアップするには何が必要か
それは、

Pythonの弱みと強みを知ること

でしょう。

これは他言語にも言えます。

Pythonを含め、プログラミング言語は道具です
道具を使うためには、その道具の最適な使い道を知ってる必要があります。

ただ、このような情報は、なかなか手に入りません。
ブログの記事は、書いた人の好みのバイアスが掛かっているため、鵜呑みに出来ません

道具を使いこなすには、自分自身で長時間、その道具に触れること
そして、他の人がどのように使っているのか、知ることが必要です。

そうして、その道具の強みと弱みを見出していきます。

前者は、あなたがひたすらに、時間を費やすしかありません。
しかし、後者に関しては良い方法があります

それが、Quoraです。

今日は、QuoraからPythonに関する質問をピックアップしていきます。

Pythonの弱みは、何か?

まずは、Pythonの弱みに関する質問です。
Pythonを使っていて微妙に思う点“がまとまっています。

スコープに関する話、インデントに関する話、lambda式に関する話…。
一度見ておくと、Pythonの弱みが見えてきます。

なぜ、PythonはPHPより良いのか?

1-byte.jpで取り上げる、もう一つの言語といえばPHPです。
つまり、この質問は外せません。

PHPとPythonの比較“がまとまっています。

PHPとPythonを両方使ったことがある人は、ここに書いてある内容に納得してしまいます。
確かに、Pythonではこんなことが出来て、PHPでは出来ないな…と。

どのプログラミング言語を始めるべきか、悩んでいる人も読んでおくと良いかと。

PHPにない、Pythonの便利な機能は?

こちらは、”PHPとPythonにおける機能面の比較“です。
先程の記事とは違い、コードベースでPHPとPythonが比較されています。

Pythonにはない、Rubyの良さは?

この質問を読むと、RubyとPythonがよく似た言語であることがわかります。
その中でも、”Pythonにはない、Rubyの使いやすい機能“がまとまっています。

これも、どの言語を始めるべきか、悩んでいる人にお勧めです。

ただし、Pythonistaがこれを読んでいると、Rubyも触りたくなるので、注意が必要です。

比較演算子を2回書かずに、値をn以上m以下で比較できる言語は?

私は、この観点で言語を比較したことがありませんでした。
確かに、

if (1 < $a and $a < 10) {
	print 'True'
}

と書くのは野暮ったいですよね。

Pythonのデコレータの主な使い道は?

デコレータを理解すれば、Pythonistaとしての幅が広がるはずです。
簡単な例と共に、デコレータの使い方が紹介されています。

まだ、デコレータなんて使ったことがない、という人にもお勧めです。

皆が嫌いな言語は?

ブログでも良く、この手の話が議論されますね。
俺はこの言語が嫌いだ。俺はPHPが嫌いだ。PHPなんて…(ry

筆頭に上がるのはPHPでしょうが、それ以外の言語もたくさん解答されています。
言語の強みと弱みを知るために、読んでおくと良いかもしれません。

どんな企業がPythonを使ってる?

この質問は、Quoraの真骨頂です。
Quora上には、有名な企業に務めるエンジニアがたくさんいます
そのため、こういった質問には多くの答えが返ってきます。

どんな企業が、Pythonを活用しているのか。
そして、自分たちがどの言語を使うべきなのか、見定めるのに参考になります。

なぜ、QuoraはPythonを選んだのか?

最後はQuoraに関する質問で閉めましょう。
Quoraは冒頭で述べたとおり、Pythonで出来ています。

この質問では、Quora創設者のAdamがPythonと他言語と比較しながら、Pythonの優位性を語っています。
Pythonの強みを知ることが出来ます。

いかがでしょうか。

ここに上げた質問はいずれも、ブログの記事として取り上げられません。
また、あったとしても、それはその人の意見です
一度に、これほどたくさんの意見を得ることは出来ません

また、QuoraがPythonで作られているせいなのか、たくさんのPythonistaがいます
新たに質問を作っても、すぐに答えてくれます。

これは私が試しに投げた質問ですが、1時間で4つの解答が得られました

ぜひ、Quoraを活用してください。
そして、一緒に、Pythonistaとしてステップアップしていきましょう。

おまけ

Pythonに限らず、こんな質問も上がってたり。

A Hard Days Nightのイントロのコードって、ビートルズの謎の一つなんですよね。

1週間でトリビア共有サイト”trivist”を作ってみた

trivist_logo.png

ここのところ、ブログの更新ツイッターのつぶやきも完全にストップしていました。
集中力のない@tfmagicianにしては珍しいことです。

何をしていたか。
こんなウェブ・サービスを作っていましたよ。

実はこれ、作成期間1週間です。

シンプルなサイトなので、恐らく、開発に慣れた人なら1週間は余裕でしょう。
今日は、まだフレームワークを使った開発、あるいはウェブ・サービスの開発自体に慣れていない人に向けて、高速開発に関するtipsを紹介します。

高速開発とは何か考える

rapid_development.png

まず、高速開発を可能にする”最強最大の魔法“を考えましょう。
それはこれです。

コーディングしない

コーディングしないで、システムが出来ればなんと良いことか!
これはエンジニアにとって、当たり前のことです。
しかし、これを念頭に置くのと置かないのでは、まるで開発速度が違ってきます

できるだけ、怠慢を考えます。

面倒だから、俺はコーディングしたくないんだよ!

これで良いんです。

フレームワークを使う

cake-logo.png

trivistではフレームワークとして、CakePHPを採用しています。

フレームワークの効果は説明するまでもないでしょう。
様々な機能がパッケージングされており、高速開発を可能にします

また、フレームワークに乗ることで、複数プロジェクトで利用可能な共通資産が作成可能です。
それが次の”プラグインを使う“です。

プラグインを使う

development_with_plugin.png

大抵のフレームワークには、プラグインの機能が備わっています。
このプラグインを活用することで、高速開発が可能です。

例えば、検索エンジン用のサイトマップ
trivistのように自動でページが増えていくサイトは、自動でサイトマップを生成する必要があります
MVCのフレームワークを使ったことがある人は、この処理がすぐにイメージ出来ます。

  1. モデル: id一覧の取得
  2. コントローラ: id一覧からURLを生成する
  3. ビュー: サイトマップの吐き出し

こんなところでしょう。
プラグインを知らない人は、この処理をプロジェクト毎に毎回作成するか、コードを前のプロジェクトからコピーするかのいずれかです。
しかし、プラグインを知っていれば、これらの処理をどんなプロジェクトでも使えるように共通化することが出来ます。
共通化したコードは、pluginsディレクトリに放り込むだけ。
あとは、configディレクトリに設定ファイルを書いておしまいです。

これなら、ほとんどコードを書かなくて済みます。
さらに、プラグインがしっかりテストされていれば、アプリケーションにおけるこの部分のテストは、ほぼ省略出来ます

trivistでは、以下のプラグインを使っています。

この中で特に強力なのが、CakePHP-Twitter-API-PluginTagsPluginです。

CakePHP-Twitter-API-Pluginは、TwitterのOAuth認証が2行で書けます
(自身のユーザテーブルにデータを格納する処理は別途必要ですが)

TagsPluginは、簡単にタグ付けとそのタグクラウドを実装できます
タグ登録時の分割処理も自分で書く必要がありません。
いつもの感覚でデータを保存すると、勝手にタグ付けして、勝手にタグクラウドを作ってくれます。

UtilsPluginのCsvImportBehaviorも便利です。

と、話出したら止まらないので、ここまでにします。
よくある機能は自分で実装する前に、プラグインを探してみましょう

ブログパーツを使う

development_with_widgets.jpg

ブログパーツも活用しましょう。
最近のウェブ・サービスで必要となるのが、ソーシャルメディアのコメントを表示する機能検索機能です。
trivistでは、以下のブログパーツを活用しています。

ソーシャルメディアのコメントを取得するのって、結構面倒なんですよね。
対応するサービス分のコードを書く必要がある
また、データが膨大に増えていくのも気になります。

Zenbackは、各ページのソーシャルメディアのコメントを自動で表示してくれるブログパーツです。
jsのコードを貼り付けるだけのお手軽実装です。
これで、どれだけ工数が削減できることか。

検索機能は、全文検索に対応させる必要があります。
その上、検索結果のランク付けも必要です。

そこはもう、本業のGoogleさんに任せてしまいましょう。
Googleカスタム検索も導入は簡単。
これで、検索実装からも解放されます。

このように、ソースコードだけでなく、外部サービスも活用します
アクセスの少ない立ち上げ当初は、これで十分かと。

自動化する

auto_deploy.png

ここまで来たら、サーバも晒します

さくらサーバVPS標準のCentOSでなく、Ubuntuを使っていることにはいくつか理由があります。

  • 最新のパッケージや比較的マイナーなパッケージを、自分でビルドせずに使える
  • セットアップスクリプトをUbuntu 10.04用に書き溜めてある

特に大きいのは、2つ目です。
セットアップスクリプトを書き溜めることで、サーバのセットアップを効率化します

先程の構成を作るには、apache_phpスクリプトとmysqlスクリプトを実行して、設定ファイルを少し変更するだけです。

また、アプリケーション自体のデプロイは、Capistranoのレシピを使います。
CakePHP用のレシピを作ってあるので、それを適用するだけで、自動デプロイ環境が完成です。

サーバ関連の作業で重要なのは、一回こっきりの作業をしないことです。
大抵の作業は、必ずと言っていいほど再び行います。
それを見越して、予め準備しておきましょう

どうでしょうか。
1週間でウェブ・サービスを立ち上げられる気がしてきましたか。

とにかく、あるモノをとことん利用しましょう
あなたの欲しいモノは、恐らく世界の誰かがすでに作っています
せっかく、公開されているのに使わなければ損です。
それが自分の要求を満たさなくても、改造することで自分の要求を満たせないか、考えてみましょう。
自分で作るよりも、そっちのほうが絶対に早いです。

最後に、trivistの応援もお願いします!
うまく起動に乗れば、もっと機能を拡張したいと考えています。
もっと、共有機能を充実して、仲間内で楽しめるサイトになれば、と。
どんなトリビアでも良いので、投稿お願いします

trivist_logo.png
『trivist』おもしろいトリビア・雑学を紹介!

CakePHP 1.3で大量のクエリを投げるときの注意点

cakephp_query_shell.png

今日は簡単な記事です。
自分がハマったので、他の人がハマらないように情報共有を。

CakePHPネタで、特にシェルを使っている人向けの記事です。
興味がない方はスルーしてください。

CakePHPで大量のクエリを投げる

CakePHPで、データベースに対して大量のクエリを投げたことがありますか?
お知らせメールでは、シェルもCakePHPで書いています。
そのため、一度の実行で大量のクエリが発行されることがあります。

クエリといっても、CakePHPのラッパーのModel::find()Model::save()のことです。
これをレコード分繰り返すことがあります。
この場合、一回の実行あたりのクエリ発行数は数千から数万に。

このような処理を書いていると、どうもおかしな現象に出くわします。

Warning: SQL Error: 1054: Unknown column 'ComicAuthorMergeQueue.base_id' in 'where clause' in /usr/lib/cakephp1.3/cake/libs/model/datasources/dbo_source.php on line 673
Query: SELECT `SearchIndex`.`id`, `SearchIndex`.`search_index` FROM `search_indices` AS `SearchIndex`   WHERE `ComicAuthorMergeQueue`.`base_id` = 6899

ん…こんなクエリ発行した覚えはないんだけど?

初めはモデル周りの参照の関係が原因だと考えました。
しかし、実はもっと根が深い問題で、CakePHPのキャッシュが絡んでいました。

CakePHPがWHEREをキャッシュしている

CakePHPはデフォルトでWHERE文をキャッシュしています
例として、Model::find()を取り上げます。

$params = array(
	'conditions' => array('id = ' => 1000));
$this->User->find('all', $params);
SELECT * FROM `users` AS `User` WHERE `User`.`id` = 1000;

この”‘conditions’ => array(‘id = ‘ => 1000)“と”WHERE `User`.`id` = 1000“の対応関係をキャッシュするのです。
キャッシュする場所はインスタンスのプロパティ。つまりメモリキャッシュです。
そのため、二度目に同じ条件のModel::find()を呼んだ場合、SQLは動的に生成されません。
メモリキャッシュからWHERE文を取り出し、そのまま使用します。

/**
 * Returns a quoted name of $data for use in an SQL statement.
 * Strips fields out of SQL functions before quoting.
 *
 * @param string $data
 * @return string SQL field
 * @access public
 */
  function name($data) {
    if (is_object($data) && isset($data->type)) {
      return $data->value;
    }
    if ($data === '*') {
      return '*';
    }
    if (is_array($data)) {
      foreach ($data as $i => $dataItem) {
        $data[$i] = $this->name($dataItem);
      }
      return $data;
    }
    $cacheKey = crc32($this->startQuote.$data.$this->endQuote);
    if ($return = $this->cacheMethod(__FUNCTION__, $cacheKey)) {
      return $return;
    }
/**
 * Cache a value into the methodCaches.  Will respect the value of DboSource::$cacheMethods.
 * Will retrieve a value from the cache if $value is null.
 *
 * If caching is disabled and a write is attempted, the $value will be returned.
 * A read will either return the value or null.
 *
 * @param string $method Name of the method being cached.
 * @param string $key The keyname for the cache operation.
 * @param mixed $value The value to cache into memory.
 * @return mixed Either null on failure, or the value if its set.
 */
  function cacheMethod($method, $key, $value = null) {
    if ($this->cacheMethods === false) {
      return $value;
    }
    if ($value === null) {
      return (isset($this->methodCache[$method][$key])) ? $this->methodCache[$method][$key] : null;
    }
    return $this->methodCache[$method][$key] = $value;
  }

ちょっと省略していますが、このコードがそのキャッシュに当たる部分です。
詳しく調べたい方はdbo_source.phpのDboSource::cacheMethod()を追ってください。

ここで見て分かる通り、crc32()を使ってハッシュを管理しています
実はこのcrc32()を使ったハッシュは数万件のデータで衝突が発生するようです。

衝突が起こると、まったく無関係のWHERE文がModel::find()やModel::save()に対して呼ばれます
その結果が冒頭のエラーです。

キャッシュをオフにする

この現象を突き止めるまでは色々と調べて回りました。
この情報に辿りつくまでに半日ぐらい掛かりました。

CakePHPのチケットです。
このチケットには、こうあります。

I think documentation is a good approach. Fully unique hashes can be slower than crc32 which is why it was chosen. Disabling the cache when its not helpful and providing good documentation on how to do that is probably more pertinent.

#870 Issues with DboSource::$methodCache – CakePHP – cakephpより

これに従って修正されたAPIドキュメントがこれです。

Caches result from query parsing operations. Cached results for both DboSource::name() and DboSource::conditions() will be stored here. Method caching uses `crc32()` which is fast but can collisions more easily than other hashing algorithms. If you have problems with collisions, set DboSource::$cacheMethods to false.

Commit e023350af57e07ac3351ab86c07361697fc1b369 to cakephp’s cakephp – GitHubより

なるほど。
DboSourceクラスcacheMethodsプロパティをfalseに設定すれば良いみたいです。
そうすれば、このキャッシュはオフになると。

余談ですが、CakePHP側ではこの部分の衝突を問題にしていないようですね。
パフォーマンスの問題と、使用方法を天秤にかけたのでしょう。
CakePHPはWebフレームワークで、シェルの重たい処理なんて想定してないでしょうから。

シェルでこのキャッシュ機能をオフにするために、AppShellクラスを作ります。

<?php
App::import('Shell', 'Shell');

class AppShell extends Shell {
  var $cacheMethods = false;

  function initialize() {
    $db =& ConnectionManager::getDataSource('default');
    $db->cacheMethods = $this->cacheMethods;
    parent::initialize();
  }
}

あとは、これを各シェルのクラスが継承すればOKです。

<?php
App::import('Shell', 'AppShell');

class EggShell extends AppShell {
}

キャッシュをオンにしたいシェルがある場合は、こう書きます。

<?php
App::import('Shell', 'AppShell');

class EggShell extends AppShell {
  var $cacheMethods = true;
}

これでOKです。
ここではシェルクラスにAppShellを継承させる形で解決しましたが、これはCakePHP 1.3.x限定です。
CakePHP 1.2.xではシェルクラスに自分で作ったクラスを継承させることができません(厳密に言えばできるのですが、色々と動かなくなるのでそれの対処が面倒くさい)。

ちなみに、この内容はPHP MatsuriのJIREI Nightで話した内容です。

JIREI NIGHT お知らせメール

もし、同じ現象でハマった場合は試してみてください。

PHP Matsuriに行って、日本のエンジニアのリアリティを感じてきた

php_matsuri_banner.jpg

先日、PHP Matsuriが盛大に開催されました。

1-byte.jpでもPHP Matsuriの紹介記事を1ヶ月ほど前に書きました。

これを書いたときは、不参戦表明をしていたのですが、急遽参加出来ることが決定
気張って参加してきました!

PHP Matsuriで何が起こったか

会場は、総勢約80名ほどのPHPer達の熱気が充満しています。
長い期間を掛け、準備してきたスタッフ達、今日のためにハックネタを用意してきたエンジニア達…。

とにかく、様々な方々が様々なところから、様々な期待を込めて集まっていました。
その中にはCakePHP、Lithium、Symfonyのコアデベロッパも含まれます。

PHP Matsuriと普通の勉強会は何が異なるか。
その一つがこのコアデベロッパの存在です。

普通に、他の勉強会でも海外のすごいデベロッパが来ています。
PHP Conference 2010でもPHPの生みの親である”Rasmus Lerdorf“さんも来ていましたしね。

しかし、PHP Matsuriでは”このデベロッパと一緒に開発出来る“ことが大きな差です。
徹夜で2日間、隣の席に座って開発出来るんです。
食事さえ一緒でした。
こんな機会は滅多にありません。

cakephp_graham.jpg
PHP Matsuriで一緒に開発するGrahamさん

Web上だけでは得られない”リアリティ“を感じた2日間でした。

英語を感じる

オープニング・セッション、”Linuxカーネル読書会“のよしおかひろたかさんから。

大人のためのプグラミングキャンプ

話の中心は”ハッカソンで何をすべきか、何を学ぶべきか“です。

内容は”Linuxカーネル読書会“や”プログラミング&セキュリティキャンプ“を踏まえて、こういった場でどのように大人のエンジニアが勉強するか
中学生や高校生が”プログラミング&セキュリティキャンプ“に来て、”英語のリアリティを感じる“という話がとても印象的でした。

始めはLinuxのカーネルを開発したいだけ。しかし、そのディスクリプションを英語で書く必要があることを知り、英語のリアリティを感じる。

これってとても大事ですよね。
自分が学生時代に足りなかったことでもあります。

さらに話は、”達人と弟子“へ。

Learn how to learn. “学び方を学ぶ”

実際に”Linuxカーネル読書会“、”プログラミング&セキュリティキャンプ“、そして”PHP Matsuri“に来て、あなたの達人を見つける。
そして、”達人がどのように学んでいるのか“を学び、弟子は達人になる。

その上で不可欠なのが”濃密なコミュニケーション“。
やはり、先ほどの”英語“の話に帰着します。

日本人技術者にとっての危機感ってなんなんだろな。などということをPHP祭りで思った。情報発信をしないと、どんどん世界からおいてけぼりをくらう。鎖国ができないとしたら、開国をして、日本から世界へ情報発信をしないと。 #phpmatsuri

まさに、”PHP Matsuri“はこれを感じ取り、学ぶ場であったように思えます。

技術を学ぶ

技術を学ぶ上で重要なのは、

  1. あなたの達人を見つけること
  2. 自分のモチベーションを高めること

この2つです。
PHP Matsuri“はこの両方が揃った場であったと言えます。

まずは1つ目、”あなたの達人を見つけること“です。
これは”コアデベロッパ“と”総勢約80名の日本のエンジニア達“です。

この中には自分よりもレベルの高いエンジニア、低いエンジニア、同じレベルのエンジニアがいます。
(ちなみに自分は最下層のほうですが:D)

そして、それぞれの得意分野も違います。

これだけいるのですから、見つからないのがおかしい話。
あとはあなたのコミュニケーション次第。積極的にいけばたくさん学べるし、一言も話さなければ一つも学べない。

それはコアデベロッパの方から学ぶときも同じことです。
今回、来日した4名の方々はとてもフレンドリーに接してくれました。
つまり、後は

英語を話せるかどうかではなく、英語を話そうとするかどうか。

ということです。
分からないことは聞いてみれば良いんです。
片言の英語と、ソースコードを見せながら。

今回、会場を見ていて思ったのは”これが出来る人と出来ない人の差が大きい“ということです。
これも一つのリアリティでした。
英語は完璧じゃなくていい“といった趣旨のブログ記事がよく上がりますが、このリアリティです。

英語を完璧に話すよりも、伝えることを優先する。

これも”日本と海外“のリアリティではないでしょうか。

ちょっと話が逸れている気がしますが、”PHP Matsuriには学ぶためのあなたの達人はたくさんいた。そして達人に学ぶかどうかはあなた次第。“ということです。

そして2つ目、”自分のモチベーションを高めること“です。
ハッカソン“という環境があなたのモチベーションを高めてくれます。

人間は不思議なもので、周りの感情次第で良くも悪くもなる。
その場の雰囲気が確実に自分に影響します。
もちろん、周りの雰囲気に関わらず、最高の状態を保てる人もごく少数いると思います。
しかし、少なくとも自分はそうではない。

私は完全に”ハッカソンの熱気“にやられた一人です。
その結果が”徹夜での開発“と”発表出来ないと思っていた成果物の完成“です。

この光景は夜中の会場を見れば感じてもらえると思います。

php_matsuri_0100am.jpg
午前1時の開発風景

これ午前1時ですよ。この時点でほぼ全員が開発を続けています。
そして、時間と共に人数は減りましたが、最終的に30名ほどの方が徹夜で開発していました。
そして残った方々はデモのぎりぎりまで、ひたすらコードを書いていました。
ちょっと皆さんおかしくないですか?(笑

しかし、この環境はすごく心地良いものです。
自分のやりたいことに没頭出来る。
聞きたいことは周りに聞ける。
周りからすぐにフィードバックがあり、それを成果物に含める。
そして、その人の開発がガンガン進み、周りもつられて開発を進める。

ま、もちろんエネルギーは使い果たしますが。

そして自分は

自分の成果物は次の2つです。

  1. “JIREI NIGHT お知らせメール”の発表
  2. “EmailPlugin”の公開

JIREI NIGHT お知らせメール

EmailPlugin

JIREI NIGHT“では15分の枠を頂き、CakePHPで開発した”お知らせメール“というサービスを発表させて貰いました。
ハッカソンではCakePHPのコアコンポーネントであるEmailComponentを使いやすくしたEmailPluginを作り、発表しました。

目に見える成果はこの2つですが、この他にたくさんの学びと自分を鍛え直す決意を得ました。

学び得たことは、技術的なことよりも精神的なことが多いです。
まさに”学び方を学んで来た“ようです。

  • 英語を話せるかどうかではなく、英語を話そうとするかどうか。
  • 自分の強みが相手の印象に。
  • ハック = アイディアであること。

英語を話せるかどうかではなく、英語を話そうとするかどうか。

これは先ほど説明した通りです。
英語を話そうとするかどうか“の意思の差はとてつもなく大きいです。
これが今の自分の”英語のリアリティ“です。

自分の強みが相手の印象に。

恐らく自分は、PHP Matsuriのエンジニアの方々に印象づけられなかったでしょうね。
強みが何一つない。
普通のことを普通にやっているだけじゃ、ダメなんです。
特にコミュニケーションの苦手な自分は。
それを感じ取れました。

テストの達人@sizuhikoさん。
KtaiLibrary@ecworks_masapさん。
TDD@hirocastさん。
CakePlugin@k1LoWさん。
CakePHP + mongoDB@cakephperさん。
CakePHPドキュメント翻訳@hiromi2424さん

他にもまだまだ印象深いエンジニアの方々は居たのですが、長くなってしまうのでここまで。

Webの情報がリアル(PHP Matsuri)に繋がり、そこ(PHP Matsuri)でやっていることでさらにその人を印象づける。

そういった流れで、今回顔と名前を覚えた方々もたくさん居ます。
こういった流れを自分も作って行きたいところです。

ハック = アイディアであること。

あくまで”ハック“なので、便利なものを追求する姿勢もありだと思います。
そもそも、その”便利“でさえ、アイディアであるとは思うのですが、今回感じたのは”別のアイディア“です。

最後のデモで”変態的“という言葉が飛び交っていました。
変態的“とは”突飛なアイディア“だと思います。

誰も考えつかなかったことを、2日間で形に仕上げる。

その短い時間と、その突飛なアイディアが相まって、最後のデモは異常な盛り上がりを見せていました。

ハック = アイディア

当たり前なのですが、これも意識出来るか、出来ないかで大きく変わってくると思います。

今回、私はこれを意識出来ませんでした。
実際に使えるものを。“という意識が、自分のアイディアを縛ってしまったのかもしれません。

次回は自由なアイディアで、”ハック“していきたいですね。

PHP Matsuriの感想はこれでおしまいです。
技術的なまとめは別に記事にしたいと思っています。
PHP Matsuriの一つの技術的中心として”PHP 5.3“がありました。
これについて、次回はまとめます。

来年は是非、皆さんも参加しましょう!
“@yandoさんは来年は開催するかどうかは分からない”と言ってますが、やってくれると信じて!:D
運営は相当しんどそうなので、皆、協力出来るところは協力して行きましょう。

PHP Matsuri、お疲れさまでした!

PHP + Apache2でMultiViewsを設定する方法

apache_server.jpg

先日、お知らせメールApache2とPHPの組み合わせに、MultiViewsを設定しました。
そうすると、Yahoo! Site Explorerの認証時に”406 Not Acceptable“のエラーが返されてしまいました。

どうやら、PHPファイルのMIMEタイプの設定が悪かったようです。
今回はメモ書き程度に、この設定方法をまとめます。

MultiViewsとは

そもそも、MultiViewsとは何でしょうか。

リソースをネゴシエーションするためには、 サーバは variant それぞれについての情報を知っておく必要があります。 これは以下の二つの方法のどちらかで行われます。
タイプマップ (すなわち *.var ファイル) を使う方法。 これは variant を明示的に挙げているファイルを指定します。
‘Multiviews’ を使って、サーバが暗黙の内にファイル名にパターン照合を 行なってその結果から選択する方法。

コンテントネゴシエーション – Apache HTTPサーバより

これにある通り、”複数あるファイルの選択肢から、クライアント側のHTTPヘッダ情報を元に送り返すファイルをApache2に選ばせる“機能です。
mod_negotiationというApacheモジュールに含まれています。

クライアント、よく使うのはFirefoxやSafariのWebブラウザですね。
このWebブラウザはサーバに対してHTTPヘッダを送信しています。

sending_http_header.png

サーバはこのHTTPヘッダを読み、その上で適切なファイルを選び、クライアントに送り返します。

receiving_http_header.png

これを使って何が出来るか、というと.php拡張子がついたPHPファイルに拡張子なしのURLでアクセス出来ます。

Apache2での設定

まず、MultiViewsを設定します。
Optionsディレクティブに+MultiViewsを指定するだけです。

<Directory /var/www/>
  Options -Indexes FollowSymLinks +MultiViews
  AllowOverride All
  Order allow,deny
  allow from all
</Directory>

Options All“だとMultiViewsがオンにならないため、注意が必要です。

次にPHP側の設定です。
設定方法はPHPのドキュメントに書いてあります。

デフォルトでは、PHPファイルのMIMEタイプは以下のように”application/x-httpd-php“に設定されています(設定ファイルはdebian系ならmods-available/php5.confに、RedHat系ならconf.d/php5.confにあります)。

<IfModule mod_php5.c>
  AddType application/x-httpd-php .php .phtml .php3
  AddType application/x-httpd-php-source .phps
</IfModule>

これを以下のように変更します。

<IfModule mod_php5.c>
  AddHandler php5-script php
  AddType text/html php
</IfModule>

これだけです。
これだけでPHPファイルに対して、MultiViewsが有効になります。
もちろん、”406 Not Acceptable“も発生しません。

ただし、この方法はApache 1.xだとうまく動かないようです。
詳しくは公式サイトを。

406 Not Acceptable

なぜ、デフォルトの設定だとYahoo! Site Explorerクローラを通さないのでしょうか
それは”クライアントが送ってきたHTTPヘッダを元に送り返すファイルを決定する“というMultiViewsの性質のせいだと思います。

デフォルト設定におけるPHPのMIMEタイプは”application/x-httpd-php“です。
このMIMEタイプだとクライアントがAcceptフィールドとして”text/html“だけを送ってきた場合に、サーバが適切なファイルとしてPHPファイルを選択出来ません
その結果が”406 Not Acceptable“です。

not_acceptable.png

これをPHPファイルが”html/text“だと指定してやることで、クライアントがAppectフィールドとして”text/html“を送ってきた場合に、サーバがPHPファイルを選択出来るようになります

html.png

簡単な説明ですが、理解出来たでしょうか?
そもそも、この説明が間違っている可能性もあるのですが…。

ちょっとサーバ周りで問題が出たので調べてまとめてみました。
間違いがある場合はご指摘ください。

CakePHP 1.3.4 リリース

cakephp_1_3_4_released.png

ちょっと記事を書くのが遅いですね(汗)
こういう記事は早く書かないと価値がない。

CakePHP 1.3.4がリリースされました。
CakePHP 1.3のマイナーバージョンアップで、いくつかのバグフィックスが含まれているようです。

簡単にThe Bakeryの記事を翻訳しておきます。

CakePHP 1.3.4 リリース

CakePHP開発チームはCakePHP 1.3.4をリリース出来たことをアナウンス出来て嬉しく思います。
このリリースにはコミュニティから上がったいくつものレポート、そしてチームの1ヶ月間の努力が含まれています。
CakePHP 1.3.3のリリースから77コミット[1]、そして54のチケットが消化されました。
あなたのアプリケーションに対して少し影響が出ます。

  • プラグインに対して生成されたスキーマファイルが、AppSchemaの代わりのクラスネームを保持する$PluginSchemaを持つようになりました。
  • Routeのパラメータに”-”を含んだ場合も正常に動くようになりました。
  • SessionComponent::destroy()がデータを削除しないことがある問題を修正しました。
  • Scaffoldがテーマに対して正常に動くようになりました。
  • String::insert()が他のキーのサブパターンから始まるキーに対して正常に動くようになりました。
  • DboMysqlがカラムとテーブルパラメータを正常に取得出来るようになりました。
  • setlocale()とfloatの10進の区切りとして’,'を使うローケルに対して起こるSQLエラーの問題が修正されました。
  • EmailComponentがネームエイリアスを含むアドレスをさらに正しく扱えるようになりました。

この最新版[2]をダウンロードし、変更歴を読んでアップデートを確認してください。

CakePHP 2.0のブランチは開発が進められており、いくつかのトピックブランチがマージされています。
どんな機能が2.0に含まれるか気になるなら、lighthouseのwiki[3]を見てください。
まだ完璧に仕上げる時間はありませんが、ここでだいたいのことはわかるでしょう。
1.3の一時wikiページはしばらく更新がないので、数週間以内に削除されるでしょう。
もし、あなたがこのページをリンクまたはブックマークしているなら、クックブックに切り替えてください。

コミットを通じて貢献してくれた全ての方に大いに感謝します。そして同じぐらい、チケットを投げてくれた方、ドキュメントを更新してくれた方、その他のところでフレームワークに貢献してくれた人に感謝します。

最新版のパッケージは[2]を。
変更歴は[1]を。
[1] http://cakephp.lighthouseapp.com/projects/42648/changelog-1-3-4
[2] http://github.com/cakephp/cakephp/downloads
[3] http://cakephp.lighthouseapp.com/projects/42648

さほど大きな変更はありませんね。
気になるのはSessionComponent::destroy()の修正ぐらいですか。

CakePHP 2.0の開発も順調に進んでいるよう。
コアデベロッパの方には本当に足を向けて寝られませんね。

PHPerのためのvi入門

viを使いこなしたいと思いませんか?

viを使いこなせればコーディング効率がアップします。
viのコマンドを考えずにコーディングできるようになれば最高!viは空気のような存在です。

そんなviですが、敷居が高いのも確か。
今回はPHPを題材にしてviで必須と思われるコマンドとその使い方をあげていきます。

なぜ、viか

まずは「なんで今更viなの?」という疑問に答えます。
ただし、私自身、様々なエディタを通してviにたどり着いた訳ではありません。
ですので、なぜ私がviを使うことにしたのか、使いたくなったのか、を書いておきます。

  • カッコいい
    • もう、8割ぐらいの理由がこれです。だって、ターミナル開いていろいろなコマンドを駆使しながらコーディングしている姿ってカッコいいじゃないですか。まさにハッカーです。GUIのエディタじゃ、ちょっと格好がつかない。ハッカーに憧れる人なら、こんな気持ちは持つでしょう?
  • 動作が速そう
    • ターミナル上で動いてるんだから、GUIと違って動作速度は断然、良いでしょう。コーディングは自分の頭の中を吐き出す作業だから、外部要因で止まってはいけない。エディタはキビキビ動かないといけないんです!

こんな単純な理由でviを使い始めました。

今では当初思い描いていた以上に、viは両方の欲望を叶えてくれています。

viのモード

vi入門といえば、まずはここから始まります。コマンドモードと入力モードです。

viには2種類の「モード」があります。
それぞれのモードには役割があり、これを切り替えて使います。

他にもありますが、切り替えるキーは”Esc”と”i”です。
viを開いた場合は自動的にコマンドモードになります。以下にモードの違いをまとめます。

モード できること
コマンドモード
  • カーソルの移動
  • 文字の削除
  • 文字の検索
  • 様々な編集機能
入力モード
  • 文字の挿入

viを開いて、”i”キーを押せば入力モードです。あなたの好きな文字を入力できます。

入力が終わったら、”Esc”キーを押してコマンドモードに戻します。各キーがコマンドとして認識されます。
キーを押してもファイルには入力されません。その代わりに、キーに割り当てられたコマンドが動作します。
試しに、”j”キーを押してみてください。カーソルが下に1つだけ下がります。

これがviの基本です。”Esc”と”i”を交互に押して感覚をつかんでください。
これを知らずにviを始めると、早速挫折します。なんたって、ファイルを開いても文字列を入力できないんだから!
(私はこれを理解せずにviを始めようとして2〜3度挫折しています。いくら習うより慣れろ、でも少しは調べてから始めましょうね…)

viでのコーディング作業

それでは早速コーディングに入ります。
ここでは白紙の状態からコードを書いていきます。
まずは作業ディレクトリを作り、その下に移動、viでファイルを開きます。

cd ~/
mkdir vi_project
cd vi_project
vi main.php

白紙の画面が開いたでしょうか?
環境によって違いますが、このようなイメージです。

screen01.png

ここで”i”キーを押します。そうすると入力モードになるので、どんどんコードを書いていきましょう。
今回書いたコードは以下の通り。

&lt;?php
$persons = array(
  'John Lennon',
  'Poal MacCartnoy',
  'George Harrison',
  'Ringo Star',
);
foreach($persons as $person) {
  printf(&quot;Hello %s.\n&quot;, $person);
}

?&gt;

余分なスペースやスペリングミスはそのままタイプしてください。
次の段階で修正します。

また、短いコードでもタイプミスはするでしょう。
そのときは何も気にせず、十字キーとバックスペースで修正してください。
慣れると入力モードとコマンドモードを行き来しながらコードを書いていくのですが、慣れないうちはこれで十分だと思います。

viでのコード修正作業

続いてコードを修正します。
コーディングしたソースコードはスペリングミスや余分なスペースがあり、美しくありません。
これをコマンドモードで修正します。

まずはスペリングミスから修正しましょう。このままではBeatlesファンに怒られてしまうので…。
スペリングミスがあるのは”Poal MacCartnoy”と”Ringo Star”です。
以下の手順で修正します。

  1. “Poal MacCartnoy”の”Mac”から”a”を取り除く。
  2. 修正された”Poal McCartnoy”の”o”を”e”に置き換える。
  3. “Ringo Star”の”r”の後ろにさらに”r”を加える。

これをviのコマンドで手順化するとこうなります。
(無駄なコマンドもありますが、練習ということでご了承ください。)

  1. “Esc”をタイプ
  2. “:”"0″をタイプ
  3. “j”をPoal MacCartnoyの行に来るまで連続タイプ
  4. “l”をMacCartnoyのaにカーソルが来るまで連続タイプ
  5. “x”をタイプ
  6. “l”をMacCartnoyのoにカーソルが来るまで連続タイプ
  7. “r”"e”をタイプ
  8. “j”をRingo Starの行に来るまで連続タイプ
  9. “l”を’Ringo Star’の最後のシングルクォーテーションに来るまで連続タイプ
  10. “i”をタイプ
  11. “r”をタイプ
  12. “Esc”をタイプ

これで修正は完了です。修正後はこのようになります。

&lt;?php
$persons = array(
  'John Lennon',
  'Poal McCartney',
  'George Harrison',
  'Ringo Starr',
);
foreach($persons as $person) {
  printf(&quot;Hello %s.\n&quot;, $person);
}

?&gt;

勘の良い人なら何をやったか、わかると思います。
viのコマンドには一定のルールと、GUIのショートカットキーと同じように単語の頭文字やキーボードの配置でコマンドが決められています。

今打ったコマンドの意味を以下にまとめます。

  • “Esc”
    • コマンドモードに戻る。
  • “:”"0″
    • 0行目にカーソルを移動する。
  • “j”
    • カーソルを下に移動する。
  • “l”
    • カーソルを右に移動する。
  • “r”"e”
    • カーソル位置の文字をeに置き換える。
  • “x”
    • カーソル位置の文字を1文字分、削除する
  • “i”
    • 入力モードに移る。

カーソルの移動

まずはカーソル移動系のコマンドです。ここで出てきているのは”j”(下に移動)と”l”(右に移動)だけですが、もちろん左と上にも移動できます。

移動系のコマンド

コマンド できること
h 左にカーソルを移動する。
j 下にカーソルを移動する。
k 上にカーソルを移動する。
l 右にカーソルを移動する。

これらの移動系コマンドはキーボードの位置で決められています。
このような形です。

vi_keyboard.png

viでも十字キーは使用できますが、頑張ってこのコマンドを覚えることをお勧めします。
カーソル移動をコマンドで出来ることがviの魅力の一つだからです。
手の位置を動かさずにカーソルの移動や編集が出来るのは気持ちいいですよ。
デメリットがあるとすれば、GUIでも間違ってコマンドを打ってしまうことでしょうか…(その場合は当然、jやらlやらがテキストとして入力される)。

指定された行への移動

次は、指定された行に移動するためのコマンドです。

“:”"行番号”

“:”キーを押すとexコマンドというコマンドが打てます。
exコマンドについてはググってもらうことにして、ここでは”:”と行番号を入力すると、特定の行に移動できることを覚えてください。
よく使うのは”0″(先頭)と”$”(末尾)です。
“$”は行番号と違うじゃないか!と思われるかもしれませんが、viでは”$”は末尾の行番号の代わりとして使われます。
いちいち、末尾の行番号を確認するのも面倒ですから、これは重宝します。

文字の削除

次は文字の削除です。

“x”

“x”を押すだけでカーソル位置の文字を1文字分、削除できます。
バックスペースに手を伸ばすよりも、早くタイプできるのでこれもよく使います。
手癖にしてしまってください。

文字の置き換え

次は文字の置き換えです。

“r”"置き換える文字”

“r”と”置き換える文字”をタイプするだけで、カーソル位置の文字を1文字分置き換えます。
typoしたときによく使用します。typoをtypeに直すのも”r”を使えば簡単です

viでの見栄えの調整

見栄えを調整します。先ほどのコードは最後に余分なスペースがあります。
これをコマンドで取り除きます。

  1. “Esc”キーをタイプ
  2. “j”キーで余分なスペースがある位置まで移動
  3. “d”"d”をタイプ
  4. もう一度、”d”"d”をタイプ

これだけです。これで下の2行分の余分なスペースが削除できました。

行の削除

行ごとを削除するには以下のコマンドを利用します。

“d”"d”

“d”を2度タイプすると、行の削除です。
GUIでは行を削除するために選択モードにして、バックスペースをタイプする、あるいはバックスペースを押しっぱなしにするしかありません。
しかし、viを使えばキーを2度タイプするだけで済んでしまいます。
これもviの魅力の一つです。

viでのコードの書き換え

最後にソースコードを書き換えます。”俺はジョンよりポール派だ!だからポールに先に挨拶したいんだ!”という人がいるかもしれません。
そんなときはviのカット&ペーストに相当する機能を使います。

  1. “Esc”キーをタイプ
  2. “j”キーでPoalの位置まで移動
  3. “d”"d”をタイプ
  4. “k”キーで$personsの行まで移動
  5. “p”キーをタイプ

これでJohnよりもPoalが先に来ます。

ペースト

viでペーストするには以下のコマンドを利用します。

“p”

“p”をタイプするとバッファに入った文字列がテキストにペーストされます。
バッファに文字列を入れる方法はいくつかありますが、”d”"d”の行の削除コマンドがその1つです。
削除時に入るバッファは1つだけです。つまり、最後に削除した行が”p”コマンドでペーストできます。

保存と終了

ここまでの編集を保存して終了しましょう。

保存

保存には次のコマンドを使います。

“:”"w”

終了

viを終了します。終了には次のコマンドを使います。

“:”"q”

これでviが終了して、コマンドを打ち込む画面が戻ってきたはずです。

最後に

ここまでのコマンドを覚えれば、とりあえずviを操作できるようになると思います。
viの世界はとにかく広いです。
そして、今回例としてあげた操作は、無駄が多すぎます。
viのコマンドを知り尽くせば、もっともっと早く編集できるはずです。

“とりあえずviを始めようという人”も、”viを極めたい!”という人も、次のステップに進むために、まずはオライリーのvi入門を読むと良いと思います。
この本を読まずにviを独学で始めたら大変苦労しました。

入門vi 第6版 vi入門

  • 著者: リンダラム
  • 出版社: オライリー
  • 発売日: 2002/05

私自身もviを使い始めて、1年経っていません。
まだまだ、勉強中の身です。もっともっとviのことを知りたいです。
一緒にviの世界を楽しみましょう。

[2010/09/02 追記]

viを使いたくなる良い記事を見つけました。
目を通してみてください。こんなエンジニアになりたいと思いますよ。

* ちなみにここでは全てviで統一していますが、実際に使うことが多いのはviを拡張したvimだと思います。操作はほとんど同じですが、vimはマルチビューに対応していたり、カラーリングしてくれたり、色々使い勝手の多い機能があります。
* “PHPerのための”とタイトルをつけましたが、なかなかチュートリアルを作るのが難しく、中途半端になってしまいました…。普通のvi入門じゃん!という苦情はご勘弁くださいorz

CakePHPを使ったテスト駆動開発

ブログ初ポストはCakePHPを使ったテスト駆動開発です。

CakePHPはユニットテストとしてSimpleTestに対応しています。
SimpleTestをインストールするだけで、モデルやコントローラ、シェル、ルーティングクラスなどのユニットテストが出来るようになります。

今日はこのCakePHPとSimpleTestを使ってテスト駆動開発の流れを説明します。
ただ、僕自身テスト駆動開発を学んだのは去年のCake祭りなので、至らない点が多々あります。
もし何かあれば、コメントでご指摘ください。

今更感もありますが、この場を借りてCake祭りでテスト駆動の指導をしてくださった、@sizuhikoさんに感謝します。

開発手順

まずは開発手順を示します。少し細かいですが、テスト駆動では以下のような順で開発していきます。

  1. 設計する。
  2. テストケースを書く。
  3. テストケースをデバッグする。
  4. コードを書く。
  5. テストケースを実行する。
  6. コードをデバッグする。
  7. テストケースを全て通す
  8. コードが完成する。

コードとテストケースが分かれていること、それに始めにある程度設計してしまうのがポイントです。
設計といっても、私がやるのは白紙のA4用紙にクラスとメソッド名を書き出すぐらいです。
こうして置くと、テストケースもコードも書きやすくなります。

これだけ見ても、実感がないと思うので実例で説明していきます。

ランキングモデルの開発

良くある例としてCakePHPを使ったランキングを作ります。
といっても全てを作る時間はないので、ランキングデータを取り扱うランキングモデルをテスト駆動開発で作ります。

設計

モデル名やスキーマを次のようにします。

  • モデル名 : Ranking
    • addGood : goodを追加するメソッド
    • getGoodTitles : goodの順にデータを取得するメソッド
  • テーブル名 : rankings
    • id : 主キー
    • good : 良いと思った人の数
    • title : ブログのタイトル

ブログにgoodボタンをつけて、それを押すとスキーマのgoodが1増えるイメージです。
例としてはありがちですが、facebookの「良いね!」機能から考えました。

今回は書きませんが、コントローラからaddGoodメソッドやgetGoodTitlesメソッドを呼びます。

テストケース・コーディング

設計が決まったらテストケースを書いていきます。さっそく書き始めたいですが、その前に色々とやることがあります。
空の状態(CakePHPのappディレクトリ)からの開発を想定してますので、モデルやテーブルを作る必要があります。

まず、データベース設定をして、この画面が出るようにしましょう(設定の仕方は省略します)。

startup.jpg

次はschema.phpを作りましょう。schema.phpはCakePHPでデータベースのスキーマを管理出来るファイルです。
ここにスキーマ情報を書き込んでおけば、データベースのマイグレーションが簡単になります。

cake schema generate

Welcome to CakePHP v1.3.2 Console
---------------------------------------------------------------
App : ranking
Path: /home/tfmagician/ranking
---------------------------------------------------------------
Cake Schema Shell
---------------------------------------------------------------
Generating Schema...
Schema file: schema.php generated

schema.php

<?php
/* SVN FILE: $Id$ */
/* Ranking schema generated on: 2010-07-13 17:07:41 : 1279010981*/
class RankingSchema extends CakeSchema {
  var $name = 'Ranking';

  function before($event = array()) {
    return true;
  }

  function after($event = array()) {
  }

}
?>

まだ、データベーステーブルを作っていないので空の状態でテンプレートが出来上がります。
ここに設計したスキーマ情報をプロパティとして加えます。

  var $rankings = array(
    'id'    => array('type' => 'integer',  'null' => false, 'default' => NULL, 'key' => 'primary'),
    'title' => array('type' => 'string',   'null' => false, 'default' => NULL, 'key' => 'index'),
    'good'  => array('type' => 'integer',  'null' => false, 'default' => 0),
    'indexes' => array(
      'PRIMARY' => array('column' => 'id',    'unique' => 1),
      'title'   => array('column' => 'title', 'unique' => 0),
    ),
    'tableParameters' => array(
      'charset' => 'utf8',
      'collate' => 'utf8_general_ci',
      'engine'  => 'MyISAM',
    ),
  );

これを元にCakePHPにテーブルを作らせます。

cake schema create

Welcome to CakePHP v1.3.2 Console
---------------------------------------------------------------
App : ranking
Path: /home/tfmagician/ranking
---------------------------------------------------------------
Cake Schema Shell
---------------------------------------------------------------

The following table(s) will be dropped.
rankings
Are you sure you want to drop the table(s)? (y/n)
[n] > y
Dropping table(s).
rankings updated.

The following table(s) will be created.
rankings
Are you sure you want to create the table(s)? (y/n)
[y] >
Creating table(s).
rankings updated.
End create.

これで指定したスキーマのテーブルが出来ているはずです。phpmyadminなどで確認しておきましょう。

次にテストで使うコードとモデルのコードをbakeで作成します。

cake bake model

Welcome to CakePHP v1.3.2 Console
---------------------------------------------------------------
App : ranking
Path: /home/tfmagician/ranking
---------------------------------------------------------------
---------------------------------------------------------------
Bake Model
Path: /home/tfmagician/ranking/models/
---------------------------------------------------------------
Possible Models based on your current database:
1. Ranking
Enter a number from the list above,
type in the name of another model, or 'q' to exit
[q] > 1
Would you like to supply validation criteria
for the fields in your model? (y/n)
[y] > y

Field: id
Type: integer
---------------------------------------------------------------
Please select one of the following validation options:
---------------------------------------------------------------
1 - alphanumeric
2 - between
3 - blank
4 - boolean
5 - cc
6 - comparison
7 - custom
8 - date
9 - decimal
10 - email
11 - equalto
12 - extension
13 - inlist
14 - ip
15 - maxlength
16 - minlength
17 - money
18 - multiple
19 - notempty
20 - numeric
21 - phone
22 - postal
23 - range
24 - ssn
25 - time
26 - url
27 - userdefined
28 - Do not do any validation on this field.
... or enter in a valid regex validation string.

[28] >

Field: title
Type: string
---------------------------------------------------------------
Please select one of the following validation options:
---------------------------------------------------------------
1 - alphanumeric
2 - between
3 - blank
4 - boolean
5 - cc
6 - comparison
7 - custom
8 - date
9 - decimal
10 - email
11 - equalto
12 - extension
13 - inlist
14 - ip
15 - maxlength
16 - minlength
17 - money
18 - multiple
19 - notempty
20 - numeric
21 - phone
22 - postal
23 - range
24 - ssn
25 - time
26 - url
27 - userdefined
28 - Do not do any validation on this field.
... or enter in a valid regex validation string.

[19] >
Would you like to add another validation rule? (y/n)
[n] >

Field: good
Type: integer
---------------------------------------------------------------
Please select one of the following validation options:
---------------------------------------------------------------
1 - alphanumeric
2 - between
3 - blank
4 - boolean
5 - cc
6 - comparison
7 - custom
8 - date
9 - decimal
10 - email
11 - equalto
12 - extension
13 - inlist
14 - ip
15 - maxlength
16 - minlength
17 - money
18 - multiple
19 - notempty
20 - numeric
21 - phone
22 - postal
23 - range
24 - ssn
25 - time
26 - url
27 - userdefined
28 - Do not do any validation on this field.
... or enter in a valid regex validation string.

[20] >
Would you like to add another validation rule? (y/n)
[n] >
Would you like to define model associations
(hasMany, hasOne, belongsTo, etc.)? (y/n)
[y] >
One moment while the associations are detected.
---------------------------------------------------------------
Please confirm the following associations:
---------------------------------------------------------------
Would you like to define some additional model associations? (y/n)
[n] >

---------------------------------------------------------------
The following Model will be created:
---------------------------------------------------------------
Name:       Ranking
DB Table:   `rankings`
Validation: Array
(
    [title] => Array
        (
            [notempty] => notempty
        )

    [good] => Array
        (
            [numeric] => numeric
        )

)

Associations:
---------------------------------------------------------------
Look okay? (y/n)
[y] >

Baking model class for Ranking...

Creating file /home/tfmagician/ranking/models/ranking.php
Wrote `/home/tfmagician/ranking/models/ranking.php`
SimpleTest is not installed. Do you want to bake unit test files anyway? (y/n)
[y] > y

You can download SimpleTest from http://simpletest.org

Baking test fixture for Ranking...

Creating file /home/tfmagician/ranking/tests/fixtures/ranking_fixture.php
Wrote `/home/tfmagician/ranking/tests/fixtures/ranking_fixture.php`
Bake is detecting possible fixtures..

Creating file /home/tfmagician/ranking/tests/cases/models/ranking.test.php
Wrote `/home/tfmagician/ranking/tests/cases/models/ranking.test.php`

(あ、忘れていましたが、cakeコマンドはcakeコンソール(cake/console/cake)のことです。適宜置き換えてくださいね。)

これでテストケースとフィクスチャ(後で説明します)、モデルのテンプレートが完成しました。
あとはコーディングしていくだけです。

と、一つ忘れていたことがありました。SimpleTestのインストールです。
SimpleTestはアプリケーションのvendorsか、CakePHP本体と同じ階層のvendorsに配置します。

cd vendors/
wget http://downloads.sourceforge.net/simpletest/simpletest_1.0.1.tar.gz
tar zxvf simpletest_1.0.1.tar.gz
rm simpletest_1.0.1.tar.gz

配置したら、ブラウザからtest.phpにアクセスします。

test.php
test_php.jpg

左メニューからApp > Test Casesをクリック
test_cases.jpg

コンテンツからmodels / Rankingをクリック
ranking_test.jpg

bakeで作ったテンプレートのテストが実行されているのがわかります。
まだ何も書いていないですが、テンプレートのテストが実行され、1件のテストケースが通っています。

これでテストが実行出来るようになりました。テストケースをコーディングします。
テストケースはさきほどのbakeの最後に書かれている通り、tests/cases/models.ranking.test.phpにあります。
これを元に、オリジナルのテストケースをコーディングします。

tests/cases/ranking.test.php

/* Ranking Test cases generated on: 2010-07-13 18:07:16 : 1279014616*/
App::import('Model', 'Ranking');

// テストケース用のクラスはTestCaseで終わる名前にし、CakeTestCaseを継承する。TestCaseより前はファイル名と一致させる。必ずしもテストするクラス名と一致させる必要はない。
class RankingTestCase extends CakeTestCase {
  var $fixtures = array('app.ranking');

  function startTest() {
    $this->Ranking =& ClassRegistry::init('Ranking');
  }

  function endTest() {
    unset($this->Ranking);
    ClassRegistry::flush();
  }

  /**
   * addGoodメソッドのテスト
   */
  // testで始まるメソッドがテストとして実行される。テストメソッドは必ずtestでメソッド名を始めること。
  function testAddGood() {
    debug('addGoodメソッドのテスト');

    // 正常: id=1のデータに対してaddGoodメソッドを実行する。
    // 確認: 返り値がtrueであること、goodがプラス1されていること。
    $ret = $this->Ranking->addGood(1);
    $this->assertTrue($ret);
    $params = array(
      'conditions' => array('id = ' => 1),
      'fields' => array('good'),
      'recursive' => -1
    );
    $data = $this->Ranking->find('list', $params);
    $expected = array(1 => 2);
    // この部分がテスト: 期待する結果とメソッドの戻り値を比較して等しいならテストが通る。等しくないならテストが失敗する。
    $this->assertEqual($expected, $data);

    // 異常: id=2のデータに対してaddGoodメソッドを実行する。
    // 確認: 返り値がfalseであること。
    $ret = $this->Ranking->addGood(1);
    // この部分がテスト: メソッドの戻り値がfalseならテストが通る。false以外ならテストが失敗する。
    $this->assertFalse($ret);

  }

  /**
   * getGoodTitlesメソッドのテスト
   */
  // testで始まるメソッドがテストとして実行される。テストメソッドは必ずtestでメソッド名を始めること。
  function testGetGoodTitles() {

    // 正常: 取得件数を5件に設定し、getGoogTitlesメソッドを実行する。
    // 確認: 上位から5件のデータが取得出来ること。
    $ret = $this->Ranking->getGoogTitles(5);
    $expected = array(
      array(
        'Ranking' => array(
          'id' => 1,
          'title' => 'title1',
          'good' => 10,
        ),
      ),
      array(
        'Ranking' => array(
          'id' => 2,
          'title' => 'title1',
          'good' => 9,
        ),
      ),
      array(
        'Ranking' => array(
          'id' => 3,
          'title' => 'title1',
          'good' => 8,
        ),
      ),
      array(
        'Ranking' => array(
          'id' => 4,
          'title' => 'title1',
          'good' => 7,
        ),
      ),
      array(
        'Ranking' => array(
          'id' => 5,
          'title' => 'title1',
          'good' => 6,
        ),
      ),
    );
    // この部分がテスト: 期待する結果とメソッドの戻り値を比較して等しいならテストが通る。等しくないならテストが失敗する。
    $this->assertEqual($expected, $ret);

  }

}
?>

コメント中にも書いてありますが、assertメソッドがテストです。
例えばassertEqualメソッドは、「2つの変数が等しいことを期待する」という意味になります。
この期待に応えない(変数が等しくない)場合、テストは失敗します。後でスクリーンショットで示します。

またテストメソッドが実行される順は、次のようになります。

  1. startTest
  2. テストメソッド1(testで始まるメソッド)
  3. endTest
  4. startTest
  5. テストメソッド2(testで始まるメソッド)
  6. endTest

なのでこのテストケースでは、以下の順でテストが実行されます。

  1. startTest
  2. testAddGood
  3. endTest
  4. startTest
  5. testGetGoodTitles
  6. endTest

必ずstartTestとendTestメソッドが呼ばれる点がポイントです。これらのメソッドでクラスの初期化などの処理を実施し、テストを書きやすくします。

このテストではデータの挿入、更新を行っています。しかし、さきほどテーブルを作ったばかりなのでデータはまだ入っていないはずです。
逐一、テストの度に手作業でデータを入れてもいいのですが、それはとても面倒です。そこでフィクスチャという機能を使います。

CakePHPのフィクスチャ機能はテーブルのデータをPHPのコードとして書いておくと、勝手にテスト前にデータを挿入してくれます。
挿入のタイミングはテストメソッド(テストケースの中のtestで始まるメソッド)毎です。つまり、特定のテストメソッドでデータを更新したり削除しても、次のテストメソッドでは新しいデータが入っています。
これを知っておくと、テストケースを書くのが楽になります。

tests/fixtures/ranking_fixture.php

/* Ranking Fixture generated on: 2010-07-13 18:07:16 : 1279014616 */
class RankingFixture extends CakeTestFixture {
  var $name = 'Ranking';

  // スキーマ情報をfieldsプロパティとして書いておきます。indixesはインデックスの指定、tableParametersはテーブルの設定値です。schema.phpを同じ形式で指定します。
  var $fields = array(
    'id' => array('type' => 'integer', 'null' => false, 'default' => NULL, 'key' => 'primary'),
    'title' => array('type' => 'string', 'null' => false, 'default' => NULL, 'key' => 'index'),
    'good' => array('type' => 'integer', 'null' => false, 'default' => '0'),
    'indexes' => array('PRIMARY' => array('column' => 'id', 'unique' => 1), 'title' => array('column' => 'title', 'unique' => 0)),
    'tableParameters' => array('charset' => 'utf8', 'collate' => 'utf8_general_ci', 'engine' => 'MyISAM')
  );

  // データをrecordsプロパティとして書いておきます。
  var $records = array(
    array(
      'id' => 1,
      'title' => 'title1',
      'good' => 5,
    ),
    array(
      'id' => 2,
      'title' => 'title2',
      'good' => 6,
    ),
    array(
      'id' => 3,
      'title' => 'title3',
      'good' => 7,
    ),
    array(
      'id' => 4,
      'title' => 'title4',
      'good' => 8,
    ),
    array(
      'id' => 5,
      'title' => 'title5',
      'good' => 9,
    ),
    array(
      'id' => 6,
      'title' => 'title6',
      'good' => 10,
    ),
  );
}
?>

フィクスチャの書き方は省略します。ただ、連想配列で指定するので少し考えれば意味はわかるかと思います。
書き方がわからない場合はschema.phpを参考にしてください。

テストケースのデバッグ

作成したテストケースをデバッグします。まだモデル側のコードは書いていませんが、まずはtest.phpを開いてみましょう。

test.php
ranking.jpg

メソッドをまだ用意していないため、モデルにクエリを発行されてしまっています(CakePHPでは存在しないメソッドは全てクエリとして扱われます)。
これではデバッグにならないので、モデルにメソッドだけ用意しましょう。

models/ranking.php

<?php
class Ranking extends AppModel {
  var $name = 'Ranking';
  var $displayField = 'title';
  var $validate = array(
    'title' => array(
      'notempty' => array(
        'rule' => array('notempty'),
        //'message' => 'Your custom message here',
        //'allowEmpty' => false,
        //'required' => false,
        //'last' => false, // Stop validation after this rule
        //'on' => 'create', // Limit validation to 'create' or 'update' operations
      ),
    ),
    'good' => array(
      'numeric' => array(
        'rule' => array('numeric'),
        //'message' => 'Your custom message here',
        //'allowEmpty' => false,
        //'required' => false,
        //'last' => false, // Stop validation after this rule
        //'on' => 'create', // Limit validation to 'create' or 'update' operations
      ),
    ),
  );

  function addGood($id) {
  }

  function getGoodTitles($n) {
  }
}
?>

再度、test.phpを開きます。

test.php
test_debug.jpg

そうすると、一つだけクエリが発行されているのがわかります。
すでにお気づきの方がいるかもしれません。そうです、テストケースで実行しているRankigクラスのメソッド名がgoogTitlesとなっているのです。
よくあるtypoですね。修正し、再度テストケースを実行します。

test.php
test_debug_passed.jpg

これでテストケースのコードは大丈夫のようです。この段階でテストは通っていなくとも大丈夫です。
なんせ、まだ本体のコードを書いていないのですから。

コーディング

それでは本体のコードを書いていきます。先ほど追加した空のメソッドに以下のようにコーディングします。

models/ranking.php

  function addGood($id) {
    $this->id = $id;
    if(!$this->exists()) {
      return false;
    }
    return $this->updateAll(array('good' => 'good + 1'), array('id = ' => $id));
  }

  function getGoodTitles($n) {
    $params = array(
      'limit' => $n,
      'page' => 1,
      'order' => 'good DESC',
      'recursive' => -1,
    );
    return $this->find('all', $params);
  }

コーディングが完了したらtest.phpのページを再度開きます。

test.php
test_passed.jpg

テストが通ると、テストした件数と緑色のバーが表示されます。
この緑色のバーと作成したテスト件数があっていれば、テストは完全に通っています。

ここではあっさりテストを通してしまいましたが、実は1度のコーディングでテストが完了した訳ではありません。
この程度のコードでも2〜3度、テストを失敗し、コードを書き直しています。

やってみるとわかるのですが、テストケースがあることで簡単に、かつ手軽にコードを書き換えることができます。

テストケースを使ったテスト駆動開発を知るまでは、var_dumpを活用し、逐一途中のデータを見ながらコードを書いていました。
しかし、この方法だと仕様が自分の頭の中にしかないため、メソッドの機能が多様化してしまったり、想定しないバグに出くわしたりします。

テストケースを書くことで、頭の中にあるメソッドの仕様をコードとして書き出すことになります。
そうすることで、曖昧だった点や考慮しなければいけないケースも浮き彫りとなります。
さらにコードとして書き出すことで、メソッドを作る上での目標が定まり、コーディングしやすくなります。

テストケースというコードが増えるため、これを書くのを面倒に思ってしまいますが、この面倒さ以上のメリットがテスト駆動開発にはあると私は思います。

テスト駆動開発をする上でのポイント

最後に、テスト駆動開発でのポイントを書いておきます。参考にしてください。

  • テストケースを必ず始めに書く。
    • 後から書こうと思うと面倒くさくなる。また、後から書くと先ほど書いたメリットを受諾出来ない。
  • メソッド、クラスの機能追加時も必ずテストケースを書く。
    • テストケースも更新していかないと、せっかく書いたコードが無駄になる。テストケースはコードがしっかり動いていることの証拠である。
  • バグを見つけたときこそ、必ずテストケースを書く。
    • テストケースは上記と同様にコードがしっかり動いていることの保証である。
  • バグを見つけたときは、テストケースでしっかり対策する。
    • 統合テストでバグを見つけた場合はデバッグし、原因を見つける。その原因をメソッドレベルにまで落とし、そのメソッドのテストケースを書き、解決する。従ってテストケースも独立していることが重要である。
  • テストケースを出来るだけ独立させる。
    • 対象のメソッド、クラス以外の部分で仕様変更があった場合に、テストが通らなくなるのはテストケースのメンテナンスが面倒になる。出来るだけテストケースの対象を1つのメソッド、あるはクラスにしぼる。

これまでテスト駆動開発をやってきて学んだことを上げてみました。
テスト駆動開発で重要なことは一言で言うとメンテナンスしやすいテストケースを書くことだと思います。
テスト駆動開発をこれから始める方は、この点に注意すると、私の二の舞*1にならないかと思います。

ご参考になれば幸いです。

*1 テスト駆動開発を始めた頃は良かったのだが、その後メンテナンスが大変になった。結局、テストケースを全て捨てることに…。

ホーム > タグ > PHP

スポンサードリンク
書いている人
つぶやき
RSS 気になるニュース
過去の記事

ページの上部に戻る