1. はじめに
  2. CakeRequest に関して
  3. CakeResponse に関して
  4. 自動的にメンバー変数としてアクセスできるクラス

1:はじめに

CakePHP2.0からの主な新機能に一つにCakeRequestクラスの搭載があります。これはリクエストに関するパラメータとメソッドをカプセル化したもので、1.3までメンバー変数配列の $this -> params としてアクセスしていたデータを完全に置き換え、さらにいくつかの便利なメソッドを追加したものになります。同じくレスポンス機能を一つにまとめたクラスとしてCakeResponseが追加されました。実際にクライアント側にHTTPレスポンスを行う仕事は全てこの CakeResponse が担っています。

処理の起点ともいえる webroot/index.php の以下の記述により、リクエスト/レスポンス のオブジェクトが Dispatcher に渡され、コアライブラリの様々な個所で使いまわされます。

$Dispatcher = new Dispatcher();
$Dispatcher->dispatch(new CakeRequest(), new CakeResponse(array('charset' => Configure::read('App.encoding'))));

CakePHP2.0では新しくリクエストとレスポンスオブジェクトが追加されました。以前のバージョンではこれらのオブジェクトは配列で表現されており、 また関連するメソッドは RequestHandlerComponentRouterDispatcherController に分散していました。そのため、 リクエストにどのような情報が含まれているかを正確に表すオブジェクトは存在しませんでした。 バージョン2.0において CakeRequest と CakeResponse は上記の目的で使用されます。
http://book.cakephp.org/2.0/ja/controllers/request-response.html

参考

詳しい使用方法は公式ドキュメントを参照するとして、ここでは調べたものを適宜メモしていきます。記事内の内容は時期が前後している箇所が複数あるため、記載時のバージョンをなるべく掲載するようにしています。

2:CakeRequestに関して

リクエストされたURLがCakeRequestのプロパティにどのようにセットされるか

リクエストURLはCakeRequestのコンストラクタ呼び出しの際に各メソッドでで解析されて、tオブジェクトのプロパティとしてセットされます。

CakeRequestのコンストラクタ呼び出しにおけるプロパティセットの流れ

CakeRequestのプロパティがセットされる流れ

URLの解析の流れを追ったエントリを用意しています。

興味のある方はこちらも併せてご覧ください。「CakeRequest のインスタンス生成時のシーケンス図

さて、それでは具体的なURLを例に、実際にリクエストされたURLがどのようにCakeRequestにセットされ、どのように取得できるかを見てみましょう。(かなり欲張って色々な要素をてんこ盛りにしています)

例:

http://my.subdomain.example.com/my_cake_webroot/my_prefix/my_plugin/my_controller/my_action/param1/param2/name1:value1/name2:value2?key3=value3

http://my.subdomain.example.com/my_cake_webroot/

が Cake の webroot とします。

プロトコルおよびホスト名

  • http: や https:
    プロトコルを示す箇所に関しては、CakeRequest::is(‘ssl’)を用いて、それがSSL経由であるか否かを知ることで間接的に導き出せます。
    ※ env(‘HTTPS’) の真偽値が返ります。
  • “my.subdomain.example.com”
    この部分の、サブドメインを含んだ完全なホスト名は CakeRequest::host() で取得できます。
  • “example.com”
    この部分すなわち、TLD(“com”)とSLD(“example”)の連なりはCakeRequest::domain() で取得できます。
    階層の深さはパラメータで指定できます。デフォルトのパラメータは1。
    デフォルトではこのようにトップレベルに1つの階層を追加した値を取得していますが、例えば引数に”2”を指定すると、この場合は”subdomain.example.com”が返ります。
    注意すべき点は、”example.co.jp”など、それぞれのTLDにより異なる使用者への割り当て事情に関してはCake本体は一切関与しないという点です。これらの世の中の実情に対応するためには個別に適切なパラメータを指定する必要があります。
  • “my.subdomain”
    この部分は、CakeRequest::subdomains() とすることで、”my” と “subdomain” の二つの要素で構成された配列を取得できます。
    デフォルトでは末尾のトップレベルドメインを取り除いた部分が対象となりますが、これも CakeRequest::domain() と同様にパラメータで指定できます(デフォルトは”1″)。
    そしてやはり”or.jp”や”co.jp”などの属性型ドメインについてはCakeは何もかかわらないので、期待する値を取得するために適当な引数を渡す必要があります。

ドメインより後のURL

