Yii2 中 DynamicFormWidget 的自定义验证不起作用

Custom validation on DynamicFormWidget in Yii2 not working

我正在尝试为 Yii2 中的 DynamicFormWidget 小部件添加自定义验证。有一个 DynamicFormWidget 小部件 'percentage',动态创建的所有 'percentage' 值的总和应该是 100。我添加了一个验证规则

public function checkPercentageTotal($attribute, $params){
        foreach (Yii::$app->request->post()['Category'] as $percentage){
        $percentage_values[]=$percentage['percentage'];
        }
        if( array_sum($percentage_values)<>100){
              $this->addError($attribute, 'Total percentage should be 100');return false;
        }
    }

并添加了

public function rules()
    {
        return [

            [['percentage'] ,'checkPercentageTotal'],
        ];
    }

但未显示 error.The 代码正在 function checkPercentageTotal()

中执行

这是我的视图文件中的代码:

<?php

use yii\helpers\Html;
use yii\bootstrap\ActiveForm;
use wbraganca\dynamicform\DynamicFormWidget;
use kartik\date\DatePicker;
use kartik\select2\Select2;
use dosamigos\tinymce\TinyMce;
use yii\helpers\ArrayHelper;
use backend\models\Department;
$script = <<< JS
$(".dynamicform_wrapper").on("afterInsert", function(e, item) {
    console.log("afterInsert");

      var row = $(this).closest('td');
       var row=jQuery(item).find('select:eq(1)');
       row.val('5')

});

JS;
$this->registerJs($script);
?>
<br>
<div class="dynamicform_wrapper">

<?php $form = ActiveForm::begin(['id' => 'dynamic-form', ]); ?>
    <div class="box box-primary box-solid">
        <div class="box-header with-border">
            <h3 class="box-title">Create Survey Form</h3>

        </div><br>
<div class="col-sm-12">
<div class="row">
    <div class="col-sm-2">
        <?= $form->field($modelSurvey, 'form_id')->textInput(['maxlength' => true]) ?>
    </div>
    <div class="col-sm-6">
        <?= $form->field($modelSurvey, 'title')->textInput(['maxlength' => true]) ?>
    </div>
    <div class="col-sm-2">
    <?php

    $data=ArrayHelper::map(Department::find()->all(), 'id', 'department');
    echo '<label class="control-label">Department</label>';
    echo Select2::widget([
        'name' => 'depart',
        'id' => 'department',
        'theme' =>Select2::THEME_BOOTSTRAP,
        'value' => isset($modelSurvey->depart) ? $modelSurvey->depart : [],
        'data' => $data,
        // 'initValueText' => isset($model->dept) ? $model->dept : [],
        'options' =>  [
            'placeholder' => Yii::t('app', 'Choose Department...'),
            'multiple' => true,
        ],
        'pluginOptions' => [
            'tags' => true,
            'allowClear' => true,
            'width' => '100%'
        ],]);

    ?>
    </div>
    <div class="col-sm-2">
        <?php

       $data=array('1'=>'Staff','2'=>'Student');
        echo '<label class="control-label">Type</label>';
        echo Select2::widget([
            'name' => 'type_id',
            'id' => 'type_id',
            'theme' =>Select2::THEME_BOOTSTRAP,
            'value' => isset($modelSurvey->type_id) ? $modelSurvey->type_id : [],
            'data' => $data,
            // 'initValueText' => isset($model->dept) ? $model->dept : [],
            'options' =>  [
                'placeholder' => Yii::t('app', 'Choose Type...'),
               // 'multiple' => true,
            ],
            'pluginOptions' => [
                'tags' => true,
                'allowClear' => true,
                'width' => '100%'
            ],]);
        ?>
    </div>
</div>
    <div class="row">

        <div class="col-sm-6">
            <?= $form->field($modelSurvey, 'abstract')->widget(TinyMce::className(), [
                'options' => ['rows' => 6],
                'language' => 'en',
                'clientOptions' => [
                    'plugins' => [
                        "advlist autolink lists link charmap print preview anchor",
                        "searchreplace visualblocks code fullscreen",
                        "insertdatetime media table contextmenu paste"
                    ],
                    'toolbar' => "undo redo | styleselect | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | link image"
                ]
            ]); ?>
        </div>
    <div class="col-sm-6">
        <?= $form->field($modelSurvey, 'ledger')->widget(TinyMce::className(), [
            'options' => ['rows' => 6],
            'language' => 'en',
            'clientOptions' => [
                'plugins' => [
                    "advlist autolink lists link charmap print preview anchor",
                    "searchreplace visualblocks code fullscreen",
                    "insertdatetime media table contextmenu paste"
                ],
                'toolbar' => "undo redo | styleselect | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | link image"
            ]
        ]); ?>

    </div>
    </div>
