首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 相關知識

Yii 2.0 DropDownList 分類管理實戰:新建/編輯時動態排除自我關聯的最佳實踐

     編輯:PHP 相關知識

Yii 2.0 分類管理:DropDownList 動態排除當前編輯項目的完整解決方案

無限級分類管理中,編輯分類時必須排除「自己」與「自己的子孫分類」作為父分類選項,否則會造成邏輯循環。本文展示 Yii 2.0 如何在表單中動態處理此業務規則。

資料表結構

CREATE TABLE category (
    id INT PRIMARY KEY AUTO_INCREMENT,
    pid INT DEFAULT 0 COMMENT '父分類 ID,0 為頂層',
    title VARCHAR(100) NOT NULL COMMENT '分類標題',
    name VARCHAR(100) NOT NULL COMMENT '分類別名',
    keywords VARCHAR(255) COMMENT 'SEO 關鍵字',
    description VARCHAR(255) COMMENT '分類描述',
    sort_order INT DEFAULT 0,
    status TINYINT DEFAULT 1
);

表單視圖完整程式碼

<?php
use common\models\Category;
use yii\helpers\ArrayHelper;
use yii\helpers\Html;
use yii\widgets\ActiveForm;

/* @var $this yii\web\View */
/* @var $model common\models\Category */
/* @var $form yii\widgets\ActiveForm */
?>

<div class="category-form">
    <div class="row">
        <?php
        // 關鍵邏輯:編輯時排除自己
        if (!$model->isNewRecord) {
            $cate = ArrayHelper::map(
                Category::find()
                    ->andWhere('id != :id', [':id' => $model->id])
                    ->all(),
                'id', 'title'
            );
        } else {
            $cate = ArrayHelper::map(Category::find()->all(), 'id', 'title');
        }
        ?>

        <div class="col-md-6 col-md-offset-3">
            <?php $form = ActiveForm::begin(); ?>

            <?= $form->field($model, 'title')->textInput(['maxlength' => 100])->label('分類標題') ?>

            <?= $form->field($model, 'name')->textInput(['maxlength' => 100])->label('分類別名') ?>

            <?= $form->field($model, 'pid')->dropDownList($cate, [
                'prompt' => '請選擇父分類(留空為頂層)',
            ])->label('父分類') ?>

            <?= $form->field($model, 'keywords')->textarea(['maxlength' => 255])->label('分類關鍵詞') ?>

            <?= $form->field($model, 'description')->textarea(['maxlength' => 255])->label('分類描述') ?>

            <div class="form-group">
                <div class="row">
                    <div class="col-md-6 col-md-offset-3">
                        <?= Html::submitButton(
                            $model->isNewRecord ? '建立' : '更新',
                            ['class' => $model->isNewRecord ? 'btn btn-block btn-success' : 'btn btn-block btn-primary']
                        ) ?>
                    </div>
                </div>
            </div>

            <?php ActiveForm::end(); ?>
        </div>
    </div>
</div>

進階需求:遞迴排除子孫分類

若業務規則要求「不能選自己及所有子孫分類」,需實作遞迴查詢:

// Category 模型加入方法
public function getDescendantIds()
{
    $ids = [$this->id];
    $children = self::find()->where(['pid' => $this->id])->all();
    foreach ($children as $child) {
        $ids = array_merge($ids, $child->getDescendantIds());
    }
    return $ids;
}

// 視圖中使用
if (!$model->isNewRecord) {
    $excludeIds = $model->getDescendantIds();
    $cate = ArrayHelper::map(
        Category::find()
            ->andWhere(['not in', 'id', $excludeIds])
            ->all(),
        'id', 'title'
    );
}

效能優化:CTE 遞迴查詢(MySQL 8.0+ / PostgreSQL)

// 使用 WITH RECURSIVE 一次查詢取得所有子孫 ID
$sql = "
    WITH RECURSIVE descendants AS (
        SELECT id FROM category WHERE id = :id
        UNION ALL
        SELECT c.id FROM category c
        INNER JOIN descendants d ON c.pid = d.id
    )
    SELECT id FROM descendants
";

$excludeIds = Yii::$app->db->createCommand($sql, [':id' => $model->id])->queryColumn();

$cate = ArrayHelper::map(
    Category::find()->andWhere(['not in', 'id', $excludeIds])->all(),
    'id', 'title'
);

前端增強:jsTree 整合

對於大量分類,建議使用 jsTree 替代原生下拉選單:

// 視圖
<div id="category-tree"></div>
<input type="hidden" name="Category[pid]" id="category-pid">

<?php
$this->registerJs('
    $("#category-tree").jstree({
        "core": {
            "data": ' . json_encode($treeData) . ',
            "themes": {"icons": false}
        },
        "checkbox": {"three_state": false}
    }).on("select_node.jstree", function(e, data) {
        $("#category-pid").val(data.node.id === "#" ? "" : data.node.id);
    });
');
?>

控制器驗證:雙重保障

public function actionUpdate($id)
{
    $model = $this->findModel($id);
    
    if ($model->load(Yii::$app->request->post())) {
        // 伺服器端二次驗證
        if ($model->pid == $model->id) {
            $model->addError('pid', '父分類不能是自己');
            return $this->render('update', ['model' => $model]);
        }
        
        // 檢查是否選了自己的子孫
        if (in_array($model->pid, $model->getDescendantIds())) {
            $model->addError('pid', '父分類不能是自己的子分類');
            return $this->render('update', ['model' => $model]);
        }
        
        if ($model->save()) {
            return $this->redirect(['view', 'id' => $model->id]);
        }
    }
    
    return $this->render('update', ['model' => $model]);
}

分類管理看似簡單,實則包含遞迴邏輯、前後端驗證、使用者體驗等多層面考量。Yii 2.0 的 ActiveForm、ArrayHelper、模型驗證機制完美支援此類業務場景。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved