‘CakePHP’ カテゴリーのアーカイブ

Controller から View に ‘title’ を セットする際の注意

2010年4月4日 日曜日

CakePHP では、意外なところに例外的な措置があって、思わぬ事態に遭遇することがあります。知っていれば便利な機能も、知らないと逆に不便。

今回は、Controller::set で、’title’ をキーにしてViewに値を渡そうとして発覚。

‘title’ というキーは、Controller::set で特別な意味を持ちます。
すなわち、’title’ というキーで変数をセットした場合、Viewファイル内で $title として展開されず、レイアウトファイルの $title_for_layout ( ページタイトル) として展開されます。

/**
 * Saves a variable for use inside a view template.
 *
 * @param mixed $one A string or an array of data.
 * @param mixed $two Value in case $one is a string (which then works as the key).
 *   Unused if $one is an associative array, otherwise serves as the values to $one's keys.
 * @return void
 * @access public
 * @link http://book.cakephp.org/view/427/set
 */
	function set($one, $two = null) {
		$data = array();

		if (is_array($one)) {
			if (is_array($two)) {
				$data = array_combine($one, $two);
			} else {
				$data = $one;
			}
		} else {
			$data = array($one => $two);
		}

		foreach ($data as $name => $value) {
			if ($name === 'title') {
				$this->pageTitle = $value;
			} else {
				if ($two === null && is_array($one)) {
					$this->viewVars[Inflector::variable($name)] = $value;
				} else {
					$this->viewVars[$name] = $value;
				}
			}
		}
	}

この25行目を見ると明らかですが、つまり、コントローラーで ‘title’というキーで値をセットしても、Viewには渡らないということです。なので、この箇所をオーバーライドするか、別のキーで値を渡すようにしなくてはなりませんね。

ただ、こんな肝心なメソッドを上書きしちゃうと、他のアプリの構築のときにまた勝手が変わって面倒なことになるのでそれはしたくないです。また、’title’という文字だけ特別扱いするというのも汎用性に欠けるため好ましくない(変数の構成が動的に変化する場合、必ずしも変数のキーを知っているとは限らないから)。

なので、今後 Viewに値を渡す場合は、常にハッシュでくるんで、Viewでそれを展開することにしたいと思っています。というか、これまではその方法だったのですが、今回初めて直接 ‘title’ というキーを用いたため気づきました。

このような親切設計も、その実装を知っていれば便利ですが、そうでない場合はかえって時間がかかって困ってしまいますね。

Element から別のElement を呼び出す

2010年3月7日 日曜日

公式マニュアルを含むWeb上の情報で見つけることができなかったので、メモしておきます。

そもそも、CakePHPを体系的に理解できている人、具体的には Element が View でどのように展開されているのかを理解している人にとっては自明なことで、いちいち疑問にならないことかもしれません。
私自身は非常に行き当たりばったりの付け焼刃な知識しかない上、ソースを読む時間もなかったので単純な処方箋のみになります。
時間のある方は是非一度詳しくお調べになってください。

何がしたいのかというと、Viewから呼び出したElementの中から、さらに別のElementを呼び出すことです。

結論だけ言いますと、Viewに記述するのと全く同じやり方で問題なさそうです。

たとえば、View ファイル view.ctp から Elementファイル elm1.ctp を呼び出す。そして、その elm1.ctp の中に、別のElementファイル elm2.ctp を呼び出す記述をする場合、次の通りで問題ないというのが今のところの結果です。

view.ctp内

echo $this -> element( "elm1" );

elm1.ctp内

echo $this -> element( "elm2" );

ViewからElementへ新たにパラメータを追加した場合、そのパラメータは引き継がない。
たとえば、view.ctp から、 elm1.ctp へ追加したパラメータを elm2.ctp でも追加したい場合は、 elm1.ctp 内で同様に明示的に追加する必要がある。

view.ctp内

$params = array( "name1" => "value1" );
echo $this -> element( "elm1", $params );

elm1.ctp内

echo $this -> element( "elm2", $params );
//echo $this -> element( "elm2" ); //これでは $params は elm2.ctp には渡らない

Scaffolding の、 index の行数を変える

2009年11月2日 月曜日

Scaffold クラスは、コントローラの $paginate メンバー変数を参照している。

デフォルトでは

var $paginate = array('limit' => 20, 'page' => 1);

なので、1ページ目の20行が表示される。

これを、Controller の継承クラスで上書きして任意に調整。

Model のメンバー変数($belongsTo など)を、コンストラクタで設定する

2009年10月21日 水曜日

気付いたのでメモ。
「Model::$belongsTo などを、コンストラクタで設定する場合、式の右辺が配列にならないようにするべき。」

たとえば、一般にメンバーフィールドとして記述する際には、必要な値のみ、

class Hoge extends AppModel
{
  var $belongsTo["Moge"] = array(
    "className" => "Moge",
    "foreignKey" => "moge_id",
  );

}

とすればいいですが、これと同じことをやったつもりでも、コンストラクタに以下のように書くと思った通りに動作しません。

class Hoge extends AppModel
{
  function __construct()
  {
     parent::__construct();
     $this -> belongsTo["Moge"] = array(
       "className" => "Moge",
       "foreignKey" => "moge_id",
     );
  }
}

