CakePHP2.0では新しくリクエストとレスポンスオブジェクトが追加されました。以前のバージョンではこれらのオブジェクトは配列で表現されており、 また関連するメソッドは RequestHandlerComponent, Router, Dispatcher, Controller に分散していました。そのため、 リクエストにどのような情報が含まれているかを正確に表すオブジェクトは存在しませんでした。 バージョン2.0において CakeRequest と CakeResponse は上記の目的で使用されます。
CakeRequest はCakePHPで使われるデフォルトのリクエストオブジェクトです。 リクエストデータへの応答と対話が中心的な機能となります。リクエストごとにCakeRequestは一つ作られ、 リクエストデータを使うアプリケーションの様々なレイヤーに参照が渡されます。 デフォルトの CakeRequest は $this->request に設定され、コントローラ、ビュー、 ヘルパーの中で利用できます。またコントローラの参照を使うことでコンポーネントの中からもアクセスすることが出来ます。 CakeRequest の役割は以下の通りです。:
CakeRequestはリクエストパラメータにアクセスするためにいくつかのインターフェイスを提供しています。 一つ目の方法は、添字付き配列です。二つ目の方法は $this->request->params を経由する方法です。 三つ目はオブジェクトのプロパティとしてアクセスする方法です。:
$this->request['controller'];
$this->request->controller;
$this->request->params['controller'];
上記はすべて同じ値へアクセスすることになります。パラメータへアクセスする方法が複数あることで既存のアプリケーションの移植が楽になるかもしれません。 すべての route-elements はこのインターフェイスを通してアクセスされます。
route-elements に加えて passed-arguments や named-parameters へのアクセスがしばしば必要になります。 これらは両方ともリクエストオブジェクトと同様に利用可能です。:
// 渡された引数
$this->request['pass'];
$this->request->pass;
$this->request->params['pass'];
// 名前付きパラメータ
$this->request['named'];
$this->request->named;
$this->request->params['named'];
すべての渡された引数と名前付きパラメータにアクセスする方法が提供されています。 この中にはCakePHPの内部で使っている重要で役に立つパラメータが存在し、また、リクエストパラメータの中ですべて見つけられます。
クエリ文字列パラメータは CakeRequest::$query を使って読み出すことができます。:
// urlは /posts/index?page=1&sort=title
$this->request->query['page'];
// 配列を経由してアクセスできます
$this->request['url']['page'];
すべてのPOSTデータは CakeRequest::$data を使ってアクセスされます。フォームデータが data 接頭辞を含んでいる場合、接頭辞は取り除かれるでしょう。例えば:
// name属性が'data[Post][title]'だった入力は次のようにアクセスします。
$this->request->data['Post']['title'];
dataプロパティに直接アクセスするか、エラーが発生しない方法でdata配列を読むために CakeRequest::data() を使うことができます。キーが存在しない場合、 null が返ります。:
$foo = $this->request->data('Value.that.does.not.exist');
// $foo == null
バージョン 2.2 で追加.
RESTサービスを構築しているとき PUT と DELETE リクエストのデータを受け付けることがよくあります。 2.2において application/x-www-form-urlencoded リクエストボディのデータは PUT と DELETE リクエストでは自動的に構文解析され $this->data に設定されます。 もしJSONやXMLデータを受け付けている場合、どうやってリクエストボディにアクセスすればいいのかについては以下の説明を見て下さい。
REST を採用しているアプリケーションではURLエンコードされていないpost形式でデータを交換することがしばしばあります。 CakeRequest::input() を使っているどんな形式であっても入力データを読み込むことができます。 デコード関数が提供されることでデシリアライズされたコンテンツを受け取ることができます。:
// PUT/POSTアクションで投稿されたデータをJSON形式にエンコードで取得する
$data = $this->request->input('json_decode');
json_decode の’as array’パラメータやXMLをDOMDocumentオブジェクトに変換したい時のように、 デシリアライズメソッドの中には呼び出し時に追加パラメータが必要なものがあるので CakeRequest::input() は追加パラメータを渡せるようになっています。:
// PUT/POSTアクションで投稿されたデータをXmlエンコードで取得する
$data = $this->request->input('Xml::build', array('return' => 'domdocument'));
CakeRequestはまたアプリケーションのパスについての役立つ情報を提供しています。 CakeRequest::$base と CakeRequest::$webroot はURLの生成や、アプリケーションがサブディレクトリにいるのかどうかの決定に役立ちます。
さまざまなリクエストの状態を検出するために以前は RequestHandlerComponent を使う必要がありました。 これらのメソッドは CakeRequest に移動され後方互換を保ちつつ新しいインターフェイスが提供されています。 使い方は以下の通りです。:
$this->request->is('post');
$this->request->isPost();
どちらのメソッド呼び出しも同じ値を返します。RequestHandlerにてそのメソッドが利用できるようになったとき、 これらのメソッドは廃止され最終リリース前に削除されるかもしれません。また、新しい種類の検出器(detector)を作成するために CakeRequest::addDetector() を使うことでリクエスト検出器を簡単に拡張することができます。4種類の異なる検出器を作成できます。:
いくつかの例を示します。:
// environment detectorを追加する
$this->request->addDetector('post', array('env' => 'REQUEST_METHOD', 'value' => 'POST'));
// pattern value detectorを追加する
$this->request->addDetector('iphone', array('env' => 'HTTP_USER_AGENT', 'pattern' => '/iPhone/i'));
// option detectorを追加する
$this->request->addDetector('internalIp', array(
'env' => 'CLIENT_IP',
'options' => array('192.168.0.101', '192.168.0.100')
));
// callback detectorを追加する。匿名関数か通常のコールバックが指定可能。
$this->request->addDetector('awesome', array('callback' => function ($request) {
return isset($request->awesome);
}));
CakeRequest には CakeRequest::domain(), CakeRequest::subdomains() や CakeRequest::host() といったサブドメインを扱うのに役立つメソッドがあるため、少し楽ができます。
利用可能な組み込みの検出器は以下の通りです。:
CakeRequest が提供している多くの機能は以前、 RequestHandlerComponent の中にあったので、 CakePHP2.0にどのように収まるのかを理解するために再考する必要がありました。2.0において RequestHandlerComponent はパトロン(sugar daddy)として振るいます。 CakeRequest が提供するユーティリティの最上位に砂糖のレイヤーを提供しています。 レイアウトの切り替えやコンテンツタイプやajaxを基にしたビューといった砂糖は RequestHandlerComponent の領域です。ユーティリティと砂糖のクラスを分離することで欲しいもの、必要なものの取捨選択が簡単になるでしょう。
CakeRequest はリクエストに関する様々なことを内省(introspect)するために使えます。 また、検出器によって様々なプロパティやメソッドからの他の情報を発見できます。
CakeRequestはリクエストパラメータのハンドリングをカプセル化し、内省化します。
アプリケーションが実行されているドメイン名を返します。
アプリケーションが実行されているサブドメインを配列で返します。
アプリケーションのホスト名を返します。
リクエストのHTTPメソッドを返します。
リクエストのリファラを返します。
現在アクセスしているクライアントのIPアドレスを返します。
リクエストで使われている``HTTP_*``ヘッダにアクセスできます。:
$this->request->header('User-Agent');
この例の場合、リクエストで使われているユーザエージェントが返るでしょう。
リクエストとデコード関数を通して渡されたinputデータを取得します。デコード関数の追加パラメータはinput()の引数として渡す事ができます。
リクエストデータへドット記法によるアクセスを提供します。リクエストデータの読み込みと変更が可能です。また次のように連鎖的に呼び出す事をできます。:
// リクエストデータを修正し、フォームフィールドを生成できます。
$this->request->data('Post.title', 'New post')
->data('Comment.1.author', 'Mark');
// データの取得もできます。
$value = $this->request->data('Post.title');
リクエストがある基準に適合するかどうかを調べます。 CakeRequest::addDetector() で追加された追加のルールと同様に組み込みの検出ルールを使えます。
クライアントがどのコンテンツタイプを受理するかを調べます。また、特定のコンテンツタイプが受理されるかどうかを調べます。
すべてのタイプを取得:
$this->request->accepts();
あるタイプについて調べる:
$this->request->accepts('application/json');
クライアントによって受理されるすべての言語を取得します。また、特定の言語が受理されるかどうかを調べます。
受理される言語のリストを取得:
CakeRequest::acceptLanguage();
特定の言語が受理されるかどうかを調べる:
CakeRequest::acceptLanguage('es-es');
POSTデータの配列です。 CakeRequest::data() を使うとエラーが発生しないようにしつつプロパティを読み込むことができます。
クエリ文字列パラメータの配列です。
ルート要素とリクエストパラメータの配列です。
現在のリクエストのuriを返します。
アプリケーションへのベースパスです。アプリケーションがサブディレクトリに配置されていない限り、普通は / です。
現在のwebrootてす。
CakeResponse はCakePHPのデフォルトのレスポンスクラスです。いくつかの機能とHTTPレスポンスの生成をカプセル化します。 また送信予定のヘッダを調べるためにモックやスタブとしてテストの手助けをします。CakeRequest のように CakeResponse は Controller や RequestHandlerComponent や Dispatcher に以前からある多くのメソッドを強化します。古いメソッドは廃止され CakeResponse の使用が推奨されます。
CakeResponse は次のような共通のレスポンスをラップするためのインターフェイスを提供します。:
CakePHPはデフォルトで CakeResponse を使います。 CakeResponse は柔軟で透過的にクラスが使われます。 しかし、このクラスをアプリケーション固有のクラスに置き換える必要がある場合、 CakeResponse をオーバーライドして独自のクラスで置き換えることができます。それはindex.phpで使われているCakeResponseを置き換えることで実現できます。
この置き換えによってすべてのコントローラが CakeResponse の代わりに CustomResponse を使えるようになります。またコントローラの中で $this->response と設定することでレスポンスインスタンスを置き換えることができます。レスポンスオブジェクトのオーバーライドは header() とやりとりするメソッドをスタブ化しやすくするので、テストで使いやすいです。 詳しくは CakeResponseとテスト を参照して下さい。
CakeResponse::type() を使うことでアプリケーションレスポンスのContent-Typeを制御することができます。 もしCakeResponseに組み込まれていないコンテンツタイプを扱う必要がある場合、以下のように type() を使って設定することが出来ます。:
// vCard タイプを追加する
$this->response->type(array('vcf' => 'text/v-card'));
// レスポンスのContent-Typeをcardに設定する
$this->response->type('vcf');
大抵の場合、追加のコンテンツタイプはコントローラの beforeFilter コールバックの中で設定したいと思うので、 RequestHandlerComponent が提供するビューの自動切り替え機能を活用できます。
コントローラからのレスポンスをダウンロードファイルとして送りたいときがあります。それは メディアビュー を使うか、 CakeResponse の機能を使うことで実現できます。 CakeResponse::download() はダウンロードファイルとしてレスポンスを送れるようにしてくれます。:
public function sendFile($id) {
$this->autoRender = false;
$file = $this->Attachment->getFile($id);
$this->response->type($file['type']);
$this->response->download($file['name']);
$this->response->body($file['content']);
}
上記の例では MediaView を使わずにファイルダウンロードのレスポンスを生成する場合、 CakeResponseをどのように使えばよいかを示しています。
ヘッダの設定は CakeResponse::header() で行われます。このメソッドは少し違ったパラメータ設定と一緒に呼ばれます。:
// ヘッダを一つ設定する
$this->response->header('Location', 'http://example.com');
// 複数ヘッダを設定する
$this->response->header(array('Location' => 'http://example.com', 'X-Extra' => 'My header'));
$this->response->header(array('WWW-Authenticate: Negotiate', 'Content-type: application/pdf'));
同じヘッダを複数回設定すると、普通のheader呼び出しと同じように、以前の値を上書きしていしまいます。 CakeResponse::header() が呼び出されなければヘッダは送られません。これらのヘッダはレスポンスが実際に送られるまでバッファリングされます。
時々、コントローラアクションの結果をキャッシュしないようにブラウザに強制する必要がでてきます。 CakeResponse::disableCache() はそういった目的で使われます。:
public function index() {
// do something.
$this->response->disableCache();
}
警告
Internet Explorerにファイルを送ろうとしている場合、SSLドメインからのダウンロードと一緒にdisableCache()を使うことをエラーにすることができます。
また、CakeResponse::cache() を使ってクライアントにレスポンスをキャッシュして欲しいことを伝えられます。:
public function index() {
//do something
$this->response->cache('-1 minute', '+5 days');
}
上記の例では、訪問者の体感スピード向上のため、クライアントにレスポンス結果を5日間キャッシュするように伝えています。 cache() は第一引数をLast-Modifiedヘッダの値に設定します。ExpiresヘッダとMax-ageヘッダは第二引数の値をもとに設定されます。Cache-Controlヘッダにはpublicが設定されます。
アプリケーションの速度を改善するための簡単で最善の方法の一つはHTTPキャッシュを使う事です。 このキャッシュモデルの元では、modified time, response entity tagなどいくつかのヘッダを設定することでレスポンスのキャッシュコピーを使うべきかどうかをクライアントが決定できるように助ける事が求められます。
キャッシュやデータが変更されたときに無効化(更新)するロジックのコードを持つのではなく、 HTTPは二つのモデル、expirationとvalidationを使います。これらは大抵の場合、自身でキャッシュを管理するよりかなり単純です。
CakeResponse::cache() と独立して、HTTPキャッシュヘッダをチューニングするための様々なメソッドが使えます。 この点に関して、ブラウザやリバースプロキシのキャッシュよりも有利だと言えます。
バージョン 2.1 で追加.
キャッシュ制御ヘッダはexpirationモデルの元で使われ、複数の指示を含んでいます。ブラウザやプロキシがどのようにキャッシュされたコンテンツを扱うのかをその指示で変更することができます。 Cache-Control ヘッダは以下の通りです。:
Cache-Control: private, max-age=3600, must-revalidate
変更されない妥当なCache-Controlヘッダを生成するいくつかのユーティリティメソッドを用いることで CakeResponse クラスはこのヘッダを設定します。一つ目は、CakeResponse::sharable() メソッドです。 このメソッドは異なるユーザやクライアントの間で共有出来ることを考慮されたレスポンスかどうかを示します。 このメソッドは実際には、このヘッダが public または private のどちらなのかを制御しています。 privateにレスポンスを設定することは、レスポンスのすべてまたはその一部が特定のユーザ用であることを示しています。 共有キャッシュのメリットを活かすためにはコントロールディレクティブをpublicに設定する必要があります。
このメソッドの二番目のパラメータはキャッシュの max-age を指定するために使われます。 このパラメータはレスポンスが古いと見なされる秒数を表しています。:
public function view() {
...
// Cache-Control を3600秒の間、publicとして設定
$this->response->sharable(true, 3600);
}
public function my_data() {
...
// Cache-Control を3600秒の間、privateとして設定
$this->response->sharable(false, 3600);
}
CakeResponse はCache-Controlヘッダの中で各コンポーネントを設定するための分割されたメソッドを公開しています。
バージョン 2.1 で追加.
cache expirationモデルのもとではまた、 Expires ヘッダを設定することが出来ます。このヘッダは、 HTTP仕様によるとレスポンスが古いと見なされる日時を表しています。このヘッダは CakeResponse::expires() メソッドを使って設定されます。:
public function view() {
$this->response->expires('+5 days');
}
またこのメソッドは、DateTimeまたはDateTimeクラスによって構文解析可能な文字列を受け付けます。
バージョン 2.1 で追加.
HTTPにおけるキャッシュの検証はコンテンツが定期的に変化するような場合によく使われ、 キャッシュが古いと見なせる場合にのみレスポンスコンテンツが生成されることをアプリケーションに求めます。 このモデルのもとでは、クライアントはページを直接使う代わりにキャッシュの中に保存し続け、アプリケーションに毎回リソースが変更されたかどうかを尋ねます。 これはイメージや他のアセットといった静的なリソースに対して使われる場合が多いです。
Etagヘッダ(entity tagと呼ばれる)は要求されたリソースを識別するための一意な文字列です。大抵の場合はファイルのチェックサムのようなもので、 リソースが一致するかどうかを調べるためにキャッシュはチェックサムを比較するでしょう。
実際にこのヘッダを使うメリットを得るためには、手動で CakeResponse::checkNotModified() メソッドを呼び出すかコントローラに RequestHandlerComponent を読み込まなければなりません。:
public function index() {
$articles = $this->Article->find('all');
$this->response->etag($this->Article->generateHash($articles));
if ($this->response->checkNotModified($this->request)) {
return $this->response;
}
...
}
バージョン 2.1 で追加.
HTTPキャッシュのvalidationモデルのもとでは、リソースが最後に変更された日時を示すために Last-Modified ヘッダを設定することができます。 このヘッダを設定するとCakePHPがキャッシュしているクライアントにレスポンスが変更されたのかどうかを返答する手助けとなります。
実際にこのヘッダを使うメリットを得るためには、 CakeResponse::checkNotModified() メソッドを呼び出すかコントローラに RequestHandlerComponent を読み込まなければなりません。:
public function view() {
$article = $this->Article->find('first');
$this->response->modified($article['Article']['modified']);
if ($this->response->checkNotModified($this->request)) {
return $this->response;
}
...
}
時には同じURLで異なるコンテンツを提供したいと思うかもしれません。これは多国語対応ページがある場合やブラウザごとに異なるHTMLを返すようなケースでしばしばおこります。 そのような状況ではVaryヘッダを使えます。:
$this->response->vary('User-Agent');
$this->response->vary('Accept-Encoding', 'User-Agent');
$this->response->vary('Accept-Language');
コントローラとコンポーネントのテストが簡単に実施できた時、CakeResponse を使っていて良かったと思うかもしれません。 いくつものオブジェクトを横断して使われるメソッドの代わりに、コントローラとコンポーネントが CakeResponse に委譲しているのをまねる(mock)オブジェクトを準備するだけでよくなります。このことで’単体’テストを作りやすくなり、コントローラのテスト実施が簡単になります。:
public function testSomething() {
$this->controller->response = $this->getMock('CakeResponse');
$this->controller->response->expects($this->once())->method('header');
// ...
}
さらに、CLIからヘッダ設定を試みた時に起こる’ヘッダ送信’エラーを避けるためにモックを使うことができるので、コマンドラインからより簡単にテストを実行できます。
CakeResponseはクライアントへ送信するレスポンスと対話するために役立つメソッドをたくさん提供しています。
レスポンスと一緒に送られる一つまたは複数のヘッダを直接設定できます。
レスポンスの中で使われる文字コードの種類を設定します。
レスポンスのコンテンツタイプを設定します。既知のコンテンツタイプの別名かコンテンツタイプの正式名称を使えます。
レスポンスにキャッシュヘッダを設定することが出来ます。
レスポンスにクライアントのキャッシュを無効にするためのヘッダを設定します。
Cache-Controlヘッダに public か private を設定し、任意で、リソースの max-age ディレクティブを設定します。
バージョン 2.1 で追加.
Expires ヘッダに特定の日付を設定することができます。
バージョン 2.1 で追加.
レスポンスリソースを一意に識別するために Etag ヘッダを設定します。
バージョン 2.1 で追加.
Last-Modified ヘッダに特定の日時を正しいフォーマットで設定します。
バージョン 2.1 で追加.
リクエストオブジェクトとレスポンスのキャッシュヘッダを比較し、まだキャッシュが有効かどうかを決定します。 もしまだ有効な場合、レスポンスのコンテンツは削除され 304 Not Modified ヘッダが送られます。
バージョン 2.1 で追加.
レスポンスのgzip圧縮を使用開始します。
添付ファイルとしてレスポンスを送り、ファイル名を設定できます。
レスポンスのステータスコードを設定できます。
レスポンスのコンテンツボディを設定します。
レスポンスの作成が完了した後に、send()を呼び出すことでボディと同様に設定されているすべてのヘッダが送られます。 各リクエストの最後に Dispatcher によって自動的に行われます。