2012年11月26日月曜日

[CakePHP]recursive=2で取得してしまう不要なデータを取り除く

モデルのアソシエイションで多対多を実現するには、以下の2通りの
方法がある。

model Aとmodl Bが多対多である場合、

1.以下のようにHABTMを設定する。
 ・A HABTM B
 ・B HABTM A

2.belongsToとhasManyの関係に分割する。
 ・A hasMany AsB
 ・AsB belongsTo A,B
 ・B hasMany AsB

1の場合も中間テーブルAsBは必要なのだが、
AとBのidしかカラムに設定できず、そのほかの情報が保存できない、
というデメリットがあるため、私は2を使用する時がある。

そのような場合、Model::find()を使用すると、デフォルトだと中間テーブルまで
しかデータを取得してきてくれない。

なので、recursiveに2を設定する。

ただ、それだと、不要なデータまでたくさん取得してきてしまう可能性がある。
例えば上記の2のアソシエイションと同時に

 ・A belongsTo C
 ・C hasMany A

といったアソシエイションも持っている状況でAがカレントのモデルの際に
A->find()を行うと想定する。

すると、取得してくるデータは以下のようになってしまう。

①A
②A->AsB
③A->AsB->B
④A->AsB->A
⑤A->C
⑥A->C->A

④と⑥は重複している情報であり、無駄である。
なので、この場合は、「AsB belongsTo B」と「C hasMany A」というアソシエイションを
unbindModel()にて一時的に解除してあげればよい。

下記のサイトを参考にさせて頂いた。

CakePHP recursive=2で不要な関係データの取り除き方

2012年11月22日木曜日

[jQuery]コロンを含むセレクタを指定する方法

以下のサイトで説明されているよう、コロンの前に二つのバックスラッシュを
つけてエスケープしなければならない。
なぜ二つかはわからない。

JQueryでコロン付きのセレクターを使う場合

私の場合は既に変数で指定されている文字列を使う必要があったので
下記のようにコロンを置換して実装した。
なお、4行目に試しに置換後の文字列を表示しているが、
なぜか「abc\:1234」とバックスラッシュが一つだけで表示される。
//以下JavaScript部分 
var test ="abc:1234"
test2=test.replace(':',"\\:")
alert(test2);  //「abc\:1234」と表示される。
alert($("#"+test2).val());  //「5678」と表示される。
//以下HTML部分
<input type="hidden" id="abc:1234" value="5678"> 

[CakePHP]セッション上の配列の一番後ろに格納する方法

以下のページの「5.7.1.1 write」にあるよう、CakePHPのsessionコンポーネントの$this->Session->write()で多次元配列を格納できるが、
連想配列でなく通常の配列の一番後ろに格納する方法がわからなかった。

メソッド :: セッション :: 主要なコンポーネント :: マニュアル :: 1.3コレクション 

そこで、以下で紹介されているように

Can I use array_push on a SESSION array in php?
 
$_SESSION['names'] = array();
array_push($_SESSION['names'],$name);
もしくは
$_SESSION['names'][] = $name; 
というようにすれば、namesの下に配列として0から格納し、
最後の添え字に+1した添え字で格納してくれることになる。
なので、シンプルな後者を使うことにした。
ちなみに、0ではない数字(例えば1)からカウントアップしていきたい場合は
以下で紹介されているよう。最初の値を1に格納してしまえばいいそう。

配列について

また、私の場合は、配列の添え字(上の例で言えば'name')
を変数で指定したいと考えていた。
そのためには下記のサイトで説明されているよう変数をダブルクオテーション
で囲む必要があるとのこと。

PHP で、変数を連想配列(ハッシュ)のキーにしたい。

こんな感じ。
$items["$item->id"] = $item->name;
ただ、私の場合は以下のように変数に角括弧を含むものを使おうとしていたら
動かなかった。
$_SESSION["$this->data['TargetSite']"][] = $result;
なので、以下のように一度別の変数に移してから格納したら正常に動いた。
$TargetSite=$this->data['TargetSite'];
$_SESSION["$TargetSite"][] = $result;