以下の説明はデフォルトのふるまいであり、いくつかの項目において根本的には Router の設定によって任意の状態に柔軟に変更できます。

  • “/my_cake_webroot/”
    この部分、すなわちドメイン以降のCakePHPまでのパスはCakeRequest::$webroot で取得できます。
    前後にスラッシュが付いていることに注意してください。
    とても似ていますが、CakeRequest::$base では、この末尾のスラッシュのない状態である、”/my_cake_webroot”が取得できます。
    $base と $webroot の違いについては下記(似て非なるもの($base と $webroot))を御覧ください
  • “my_prefix/my_plugin/my_controller/my_action/param1/param2/name1:value1/name2:value2”
    この部分は CakeRequest::$url で取得できます。
    先頭にスラッシュはないことと、”?key3=value3″の部分は含まれないことに注意してください。
    これをルーティングの設定をもとに解析したものがCakePHPにとってのパラメータの基本といえます。
  • “/my_cake_webroot/my_prefix/my_plugin/my_controller/my_action/param1/param2/name1:value1/name2:value2”
    この部分を CakeRequest::$here で取得できます。スラッシュで始まることと、”?key3=value3″の部分のいわゆるクエリ文字列を含まないことに注意してください。
  • “?” 以降のクエリ文字列パラメータ
    これは CakeRequest::$query で、キーと値をペアとした連想配列として取得できます。
    キーのみが指定されて値が指定されていない場合は空の文字列がセットされています。
    一般的にはサーバーサイドとGETでパラメータを渡す際に使われるこのストリングスは、CakePHP本体は可能な限り関わらないように設計されているようです。
  • “my_prefix”, “my_plugin”, “my_controller”, “my_action”
    これら部分はそれぞれ、CakeRequest::$params[‘prefix’], CakeRequest::$params[‘plugin’], CakeRequest::$params[‘controller’], CakeRequest::$params[‘action’] にセットされています。”my_prefix” がプリフィクスとして認識されるかどうかはRouting.prefixesの設定に依存します。
    “my_plugin” がプラグインの名前として認識されるかどうかは、プラグインのロード状況により異なります。
    この値は根本的にはRouter の設定に影響を受けます。
  • “param1”, “param2”
    この部分は CakeRequest::$params[‘pass’] に配列としてセットされています。
    この値は根本的にはRouter の設定に影響を受けます。
  • “name1:value1”, “name2:value2”
    この部分は CakeRequest::$params[‘named’] にキーと値のペアの連想配列としてセットされています。つまり、

    $this->request->params['named']['name1'];

    などとすると、 “value1” を取得できます。
    この値は根本的にはRouter の設定に影響を受けます。

CakeRequest::$params を改めて整理すると次のようになります

prefix “my_prefix”
plugin “my_plugin”
controller “my_controller”
action “my_action”
pass array(“arg1”, “arg2”,…)
named array(“key1″=>”value1″,”key2″=>”value2”,…)

なお、$params のキーにはこのほかにも、例えば “models” や “requested”,”bere” などがセットされる場合もあります。

@see リクエストパラメータにアクセスする
@see Object::requestAction

あるURLが、どのような$params (リクエストパラメータ)に解析されるかを知るには?

あるURLがどのように解析され、結果的にどのような値が $params に含まれるかを知りたい場合は Router::parse($url) メソッドで取得できます。

$params = Router::parse('plugin/controller/action/arg/name:value');

このとき引数の $url は、CakePHP の webroot までを省いたもの、すなわち CakeRequest::$url に相当するものを渡します。

引数の先頭のスラッシュはあってもなくても同じ結果が返ります。

CakeRequest::is で携帯判別も簡単

CakeRequest::is を用いて、リクエストの情報を一定のパターンに分類して識別することができます。このメソッドはチュートリアルなどで送信タイプの判定でよく目にするものですが、用途は送信タイプ判定だけにとどまらず、そのリクエストが「何」であるかを検出するための汎用メソッドと言えます。

CakeRequest::isの判定処理の種類

判定処理の種類は大きく分けて

  1. 環境変数のパターンマッチ
  2. URL要素(plugin, controller, action)の一致
  3. 指定したコールバックの結果

の3つがあります。

コントローラでリクエストの種類判定に用いられる

