CakePHP2系のコントローラーでrender結果を取得する方法

某所で僕が質問&自己レスして自己完結した某トピックをまとめておきます。

たとえば表示中のWebページからAJAXでサーバーにアクセスして、サーバー側ではリクエスト内容に応じたJSON文字列を構成して出力する、といった処理を書く場合。
出力したいJSON文字列が単純に、

{"result":true,"value":1}

みたいな構造なのであれば、サーバー側の処理もシンプルに

Configure::write('debug', 0);
$this->autoRender = false;
$this->layout = 'ajax';
header("Content-type:application/json; charset=UTF-8");
echo json_encode(array('result' => true, 'value' => 1));

とでもすればいい。この処理はたぶんCakePHP2系でもそのまま使えるんじゃないかと思います。

問題は、ここで

{"result":true,"html":"<div class=\"example\">いろんな変数を反映させた<strong>ものすごく複雑な</strong>HTML文字列!!</div>"}

みたいなJSON文字列を出力したい場合。
複雑なHTML文字列を準備する必要がある以上、CakePHP本来のrender()機能を活用して、ちゃんと

$this->set(aray('var1' => 'value1', 'var2' => 'value2'));
$this->render();

といった描画処理を行い、その結果生成されるHTML文字列を取得して、それをJSON文字列の一部として組み込んで出力したいわけです。

ここで本題。

CakePHP1.3では、コントローラーのrender()で描画したHTMLをそのまま出力せずに、アクション内で取得して使いたい場合、その処理は以下のようなコードで簡単に実現できました。

// ビュー用の変数をセット
$this->set(aray('var1' => 'value1', 'var2' => 'value2'));

// ヘッダやデバッグ情報など不要な文字列の出力を抑止
Configure::write('debug', 0);
$this->layout = 'ajax';

// 描画結果の取得
$html = $this->render();

// render()結果の出力を解除
$this->output = null;

// JSON形式で出力
header("Content-type:application/json; charset=UTF-8");
echo json_encode(array('result' => true, 'html' => $html));

ところがこの書き方、CakePHP2.0ではまったく機能しません。

具体的に何が変わったかといえば、render()の戻り値はHTML文字列じゃなくオブジェクトになっちゃってるし、コントローラーのoutputプロパティに至ってはそもそも存在しないしと、それはそれは劇的な変更があった様子。
Web上にもあまり参考になる資料がなくて正直、困り果てました。

ソースを追っかけたりして苦労した末見つけた代替のコードは、以下の通りです。ほかにもやり方あるかもしれないけど、誰かの参考になれば。

$this->layout = 'ajax';
$this->render();
$this->response->type('json');
$this->response->body(json_encode(array(
    'result' => true,
    'html' => $this->response->body()
)));
return $this->response;
}    // ※end of function

「$this->response->type(‘json’);」とすることでデバッグ文字列の抑止やヘッダーの追加は勝手にやってくれるようです。アクションの最後にController->responseオブジェクトを返すという処理が特徴的で、これを元に実際の出力が行われる、という挙動らしい。

パーフェクトPHP (PERFECT SERIES 3)
Powered by AmazonLink 2.0.0 beta4.
小川 雄大
技術評論社

[CakePHP]DebugKitのEXPLAINがエラーになる場合のチェック事項

DebugKitツールバーの「Sql Log」タブのEXPLAINボタンが、「Could not fetch EXPLAIN for query」というエラーがポップアップするばかりで使えなくなっている場合。
なんらかの理由で、EXPLAINボタンからのAJAX通信に異常が発生していることが考えられます。

たとえば、そのWebアプリ自体がDebugKitとは関係ないところでAJAXを使っていたとすると。
AJAX通信時に余分なデバッグ情報が出力されるのを避けるために、AppコントローラーのbeforeFilter()あたりで一律に

/*
 * Ajax通信時はデバッグ出力回避
 */
if ($this->RequestHandler->isAjax()) {
    Configure::write('debug', 0);
}

なんてやってませんか?僕はやってました(´~`;)
こういう書き方だと、EXPLAINボタン押下時のAJAX通信でも当然ながらデバッグレベルは0。結果、DebugKitプラグインのToolbarAccessController::sql_explain()のしょっぱなで、

if (
    !$this->RequestHandler->isPost() ||
    empty($this->data['log']['sql']) ||
    empty($this->data['log']['ds']) ||
    empty($this->data['log']['hash']) ||
    Configure::read('debug') == 0
) {
    $this->cakeError('error404', array(array(
        'message' => 'Invalid parameters'
    )));
}

に引っかかって漏れなく404エラーになるわけです。
この問題に対処するためには、さっきのAppコントローラーの記述を

/*
 * Ajax通信時はデバッグ出力回避
 */
if ($this->RequestHandler->isAjax() && $this->params['plugin'] !== 'debug_kit') {
    Configure::write('debug', 0);
}

などとするとよいかと思います。

よくわかるPHPの教科書
Powered by AmazonLink 2.0.0 beta4.
たにぐち まこと
毎日コミュニケーションズ

CakePHP2.0 トップページを作る

「CakePHP2.0 データベースの初期設定」の続き。CakePHPのデフォルトのトップページから赤や黄色の帯が消えたところ。

これで開発を始めることができるわけだけど、最初に何をしたらいいのか?
CakePHP1系に馴染んでた人なら、さっそく何か適当に一通りBakeして、1系で動いてたビヘイビアやらコンポーネントやらが2.0だとどうなっているのか挙動をテストして・・・とか思うとこだろうけど、この記事は徹頭徹尾、ゼロからスタートの感じで書きたい。
というわけで、平凡ですがまずはトップページからいきます(´~`;)