【課題】
やはり、 CakePHP標準の$this->Session->write()を使用して
同じことができるようになりたいな。
そちらのほうが、CakePHPの機能のセッション領域をDBにしたりする
のに簡単に対応できそう。
まぁ、ひとまず上記のやり方でいいや。

2012年11月18日日曜日

[CakePHP]error_logを使う際の注意点

ログに配列の中身を表示する際、CakePHPで通常使用するCakeLog::write()だと、
配列の中身は表示できない。
そこで、下記のブログで紹介されているようにerror_logを使用していた。

[php][tips]phpのエラーログに、array配列の中身をきれいに吐く。

ただ、ここで不思議な現象がおきた。

error_logを使用すると、

「Notice (8): Undefined variable」や
「Warning (2): Invalid argument supplied for foreach()」
など、今まで画面に表示されなかったメッセージが画面に表示される
ようになる。
ただ、error_logに渡したメッセージは相変わらずログにしか表示されない。

AJAXの出力でこれが出力されてしまっていて、
動きがおかしくなっていた。
これを防止するには、下記のコマンドでメッセージを出さない
ようにする。
Configure::write('debug', 0);

[CakePHP]update文を発行する保存は同じselect文を二回発行する件について

Model::save()やModel::saveAll()でプライマリーキーがセットされていたら、
自動的にSELECT文を発行して、すでに存在するレコードかどうか確認してくれるんだけど、
なぜか、このSELECT文、まったく同じものが2回発行されるんだよね。

大してユーザがいないシステムなら問題なら良いけど、もし人気サイトなんかに
なったりしたら、アップデートを行う際は、1.5倍のSQL発行量にになってしまうのは
よろしくない気がする。

以下のサイトで対策を教えて下さっているので、
今後、パフォーマンスに問題を感じるようになったらやろう。

CakephpでUPDATEするときのクエリを(少しだけ)減らしてみた

2012年11月6日火曜日

[CakePHP]scriptBlock()やscriptStart()をechoする必要がある場合。

CakePHPのHtmlHelper->scriptBlock()やscriptStart()は$scripts_for_layoutで出力されない

 このサイトに書いてあるように、'inline' => trueで使用する場合は、
echoしてあげなっきゃスクリプトがはかれない。
少しはまった。 

[Ajax]Ajaxで取得したHTML内のスクリプトを実行するには

このページに書いてあるよう、scriptタグを指定して、evalをかければ実行できた。

Ajax/Ajaxで取ってきたHTMLコンテンツ内の<script>タグを実行させたい

[jQuery]$.ajaxではまった件

$.ajax({
    complete:function (XMLHttpRequest, textStatus) {
        document.getElementById("result").innerHTML=XMLHttpRequest.responseText;
    },
    data:"data[Keywords]="+$("#lastKeywords").val()+"&data[ItemPage]="+$("#nextPage").val();
    type:"post",
    url:    "\/rakuzon\/themes\/itemSearch"
});
このコードが動かなかったんだけど、5行目のセミコロンをカンマにしてあげたら
動いた。(丸一日つぶした。。。)
また、dataには配列として渡してあげたほうが見やすいきがしたので以下のように修正。
$.ajax({
    complete:function (XMLHttpRequest, textStatus) {
        document.getElementById("result").innerHTML=XMLHttpRequest.responseText;
        eval($("#result script").text());
    },
    data:{
        'data[Keywords]':$("#lastKeywords").val(),
        'data[ItemPage]':$("#nextPage").val()
    },
    type:"post",
    url:"\/rakuzon\/themes\/itemSearch"
}); 
以下が参考にしたサイト。

[jQuery]postメソッドとajaxメソッドを使ってPOST送信を行うサンプル
連想配列の使用方法

