イベントとイベントの属するカテゴリがあるとします。各イベントは複数のカテゴリに所属する。またカテゴリには多くのイベントが登録されているような関係をMANY_MANYの関係といいます。
このような関係を扱う場合、テーブルの設定、モデルでの設定、コントローラでの利用方法をまとめておきます。
テーブルの設定
イベントデータを格納するtbl_eventテーブル、イベントのカテゴリデータを格納するtbl_categoryとイベントがどのカテゴリに所属するかを格納するtbl_event_categoryテーブルを以下のように作成します。作成にはYiiのマイグレーションを利用します。
class m121120_075108_create_event_table extends CDbMigration
{
protected $options = 'ENGINE=InnoDB DEFAULT CHARSET=utf8';
public function up()
{
//tbl_eventテーブル
$this->createTable('tbl_event',array(
'id'=>'pk',
'title'=>'string not null',
'detail'=>'text not null',
'created'=>'datetime',
'modified'=>'datetime'
),
$this->options);
//tbl_categoryテーブル
$this->createTable('tbl_category',array(
'id'=>'pk',
'name'=>'VARCHAR(128) not null',
'position'=>'integer not null',
),
$this->options);
//tbl_event_categoryテーブル
$this->createTable('tbl_event_category',array(
'event_id'=>"integer NOT NULL",
'category_id'=>"integer not null",
"PRIMARY KEY (event_id,category_id)",
),
$this->options);
//外部キーの設定
$this->addForeignKey('FK_event', 'tbl_event_category', 'event_id', 'tbl_event', 'id', 'CASCADE', 'CASCADE');
$this->addForeignKey('FK_category', 'tbl_event_category', 'category_id', 'tbl_category', 'id', 'CASCADE', 'CASCADE');
}
public function down()
{
$this->dropTable('tbl_event');
$this->dropTable('tbl_category');
$this->dropTable('tbl_event_category');
echo "m121120_075108_create_event_table does not support migration down.\n";
return false;
}
}モデルの設定
giiを利用して、Eventモデル、Categoryモデル、EventCategoryモデルの3つを作成します。
作成後、Eventモデルの
public function relations()
{
return array(
'tblCategories' => array(self::MANY_MANY, 'Category', 'tbl_event_category(event_id, category_id)'),
);
}は
public function relations()
{
return array(
'categories' => array(self::MANY_MANY, 'Category', 'tbl_event_category(event_id, category_id)'),
);
}のように修正しておきます。これで、EventモデルとCategoryモデルがMANY_MANYの関係であることの設定ができました。
イベントを登録、更新する
イベントデータ登録後、イベントの属するカテゴリのデータも更新できるようにEventモデルのafterSave関数を利用して、tbl_event_categoryテーブルを更新するようにします。
protected function afterSave()
{
parent::afterSave();
EventCategory::model()->updateCategory($model->id,$model->category1,$model->category2);
}EventCategoryモデルには、以下の処理を追加しておきます。
public function updateCategory($event_id, $category1,$cateogory2)
{
//すでに登録されている場合はすべて削除する。
$this->deleteCategory($event_id);
$cat1=is_array($category1) ? $category1 : explode(',',$category1);
$cat2=is_array($cateogory2) ? $cateogory2 : explode(',',$cateogory2);
$arr=array();
if(!empty($cat1))
$arr=array_merge($arr,$cat1);
if(!empty($cat2))
$arr=array_merge($arr,$cat2);
$arr=array_unique($arr);
//新規に登録する。
foreach($arr as $category)
{
if(empty($category)) continue;
$this->isNewRecord = true;
$this->primaryKey = NULL;
$this->event_id=$event_id;
$this->category_id=$category;
$this->save(false);
}
}
public function deleteCategory($event_id)
{
$this->deleteAll('event_id=:event_id',array(':event_id'=>$event_id));
}EventコントローラのactionCreateやactionUpdateの
if($model->save())
$this->redirect(array('view','id'=>$model->id));の部分については
$transaction = $model->dbConnection->beginTransaction(); // transaction
try
{
$model->save();
$transaction->commit(); // commit
$this->redirect(array('view','id'=>$model->id));
}
catch(Exception $e)
{
$transaction->rollBack(); // rollBack
Yii::app()->user->setFlash('error','更新に失敗しました。');
}のようにトランザクションを利用するようにします。
イベントを削除する
イベントデータ削除後については、テーブルの作成時に外部キーで ON DELETE CASCADE を設定しているので、tbl_eventテーブルのデータを削除するとtbl_event_categoryのデータも連動して削除されるので、PHPで削除処理を書く必要はありません。
もし、DBのほうで外部キーをサポートしていないなら、EventモデルのafterDelete関数を利用してtbl_event_categoryテーブルのデータも削除するようにしたらよいでしょう。
protected function afterDelete()
{
parent::afterDelete();
EventCategory::model()->deleteCategory($this->getPrimaryKey());
}EventコントローラのactionDeleteも
$this->loadModel($id)->delete();
の部分を
$model=$this->loadModel($id);
$transaction = $model->dbConnection->beginTransaction(); // transaction
try
{
$model->delete();
$transaction->commit(); // commit
}
catch(Exception $e)
{
$transaction->rollBack(); // rollBack
}のように修正します。
イベント名やカテゴリで検索する
検索ページで入力したイベント名やカテゴリなどの検索条件をもとに、データを検索し、検索結果ページに表示するような処理を組み込みます。
Eventコントローラでは、以下のようにsearchアクションを作成します。
public function actionSearch()
{
$model=new Event();
if(isset($_GET['Event']))
{
$model->attributes=$_GET['Event'];
if ($model->validate())
{
$dataProvider = $model->loadAll();
$this->render('result', array('dataProvider'=>$dataProvider));
}
else
$this->render('search',array('model'=>$model));
}
else
$this->render('search',array('model'=>$model));
}検索ページとしてevent/search.phpビューを作成します。
<?php $form=$this->beginWidget('bootstrap.widgets.TbActiveForm',array(
'id'=>'search-form',
)); ?>
<?php echo $form->dropDownListRow($model,'category',Category::categories(),array('class'=>'span5','empty'=>'--')); ?>
<?php echo $form->textFieldRow($model,'title',array('class'=>'span5','maxlength'=>128)); ?>
<div class="form-actions">
<?php $this->widget('bootstrap.widgets.TbButton', array(
'buttonType'=>'submit',
'type'=>'primary',
'label'=>'Search',
)); ?>
</div>
<?php $this->endWidget(); ?>なお、Category::categories()はselectボックスの選択肢としてカテゴリのリストを取り出す関数です。Cateogoryモデル内で以下のようになっています。
private static $_categories=array();
public static function categories()
{
if(!isset(self::$_categories) || empty(self::$_categories))
self::loadCategories();
return self::$_categories;
}
private static function loadCategories()
{
self::$_categories=array();
$models=self::model()->findAll(array(
'select'=>'id,name',
'order'=>'position',
));
foreach($models as $model)
self::$_categories[$model->id]=$model->name;
}また、tbl_eventフィールドにないcategory属性を利用するため、Eventモデル内には、
public $category=null;
を追加しておく必要があります。この他にもrules関数やattributeLabels関数にも適切に設定しなければなりません。
次に、検索結果ページとしてevent/result.phpビューを用意します。
<?php $this->widget('bootstrap.widgets.TbListView', array(
'dataProvider' => $dataProvider,
'itemView' => '_result',
)); ?>event/_result.phpは以下のとおりです。イベントが属するカテゴリをforeachで書き出しています。
<div class="view row">
<?php echo CHtml::encode($data->title); ?><br />
<?php foreach($data->cateogories as $category): ?>
<?php echo CHtml::encode($category->name); ?>,
<?php endforeach; ?>
<?php echo CHtml::encode($data->detail); ?>
</div>最後に、dataProviderを用意するloadAll関数ですが、Eventモデル内で以下のようになっています。
public function loadAll()
{
$criteria = new CDbCriteria;
if($this->title!='')
{
$criteria2 = new CDbCriteria;
$criteria2->compare('title',$this->title,true);
$criteria->mergeWith($criteria2);
}
if($this->category)
{
$criteria->mergeWith(array(
'with' => array(
'categories'=>array(
'condition'=>'categories.id=:id',
'params'=>array(':id'=>$this->category),
),
),
'group'=>'t.id',
'together'=>true));
}
return new CActiveDataProvider(get_class($this), array(
'criteria' => $criteria,
));
}