</div>

<div class="padding-v-md">
    <div class="line line-dashed"></div>
</div>
<div class="col-md-12">



    <table class="table table-bordered table-striped">

    <tbody class="container-items">
    <?php foreach ($modelsCategory as $indexCategory => $modelCategory): ?>
        <tr class="house-item" >

                <?php
                // necessary for update action.
                if (! $modelCategory->isNewRecord) {
                    echo Html::activeHiddenInput($modelCategory, "[{$indexCategory}]id");
                }
                ?>
            <th colspan="5" bgcolor="#EC7063" style="display: inline-grid; width: 100%;">Categories</th>

            <td style="display: inline-grid; width: 8%;"><?= $form->field($modelCategory, "[{$indexCategory}]percentage")->label(true)->textInput(['maxlength' => true]) ?></td>

 <td style="display: inline-block; width: 10%;">
                <button type="button" class="add-house btn btn-success btn-xs"><span class="glyphicon glyphicon-plus"></span></button>
                <button type="button" class="remove-house btn btn-danger btn-xs"><span class="glyphicon glyphicon-minus"></span></button>
            </td>


            <td style="display: block;" >

                <?= $this->render('_form-rooms', [
                    'form' => $form,
                    'indexCategory' => $indexCategory,
                    'modelsQuestion' => $modelsQuestion[$indexCategory],
                ]) ?>

            </td>
    </tr>



    <?php endforeach; ?>
    </tbody>
</table>
<?php DynamicFormWidget::end(); ?>

<div class="form-group">
    <div style="padding-left: 1%;">
    <?= Html::submitButton($modelSurvey->isNewRecord ? 'Create' : 'Update', ['class' => 'btn btn-lg btn-danger']) ?>
</div></div>

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

CategoryModel 中的代码是

<?php

namespace backend\models;

use Yii;

/**
 * This is the model class for table "category".
 *
 * @property int $id
 * @property string $survey_id

 */
class Category extends \yii\db\ActiveRecord
{
    /**
     * @inheritdoc
     */
    public static function tableName()
    {
        return 'category';
    }

    /**
     * @inheritdoc
     */
    public function rules()
    {
        return [
            [['survey_id', 'percentage'], 'required'],
            [['survey_id' ], 'string', 'max' => 250],
            [['percentage'] ,'checkPercentageTotal'],
        ];
    }

    /**
     * @inheritdoc
     */
    public function attributeLabels()
    {
        return [
            'id' => Yii::t('app', 'ID'),
            'survey_id' => Yii::t('app', 'Survey ID'),

            'percentage' => Yii::t('app', 'Percent'),
        ];
    }
    public function getQuestions()
    {
        return $this->hasMany(Question::className(), ['cat_id' => 'id']);
    }

    public function checkPercentageTotal($attribute, $params){
        foreach (Yii::$app->request->post()['Category'] as $percentage){
        $percentage_values[]=$percentage['percentage'];
        }
        if( array_sum($percentage_values)<>100){
              $this->addError($attribute, 'Total percentage should be 100');return false;
        }
    }
}

SurveyModel 的代码是

<?php

namespace backend\models;

use Yii;

/**
 * This is the model class for table "survey".
 *
 * @property int $id
 * @property string $form_id
 * @property string $dept
 * @property string $title
 * @property string $period
 * @property string $abstract
 * @property string $ledger
 * @property string $apr_name
 * @property string $apr_desg
 * @property string $sem
 * @property string $acad_yr
 * @property string $sec
 * @property string $qualification
 */
class Survey extends \yii\db\ActiveRecord
{
    /**
     * @inheritdoc
     */

    /**===============================
     * floated variable for survey form
     */

      /**
       * end here =====================
       */

    public static function tableName()
    {
        return 'survey';
    }

