レベル: 中級 Duane O'Brien (d@duaneobrien.com), PHP developer, Freelance
2007年 12月 04日 初期のバージョンの PHP に対する一般的な批判の中に、PHP が MVC (Model-View-Controller) スタイルのアーキテクチャーをサポートしていないというものがありました。現在では、開発者は数多くの PHP フレームワークの中から好きなものを選択することができます。この「PHP のフレームワーク」シリーズは、広く使われている 3 つの PHP フレームワークである、Zend、symfony、そして CakePHP を取り上げます。そして、この 3 つの各フレームワークを使ってサンプル・アプリケーションを作成し、また拡張しながら、各フレームワークの類似点と相違点を検証します。このシリーズの第 1 回ではこのシリーズ全体の概要を示し、このシリーズを進めていく上で必要なことを行いました。第 2 回では 3 つの各フレームワークを使ってサンプル・アプリケーションを作成しました。この第 3 回では、このサンプル・アプリケーションを拡張し、またフレームワークのルールから外れた場合の処理について調べます。
このシリーズについて
このシリーズは、フレームワークを使いたいと思いながら、現在利用可能なフレームワークの詳細を検討する機会がなかった PHP 開発者のためのものです。このシリーズでは、この 3 つのフレームワークが選ばれた理由と各フレームワークのインストール方法を検証します。また、この 3 つの各フレームワークでテスト・アプリケーションを拡張して適切に扱えるようにします。そう聞くと膨大な作業に思えるかもしれませんが、心配はありません。必要な情報は理解しやすいブロックに分かれています。
第 1 回では、まずこのシリーズ全体の概要を示し、検証対象となるフレームワークを紹介するとともに、そのインストール方法を説明しています。さらに、これから作成する最初のテスト・アプリケーションについても説明しています。
第 2 回では、3 つの各フレームワークを使ってサンプル・アプリケーションを作成するための手順を、それぞれの類似点と相違点に焦点をあてながら説明しています。
第 3 回では、まずテスト・アプリケーションを拡張し、次にフレームワークのルールから外れた場合の処理を扱います。どのフレームワークも、それらのフレームワークが本来目的とする作業を実行する場合は適切に動作します。しかしどのプロジェクトでも、そのフレームワークでは想定されていない作業をしなければならない場合が必ずあります。この記事では、そうした例について説明します。
第 4 回では Ajax のサポートに主な焦点を当て、ネイティブ・コードとサードパーティーのライブラリーを使って Ajax を利用する方法を検証します。具体的には、各フレームワークがどう振る舞い、よく使われる特定のライブラリーをどう扱うかを検証します。
第 5 回では、フレームワークの外部での作業について説明します。1 つのタスク (毎晩更新されるスクリプト) を考え、このタスクを完了するためのプロセスを各フレームワークについて検証します。
この記事について
この記事は、サンプル・アプリケーション Blahg を 3 つの各フレームワーク (Zend、symfony、そして CakePHP) を使って拡張する方法を、順を追って説明します。そして次に、フレームワークのルールから外れた場合の処理を扱います。大部分のフレームワークは、そのフレームワーク用にアプリケーションが設計されている場合には適切に動作します。しかし、アプリケーションがフレームワークに準拠していない場合にはどうなるのでしょう。
この記事を読むためには、このシリーズの第 1 回と第 2 回を読み終えて理解しておく必要があります。
Blahg を拡張する
第 2 回の最後で、Blahg を拡張してコメント・コントローラーを追加してみることを提言しました。コメント・コントローラーを追加することで、投稿を読む人は特定の投稿に対して、独自の洞察に満ちた創造的で有意義な内容で返信できるようになります。また、コメント・コントローラーと投稿コントローラーを統合するために大きな努力をする必要がないことも示唆しました。その方法の概要を示すコードは今回の記事で得られるからです。もちろんその動作は、その時に使用されているフレームワークによって異なります。しかしフレームワークにかかわらず、comments テーブルは必要です。
comments テーブルを作成する
Zend Framework と symfony の場合には、下記の SQL を使ってcomments テーブルを作成します。
リスト 1. Zend Framework と symfony でcomments テーブルを作成する
CREATE TABLE 'comments' (
'id' INT( 10 ) NOT NULL AUTO_INCREMENT PRIMARY KEY ,
'post_id' INT( 10 ) NOT NULL ,
'text' TEXT NOT NULL ,
'created' TIMESTAMP ON UPDATE CURRENT_TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
) ENGINE = MYISAM ;
|
CakePHP の場合には下記の SQL を使います。
リスト 2. CakePHP でcomments テーブルを作成する
CREATE TABLE 'comments' (
'id' INT( 10 ) NOT NULL AUTO_INCREMENT PRIMARY KEY ,
'post_id' INT( 10 ) NOT NULL ,
'text' TEXT NOT NULL ,
'created' DATETIME DEFAULT NULL
) ENGINE = MYISAM ;
|
comments テーブルが用意できたら、コメント機能を追加する作業に移ることができます。
Zend Framework でのコメントの追加
Zend Framework でコメントを追加する作業は、投稿機能を作成した際に行った作業と似ています。データベースとコメントのやり取りを処理するコメント・モデルを作成し、もちろん実際の動作を行うコメント・コントローラーを作成します。しかしビューは必要ありません。コメントは投稿を読み取る際に表示されるからです。
コメントを追加するためには、必ずしもこのようにする必要はありません。コメントを投稿の論理部分と見なし、コメントをすべて同じコントローラーやモデル、その他の中に含めてしまうこともできます。同様に、投稿を、親を持たないコメントと見なし、1 つのテーブルの中ですべてを行ってしまうこともできます。また、場合によっては Blahg を完全に 1 つの PHP ファイルとして作成することもできますが、それが良い案であるというわけではありません。ここでのポイントは、アプリケーションを異なる機能部分に分割する、ということです。
コメント・モデルは、(名前の変更を別にすれば) 投稿モデルと非常に似たものになります。Zend_Db_Table から継承したメソッドをコメント・モデルで使うことで、必要なことをすべて行うことができます。また、先ほど触れたようにコメント専用のビューは必要ありませんが、投稿を読むためのビューを変更して、任意のコメントが出力されるように、またユーザーがコメントを投稿するためのフォームが表示されるようにする必要があります。このフォームはコメント・コントローラーの書き込みアクションにリンクされます。
ここで必要なものは writeAction のみなので、コメント・コントローラーは簡略化した投稿コントローラーのように見えます。しかし重要な違いがあります。コメント・コントローラーではデータが書き込まれたら、そのコメントを送信した時にユーザーが読んでいた投稿にユーザーをリダイレクトする必要があります。一方、投稿コントローラーは各投稿に対するコメントを取得し、それらのコメントをビューに割り当てるために、readAction を変更する必要があります。
こうしたことすべてを行うコード・サンプルが、コード・アーカイブの中にあります。皆さんは別のソリューションを思いついているかもしれません。それは結構なことです。さて、Zend でコメントを追加できたので、今度は symfony でコメントを追加する方法を見てみましょう。
symfony でのコメント追加
symfony バージョンの Blahg でコメントを追加するためには、schema.yml (/column/protected/sf_column/config の中にあるはずです) から始め、そしていくつかの定義をcomments テーブルに追加する必要があります。そのためには下記の行を追加します。
リスト 3. symfony でコメントを追加する
comments :
_attributes: {phpName: Comment }
id:
post_id:
text: longvarchar
created: timestamp
|
先ほどと同じく、このテキストをコピーし、貼り付けたら、コメントが空白 2 つ分インデントされていること、またコメントの下の要素が空白 4 つ分インデントされていることを確認します。空白は重要です。
注意: 作成され、変更された列に対してそれぞれ created_at と modified_at という名前を付けると、symfony は CakePHP と同様、ちょっとした魔法を行ってくれます。この場合には、この魔法は CakePHP の場合とは違って MySQL データベースが行います。
それが終わったら、Propel モデルを作成し、キャッシュをクリアーする必要があります。/column/protected/sf_column のコマンドラインで下記のコマンドを実行します。
php /column/src/symfony/data/bin/symfony propel-build-model
php /column/src/symfony/data/bin/symfony clear-cache
|
これらのコマンドを実行すると、/column/protected/sf_column/lib/model ディレクトリーにファイル Comment.php と CommentPeer.php があるはずです。これでモデルをソートできたので、残りのコメント機能を作り上げましょう。
注意: 第 2 回で触れておくべきでしたが、もし PEAR (PHP Extension and Application Repository) を使って symfony をインストールした場合には、最初に PHP を呼び出したりバイナリーに対する完全なパスを提供したりしなくても、単純に symfony propel-build-model を実行することができます。ただし、誰もがその人の環境の中で PEAR インストールを使えるわけではありません。(第 2 回で) 別のインストール方法を説明したのはそのためです。
これでモデルは用意できたので、コメントに対する何らかの選択基準を追加し、そして投稿にコメントを割り当てるために、/column/protected/sf_column/apps/blahg/modules/post/actions/ の actions.class.file.php を変更する必要があります。また、指定された投稿に対するコメントを出力するために /column/protected/sf_column/apps/blahg/modules/post/templates/ ディレクトリーの readSuccess.php ファイルに少し変更を加える必要もあります。この両方の詳細については、コード・アーカイブの中のファイルを見てください。
次に、Blahg のコメント・モジュールに init コマンドを実行する必要があります。/column/protected/sf_column ディレクトリーからコマンド php /column/src/symfony/data/bin/symfony init-module blahg comment を実行します。
そして、/column/protected/sf_column/apps/blahg/modules/comment/actions/ ディレクトリーの actions.class.php に executeWrite アクションを追加します。これは投稿モジュールの executeWrite アクションに似ていますが、書き込みを行った後、投稿を読むために、その投稿に再度リダイレクトする必要がある点が異なります。
最後に、投稿モジュールの readSuccess.php ファイルを変更し、投稿を読んだ人がコメントを投稿できるようにフォームを追加する必要があります。そのためのコードはすべてコード・アーカイブに含まれています。
CakePHP でのコメントの追加
CakePHP バージョンの Blahg にコメントを追加するためには、(コメントの書き出しを処理するための) コメント・コントローラーが必要ですが、もっと重要な点として、コメント・モデルを作成し、コメントと投稿の間のつながり (アソシエーション) を定義する必要があります。投稿がコメントと「hasMmany」の関係にあること、そしてコメントが 1 つの投稿と「belongsTo」の関係にあることを指定すると、投稿を読むために取得するクエリーを使って、その投稿に対するコメントを取得することができます。こうした動作は CakePHP ではモデルのアソシエーションと呼ばれます。
更新された CakePHP バージョンの Blahg のコード・アーカイブを見ると、非常に典型的なコメント・コントローラー (comments_controller.php) がありますが、これは投稿コントローラーとは大幅に異なっており、クラス変数 autoRender が偽に設定されています。これによって、ビューなしでコントローラーのアクションを使用できるようになります。autoRender を偽に設定しないと、コメントを送信する際に「missing view (ビューがありません)」というエラーが起きます。またコード・アーカイブには、新しいコメント・モデルと、(モデルのアソシエーションを反映した) 投稿モデルへの変更が含まれています。これを見るとわかるように、モデルのアソシエーションを作成すれば、大量の面倒な作業を CakePHP が行ってくれるのです。
フレームワークのルールから外れた処理
ここまでは、Blahg は各フレームワークに合うように、各フレームワークの強みを利用して作成されました。これは非常に容易です。それぞれのフレームワークがアプリケーションで必要なことをすべて期待通り行ってくれる限り、Blahg は順調に動作します。しかし、もしアプリケーションがフレームワークの枠の中に収まらなかったらどうなるのでしょう。
もしそうした困難に直面したら、まず最初に、(既に十分考慮してあるとしても)「なぜ私はアプリケーションを定義できないフレームワークを使っているのか」と自問する必要があります。その答えとして、いくつか考えられます。そのフレームワークは初めから存在しており、置き換えることは問題外かもしれません。上司がそのフレームワークしか承認してくれなかったのかもしれません。あるいは単に、そのアプリケーションが通常とは異なっており、市場にあるどのフレームワークもそのモデルに合わないのかもしれません。もしそうであれば、不適切なフレームワークにアプリケーションを無理に押し込もうとせず、アプリケーションが適切なものかどうか、そして有効な構造になっているかどうかを確認する必要があります。
アプリケーションは、本当にそのような動作をする必要があるのでしょうか。もしそうであれば問題ありませんので、フレームワークを拡張する方法について説明しましょう。
問題
Blahg に項目を投稿でき、そして今や投稿を読む人が投稿内容に返信できるようになりました。しかし Blahg 全体の話題が何なのか一目でわかると非常に便利です。カバーするつもりのトピックを象徴するような気のきいたタイトル (例えば「World of Spatulas (フライ返しの世界)」や「Polka for the Rest of Us (私たちのポルカ)」など) を付けることはできますが、投稿を読む人達が投稿の中で話し合うかもしれない内容までコントロールすることはできません。またときには、別の話題について語りたくなるかもしれません。あるいは数学好きなため、数字が関係する話をしたくなるかもしれません。
こうした問題に対して考えられるソリューションとしては、入力される内容を取得し、使われている単語のカウントを累積していき、Blahg の中で最もよく使われる単語の簡単なリストを作成することでしょう。しかし問題は、どこでそれを行うかです。これはデータのカテゴリー分けなので、モデル (Model) レイヤーで実現するタスクだと主張する人がいるかもしれません。あるいは、何らかの分析を行う必要があり、分析はロジックを意味するため、コントローラー (Controller) のタスクだと主張する人がいるかもしれません。そしてまた、使われている回数を見たいのであれば、ビュー (View) で何かが必要ではないでしょうか。しかしユーザーが単語コントローラーを実際に訪れることは決してありません。ではこの実現のために、フレームワークをどのように拡張すればよいのでしょう。
基本的な手法を明確にするために、単語を保持するテーブルが必要です (このテーブルを「words」と呼ぶことにしましょう)。このテーブルは、単語と、その単語が使われた回数を保持します。リスト 4 の構文を使って、このテーブルを定義することができます。
リスト 4. 単語を保持するテーブル
CREATE TABLE 'words' (
'id' INT( 10 ) NOT NULL AUTO_INCREMENT PRIMARY KEY ,
'word' varchar(255) NOT NULL,
'occurrences' INT( 10 ) NOT NULL
) ENGINE = MYISAM ;
|
単語の分析を行うコードは以下の作業を行う必要があります。
- すべての HTML 部分をすべて削除する (これはいずれにしても行う必要があります)
- 空白ではない、アルファベット以外の文字を削除する (そうするとアポストロフィで問題が起きますが、出発点としては適切です)
- 複数の空白文字を 1 つの空白文字で置き換える
- すべてを小文字にする
- 空白を区切りとして配列に分解する
- 作成される配列から単語のカウントを生成する
- 新しいカウントで words テーブルを更新し、新しい単語のための行を追加し、また既に存在している単語の行を更新する
これは大量の作業に思えるかもしれませんが、心配する必要はありません。コードを作成しているうちに、これらが組み合わさって MVC になるのです。最終的に得られるものは、基本的に、words テーブルを処理するモデルと、単語のカウントを表示するビュー、そして何らかのロジックを実行するコントローラーです。
Zend で単語の統計を追加する
もし、単語のカウントを (表示せずに) 累積していくことだけ必要なのであれば、この問題はおそらく Zend_Controller_Plugin_Abstract を使って解決することができます。しかしここでは実際に単語のカウントを表示できなければならないので、おそらくモデルとコントローラーを持つ単語モジュールを作成した方が適切です。モデルは、Zend で他のことを行った場合と同じく、非常に基本的なものです。コントローラーは単語のカウントを取得して表示する indexAction メソッドを持ちますが、テキストを処理するためのメソッド processText も必要です。このメソッド名は Action で終わっていませんが、これは意図的なものです。index ビューも非常に単純であり、単語のリストに対して反復的に単語を画面にダンプすればよいだけです。
indexAction のコードは非常に単純です。単に単語を取得し、カウント数に従って (降順で) 並び替えるだけです。しかし processText メソッドのコードは、もう少し作業を行う必要があります。まずテキストから余分なものを削除し、それを配列に分解して、各単語の発生回数をカウントする必要があります。次に、words テーブルから単語を取得し、それらの単語をリストの中に入れる必要があります。ある単語がもしマスター・リストの中にあれば、その行を新しいカウントで更新する必要があります。もし新しい単語であれば、その単語を持つ行を挿入する必要があります。これらの処理はどれも非常に単純です。この 2 つのコードはすべてコード・アーカイブの中にあります。
投稿コントローラーとコメント・コントローラーで processText メソッドを使うためには、まず WordController.php ファイルを要求する必要があります。そして writeAction の中で、WordController のインスタンスを作成する必要があります。これを行う際には、Request オブジェクトと Response オブジェクトを渡す必要があります。次に、あたかも任意の古いオブジェクトに対するメソッドであるかのように、processText メソッドを呼び出します。投稿に対して、タイトルと、そしてテキストを渡すのを忘れてはいけません。
ヒント: StripTags フィルターを通してからテキストを渡す場合には、後でタグを削除する必要がありません。この場合、以下のようになります。
$wc = new WordController($this->_request, $this->_response);
$wc->processText($data['title'] . ' ' . $data['text']);
|
詳細はコード・アーカイブで確認してください。すべてが用意でき、投稿コントローラーとコメント・コントローラーを変更したら、投稿とコメントを入力できるはずです。そして http://localhost/zend/word で単語のカウントが更新される様子を見てください。
注意: 大量の処理が必要な場合には、おそらくこうしたコードの一部を少し異なった形で作成することになるでしょう。ここでの意図は、テキスト構文解析や単語のカウントの統計分析の素晴らしい方法を示すことではなく、こうした方法を一例としてフレームワークではうまく扱えない処理をしようとすることなのです。
symfony で単語の統計を追加する
皆さんの想像されるとおり、最初に必要なことは、words テーブルの定義を schema.yml 構成ファイルに追加することです。適切にインデントすることを意識しながら行うと、この定義はリスト 5 のようになるはずです。
リスト 5. words テーブルの定義を schema.yml ファイルに追加する
words
_attributes: {phpName: Word }
id:
word: varchar(255)
occurrences: integer
|
では次は何をするのでしょう。そうです。上記でコメントを追加した際に行ったこととまったく同じように、モデルを作成し、キャッシュをクリアーしなければなりません。次に単語モジュールに init コマンドを実行する必要があります。これらのコマンドは、すべて /column/protected/sf_column ディレクトリーから実行する必要があります。
では早速、簡単な部分から始めましょう。単語モジュールは 1 つのことしかせず、それをユーザーが利用することがわかっています。つまり単語モジュールは words テーブルの単語リストを表示します。投稿モジュールを見ると、executeIndex アクションと indexSuccess テンプレートがどのようになるかを理解できるでしょう。
簡単な部分は処理できたので、構文解析やカウント取得などの処理をされた投稿とコメントから、テキストを取得する必要があります。これを行うための 1 つの方法は、単語モジュールの actions.class.php の中に executeParsetext というアクションを作成し、投稿またはコメントが保存された後、このアクションに転送する方法です。その前に、どこに戻ればよいかわかるように、投稿の ID を保持する ID を設定する必要があります。これは投稿モジュールの actions.class.php では、次のようなものになります。
$this->id = $post->getId();
$this->forward('word', 'Parsetext');
|
投稿モジュールから単語モジュールへ転送したら、渡された変数を抽出する必要があります。これは実は少し面倒です。変数を取得するためのコードは下記ようなものです。
$vars =
$this->request->getContext()->getActionStack()->getFirstEntry()->\
getActionInstance()->getVarHolder()->getAll(); |
注意: これは実際のところ、もっと簡単に行える方法がありそうに思えます。別の方法としては、flashMessage に値を設定し、それを転送する方法ですが、それが大幅に優れた方法にも思えません。これよりもスマートな方法があったら、ぜひフィードバックをお送りください。
変数を取得したら、先ほどと同じくテキストを分割し、単語モデルを使って対象の行を保存します。この 1 つのアクションを使って投稿とコメントのテキストを構文解析するためには、必ずタイトル変数に init コマンドを実行してこの変数を初期化します。それが終われば投稿モジュールに戻ることができ、保持してある投稿の ID を使って、そのままその投稿の内容を読むことができます。
これらはすべて、コード・アーカイブに含まれています。少し時間をかけてそれを調べ、その後で CakePHP に移ってください。
CakePHP で単語の統計を追加する
CakePHP では、この機能を 2 つの部分で実装します。まず、コンポーネントと呼ばれるものを作成します。これは一種のコントローラーのヘルパーであり、このコンポーネントがテキストを処理します。次に単語に対する標準のモデル、コントローラー、ビューを作成し、データベースから得られる単語のリストを表示します。投稿コントローラーとコメント・コントローラーは単語モデルを使って、単語が処理された後に単語のカウントを保存します。
WordcountComponent を作成するためには、/column/protected/cakephp/app/controllers/components ディレクトリーに wordcount.php というファイルを作成します。クラス WordcountComponent を object の拡張として定義し、そして今やおなじみとなったコードを使ってメソッド processText を作成し、テキストから余分なものを削除し、テキストを分割し、そしてテキストをカウントして配列に入れます。このメソッドはカウントされた単語の配列を返す必要があります。
words テーブルの内容を取得してインデックス・アクションで表示するための基本的なモデル、ビュー、コントローラーを、CakePHP に関して既に持っている知識を使って作成します。その他のアクションは必要ありません。
この投稿コントローラーとコメント・コントローラーでは、単語モデルと新しいコンポーネントを使用していることを指定するために、以下のように 1、2 行追加する必要があります。
var $uses = array ('Post', 'Word');
var $components = array('Wordcount');
|
これらがすべて用意できたら、両方のコントローラーの書き込みアクションにコードを追加する必要があります。それによってタグを削除し、テキストを WordcountComponent に渡し、words テーブルの内容を取得し、WordcountComponent が返すカウントされた単語の配列に対して繰り返しの処理を行い、適切に保存します。ここまで来ると、これらのすべてがどのように組み合わされるのか十分に想像できるはずです。しかしこの場合も同じですが、このためのコードはすべてコード・アーカイブに含まれています。
まとめ
フレームワークに対してどれほどの精力を費やしても、PHP 開発者が作り出す、ありとあらゆるアプリケーションを 1 つのフレームワークですべて処理することはとても不可能です。フレームワークが厳密であればあるほど、そのフレームワークは (フレームワークの) ルールから外れた処理に関して柔軟性が乏しくなります。フレームワークを選択する際の、ごく初期の段階で、皆さんに特有の要求をそのフレームワークがどう処理するかを調べる必要があります。CRUD (Create, Read, Update, Delete) を試す必要はありません。ほとんどのフレームワークは十分適切に CRUD を処理することができます。フレームワークの能力は、どの程度柔軟にユーザーの要求に応じられるかによって示されます。ユーザーは妥協するために少し考え方を変更する必要があるかもしれませんが、あまりにも厳格なフレームワークは多様さの要求に対応できないかもしれません。
フレームワークではうまく扱えない、特有の問題が他にないかどうかを調べてみてください。そしてそのフレームワークのドキュメンテーションを読み、その問題が解決可能かどうかを調べてください。あるいは、もしうまく問題を考えつかなかったら、冠詞や前置詞、代名詞など、一般的すぎて有用ではない単語を文字カウントのプロセスから省略する適切な方法を考えてみてください。
第 4 回では Ajax を使用し、ネイティブ・コードとサードパーティー・ライブラリーの使い方について検証します。具体的には、よく使われる特定のライブラリーに対して各フレームワークがどう振る舞い、それらをどう扱うかについて説明します。
ダウンロード | 内容 | ファイル名 | サイズ | ダウンロード形式 |
|---|
| Part 3 sample code | os-php-fwk3.source.zip | 21KB | HTTP |
|---|
参考文献 学ぶために
製品や技術を入手するために
- 皆さんの次期オープン・ソース開発プロジェクトを IBM trial software を使って革新してください。ダウンロード、あるいは DVD で入手することができます。
-
IBM 製品の評価版をダウンロードし、DB2® や Lotus®、Rational®、Tivoli®、WebSphere® などが提供するアプリケーション開発ツールやミドルウェア製品をお試しください。
議論するために
著者について  | |  | Duane O'Brien はゲーム Oregon Trail がテキストのみで構成されていた頃から、万能な技術者として活躍しています。彼の好きな食べ物は寿司です。彼はまだ月に行ったことがありません。 |
記事の評価
|