Symfony2 と HTTP の基礎¶
おめでとう! Symfony2 を身につけると、もっともっと生産性が高くて、幅広くて、引っ張りだこな WEB デベロッパーへの道がひらけます。 (実際ひっぱりだこになるにはがんばらなきゃいけないけど)。 Symfony2 は、基本に立ち帰りました。 すなわち、より迅速でより堅牢なアプリケーション開発が可能で、それでいて邪魔をしないツールになっているということです。 Symfony は、たくさんのテクノロジーのたくさんのアイディアを基に作られていて、今まさに身につけようとしているツールやコンセプトは、たくさんの人々の長年に渡る努力の結晶ということになります。 つまり、”Symfony” をただ単にマスターするわけではないのです。 WEB の基礎や開発のベストプラクティス、Symfony2 内外の最新で素晴らしいPHPライブラリの使い方をも身につけることになるのです。 準備はいい?
Symfony2 の哲学に従い、この章では WEB 開発に共通した基礎的なコンセプトである、HTTP を説明することから始めましょう。 どんな経歴を持っていても、どんなプログラミング言語が好きでも、この章はすべての人にとって必読ですよ。
HTTP はシンプル¶
HTTP(ギーク向けに言うところの Hypertext Transfer Protocol) は、2台のマシンがお互いにやり取りするためのテキスト言語。 ただそれだけです! 例えば、WEB コミックの xkcd 最新分をチェックしたいとしましょう。 その場合、(おおよそ)こんな会話が起こっています。
実際はもう少し形式ばっているけども、それでも本当にシンプル。 HTTP はこのシンプルなテキストベースの言語を記述するための規約です。 WEB の開発ということであれば、常にシンプルなテキストリクエストを解釈し、シンプルなテキストレスポンスを返すことが、サーバの役割となります。
Symfony2 はそういう部分を一から創り上げています。 自覚があろうがなかろうが、HTTP は毎日使っていますよね? Symfony2 を使って、HTTP マスターになる方法を学びましょう。
ステップ1: Client は Request を送るのだ¶
WEB 上でのやりとりは、どんな時でもリクエストから始まります。 リクエストとは、クライアント(ブラウザやiPhoneアプリ等)が作成するテキストメッセージで、HTTP として知られている特別なフォーマットを使って作成されます。 クライアントは、サーバにリクエストを送り、レスポンスを待ちます。
ブラウザと xkcd の WEB サーバ間のやりとりのうち、最初の部分(リクエスト)を見てみましょう。
HTTP 的に言えば、リクエストは実際は下記のようになります。
GET / HTTP/1.1
Host: xkcd.com
Accept: text/html
User-Agent: Mozilla/5.0 (Macintosh)
こんなシンプルなメッセージだけで、クライアントがどのリソースをリクエストしてきているのか、必要なことがすべてわかります。 1行目が最も重要で、URI と HTTP メソッドが記載されています。
URI (/ や /contact など) は、クライアントが必要としているリソースのユニークなアドレス/場所です。 HTTP メソッド (GET など)は、そのリソースに対して、どうしたいのか定義するものであり、そのリクエストの動詞にあたり、そのリソースに対してどの様に振舞いたいのかを定義します。
GET | サーバからリソースを読み込む |
POST | サーバ上でリソースを作成する |
PUT | サーバ上のリソースをアップデートする |
DELETE | サーバ上のリソースを削除する |
上記を考慮すると、例えばブログのとあるエントリを削除するHTTP リクエストはこうなります。
DELETE /blog/15 HTTP/1.1
Note
HTTP の仕様としては、立派な HTTP メソッドがいくつか定義されていますが、その多くは、それほど広く使われてもいないし、サポートもされていません。 実際、最近のブラウザでも、 PUT や DELETE はサポートされていません。
HTTP リクエストは、1行目の後に、必ずリクエストヘッダと呼ばれる行が複数行続きます。 ヘッダには幅広い範囲の情報を与えることができ、例えば、リクエスト元の Host や、クライアントが Accept できるレスポンスフォーマット、リクエストを作成する際に使用したアプリケーション(User-Agent)などの情報を与えることができるようになっています。 ヘッダは他にもたくさんあるので、Wikipedia の List of HTTP header fields の記事を参照してみてください。
ステップ2: サーバはレスポンスを返すのだ¶
サーバはリクエストを受け取ると、URI を通じて、クライアントが何を必要としているのか、また、メソッドを通じて、そのリソースに対してどうしたいのか、を知ることができます。 例えば、GET リクエストであれば、サーバはリソースを用意し、HTTP レスポンスとして返します。 xkcd WEB サーバのレスポンスを見てみましょう。
HTTP では、次のようなレスポンスを返すことに相当します。
HTTP/1.1 200 OK
Date: Sat, 02 Apr 2011 21:05:05 GMT
Server: lighttpd/1.4.19
Content-Type: text/html
<html>
<!-- xkcd のHTML -->
</html>
HTTP レスポンスには、リクエストされたリソース(この場合だと HTML コンテンツ)、および、レスポンスに関する情報が含まれています。 1行目が特に重要で、HTTP レスポンスのステータスコード(ここでは200)が記載されます。 ステータスコードは、リクエストがどのような結果になったのかをクライアントに伝える役割を果たします。 リクエストは成功したのだろうか?それともエラーがあったのだろうか? そんな疑問に答えるために、ステータスコードには、成功、エラー、クライアントがすべきこと(リダイレクトで別ページに遷移するなど)などの意味が割り振られています。 Wikipedia の List of HTTP status codes に、ステータスコードのリストがありますので、参照してみてください。
リクエストと同様に、HTTP レスポンスには、HTTP ヘッダとして、付加的な情報が含まれています。 例えば、HTTP レスポンスヘッダとして重要なものに、Content-Type があります。 同じリソースを返すにしても、例えば HTML や XML、JSON といった様々な返し方がありますよね。 この Content-Type ヘッダは、どんなフォーマットで返されているのかを教えてくれるヘッダです。
他にもたくさんのヘッダがあって、そのうちのいくつかはとても効果的で、例えば、強力なキャッシュシステムのために使われるヘッダもあります。
リクエスト、レスポンス、そして WEB 開発¶
このリクエスト-レスポンス間のやりとりというのは、WEB 上での通信を行う上で基本的なプロセスとなります。 このプロセスは、重要で強力であると同時に、必然的にシンプルです。
もっとも重要なのは、どんな言語を使っていようとも、どんなアプリケーション(WEB、モバイル、JSON API)を作ろうとも、そして、どんな開発方針に従っていようとも、アプリケーションの最終的な目的は、常に、リクエストを解釈して、適切なレスポンスを返すことにある、ということです。
Symfony は、この事実に応えることができるように設計されています。
Tip
HTTP の仕様について、より詳しく知りたければ、オリジナルの HTTP 1.1 RFC を読んで見てください。 もしくは、オリジナル仕様を積極的に明確化している HTTP Bis を読んでみてもいいでしょう。 Live HTTP Headers という、ブラウジング中のリクエスト/レスポンスヘッダを検証する Firefox のエクステンションもあります。
リクエストとレスポンスとPHP¶
それでは、PHPを使った場合、「リクエスト」したり「レスポンス」するには、どのようにすればいいのでしょうか。 PHPを使うと、作業は少し簡単になります。
<?php
$uri = $_SERVER['REQUEST_URI'];
$foo = $_GET['foo'];
header('Content-type: text/html');
echo 'リクエストされたURIは '.$uri;
echo 'パラメータ "foo" の中身は '.$foo;
奇妙に聞こえるかもしれませんが、上記の例では、HTTP リクエストから情報を取得して、HTTP レスポンスを作成するために使用しています。 PHP では、わざわざ HTTP リクエストをパースしなくても、$_SERVER や $_GET のようなスーパーグローバル変数にリクエストの情報がすべて格納されています。 同様に、HTTP フォーマットに従ったテキストレスポンスを返さなくても、header() 関数を使用してレスポンスヘッダを作成することが可能で、単にコンテンツ内容部のみを出力するだけで、PHP が正しいフォーマットの HTTP レスポンスを作成し、クライアントに返します。
HTTP/1.1 200 OK
Date: Sat, 03 Apr 2011 02:14:33 GMT
Server: Apache/2.2.17 (Unix)
Content-Type: text/html
リクエストされたURIは /testing?foo=symfony
パラメータ "foo" の中身は symfony
リクエストとレスポンスと Symfony¶
Symfony では、HTTP リクエストとレスポンスを簡単に扱えるようなクラスを使って、生で PHP を扱う場合と同様のアプローチを提供しています。 Request クラスは、HTTP リクエストメッセージをシンプルなオブジェクト指向で表現したクラスです。 これを使えば、すべてのリクエスト情報が簡単に手に入ります。
use Symfony\Component\HttpFoundation\Request;
$request = Request::createFromGlobals();
// リクエストされたURI(例: /about)
// ただし、クエリパラメータはすべて除去されます
$request->getPathInfo();
// それぞれ GET 変数と POST 変数を取得
$request->query->get('foo');
$request->request->get('bar');
// foo で指定した UploadedFile オブジェクトの取得
$request->files->get('foo');
$request->getMethod(); // GET, POST, PUT, DELETE, HEAD
$request->getLanguages(); // クライアントが許可している言語のリスト
さらに、Request クラスは、手を煩わせないようにバックグラウンドでもたくさんの仕事をしています。 例えば、isSecure() メソッドは、 PHP 内の3つの値をチェックしていて、ユーザがセキュアなコネクション(https)に接続しているのかどうかをチェックすることができます。
Symfony は、HTTP レスポンスメッセージも、Response クラスでシンプルな PHP 表現を提供していて、オブジェクト指向インターフェイスを通じて、クライアントに返すレスポンスを作成することができます。
use Symfony\Component\HttpFoundation\Response;
$response = new Response();
$response->setContent('<html><body><h1>Hello world!</h1></body></html>');
$response->setStatusCode(200);
$response->headers->set('Content-Type', 'text/html');
// HTTP ヘッダを出力し、続いてコンテンツも出力する
$response->send();
もし Symfony にこれ以上の機能がなかったとしても、リクエスト情報に容易にアクセス可能な、そして、オブジェクト指向インターフェイスでレスポンス作成が可能なツールキットを手にしたことになります。 Symfony のもっと強力な機能を勉強したとしても、次のことだけは心に留めておいて下さい。 アプリケーションの目標は、常に、リクエストを解釈し、ロジックに基づき、適切なレスポンスを作成することにあります。
Tip
Request クラスと Response クラスは、 HttpFoundation という、Symfony 付属のスタンドアロンなコンポーネントに含まれています。 このコンポーネント自体は、Symfony とは全く別のものであり、他にも、セッションやファイルアップロードを扱うクラスも含んでいます。
Request から Response ができるまで¶
HTTP それ自体がそうであるように、Request と Response オブジェクトは本当にシンプルです。 アプリケーションを作る上で一番大変なのは、それらの「間」を実装することです。 つまり、本来やるべき作業は、リクエストを解釈し、レスポンスを作成するコードを書くこと、ということなのです。
アプリケーションは、時に、メールを送ったり、フォームの送信を受け付けたり、データベースに値を保存したり、HTML ページをレンダリングしたり、コンテンツをセキュアにしておいたりすることがありますが、これらを達成し、整ったコードを保ち、そして保守し続けることが可能なようにするにはどうしたらいいのでしょうか。
Symfony はこれらの問題を解決しているので、気にする必要はありません。
フロントコントローラ¶
従来、アプリケーションは、サイト内の各「ページ」に物理的なファイルを置くように作られてきました。
index.php
contact.php
blog.php
この方針のままでは、いくつか問題が生じてきます。 例えば、URL の柔軟性についてです。 「blog.php から news.php にファイル名変更したいけど、リンクは壊したくない。」 こんな場合はどうしたら良いでしょうか。 また、各ファイルは、セキュリティの確保やデータベース接続、サイトの「見た目」を一貫したものにするために、コアファイルを例外なくいちいちインクルードしているという点があります。
フロントコントローラ を使うのは、良い解決方法でしょう。 アプリケーションへのすべてのリクエストを、1つの PHP ファイルで一手に引き受けるのです。
/index.php | index.php を実行する |
/index.php/contact | index.php を実行する |
/index.php/blog | index.php を実行する |
Tip
Apache の mod_rewrite (Apache以外でも、それに相当するもの) を使えば、URLを / や /contact や /blog のように短縮することは簡単です。
さて、すべてのリクエストが全く同じように処理されるようになりました。 URL によって別々の PHP ファイルが実行されるのではなくて、フロントコントローラが常に実行されます。 そして内部で、URL によって別の処理にルーティングされるのです。 こうしておけば、例の2つの問題は解決されます。 モダンな WEB アプリケーションでは、ほとんどこのアプローチをとっていて、例えば WordPress のようなアプリケーションも同様です。
整ったコードを保て¶
それでは、フロントコントローラの中では、どうやってレンダリングすべきページを決定していて、同じレンダリングをするにしても、どうやって異なるページをだし分けているのでしょうか。 URI をチェックして、その値によって、異なるコードを実行する必要がありそうです。 とはいえ、次のようなコードでは早々に破綻しそうです。
// index.php
$request = Request::createFromGlobals();
$path = $request->getPathInfo(); // the URL being requested
if (in_array($path, array('', '/')) {
$response = new Response('Welcome to the homepage.');
} elseif ($path == '/contact') {
$response = new Response('Contact us');
} else {
$response = new Response('Page not found.', 404);
}
$response->send();
難しい問題ではありますが、Symfony はまさにこれを解決するように設計されています。
Symfony アプリケーションのフロー¶
Symfony がリクエストを扱えるようになれば、人生楽しくなります。 Symfony はすべてのリクエストで、同じシンプルなパターンを踏みます。
「ページ」は、ルーティングの設定ファイルで定義します。 URL を PHP に割り振るというわけです。 この割り当てられた PHP の処理を、controller と呼び、リクエストの情報を使用し、また同時に Symfony により使用可能になる様々なツールも使用して、Response オブジェクトを作成して返します。 コントローラこそが、実装していく対象で、リクエストからレスポンスを作る場所なのです。
たったそれだけのことなのです。おさらいしていきましょう。
- リクエストが来たら、常にフロントコントローラが実行される
- ルーティングシステムは、リクエストの情報と、ルーティング設定ファイルから、どの PHP コードが実行されるべきか決定する。
- 適切な担当 PHP コードが実行され、そのコードは Response オブジェクトを作って返す。
A Symfony Request in Action¶
あまり深追いはしませんが、一連の流れがどのように処理されていくのか見てみましょう。 Symfony アプリケーションに /contact ページを追加してみます。 まずは、ルーティングの設定ファイルに /contact 関連の行を追加します。
contact:
pattern: /contact
defaults: { _controller: AcmeDemoBundle:Main:contact }
Note
ここでは YAML を使って設定を書いています。 XMLやPHPで記述することも可能です。
/contact に誰かがアクセスしてきたら、このルートがマッチして、指定したコントローラが実行されます。 詳しくは ルーティング で説明しますが、AcmeDemoBundle:Main:contact という文字列は、MainController クラス内の contactAction メソッドを示す省略記法です。
class MainController
{
public function contactAction()
{
return new Response('<h1>Contact us!</h1>');
}
}
この例はとてもシンプルで、”<h1>Contact us!</h1>” という HTML コードで Response オブジェクトを作っています。 コントローラ では、コントローラがどうやってテンプレートをレンダリングするのか、や、バラバラのテンプレートファイルから “presentation” コード(つまり HTML を出力するもの)をどうやって作っているのか、などを説明しています。 ということで、コントローラでは、データベースとのやりとりや、送信されてきたデータの処理、メール送信などの肝心な部分だけを心配すればいいのです。
Symfony2: ツールを作るんじゃないんだ。アプリを作るんだ。¶
どんなアプリケーションでも、リクエストを解釈して、適切なレスポンスを返すことが目標だということは分かっていただけたと思います。 アプリケーションが大きくなってくると、整理されていてメンテしやすいコードを保つことは難しくなってきます。 それでも、同じように複雑なタスクは矢のように降ってきます。 データベースへの永続化とか、テンプレートのレンダリング・再利用、フォームの処理、メール送信、入力のバリデーション、セキュリティの確保など。
とはいえ、別にどの問題もユニークなわけではありません。 Symfony はフレームワークに、アプリケーションを作成するためのツールをたくさん提供しています。 従ってツールを作る必要はありません。 Symfony2 を使えば、面倒な作業は必要ありません。 Symfony フレームワークを使い倒すもよし、一部分だけを使うもよし、です。
スタンドアロンなツール: Symfony2 Components¶
結局のところ、Symfony2 とはいったい何なのでしょうか? Symfony2 は、独立した20以上のライブラリの集合体で、それらの一つ一つは どんな PHP プロジェクトでも使用可能です。 それらを Symfony2 Components と呼んでいますが、どんな開発の場合でも、ほとんどすべてのシチュエーションで便利なものとなっています。 いくつか紹介しましょう。
- HttpFoundation - Request クラスや Response クラスの他、セッションやファイルアップロードを扱うクラスもある
- Routing - 強力で高速なルーティングシステムで、指定されたURL(例: /contact)を どう処理すべきかという情報にマップする(例: contactAction() を実行する)
- Form - フォーム用のフル機能で柔軟なフレームワークで、フォームの作成や、受付を扱うことができる
- Validator データに対するルールづくりのシステムで、ユーザの送信してきたデータが、そのルールに則っているか検証する
- ClassLoader クラスを使うときに、そのファイルを手動で require しなくても、ライブラリをオートロードしてくれる
- Templating テンプレートのレンダリング、テンプレート継承(レイアウトでテンプレートをデコレートできる)、その他テンプレートの共通タスクなどを扱うツールキット
- Security - アプリ内のあらゆるタイプのセキュリティ事項を扱う強力なライブラリ
- Translation アプリ内の文字列を翻訳するためのフレームワーク
すべてのコンポーネントは互いに独立していて、Symfony2 フレームワークを使っていようがいまいが、どんな PHP プロジェクトでも使用できます。 すべて、必要があるときに使えばいいように作られていますし、必要であれば置き換えも可能です。
完全なソリューション: Symfony2 Framework¶
では、Symfony2 Framework と言ったときは、一体何を指すのでしょうか。 Symfony2 Framework は、PHPライブラリで、次の独立した二つのタスクを達成しています。
- コンポーネント(Symfony2 Components)とサードパーティ製のライブラリ(メール送信の Swiftmailer など)を選択して提供すること
- 実用本位な設定ができ、上記のバラバラになっている部品群を紐付けする”接着剤”としての役割を提供すること
フレームワークの目的は、独立したツール群を統合し、開発者に一貫したエクスペリエンスを提供することです。 フレームワークそれ自体も、Symfony2 バンドル(プラグイン)であり、設定可能で、まるっと置き換えることすら可能になっています。
Symfony2 は強力なツール群を提供しており、無理を強いること無く、迅速な WEB アプリケーションの開発が可能になっています。 一般的なユーザであれば、実用的なデフォルト値が設定済みのスケルトンプロジェクトが入っている Symfony2 ディストリビューションが準備されているので、簡単に開発を始めることができます。 上級ユーザであれば、制限なんてありませんよ。