CakePHP1.3とかを使い慣れていて、そこからの移行を検討してる人にはあまり役に立たないと思いますが悪しからず。

トップページがCakePHPのデフォルトのままでは困ってしまうので、まずはそこからいじりたいわけだけど、どうすればいいのか。実は上の画像の画面からちょっと下にスクロールしたところにちゃんと書かれてます。

Editing this Page
To change the content of this page, create: APP/View/Pages/home.ctp.
To change its layout, create: APP/View/Layouts/default.ctp.
You can also add some CSS styles for your pages at: APP/webroot/css.

このページを編集するには
このページの内容を変えたければ「APP/View/Pages/home.ctp」を作れ。
レイアウトも変えたいなら「APP/View/Layouts/default.ctp」を作れ。
CSSでスタイルを設定したければ、自作のページ用のCSSファイルを「APP/webroot/css」フォルダ内に作れ。

はい。たしかに、この通りにやればできます。
ただ、単に「こう書いてあるからやってみました」でやってみて無事できても、多分トップページしかいじれないで終わっちゃうと思うんですよね。(笑)今後のためにも、ここで少しCakePHPのリクツを理解しておく必要がある。

あと、やっぱりCakePHPにはCakePHP風の書き方ってものがあるので、ファイルを作る場合も単に白紙のファイルとして作るんじゃなく、まずはデフォルトで使われてるものをコピーして、そこから作っていった方が便利なことも多いです。
この場合も、home.ctpは白紙で充分だけど、レイアウトファイルであるdefault.ctpに関してはデフォルトのファイルをコピーして使った方が話が早い。

ただ、「デフォルトのファイルをコピー」といっても、デフォルトのファイルがどこにあるのかが問題です。そもそも今、このトップページは、どこの何ていうファイルを表示している状態なのか?

ここでCakePHPのフォルダ構成を見てみます。CakePHPの親フォルダの中には「app」や「lib」といったフォルダが入ってるんですが、この「app」が「CakePHPを使ってアプリケーションを開発していくためのフォルダ」です。我々がファイルを作ったり編集したりするのは、基本的にすべてこのフォルダの中です。

それに対して、「lib」というのはCakePHPのシステムがそのまま入っているところです。CakePHPのユーザーは「app」フォルダ内で少し命令を書くだけで、ややこしい処理の大半を「lib」内のファイルが引き受けてくれるので、比較的簡単にいろんな処理が実現できちゃうという仕組みです。
それだけに、「lib」フォルダは我々アプリケーション開発者が「いじるべきではない」領域、ということになるわけですが、じゃあブラックボックスとして単にほっとけばいいのかというと、そうではないんですね。

今みたいにデフォルトのファイルを見つけてコピーする場合はもちろんのこと、デバッグのためにソースをたどったり、ときには自作のクラスの継承元として使ったり。編集はしないが、参照は大いにする、という感じの関わり方になるわけです。

そこで「lib」フォルダも含めたCakePHP全体のフォルダ構成をよくよく見てみると、実はこれから開発を進めていく「app」フォルダって、「lib」フォルダの中の「Cake」フォルダと構成がよく似ています。

「Config」「Controller」「Model」「View」などの共通するフォルダが格納されてることがわかると思います。
これがどういうことかというと、

  • CakePHPのシステムは基本的に「lib/Cake」フォルダ内のファイルを使って動作していますが、
  • appフォルダ内の同じ場所に同じ名前のファイルがあった場合は、そちらを優先して使おうとします。

構成が似ているのはそのためです。なので、CakePHPがメッセージとして

このページの内容を変えたければ「APP/View/Pages/home.ctp」を作れ。
レイアウトも変えたいなら「APP/View/Layouts/default.ctp」を作れ。

といっている、というのは、要するに今現在、

このトップページの内容は「lib/Cake/View/Pages/home.ctp」に、
レイアウトは「lib/Cake/View/Layouts/default.ctp」にありますよ!

といっている、ということにほかならないわけです。従って、ユーザーが新しく「APP/View/Pages/home.ctp」や「APP/View/Layouts/default.ctp」を作れば、CakePHPはlibフォルダ内の同名のファイルの代わりにそちらを使って動こうとするはずです。

フォルダ構成がなんとなく理解できたところで、さっそく実際に白紙のhome.ctpを「APP/View/Pages」フォルダ内に作ってみることにします。
ブラウザを更新してみると、

大変スッキリとなりました。上下の青い帯がCakePHPでいう「レイアウトファイル」に依存する部分で、そこだけが残っている状態です。

