photoBy: http://www.shellbarbakery.com/wp-content/uploads/…
hasAndBelongsToManyをできるだけ簡単に
hasAndBelongsToMany ビヘイビアを作ってみた
cakephpで一番便利だけど、一番難しいのが hasandbelongstomany。
ビヘイビアもネットでいくつかあるけどうまく動かない。
今回は以下の条件で作ってみました。
関連タグ を保存。ただし、すでにあるタグの場合は作成しない。
指定タグがいくつの記事を持っているか を countercacheを利用し、 Tagモデルに保存
できるだけ、簡単に。
Habtmビヘイビアを保存しよう
/app/Model/Behavior/HabtmBehavior.php
class HabtmBehavior extends ModelBehavior {
public function afterSave(Model $Model, $created) { if (count($Model->hasAndBelongsToMany) > 0) { $Model->data = $Model->find('first',array('conditions' => array($Model->name . '.id' => $Model->getLastInsertID()))); reset($Model->hasAndBelongsToMany); $alias=key($Model->hasAndBelongsToMany); $HabtmModel = ClassRegistry::init($Model->hasAndBelongsToMany[$alias]['with']); $tag_ids = Set::combine($Model->data,'/'.$alias.'/./id','/'.$alias.'/./id'); foreach($tag_ids as $v){ $HabtmModel->updateCounterCache(array($HabtmModel->belongsTo[$alias]['foreignKey'] => $v)); } } return true; } function beforeDelete(Model $Model) { //hasAndBelongsToManyが設定されていれば
if (count($Model->hasAndBelongsToMany) > 0) { $Model->data = $Model->find('first', array( 'conditions' => array($Model->name . '.id' => $Model->id) )); } return true; } function afterDelete(Model $Model) { //関連モデルのカウンターキャッシュを更新
foreach ($Model->data as $alias => $modelData) { if (!empty($this->hasAndBelongsToMany[$alias])) { $HabtmModel = ClassRegistry::init($Model->hasAndBelongsToMany[$alias]['with']); $tag_ids = Set::combine($Model->data,'/'.$alias.'/./id','/'.$alias.'/./id'); foreach($tag_ids as $v){ $HabtmModel->updateCounterCache(array($HabtmModel->belongsTo[$alias]['foreignKey'] => $v)); } } } return true; }
}
Appモデルに読み込ませる
/Model/AppModel.php
class AppModel extends Model {
public $actsAs = array( 'Habtm' );
}
・appモデルに追記
/*
保存時にタグをカウントして保存
*/ function saveAssociated($data = null, $options = array()) { foreach ($data as $alias => $AsModelData) { if (!empty($this->hasAndBelongsToMany[$alias])) { $habtm = array(); $AsModel = ClassRegistry::init($this->hasAndBelongsToMany[$alias]['className']); foreach ($AsModelData as $AsModelDatum) { $AsModel->create(); $id = $AsModel->find('first',array('conditions' => $AsModelDatum , 'fields' => array('id'))); if($id){ //すでにデータが有る場合
$habtm[] = $id[$AsModel->name]['id']; } else { //データがない場合 (タグ登録)
$AsModel->save($AsModelDatum); $habtm[] = $AsModel->getLastInsertID(); } } $data[$alias] = array($alias => $habtm); } } return parent::saveAssociated($data, $options); }
ここまでで behaviorの準備完了。
DBとモデルの準備
今回はそれぞれ以下のphpと以下のテーブルフィールドを作る
meigen.php (名言集)
tag.php (タグ)
habtmstag.php (名言とタグのリレーション)
を作る。
hasAndBelongsToManyは、理解するまで難しいので、とにかく以下のコードを作ってみること。
テーブル作成
yourprefix を変更してテーブル作成
CREATE TABLE IF NOT EXISTS `yourprefix_habtms_tags` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`tag_id` int(11) NOT NULL,
`meigen_id` int(11) NOT NULL COMMENT 'meigens テーブルの中間キー',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=ujis AUTO_INCREMENT=783 ;
CREATE TABLE IF NOT EXISTS `yourprefix_meigens` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` text NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=243 ;
CREATE TABLE IF NOT EXISTS `yourprefix_tags` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` text NOT NULL,
`meigen_count` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=33 ;
モデルを作成
Meigen.php
<?php
App::uses('Model', 'Model');
class Meigen extends AppModel {
var $hasAndBelongsToMany = array( 'Tag' => array( 'with' => 'HabtmsTag', 'foreignKey' => 'meigen_id', 'associationForeignKey' => 'tag_id' ), );
}
Tag.php
<?php App::uses('AppModel', 'Model'); class Tag extends AppModel { var $validate = array( 'name' => array( 'rule' => 'isUnique' ) ); var $hasMany = array( 'HabtmsTag' => array( 'foreignKey' => 'tag_id', ) ); }
HabtmsTag.php
<?php
App::uses('AppModel', 'Model'); class HabtmsTag extends AppModel { var $belongsTo = array( 'Tag' => array( 'className' => 'Tag', 'foreignKey' => 'tag_id', 'conditions' => '', 'fields' => '', 'order' => '', 'counterCache' => array( 'meigen_count' => array( ), ) ) ); }
いざ実践!
MeigensController.php
class MeigensController extends AppController {
function index() { $data = array( 'Meigen'=>array( 'name'=>'美味しい果物', ), 'Tag'=>array( 0 => array( 'name'=>'うどん' ), 1 => array( 'name'=>'スープ' ), 2 => array( 'name'=>'チゲなべ' ) ) ); if($this->Meigen->saveAssociated($data)){ } $this->autoRender = false; }
}
これで /meigens/index/ にアクセスすれば、
美味しい果物 という名言テーブルに
うどん、スープ、チゲなべ
というTagが登録、関連付されさらに Tag.meigen_count に、そのタグがいくつの名言を持っているか
が保存されます。
deleteにも対応
deleteした時も Tag.meigen_count を計算しなおすので、便利です。
↧