コントローラ
コントローラは Web アプリケーションの中心的なクラスであり、ブラウザからのリクエストを受け取り、モデルを中心としたビジネスロジックを呼び出し、その結果をもとにしてビューにHTMLの生成を依頼して、レスポンスを返します。
アクションの定義
アクションとは、リクエストされた URL に基づいて呼び出される、コントローラのメソッドのことです。
「足場」が生成されているとして、ここにアクションを追加しましょう。
アクションはヘッダファイルの public slots の部分に宣言します。これ以外の部分に宣言すると、アクションとは認識されません(通常のメソッドになる)ので注意してください。もしアクションに引数を追加する場合は、お約束としてQString 型で宣言します。引数は、最大10個まで指定可能です。
class T_CONTROLLER_EXPORT FooController : public ApplicationController
{
Q_OBJECT
:
public slots: // この下にアクションを定義する!
void bar();
void baz(const QString &str);
:
あとは、いつもと同じように実装するだけです。
アクションの決定
リクエストされたURL文字列から、呼び出されるアクションが1つ決まります。デフォルトで、次のルールが使われます。
/コントローラ名/アクション名/引数1/引数2/…
※ これらの引数を「アクション引数」と呼ぶことにします
アクション引数は0個から10個まで指定することができます。11個以上指定された場合は、10個指定されたものとみなされます。もし11個以上の引数を使いたい場合は、URL引数(下記参照)かポストデータを使ってください。
ということで、具体的な例を次に挙げます。いずれの場合も BlogController クラスのアクションが呼び出されます。
/blog/show → show();
/blog/show/2 → show(QString("2"));
/blog/show/foo/5 → show(QString("foo"), QString("5"));
アクション名が省略された場合、デフォルトで index アクションが呼ばれます。つまり、こうなります。
/blog → index();
リクエストされたURLに該当するアクションが定義されていない場合、ブラウザにはステータスコード 500 (Internal Server Error) が返ります。
呼び出されるアクションでは、主に次の処理が行われるでしょう。
- リクエストの精査
- セッションやクッキーへのアクセス
- アップロードファイルへのアクセス
- モデル(ビジネスロジック)の呼び出し
- ビューへの変数受け渡し
- HTMLレスポンスの作成を依頼
プログラマの中は、アクションにたくさん処理を詰め込んで、その結果コントローラを大きくて複雑にしてしまう人がいます。あくまでビジネスロジックはモデル側に実装するようにし、コントローラはできる限り小さくてシンプルになるように心がけましょう。
アクションが呼び出されるメカニズムについて説明しましたが、URL によって決定されるアクションはカスタマイズすることが可能です。後述のURLルーティングの章をご覧ください。
リクエストの取得
HTTP リクエストは、THttpRequest クラスによって表されます。
コントローラでは、httpRequest() メソッドによって THttpRequest オブジェクトを取得することができます。
const THttpRequest & TActionController::httpRequest () const;
ここから様々なデータを取り出すことができます。
リクエストデータを取得する
クライアント(ブラウザ)から送られてくるHTTPリクエストは、メソッド、ヘッダ、ボディによって構成されます。ここに含まれて送られくるデータは次の用語で呼ぶことにします。
- ポストデータ ・・・ POSTメソッドでフォームから送信されたデータ
- URL引数(クエリパラメータ) ・・・ URL で”?”以降に付与されたデータ (キー=値&…の形式)
- アクション引数 ・・・ URLでアクション名以降に付与されたデータ (「/blog/edit/3」の 3 の部分) ← 上述
まず、コントローラの中でポストデータを取得する方法です。
ビューに、次のようなinput タグを作ったとします。
<input type="text" name="title" />
その送られた先のコントローラ側で値を取得するには、次のようにします。
QString val = httpRequest().formItemValue("title")
ちなみに、val を int 型に変換したければ、Qt 付属の toInt() メソッドが使えるでしょう。
送られてくるデータが多数ある場合、一つずつ取得するのは少々面倒です。値を一気に取得するメソッドもあります。
例えば、タグに次のように名前を付けます。
<input type="text" name="blog[title]" />
<input type="text" name="blog[body]" />
コントローラ側では、次のように取得することができます。リクエストデータはハッシュ形式で表現されます。
QVariantMap blog = httpRequest().formItems("blog");
QVariant t = blog["title"];
QVariant b = blog["body"];
:
今度は、URL引数(クエリパラメータ)を取得する方法です。
次のURL文字列があったとします。
http://example.com/blog/index?mode=normal
呼びだされるblogコントローラのindex()メソッドで、mode の値を取得するには次のメソッドを使います。
QString val = httpRequest().queryItemValue("mode");
// val = "normal"
ちなみに、ポストデータと URL 引数を区別しないでデータを取得したい場合は、parameter()メソッドや allParameters()メソッドを使うことができます。
リクエストデータがアプリ側の望む形式であるかどうかを検証する仕組みとして、バリデーション機能が提供されています。詳細は、バリデーションの章をご覧ください。
変数をビューへ渡す
ビューに対し、変数を渡すためには、texport( variable ) または T_EXPORT( variable ) を使います(これらはマクロです)。引数には、QVariant 型になりうる全ての変数を指定することができます。int, QString, QList, QHash はもちろん、ユーザ定義のモデルのインスタンスも指定することができます。次のように使います。
QString foo = "Hello world";
texport(foo);
// あるいは
int bar;
bar = …
texport(bar);
注意: texport の引数には必ず変数を指定してください。文字列(“Hello world”)や数値(100) などを直接指定することはできません。
その後、ビューでこの変数を使う場合、まず tfetch( Type, variable ) で宣言する必要があります。詳細はビューの章をご覧ください。
結論: オブジェクトは tfetch() でビューへ渡せ。
ユーザ定義クラスの場合:
ビューに渡すクラスを新規に実装した場合(ユーザ定義クラス)、そのクラスのヘッダークラスの末尾に、クラス名を引数とした次のおまじないを追加してください。「独自モデルの作成」の節をご覧ください。
Q_DECLARE_METATYPE(ClassName) // ← クラス名で置き換えてください
Qt で提供されているクラスは Q_DECLARE_METATYPE 宣言する必要はありませんが、QList や QHash などのテンプレートクラスのオブジェクトをビューに渡す場合は宣言が必要です。その場合は、helpers/applicationhelper.h の末尾のところに次のように追加してください。
:
Q_DECLARE_METATYPE(QList<float>)
Q_DECLARE_METATYPE マクロの引数はクラス名ですが、QHash<Foo, Bar> のようなカンマ付きを指定すると、コンパイルエラーになります。この場合、QHash<Foo, Bar> を typedef 宣言した名称を使えば良いです。
typedef QHash<Foo, Bar> BarHash;
Q_DECLARE_METATYPE(BarHash)
エクスポートオブジェクト
ビューに渡されたオブジェクト(texport されたオブジェクト)を「エクスポートオブジェクト」と呼ぶことにします。
レスポンスの作成を依頼する
アクションでビジネスロジックを処理した後は、その処理結果を HTML のレスポンスとして返します。BlogController の show アクションが実行された場合は、views/blog/show.xxx (拡張子はテンプレートシステムに拠る) という名前のテンプレートによってレスポンスが生成されます。それを依頼するには、次の render メソッドを使います。
bool render(const QString &action = QString(), const QString &layout = QString());
もし別のテンプレートを使ってレスポンスを返したい場合は、引数 action にそのアクション名を指定します。引数 layout にはレイアウトファイル名を指定することができます。
※ render というメソッド名が示すように 「レスポンスの作成をビューに依頼する」ことを、「描画する」とも言います。
結論: render() でテンプレートを描画せよ。
レイアウトとは
サイトを作ると、ページのヘッダやフッタといった共通部分は同じで、中身が異なることはよくあります。レイアウトとは、この共通部分だけを記述した、土台となるテンプレートのことを言います。レイアウトファイルは、views/layouts ディレクトリに置かれます。
テンプレートシステムについて
TreeFrog のテンプレートシステムには、今のところ ERB と Otama の2つが採用されています。ERB はご存じのとおり、<% %> を使ってコードを埋め込むものです。Otama は TreeFrog 独自のもので、テンプレート(.html)とプレゼンテーションロジック(.otm)を完全に分離したテンプレートシステムです。
- ERB ファイルの拡張子: xxx.erb
- Otama ファイルの拡張子: xxx.html と xxx.otm
(xxx はアクション名)
文字列を直接描画する
文字列を直接描画するには、renderText メソッドを使います。
// "Hello world" という文字列を描画する
renderText("Hello world");
デフォルトではレイアウトが適用されません。レイアウトを適用したい場合は、第2引数に true を指定します。
// レイアウトを適用しつつ "Hello world" という文字列を描画する
renderText("Hello world", true);
レイアウトの詳細については、「ビュー」の章をご覧ください。
リダイレクトする
ブラウザを別の URLへリダイレクトされるためには、redirect メソッドを使います。第1引数には、QUrl クラスのインスタンスを指定します。
// www.example.org へリダイレクトさせる
redirect(QUrl("www.example.org"));
同一ホストの他のアクションへリダイレクトさせるには次にようにします。
// Blog コントローラの index アクションへリダイレクトさせる
redirect( url("blog", "index") );
ここに出た url メソッドは便利なメソッドで、コントローラ名とアクション名を渡すと適切な QUrl インスタンスを返してくれるのです。
同一コントローラの他のアクションへリダイレクトさせるには、コントローラ名を省略できます。
// 同じコントローラの show アクションへリダイレクトさせる
redirect( urla("show") );
※ urla メソッドを使うのがポイントです
リダイレクトした先でメッセージを表示するには
リダイレクトとは、別のURLへ転送させることです。サーバの視点で考えれば、その転送先のURLで新たなリクエストを受信するので、別のアクションが呼ばれることになります。
TreeFrog Framework では、リダイレクトで転送した先のコントローラに対してメッセージ(変数)を渡す仕組みがあります。
次のように、変数を tflash メソッドまたはT_FLASH メソッドに渡すだけです。
// foo をフラッシュオブジェクトに設定する
QString foo = "successfully";
tflash( foo );
ここで渡された変数のことを「フラッシュオブジェクト」と呼びます。
このフラッシュオブジェクトは、リダイレクト先のビューでエクスポートオブジェクトに変換されます。あとは eh メソッドあるいは echo メソッドで出力すれば、メッセージを表示させることができるという訳です。
フラッシュオブジェクトを使うと何が嬉しいのか
実際のところ,フラッシュオブジェクトを全く使わなくともWebアプリケーションは作成できます。ただ、ある条件がそろった時にフラッシュオブジェクトを使えば、コードが分かりやすくなります(慣れもありますが)。
個人的に、アクションはそれ自体で独立かつ完結しているほうが分かりやすいと思います。つまり、アクション同士の関連性を減らすのです。1つのリクエストで2つ以上のアクションが呼び出されるような実装は良いと言えません。できるだけ1リクエストで1アクションという関係を保ちましょう。
もし、独立した別々のアクションがほぼ同じ内容を表示する場合、フラッシュオブジェクトを使うことでコードがシンプルになるのです。
チュートリアルの章で紹介した blogapp の show アクションと create アクションがいい例になります。これらのアクションの処理は異なっていますが、処理の結果としてどちらも1件のブログ内容を表示するだけです。create アクションでは、データの登録が成功したら show アクションへリダイレクトさせてデータを表示していますが、同時にフラッシュオブジェクトを使い「登録が成功した」というメッセージを表示しているのです。
実際のアプリケーションはこんな単純ではないので、必ずしもフラッシュオブジェクトが使えるとは限りません。バランスを考えて利用してみてください。
ということで、
ほとんどのアクションは、render メソッドか redirect メソッドで締めるのです。
ちなみに・・ 上記のとおり、リダイレクトするということは Webアプリケーションとして処理が一旦切れるということです。別のコンテキスト(アクション)になるにも関わらずオブジェクトが生存しているということで、フラッシュオブジェクトはセッションを使って実装されています。
staticInitializeメソッド
開発を進めていると、起動時に一度だけ行いたい処理というのが出てきます。例えば、あらかじめDBから初期データを読み込んでおきたいケースです。
この場合は、ApplicationController の staticInitialize() に処理を書きます。
void ApplicationController::staticInitialize()
{
// do something..
}
この staticInitialize() メソッドは、サーバプロセスが起動した時に一度だけ呼ばれることが約束されます。
コントローラのインスタンスの寿命
コントローラのインスタンスは、アクションが呼び出される直前に生成され、アクションの処理が終わると直ちに破棄されます。HTTPリクエスト毎に生成と破棄が行われるということです。
このような仕様であるので、コントローラはインスタンス変数を持つことがあまりありません。ApplicationController の実装も同様にしてください。