Saya pakai path, IMHO lebih simple - tapi kata orang gak efisien kalau recordnya banyak -
Bikin table terus bikin model dan CRUD pakai gii.
<?php
/**
* This is the model class for table "{{category}}".
*
* The followings are the available columns in table '{{category}}':
* @property integer $id
* @property integer $category_id
* @property string $title
* @property string $description
* @property string $path
* @property string $status
*
* The followings are the available model relations:
* @property Post[] $posts
*/
class Category extends CActiveRecord
{
public $oldParent;
public $oldPath;
/**
* Returns the static model of the specified AR class.
* @return Category the static model class
*/
public static function model($className=__CLASS__)
{
return parent::model($className);
}
/**
* @return string the associated database table name
*/
public function tableName()
{
return '{{category}}';
}
/**
* @return array validation rules for model attributes.
*/
public function rules()
{
// NOTE: you should only define rules for those attributes that
// will receive user inputs.
return array(
array('title', 'required'),
array('title', 'filter', 'filter'=>'trim'),
array('title', 'match','pattern'=>'/^[\w\s]+$/i'),
array('title', 'unique'),
array('category_id', 'filter', 'filter' => 'intval'), // if empty category_id become 0
array('category_id', 'numerical', 'integerOnly'=>true),
array('title', 'length', 'max'=>25),
array('description', 'length', 'max'=>255),
array('status', 'match','pattern'=>'/^draft$|^published$|^archived$/'),
// The following rule is used by search().
// Please remove those attributes that should not be searched.
array('id, category_id, title, description, path, status', 'safe', 'on'=>'search'),
);
}
/**
* @return array relational rules.
*/
public function relations()
{
// NOTE: you may need to adjust the relation name and the related
// class name for the relations automatically generated below.
return array(
'posts' => array(self::HAS_MANY, 'Post', 'category_path'),
);
}
/**
* @return array customized attribute labels (name=>label)
*/
public function attributeLabels()
{
return array(
'id' => 'ID',
'category_id' => 'Parent Category',
'title' => 'Title',
'description' => 'Description',
'path' => 'Path',
'status' => 'Status',
);
}
/**
* Retrieves a list of models based on the current search/filter conditions.
* @return CActiveDataProvider the data provider that can return the models based on the search/filter conditions.
*/
public function search()
{
// Warning: Please modify the following code to remove attributes that
// should not be searched.
$criteria=new CDbCriteria;
$criteria->compare('id',$this->id);
$criteria->compare('category_id',$this->category_id);
$criteria->compare('title',$this->title,true);
$criteria->compare('description',$this->description,true);
$criteria->compare('path',$this->path,true);
$criteria->compare('status',$this->status,true);
return new CActiveDataProvider(get_class($this), array(
'criteria'=>$criteria,
));
}
/**
* Replace path for easy read, used on dropdownlist
*/
static function alterPath(&$item)
{
$item['path'] = ucwords(str_replace('-',' - ',str_replace('_',' ',$item['path'])));
}
/*
* List path,path for looking post category path
*/
public function getPathOptions()
{
$rows = Yii::app()->db->createCommand()
->select('path as id,path')
->from('{{category}}')
->order('path ASC')
->queryAll();
array_walk($rows,'Category::alterPath');
return CHtml::listData($rows,'id','path');
}
/*
* List id,path for looking parent category
*/
public function getOptions()
{
$rows = Yii::app()->db->createCommand()
->select('id,path')
->from('{{category}}')
->order('path ASC')
->queryAll();
array_walk($rows,'Category::alterPath');
return CHtml::listData($rows,'id','path');
}
protected function afterFind()
{
parent::afterFind();
$this->oldParent=$this->category_id;
$this->oldPath=$this->path;
}
protected function beforeSave()
{
if (parent::beforeSave())
{
if ($this->isNewRecord || $this->oldParent != $this->category_id)
{
if ($this->category_id > 0 && $this->id == $this->category_id) return false; // parent not my self
$parentPath=$this->findPath((int)$this->category_id);
$_title = strtolower(str_replace(' ','_',$this->title));
if ($parentPath != '' && strpos($parentPath,$_title.'-') !== false) return false; // parent not recursive
$this->path = $parentPath == '' ? $_title : $parentPath . '-'. $_title;
}
return true;
} else
return false;
}
protected function afterSave()
{
parent::afterSave();
if(!$this->isNewRecord && $this->oldParent != $this->category_id)
{
//update sub category
$sql = "UPDATE {{category}} SET path=REPLACE(path,'".$this->oldPath.'-'."','".$this->path.'-'."') WHERE path LIKE '".$this->oldPath."-%' AND id !=".$this->id;
$conn = Yii::app()->db->createCommand($sql);
$conn->execute();
}
}
protected function afterDelete()
{
parent::afterDelete();
//delete sub category
$sql = "DELETE FROM {{category}} WHERE path LIKE '".$this->oldPath."-%'";
$conn = Yii::app()->db->createCommand($sql);
$conn->execute();
}
protected function findPath($cat=0)
{
$ret = Yii::app()->db->createCommand()
->select('path')
->from('{{category}}')
->where('id='.$cat)
->limit(1)
->queryScalar();
return $ret;
}
public function getStatusOptions()
{
return array(
'draft' => 'Draft',
'published' => 'Published',
'archived' => 'Archived'
);
}
public function getUrl()
{
return Yii::app()->createUrl('post/index',array('tpath'=>$this->path));
}
}
View _form
<div class="form">
<?php $form=$this->beginWidget('CActiveForm', array(
'id'=>'category-form',
'enableAjaxValidation'=>false,
)); ?>
<p class="note">Fields with <span class="required">*</span> are required.</p>
<?php echo $form->errorSummary($model); ?>
<div class="row">
<?php echo $form->labelEx($model,'category_id'); ?>
<?php echo $form->dropDownList($model,'category_id',$model->options,array('empty'=>'-- Select --')); ?>
<?php echo $form->error($model,'category_id'); ?>
</div>
<div class="row">
<?php echo $form->labelEx($model,'title'); ?>
<?php echo $form->textField($model,'title',array('size'=>25,'maxlength'=>25)); ?>
<?php echo $form->error($model,'title'); ?>
</div>
<div class="row">
<?php echo $form->labelEx($model,'description'); ?>
<?php echo $form->textArea($model,'description',array('rows'=>6, 'cols'=>50)); ?>
<?php echo $form->error($model,'description'); ?>
</div>
<div class="row">
<?php echo $form->labelEx($model,'status'); ?>
<?php echo $form->dropDownList($model,'status',$model->statusOptions); ?>
<?php echo $form->error($model,'status'); ?>
</div>
<div class="row buttons">
<?php echo CHtml::submitButton($model->isNewRecord ? 'Create' : 'Save'); ?>
</div>
<?php $this->endWidget(); ?>
</div><!-- form -->
Kolom "path" yang dipakai untuk kategori di tabel lain.
<div class="row">
<?php echo $form->labelEx($model,'category_path'); ?>
<?php echo $form->dropDownList($model,'category_path',Category::model()->pathOptions); ?>
<?php echo $form->error($model,'category_path'); ?>
</div>