    /**
     * @inheritdoc
     */
    public function rules()
    {
        return [
            [['form_id', 'title', 'abstract', 'ledger','date_created'], 'required'],
            [['form_id'], 'string', 'max' => 1250],


        ];
    }

    /**
     * @inheritdoc
     */
    public function attributeLabels()
    {
        return [
            'id' => Yii::t('app', 'ID'),
            'form_id' => Yii::t('app', 'Form ID'),
            'title' => Yii::t('app', 'Title'),
            'abstract' => Yii::t('app', 'Abstract'),
            'ledger' => Yii::t('app', 'Ledger'),
            'date_created' =>Yii::t('app', 'Date Created'),
            'depart' =>Yii::t('app', 'Department'),


        ];
    }


}

创建动作控制器的代码是

public function actionCreate()
    {
        $modelSurvey = new Survey;
        $modelsCategory = [new Category];
        $modelsQuestion = [[new Question]];

        if ($modelSurvey->load(Yii::$app->request->post())) {

            foreach($_POST['depart'] as $departindex => $departvalue){
                    $department[]=$departvalue;

            }
            $dep=implode(',',$department);

            $modelsCategory = Model::createMultiple(Category::classname());
            Model::loadMultiple($modelsCategory, Yii::$app->request->post());

            // validate survey and category models
            $valid[] = $modelSurvey->validate();


            $valid[] = Model::validateMultiple($modelsCategory) && $valid;

            if (isset($_POST['Question'][0][0])) {
                foreach ($_POST['Question'] as $indexCategory => $questions) {
                    foreach ($questions as $indexQuestion => $question) {
                        $data['Question'] = $question;
                        $modelQuestion = new Question;
                        $modelQuestion->load($data);
                        $modelsQuestion[$indexCategory][$indexQuestion] = $modelQuestion;
                        $valid[] = $modelQuestion->validate();
                    }
                }
            }

            if ($valid) {
                $transaction = Yii::$app->db->beginTransaction();
                try {

                    $today = date("Y-m-d H:i:s");
                    $modelSurvey->date_created=$today;
                    $modelSurvey->depart=$dep;
                    $modelSurvey->type_id=$_POST['type_id'];
                    if ($flag = $modelSurvey->save(false)) {
                        foreach ($modelsCategory as $indexCategory => $modelCategory) {

                            if ($flag === false) {
                                break;
                            }

                            $modelCategory->survey_id = $modelSurvey->id;

                            if (!($flag = $modelCategory->save(false))) {
                                break;
                            }

                            if (isset($modelsQuestion[$indexCategory]) && is_array($modelsQuestion[$indexCategory])) {
                                foreach ($modelsQuestion[$indexCategory] as $indexQuestion => $modelQuestion) {
                                    $modelQuestion->cat_id = $modelCategory->id;
                                    if (!($flag = $modelQuestion->save(false))) {
                                        break;
                                    }
                                }
                            }
                        }
                    }

                    if ($flag) {
                        $transaction->commit();
                        return $this->redirect(['view', 'id' => $modelSurvey->id]);
                    } else {
                        $transaction->rollBack();
                    }
                } catch (Exception $e) {
                    $transaction->rollBack();
                }
            }
        }

        return $this->render('create', [
            'modelSurvey' => $modelSurvey,
            'modelsCategory' => (empty($modelsCategory)) ? [new Category] : $modelsCategory,
            'modelsQuestion' => (empty($modelsQuestion)) ? [[new Question]] : $modelsQuestion,
        ]);
    }

我认为自定义验证规则对动态表单小部件元素没有任何影响

我想这是主要问题:

$valid[] = $modelSurvey->validate();
$valid[] = Model::validateMultiple($modelsCategory) && $valid;

// ...

if ($valid) {

即使你所有的验证 returns false,你也会得到 false 的数组,这将被视为 true (非空数组 == = 真)。您可以尝试使用以下内容更改此代码:

$valid = Model::validateMultiple($modelsCategory) && $valid;

但老实说,您的代码确实很乱,需要进行认真的重构。您不应该直接在模型内部访问 GET 或 POST 数据,并且此操作和验证非常复杂。

对于这种情况,您应该创建单独的 SurveyForm 并且所有验证和保存都应该在其中。 checkPercentageTotal 验证器并不真正适合 Category 模型 - 现在您正在多次执行相同的验证(分别针对每个类别)。此验证可能应该在 SurveyForm 模型中完成,仅一次。