You are viewing revision #11 of this wiki article.
This version may not be up to date with the latest version.
You may want to view the differences to the latest version or see the changes made in this revision.
Scenario ¶
Assume we have many categories and many posts.
A post can belongs to multiple categories, and a category can have many posts. We define posts' categories using a junction table.
Now, we want to develop a page to create/update a post with a list box (or a check box list) that enables the user to define the categories of the post.
Models ¶
Here are 4 models involved in the page we want to develop.
- Post extends ActiveRecord (representing
post
table)- id
- title
- body
- ... etc.
- Category extends ActiveRecord (representing
category
table)- id
- name
- ... etc.
- PostCategory extends ActiveRecord (representing
post_category
junction table)- post_id
- category_id
- PostWithCategories extends Post
- (all the inherited attributes of Post)
- category_ids
As for the first 3 models, you can create them easily with the help of Gii.
PostWithCategories model ¶
And the last one is extended from Post. It has an additional attribute called 'category_ids' that will be used to handle the categories of the post. Note that 'category_ids' attribute is an array of category ids.
class PostWithCategories extends Post
{
/**
* @var array IDs of the categories
*/
$category_ids = [];
/**
* @return array the validation rules.
*/
public function rules()
{
return ArrayHelper::merge(parent::rules(), [
// each category_id must exist in category table (*1)
['category_ids', 'each', 'rule' => [
'exist', 'targetClass' => Category::className(), 'targetAttribute' => 'id'
]
],
]);
}
/**
* @return array customized attribute labels
*/
public function attributeLabels()
{
return ArrayHelper::merge(parent::attributeLabels(), [
'category_ids' => 'Categories',
]);
}
/**
* load the post's categories (*2)
*/
public function loadCategories()
{
$this->category_ids = [];
if (!empty($this->id)) {
$rows = PostCategory::find()
->select(['category_id'])
->where(['post_id' => $this->id])
->asArray()
->all();
foreach($rows as $row) {
$this->category_ids[] = $row['category_id'];
}
}
}
/**
* save the post's categories (*3)
*/
public function saveCategories()
{
/* clear the categories of the post before saving */
PostCategory::deleteAll(['post_id' => $this->id]);
if (is_array($this->category_ids)) {
foreach($this->category_ids as $category_id) {
$pc = new PostCategory();
$pc->post_id = $this->id;
$pc->category_id = $category_id;
$pc->save();
}
}
/* Be careful, $this->category_ids can be empty */
}
}
(*1) In the rules for the validation, we use EachValidator to validate the array of
category_ids
attribute.(*2) loadCategories method loads the IDs of the post's categories into this model instance.
(*3) saveCategories method saves the post's categories specified in
category_ids
attribute.
Category model ¶
We want to add a small utility method to Category model.
class Category extends ActiveRecord
{
...
/**
* Get all the available categories (*4)
* @return array available categories
*/
public static function getAvailableCategories()
{
$categories = self::find()->order('name')->asArray()->all();
$items = ArrayHelper::map($category, 'id', 'name');
return $items;
}
}
(*4) getAvailableCategories method is a static utility function to get the list of available categories. In the returned array, the keys are 'id' and the values are 'name' of the categories.
Controller Actions and Views ¶
Since we already have all the necessary logic in models, the controller actions can be as simple as the following examples.
Create action ¶
/**
* Create Post with its categories
*/
public function actionCreate()
{
$model = new PostWithCategories();
if ($model->load(Yii::$app->request->post()) {
if ($model->save()) {
$model->saveCategories();
return $this->redirect(['index']);
}
}
return $this->render('create', [
'model' => $model,
'categories' => Category::getAvailableCategories(),
]);
}
create.php view script ¶
In the view script, we can use a listBox with multiple selection or a checkboxList to select the categories.
<?php $form = ActiveForm::begin([
'id' => 'post-form',
'enableAjaxValidation' => false,
]); ?>
<?= $form->field($model, 'title')->textInput(); ?>
<?= $form->field($model, 'body')->textArea(); ?>
...
<?= $form->field($model, 'category_ids')
->listBox($categories, ['multiple' => true])
/* or, you may use a checkbox list instead */
/* ->checkboxList($categories) */
->hint('Select the categories of the post.');
?>
<div class="form-group">
<?= Html::submitButton('Create', [
'class' => 'btn btn-primary'
]) ?>
</div>
<?php ActiveForm::end(); ?>
Update action ¶
It's almost the same with Create action:
/**
* Update a post with its categories
* @param integer $id the post's ID
*/
public function actionUpdate($id)
{
$model = PostWithCategories::findOne($id);
$model->loadCategories();
if ($model->load(Yii::$app->request->post()) {
if ($model->save()) {
$model->saveCategories();
return $this->redirect(['index']);
}
}
return $this->render('update', [
'model' => $model,
'categories' => Category::getAvailableCategories(),
]);
}
update.php view script ¶
It's different from create.php only in the text of the submit button:
...
<?= Html::submitButton('Update', [
'class' => 'btn btn-primary'
]) ?>
...
Listbox
I use view script, and its me help
Thanks alot for a clear and wonderfull examle
it helps me alot
and thank you for all the yii2 framework!
it is great and very usefull
There is a problem: Getting unknown property for 'category_ids'
Hi there is a problem when running this code
base component throws an exception of unknown property for the array that was defined in the extending model class.
how can i solve this?
re: Getting unknown property for 'category_ids'
Probably you are using 'Post' model, because 'category_ids' is not a property of 'Post' but 'PostWithCategories'.
should be
public $category_ids = [];
If you have any questions, please ask in the forum instead.
Signup or Login in order to comment.