続いてデフォルトのレイアウトファイル「lib/Cake/View/Layouts/default.ctp」をコピーして「APP/View/Layouts/default.ctp」を作ります。
こちらはファイルを作ったといっても中身がまったく一緒なので、ブラウザを更新してもページの見かけは上の画像のままですが、新しい方のレイアウトファイルのBODYタグ内に何か適当な文字列でも書き加えてみれば、間違いなくappフォルダ側のdefault.ctpが読み込まれているということが確認できると思います。

ここから先は、このappフォルダ側の2つのファイルを編集していくことで独自のトップページを作っていきます。

両ファイルの関係性についてですが、2つのファイルのうちレイアウトファイル(default.ctp)はトップページ以外のページにも共通する部品を書くためのファイルです。共通する部品としてはHEADタグ内の全要素に加え、ヘッダー/フッター/サイドバーなどが考えられると思います。
一方、ページ別のファイル(home.ctp)に書かれた内容はレイアウトファイル内で変数「$content_for_layout」として呼び出されるため、

<?php echo $content_for_layout; ?>

の行に出現することになります。

CakePHP2.0 データベースの初期設定

「CakePHP2.0を試してみる」の続き。前回はSecurity.saltとSecurity.cipherSeedの設定が終わるところまで書いた。

考えてみると、このへんってCakePHP1.3のころとべつだん変わってないから、いつ本題にたどり着けるのかって感じがしないでもないな。(笑)今んとこ、フォルダ名がCake1.3までとはところどころ変わってて「へえ」と思う、くらいの違和感しかないんですが。多分そのうち何か出てくると思う。
リアルタイムの記事なのでどうぞ大目に(^^;

続いて最後に残った黄色い帯を片付ける。これは帯にある文言の通り、データベース(MySQLとか)への接続を定義するファイルが作られていないよ!という内容です。
これまで問題になった2本の赤帯と違って黄色(警告)止まりなのは、CakePHPはデータベースなしでも動くから。ただ、ちょっと複雑なものを作ろうとするとデータベースが使えた方がやっぱり便利なので、ここでは設定しておくことにします。

帯のとこに書いてある通りにapp/Config/database.php.defaultファイルを改名・・・するのではなく、コピーしてapp/Config/database.phpを作ってみる(前回のbootstrap.phpと同じ考え方で、デフォルトはなるべく残すようにしてます)と、黄色い帯は消えないものの表示内容が変わる。

「(定義ファイルはあるけど)データベースに接続できないよ」、という意味の文言に変わってるのがわかると思います。なので引き続き、

  1. このサイトで使うデータベースを準備して、
  2. そのデータベースの設定に従ってdatabase.phpの内容を書き換える、

という作業に取りかかります。

ここでは王道中の王道であるMySQLを使うことにします。というか僕はこれしか使ったことがなXAMPPにも最初から入ってるし。
データベースはXAMPPに入ってるphpMyAdminから作ります。コマンドラインからもやれるらしいけど個人的に詳しくないのでパス。phpMyAdminには、XAMPPがちゃんと動いてれば「http://localhost/phpmyadmin/」からアクセスできるはず。

初期画面から「データベース」タブを選んで、

作成したいデータベース名を入力し(仮に「sample」とします)、「照合順序」には「utf8_general_ci」あたりを選択して送信すると、

作ったばかりのDBが開きます。

さあ早速テーブル(表)を作れ!といわんばかりの画面になってるけどそこはスルーして「特権」タブを開き、「新しいユーザを追加する」リンクをクリック。

「ユーザ名」「パスワード」「もう一度入力してください」の3箇所だけ入力して、だいたい画像の通りになってると思ったら「実行する」を押します。

ユーザー名とパスワードはお好きなように。画像の例では「username」さんとしてみました。パスワードは「password」。

これで、できたばかりのデータベース「sample」に対して、指定のパスワードを使って接続すれば「どんな操作でも」実行できる新規ユーザー「username」さんが作成できたわけです。
あとは、この設定をさっきのCakePHPのdatabase.phpに反映する。

    public $default = array(
        'datasource' => 'Database/Mysql',
        'persistent' => false,
        'host' => 'localhost',
        'login' => 'user',
        'password' => 'password',
        'database' => 'database_name',
        'prefix' => '',
        //'encoding' => 'utf8',
    );

    public $default = array(
        'datasource' => 'Database/Mysql',
        'persistent' => false,
        'host' => 'localhost',
        'login' => 'username',
        'password' => 'password',
        'database' => 'sample',
        'prefix' => '',
        'encoding' => 'utf8',
    );

というような具合に編集して保存します。ここでブラウザ(phpMyAdminじゃなくてCakePHPを開いてる方!)を更新すると、

やっと警告の帯が消えました。これで開発に取りかかれます!

これ、正直なところ書くのはけっこう大変だったし(ボソ)、読むのもちょっと大変だろうけど、実際の作業としては順調にいけば10分もかかりません。大したことないからやってみて。
解説記事って、そうだよな。そういうものだよな。(笑)

できればまた続き書きます。