ソースを読む時間がないので処方箋だけを言えば、

class Hoge extends AppModel
{
  function __construct()
  {
    parent::__construct();
    $this -> belongsTo["Moge"]["className"] = "Moge";
    $this -> belongsTo["Moge"]["foreignKey"] => "moge_id";
  }
}

と、する。

親クラスのインスタンス化の際に、”className”, “foreignKey” だけでなく、”conditions”, “fields”, “order”, “counterCache” にも空の値(stirng(0) “”)がセットされています。

$this -> belongsTo = array(
  "Moge" => array(
    "className" => "Moge",
    "foreignKey" => "Hoge.moge_id",
    "conditions" => "",
    "fields" => "",
    "order" => "",
    "counterCache" => "",
  )
);

これら6つの要素のすべてか、一部かは分かりませんが、$belongsTo["Moge"]配列のキーとして、たとえ空の値であってもセットされていないと、アソシエーションを組んでくれないみたいです。

なので、コンストラクタでこれを定義する際は、それらの空の要素も含めて指定するか、必要な値のみを(配列ではなく、次数を落として)文字でセットする必要があります。

毎度まいど、バカバカしいことで・・・

2009年10月4日 日曜日

CakePHPの、ついうっかり忘れてしまう基本事項。
何ヶ月かぶりにさわると、よく間違える。
箇条書きにして、機会があれば見直すための備忘録。

  • コントローラがビューするメソッドは Controller::render()、よく Controller::display() と Smarty風にやってしまう。
  • モデルからレコードをセレクトするメソッドは Model::find()、よく Model::select() とやってしまう。

CakePHP の scaffolding で、date に 空の値をセットしたい

2009年10月3日 土曜日

CakePHP の scaffolding で、date (もしくは datetime )形式の入力フォームに NULLが無いことが不便でした。

そこでカスタマイズしたのでメモ。

フォームの部品を生成するために FormHelper を使っているだろうことは想像に難くないので、途中でいかにオプションを渡してやるかがポイントになりそう。

まず、フォームがどの順番に呼び出されているのか debug_backtrace で調べる。
すると、

app/views/helpers/form.php (1604)#dateTime
app/views/helpers/form.php (797)#input
app/views/helpers/form.php (538)#inputs
app/views/scaffolds/edit.ctp (28)#include

(※上に行くほど新しい)

となっていました。
FormHelper::inputs() なんていうのは私には聞き初めだったのですが、見てみると引数でカラムの配列を渡すと、それを自動的に FormHelper::input() に展開してくれるメソッドらしい。

詳しくはこちら
http://api.cakephp.org/class/form-helper#method-FormHelperinputs

で、ここに渡す時点で、 scaffolds/edit.ctp から何もオプションを渡していないわけです。
scaffolds/edt.ctp から FormHelper::inputs() へ渡している第一引数は、null です。
そして、この場合、FormHelper のメンバー変数、 $fieldset['fields'] のキーを array_keys を用いて配列に展開しています。
この $fieldset['fields'] というは、FormHelper::create() を呼び出したときに、Model::schema() から取得したテーブルスキーマがそのまま格納されています。

このままでは、 FormHelper::input() に渡すオプションは空配列になってしまうので、日付のプルダウンリストはデフォルトのまま出力されてしまい、空の値はセットされません。

では、どこを触るべきか?
まず、肝心のコントローラー Scaffold は、コアライブラリにしか存在せず、また、これをアプリにコピーして編集しても反映されません。(Scaffoldクラスは、MVCのCではあるもののAppControllerは継承していないです)
仕方ないので、 scaffolds/edit.ctp において、オプション指定したい場合に何らかの操作をします。

触る箇所は、

echo $form->inputs(null, array('created', 'modified', 'updated'));

の、最初の引数 null に、具体的な値を指定します。

以下、説明をすっ飛ばして結論だけ
(ついでに、’dateFormat’と’timeFormat’オプションも指定しました。ただし、これはFormHelper自体をカスタマイズしてる方が多いと思われます。)

/*------------------------------------------------
app/views/scaffolds/edit.ctp
( 2008-12-18 18:16:01 更新のファイルの場合、27行目 )
------------------------------------------------*/
echo $form->create();
echo $form->inputs(null, array('created', 'modified', 'updated'));

この2行の間に、いくつかのコードを挿入し、最後の行で引数を指定します。

こんな感じ。

echo $form->create();
/*---------------------------------
カスタマイズ
date datetime に、empty をセットする
----------------------------------*/
$_fields = $form->fieldset['fields'];
$fields = array();
foreach( $_fields as $_name => $_field )
{
  if( $_field['type'] == 'date' ||
    $_field['type'] == 'datetime' )
    $fields[$_name] = array(
      "empty" => true,
      "dateFormat" => "YMD",
      "timeFormat" => "24",
    );
  else
    $fields[$_name] = array();  // または、 $fields[] = $_name; でも良い
}
echo $form->inputs($fields, array('created', 'modified', 'updated'));

以上。

ここがおかしいとか、もっといい方法があるよ~って方は、コメントしていただけるとありがたいです。

scaffolding のカスタマイズに関する実例が詳細に掲載されています。