if ($this->is('post')) { ...

に関しては、このうち1のマッチングを CakeRequest::$_detectors にあらかじめ設定された検出用のパラメータに基づいて行っています。

環境変数を調べるパラメータはデフォルトでは以下の通り設定されています。(v.2.2.3)

type 環境変数 マッチング種別 マッチパターン
get REQUEST_METHOD 完全一致 GET
post REQUEST_METHOD 完全一致 POST
put REQUEST_METHOD 完全一致 PUT
delete REQUEST_METHOD 完全一致 DELETE
head REQUEST_METHOD 完全一致 HEAD
options REQUEST_METHOD 完全一致 OPTIONS
ssl HTTPS 真偽値 true
ajax HTTP_X_REQUESTED_WITH 完全一致 XMLHttpRequest
flash HTTP_USER_AGENT 部分一致 ^(Shockwave|Adobe) Flash
mobile HTTP_USER_AGENT 部分一致(要素をOR連結) ‘Android’, ‘AvantGo’,
‘BlackBerry’, ‘DoCoMo’,
‘Fennec’, ‘iPod’,
‘iPhone’, ‘iPad’,
‘J2ME’, ‘MIDP’,
‘NetFront’, ‘Nokia’,
‘Opera Mini’, ‘Opera Mobi’,
‘PalmOS’, ‘PalmSource’,
‘portalmmm’, ‘Plucker’,
‘ReqwirelessWeb’, ‘SonyEricsson’,
‘Symbian’, ‘UP\\.Browser’,
‘webOS’, ‘Windows CE’,
‘Windows Phone OS’, ‘Xiino’

CakeRequest::isのルールを独自に追加する

そして、これらのデフォルト値以外にも CakeRequest::addDetector を用いて独自で検出ルールを追加することができます。

例えば、

$request->addDetector('crawlers', array('env' => 'HTTP_UESR_AGENT', 'options' => array('Googlebot', 'Slurp', 'Y!J'));

のように ‘crawlers’ というタイプを検出するための設定を行うと、

$this->request->is('crawlers');

の呼び出しで、ユーザーエージェントのパターンマッチを行い、その真偽値が返ってきます。

また、マジックメソッド __call のふるまいにより、

$this->request->isCrawlers()

でも同じ結果を得ることができます。

似て非なるもの($base と $webroot)

CakeRequest には $base と $webroot というとても似ているプロパティがあります。デフォルトでは $base の末尾に単にスラッシュがついたものが $webroot で、どちらもブラウザのリクエストを受け付ける index.php の存在するパスを示しています。

では何のためにこの二つが別々に存在しているのかというと、$base の方はリクエストをルーターが切り分けたり組み立てたりするときの基準として用いるのに対し、 $webroot はViewがアセットのパスを提供するために用いられます。通常はこれらは同じ箇所を起点とするため問題にはなりませんが、これらを異なる設定にしたい場合にはこの違いは重要になります。

単純に言うと、CakeRequest::$base は Router のための基点、CakeRequest::$webroot は View のための基点と言えます。

3:CakeResponse

CakeResponse クラスはHTTPレスポンスを一手に引き受けるクラスです。

一番重要な仕事はクライアント側へのレスポンス、より具体的には php の header() 関数の呼び出しと echo 文の実行がここで行われます。

そしてそれ以外にも便利なメソッドがたくさん用意されていて、例えばステータスコードやコンテンツタイプを変更したり、ダウンロードファイルをレスポンスとして送り出すことが簡単に行えます。

ヘッダの送信、ボディの送信

通常の使い方ではまず意識する必要ありませんが、実際にどこで行われているのかを知るといざというときに安心です。

ヘッダの送信は CakeResponse::_sendHeader() 、ボディの送信は通常のページは CakeResponse::_sendContent() で、ファイルの出力は CakeResponse::_sendFile() で行なっています。

ステータスコードの変更

CakeRequest::statusCode()メソッドを用いることで、コントローラやビューから簡単にステータスコードを変更できます。
例えば検索結果が0件のクエリを発行するページが有ったとして、NotFoundException の例外を投げるという方法もありますが、この機能を用いればビューファイルは通常のものを用いつつレスポンスのみを404にするができるわけです。

サンプルコード

コントローラでNotFoundExceptionを投げるよくあるパターン
/**
 * PostsController::search()
 */
    function search() {
        ....
        if (empty($results)) {
            throw new NotFoundException(__('The post is not found.'));
        }
        ....
    }
例外を投げずに通常のレンダリングを行なってヘッダだけを404にする
<php 
/**
  * PostsController::search()
  */
    function search() {
        ....
        if (empty($results)) {
            $this->response->statusCode(404);
        }
        ....
    }

コンテンツタイプの変更

コンテンツタイプを任意のものに変更する場合、CakeResponse::type() メソッドを用います。ヘッダの送出は自動的に行われます。

<?php
// csvファイルにマップされたコンテンツタイプを返す
$this->response->type('csv');

このように、ファイルの種類をひとつ指定するだけでそれにマッピングされたContent-Typeが選び出されて適切な応答がなされます。予め用意されているタイプは現時点では以下のとおりになっています。バージョンによって適宜更新される可能性があるため直接ソースコードをご覧になることをおすすめしますが、とりあえず記述時現在(v2.3.0β)の一覧をアルファベット順に掲載しておきます。

何らかのコンテンツタイプにマッピングされたタイプ名の一覧(アルファベット順)

  • 7z
  • aac, ai, aif, aifc, aiff, amf, appcache, asc, atom, au, avi
  • bcpio, bin, bz2
  • c, cc, ccad, cdf, class, cpio, cpt, crx, csh, css, csv
  • dcr, dir, dms, doc, docx, drw, dvi, dwg, dxf, dxr
  • eot, eps, etx, exe, ez
  • f, f4a, f4b, f4p, f4v, f90, file, fli, flv, form
  • gif, gtar, gz
  • h, hdf, hh, hqx, htc, htm, html
  • ice, ico, ics, ief, iges, igs, ips, ipx
  • javascript, jpe, jpeg, jpg, js, json
  • kar
  • latex, lha, lsp, lzh
  • m, m4a, m4v, man, manifest, me, mesh, mid, midi, mif, mime, mov, movie, mp2, mp3, mp4, mpe, mpeg, mpg, mpga, ms, msh
  • nc
  • oda, oex, oga, ogg, ogv, otf
  • pbm, pdb, pdf, pgm, pgn, png, pnm, pot, ppm, pps, ppt, pptx, ppz, pre, prt, ps
  • qt
  • ra, ram, ras, rdf, rgb, rm, roff, rpm, rss, rtf, rtx
  • safariextz, scm, set, sgm, sgml, sh, shar, silo, sit, skd, skm, skp, skt, smi, smil, snd, sol, spl, spx, src, step, stl, stp, sv4cpio, sv4crc, svg, svgz, swf
  • t, tar, tcl, tex, texi, texinfo, text, tif, tiff, tpl, tr, tsi, tsp, tsv, ttc, ttf, txt
  • unv, ustar
  • vcd, vcf, vda, viv, vivo, vrml, vtt
  • wap, wav, wbmp, webapp, webm, webp, wml, wmlscript, woff, wrl
  • xbm, xhtml, xhtml-mobile, xlc, xll, xlm, xls, xlsx, xlw, xml, xpi, xpm, xwd, xyz
  • zip

これらのタイプ名(≒拡張子)が具体的にどのコンテンツタイプにマッピングされているかは直接ソースをご覧ください。また、その対応を変更したい場合は、CakeResponse::type()メソッドで上書きや追加が可能。

たとえば

<?php
$this->response->type('newtype', 'text/newcontenttype');

のような具合に追加できます。

ファイルダウンロードのレスポンス

ファイルダウンロード時に、ファイル名を任意に指定したい場合。これまた超カンタン。CakeResponse::download(ファイル名) でOK。ファイルのコンテンツは通常のビューの呼び出しで問題ないし、それ以外のものを直接指定したい場合はCakeResponse::body(コンテンツ) とすれば良い。昔のバージョンではこのあたりのロジックはメディアビューを用いていましたが、2.3からは廃止され、完全に CakeResponse の仕事になりました。

4:自動的にメンバー変数としてアクセスできるクラス

コントローラーを始め幾つかのクラスにおいて、CakeRequest及びCakeResponseのオブジェクトがプロパティとして自動的にセットされています。以下は、その一覧(ver. 2.2.1)

Class name CakeRequest CakeResponse
CookieComponent::$_response のみ protected 宣言で、その他は public です。
したがって、それらの中では $this->request などで各インスタンスにアクセスでき、そうでないオブジェクト内からは、例えばコントローラのインスタンスのプロパティにアクセスする形で操作できます。
Controller と View と Helper に両方がセットされているってこと、そしてModel にはそのどちらも直接渡されていないという点が重要ですね。
Controller public $request public $response
View public $request public $response
Helper public $request public $response
AuthComponent public $request public $response
RequestHandlerComponent public $request public $response
Scaffold public $request none
SecurityComponent public $request none
RedirectRoute none public $response
CookieComponent none protected $_response
Model none none