【追記 2012/11/6】
なお、data:に設定するのは連想配列となる。
事前に連想配列を以下のように用意しておくことも可能だ。
postData={
    'data[Keywords]':$("#lastKeywords").val(),
    'data[ItemPage]':$("#prevPage").val()
};
$.ajax({
    complete:function (XMLHttpRequest, textStatus) {
        $("#result").html(XMLHttpRequest.responseText);
        bindPager();
    },
    data:postData,
    type:"post",
    url:"\/rakuzon\/themes\/itemSearch"
});
しかし、以下のように連想配列の要素ごとに代入していくやり方だと
なぜかうまく$.ajax()が動かなかった。原因不明。
postData['data[Keywords]']=$("#lastKeywords").val();
postData['data[ItemPage]']=$("#prevPage").val();
$.ajax({
    complete:function (XMLHttpRequest, textStatus) {
        $("#result").html(XMLHttpRequest.responseText);
        bindPager();
    },
    data:postData,
    type:"post",
    url:"\/rakuzon\/themes\/itemSearch"
}); 

2012年11月5日月曜日

[PHP]xdebugで変数の値が変化しない場合

下記のサイトで紹介されているバージョンのxdebugを 導入したら変化するようになった。
 自分が使っているのはおそらくもう少し古いやつだ。

eclipseでphpのデバッグをすると変数の値がになる~xdebug

2012年11月3日土曜日

[CakePHP]一気に複数のアソシエイションをunbindModelする

単純にそのモデルだけにfindしたいだけなのに、
アソシエイション先のモデルも引っ張ってきてしまうのは
負荷の問題上よろしくない気がする。

今までは全てのアソシエイションを指定してunbindModelをしていたのだが、
以下の記事によると、recursiveを設定したほうが手軽ということがわかった。

まとめてunbindModelするとき

ただし、設定の仕方は、
$params = array(‘recursive’ => -1);
よりも
 $this->Model->recursive = -1;

のほうが良い気がします。

[JavaScript]処理の途中で強制終了させるには。

exit、returnのようなコマンドで処理を強制終了させることができる
プログラミング言語って結構あるけど、JavaScriptはそういうコマンドが
ないとのこと。

なので、try~cacth構文を使用して代替する。
以下のサイトがわかりやすい。

JavaScript try catchによる例外処理

2012年11月2日金曜日

[CakePHP]$usesの謎

以下のようなアソシエイションの関係がある場合。

Theme hasAndBelongsToMany Tag
Tag hasAndBelongsToMany Theme
Tag hasMany ThemesTag
Theme hasMany ThemesTag
ThemesTag belongsTo  Theme
ThemesTag belongsTo Tag

 MainControllerというどのモデルの名前も使わない
コントローラーにて、
public $uses = array('Item','Tag','Theme','ThemesTag');

というように使用するモデルを指定した場合、問題が一つ起きる。

以下のような、HABTM関係の中間モデル、ThemesTagでのpaginate(もしかしたらfindも)にて
アソシエイション先のテーブルからデータをとってこない、という問題。
上記のアソシエイションの関係では、belongsToの関係を持つ、
ThemeとTagをJOINして持ってくると期待していたのに。
また、ThemeやTagの場合は期待通りアソシエイション先からデータを取得してくれる。
 $this->paginate('ThemesTag');

そこで、ためしに以下のように$usesで指定する順番を変えてみる。
 public $uses = array('ThemesTag','Item','Tag','Theme);

このように1番目にThemesTagを持ってくると期待通り、
アソシエイション先のテーブルからデータを取得してく れた。

次に、以下のようにThemesTagを2番目に指定したところ、
やはりだめだった。
 public $uses = array('Item','ThemesTag','Tag','Theme);

続いて、以下のようにpaginateの直前でbindModelを行ったところ、
アソシエイション先のテーブルからデータを取得してく れた。
$this->ThemesTag->bindModel(array('belongsTo'=>array('Theme','Tag')),false);

 【推論】
■中間テーブルは自モデルのコントローラでのみ、
 bindModel無しでアソシエイション先のテーブルからデータを取得する。
 ※自モデルのコントローラとは上記の例でいえば
     ・ThemesTagController。
     ・nameにThemesTagを指定したController 。
     ・どうやら以下のサイトによると$usesの先頭で指定したモデルが自モデルとなるらしい。
   Controller $uses への Model 名の登録順序
 

【結局どうするか】
$nameで中間テーブルを指定するのも変だし、
$usesで1番目を指定するというトリッキーなこともしたくないので、
bindModelをすることにする。

でも、なんで中間テーブルだけ違うのか謎だよなぁ。