Yii2动态表单约束违反数据库表中的唯一索引

时间:2017-04-23 18:08:58

标签: forms dynamic yii2 constraints

我正在创建一个包含动态字段的表单,其中2个字段在数据库表中具有唯一索引。第一个“固定电话”和第二个“地址”。如果任何动态添加的字段中没有重复值,则表单提交时没有任何错误,但如果我输入匹配任何先前添加的字段的地址或固定电话,则显示约束违规,而不是显示错误消息,即使我已经添加了规则在模型中。

例如:我有1条记录,地址='地址',固定电话= 123,我创建了一条地址='地址'或固定电话= 123的新记录,然后没有错误,但是当我提交时显示:< / p>

Exception (Integrity constraint violation) 'yii\db\IntegrityException' with message 'SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry '15-address' for key 'unique_doctors_id__address'

如果从表中删除索引,则数据会成功保存。

我试图正常提交这个并通过ajax但每次都有相同的问题,我的主要兴趣是通过ajax提交表单。

我已经搜索了这个问题,但找不到任何解决方案,请帮忙。

模型/ DoctorClinics

class DoctorClinics extends \yii\db\ActiveRecord
{
    public function rules()
    {
        return [
            [['name', 'incharge', 'landline', 'address'], 'required', 'message' => "'{attribute}' can not be empty."],
            [['name', 'incharge', 'address', 'landline', 'landmark'], 'string', 'max' => 255],

            ['status', 'required', 'message' => "'{attribute}' can not be unselected"],
            ['status', 'in', 'range' => Common::get_array('range_active_inactive'), 'message' => "'{attribute}' has an invalid value"],
            ['status', 'string', 'max' => 1],

            [['doctors_id', 'landline'], 'unique', 'targetAttribute' => ['doctors_id', 'landline'], 'message' => 'The combination of Doctors and Landline has already been taken.'],
            [['doctors_id', 'address'], 'unique', 'targetAttribute' => ['doctors_id', 'address'], 'message' => 'The combination of Doctors ID and Address has already been taken.'],

            [['token'], 'string', 'max' => 50],

            [['token'], 'unique'],
            [['doctors_id'], 'exist', 'skipOnError' => true, 'targetClass' => Doctors::className(), 'targetAttribute' => ['doctors_id' => 'id']],

            [['doctors_id', 'created_at', 'updated_at'], 'integer'],
        ];
    }

    public static function createMultiple($modelClass, $multipleModels = [])
    {
        $model    = new $modelClass;
        $formName = $model->formName();
        $post     = Yii::$app->request->post($formName);
        $models   = [];

        if (! empty($multipleModels))
        {
            $keys           = array_keys(ArrayHelper::map($multipleModels, 'id', 'id'));
            $multipleModels = array_combine($keys, $multipleModels);
        }

        if ($post && is_array($post))
        {
            foreach ($post as $i => $item)
            {
                if (isset($item['id']) && !empty($item['id']) && isset($multipleModels[$item['id']]))
                {
                    $models[] = $multipleModels[$item['id']];
                }
                else
                {
                    $models[] = new $modelClass;
                }
            }
        }

        unset($model, $formName, $post);

        return $models;
    }
}

控制器

class DoctorsController extends Controller
{
    ...

    public function actionCreate()
    {
        # models
        $modelDoctors       = new Doctors();
        $modelDoctorClinics = [new DoctorClinics];

        # scenario
        $modelDoctors->scenario = Doctors::SCENARIO_CREATE;

        $transaction = Yii::$app->db->beginTransaction();

        # checking post method
        if(($arrayPost = \Yii::$app->request->post()) != null)
        {
            $modelDoctorClinics = DoctorClinics::createMultiple(DoctorClinics::classname());

            // $modelDoctorClinics->scenario = Doctors::SCENARIO_CREATE;

            DoctorClinics::loadMultiple($modelDoctorClinics, Yii::$app->request->post());

            # loading posted data to model
            $modelDoctors->load($arrayPost);

            # setting data
            $modelDoctors->token            = Common::generate_token();
            $modelDoctors->added_by         = \Yii::$app->user->identity->id;
            $modelDoctors->auth_key         = \Yii::$app->security->generateRandomString();
            $modelDoctors->password_hash    = \Yii::$app->security->generatePasswordHash(Common::DEFAULT_PASSWORD);
            $modelDoctors->created_at       = time();

            # validate all models
            $valid = $modelDoctors->validate();
            $valid = DoctorClinics::validateMultiple($modelDoctorClinics) && $valid;

            if($valid)
            {
                try
                {
                    if ($flag = $modelDoctors->save(false))
                    {
                        foreach ($modelDoctorClinics as $modelDoctorClinic)
                        {
                            $modelDoctorClinic->token       = Common::generate_token();
                            $modelDoctorClinic->doctors_id  = $modelDoctors->id;

                            $flag = $modelDoctorClinic->save(false) && $flag;

                            if(!$flag)
                            {
                                $transaction->rollBack();
                                break;
                            }
                        }
                    }

                    if ($flag)
                    {
                        $transaction->commit();

                        Yii::$app->session->setFlash('success', 'Doctor\'s details saved successfully');

                        # setting response format
                        \Yii::$app->response->format = Response::FORMAT_JSON;

                        return true;
                    }
                }
                catch (Exception $e)
                {
                    $transaction->rollBack();

                    Yii::$app->response->format = Response::FORMAT_JSON;

                    return ArrayHelper::merge(
                        ActiveForm::validateMultiple($modelDoctorClinics),
                        ActiveForm::validate($modelDoctors)
                    );
                }
            }
            else
            {
                Yii::$app->response->format = Response::FORMAT_JSON;

                return ArrayHelper::merge(
                    ActiveForm::validateMultiple($modelDoctorClinics),
                    ActiveForm::validate($modelDoctors)
                );
            }
        }
        else
        {
            # returning data
            return $this->render('create', [
                'model_doctors'         => $modelDoctors,
                'model_doctor_clinics'  => (empty($modelDoctorClinics)) ? [new DoctorClinics] : $modelDoctorClinics
            ]);
        }
    }

    public function actionValidations($scenario)
    {
        # fetching posted data
        $arrayPost = \Yii::$app->request->post();

        # models
        if(empty($scenario) || !in_array($scenario, [Doctors::SCENARIO_CREATE, Doctors::SCENARIO_UPDATE]))
        {
            $modelDoctors = new Doctors(['scenario' => Doctors::SCENARIO_CREATE]);
        }
        else
        {
            if($scenario == Doctors::SCENARIO_UPDATE)
            {
                $modelDoctors = Doctors::find()
                    ->where(['token' => $arrayPost['Doctors']['token']])
                    ->one();
            }
            else
            {
                $modelDoctors = new Doctors();
            }

            # scenario
            $modelDoctors->scenario = $scenario;
        }

        if(!empty($arrayPost) && \Yii::$app->request->isAjax)
        {
            # setting response format
            \Yii::$app->response->format = Response::FORMAT_JSON;

            # loading posted data to model
            $modelDoctors->load($arrayPost);

            return ActiveForm::validate($modelDoctors);
        }
    }

    ...
}

形式

<?php
    $form = ActiveForm::begin([
        "enableAjaxValidation"  => true,
        "validateOnSubmit"      => true,
        'validationUrl'         => \Yii::$app->urlManager->createUrl('doctors-validation/' . (($model_doctors->isNewRecord) ? 'create' : 'update')),
        'options' => [
            'id'    => $model_doctors->formName(),
            'class' => 'forms'
        ]
    ]);
?>
    <div class="boxBody">
        <div class="row form-page-image">
            <div class="image mb10">
                <div class="col-sm-12 thumbnail">
                    <?php
                        if(!empty($model_doctors->image) && file_exists(\Yii::getAlias('@uploads') . "/{$model_doctors->image}"))
                        {
                            echo Html::img(\Yii::$app->urlManagerFrontend->createUrl('/thumbnails') . '/' . $model_doctors->image, [
                                'alt'   => $this->title,
                                'class' => 'js-thumbnail'
                            ]);
                        }
                        else
                        {
                            echo Html::img(\Yii::$app->urlManager->createUrl('/images') . '/' . Yii::getAlias('@staff-no-image'), [
                                'alt'   => $this->title,
                                'class' => 'js-thumbnail'
                            ]);
                        }
                    ?>

                    <div class="button-section">
                        <?php
                            echo Html::a('<i class="'. Common::ICON_IMAGE .'"></i> ' . Yii::t('app', 'Picture'), [
                                'images/upload-avatar',
                                'token' => $model_doctors->token
                            ], [
                                'class' => "js-popup buttons tiny " . Common::LINK_IMAGE
                            ]);
                        ?>
                    </div>
                </div>
            </div>

            <div class="image-form">
                <div class="col-sm-12">
                    <div class="row">
                        <div class="col-xs-12 col-sm-6">
                            <?php
                                echo $form
                                    ->field($model_doctors, 'first_name')
                                    ->textInput([
                                        'autofocus' => true,
                                        'maxlength' => true
                                    ])
                            ?>
                        </div>

                        <div class="col-xs-12 col-sm-6">
                            <?php
                                echo $form
                                    ->field($model_doctors, 'last_name')
                                    ->textInput([
                                        'maxlength' => true
                                    ])
                            ?>
                        </div>
                    </div>

                    <div class="row">
                        <div class="col-xs-12 col-sm-6">
                            <?php
                                echo $form
                                    ->field($model_doctors, 'email')
                                    ->textInput([
                                        'maxlength' => true
                                    ])
                            ?>
                        </div>

                        <div class="col-xs-12 col-sm-6 selectBox">
                            <?php
                                echo $form
                                    ->field($model_doctors, 'status')
                                    ->dropDownList(Common::get_array('active_inactive'), [
                                        'prompt' => '- Status -'
                                    ])
                            ?>
                        </div>
                    </div>
                </div>
            </div>
        </div>

        <div class="section">
            <h3 class="heading">Residence Details</h3>

            <div class="res-details">
                <div class="col-sm-9">
                    <?php
                        echo $form
                            ->field($model_doctors, 'residence_address')
                            ->textArea(['maxlength' => true]);
                    ?>
                </div>

                <div class="col-sm-3">
                    <?php
                        echo $form
                            ->field($model_doctors, 'residence_telephone')
                            ->textInput(['maxlength' => true]);
                    ?>
                </div>

                <div class="col-sm-3">
                    <?php
                        echo $form
                            ->field($model_doctors, 'mobile')
                            ->textInput(['maxlength' => true])
                    ?>
                </div>
            </div>
        </div>

        <div class="section">
            <?php
                DynamicFormWidget::begin([
                    'widgetContainer'   => 'jsDoctorsClinics', // required: only alphanumeric characters plus "_" [A-Za-z0-9_]
                    'widgetBody'        => '.clinics-container', // required: css class selector
                    'widgetItem'        => '.js-clinic-clonable', // required: css class
                    // 'insertPosition' => 'top',
                    'limit'             => 5, // the maximum times, an element can be cloned (default 999)
                    'min'               => 1, // 0 or 1 (default 1)
                    'insertButton'      => '.clinic-add-item', // css class
                    'deleteButton'      => '.clinic-remove-item', // css class
                    'model'             => $model_doctor_clinics[0],
                    'formId'            => $model_doctors->formName(),
                    'formFields' => [
                        'name',
                        'incharge',
                        'address',
                        'landline',
                        'landmark',
                        'status'
                    ]
                ]);
            ?>
                <h3 class="heading">Clinics (max. 5)</h3>

                <div class="doctor-clinics">
                    <div class="clinics-container">
                        <?php
                            foreach ($model_doctor_clinics as $i => $clinic)
                            {
                        ?><div class="col-sm-12 clinic js-clinic-clonable clonable">
                            <div class="row">
                                <div class="col-md-8 col-sm-12">
                                    <?php
                                        // necessary for update action.
                                        if(!$clinic->isNewRecord)
                                        {
                                            echo Html::activeHiddenInput($clinic, "[{$i}]id");
                                        }

                                        echo $form
                                            ->field($clinic, "[{$i}]name")
                                            ->textInput(['maxlength' => true]);
                                    ?>
                                </div>

                                <div class="col-md-4 col-sm-12">
                                    <?php
                                        echo $form
                                            ->field($clinic, "[{$i}]incharge")
                                            ->textInput(['maxlength' => true]);
                                    ?>
                                </div>
                            </div>

                            <div class="row">
                                <div class="col-sm-8">
                                    <?php
                                        echo $form
                                            ->field($clinic, "[{$i}]address")
                                            ->textArea(['maxlength' => true]);
                                    ?>
                                </div>

                                <div class="col-sm-4">
                                    <?php
                                        echo $form
                                            ->field($clinic, "[{$i}]landline")
                                            ->textInput(['maxlength' => true]);
                                    ?>
                                </div>

                                <div class="col-sm-4">
                                    <?php
                                        echo $form
                                            ->field($clinic, "[{$i}]landmark")
                                            ->textInput(['maxlength' => true])
                                    ?>
                                </div>
                            </div>

                            <div class="row">
                                <div class="col-sm-4 selectBox">
                                    <?php
                                        echo $form
                                            ->field($clinic, "[{$i}]status")
                                            ->dropdownList(Common::get_array("active_inactive"), [ 'prompt' => ' - Select - ' ])
                                    ?>
                                </div>

                                <div class="col-sm-4 col-sm-offset-4">
                                    <div class="form-group">
                                        <label class="hidden-480 control-label">&nbsp;</label>
                                        <button type="button" class="clinic-remove-item width-100 buttons <?php echo Common::LINK_CLOSE; ?>"<?php
                                            if($clinic->isNewRecord || (!$clinic->isNewRecord && (count($model_doctor_clinics) > 1) && ($i == 0)))
                                            {
                                                echo ' style="display: none;"';
                                            }
                                        ?>><i class="<?php echo Common::ICON_DELETE; ?>"></i> Delete</button>
                                    </div>
                                </div>
                            </div>
                        </div>
                        <?php
                            }
                        ?>
                    </div>

                    <div class="col-sm-12 mt-auto mb-auto js-clinic-add-more">
                        <div class="form-group col-md-4 col-md-offset-4 mb0">
                            <a href="javascript:void(0)" class="clinic-add-item width-100 buttons <?php echo Common::LINK_ADD; ?> textCenter"><i class="<?php echo Common::ICON_ADD; ?>"></i> Add more clinics</a>
                        </div>
                    </div>
                </div>
            <?php DynamicFormWidget::end(); ?>
        </div>
    </div>

    <div class="boxFooter">
        <?php
            if(!$model_doctors->isNewRecord)
            {
                echo $form
                    ->field($model_doctors, 'token', [
                        'options' => [ 'tag' => false ]
                    ])->hiddenInput([
                        'readonly' => 'readonly'
                    ])->label(false);
            }
            else
            {
                echo $form
                    ->field($model_doctors, 'token', [
                        'options' => [ 'tag' => false ]
                    ])->hiddenInput([
                        'value' => Common::generate_token()
                    ])->label(false);
            }

            echo $form
                ->field($model_doctors, 'uploaded_files', [
                    'options' => [ 'tag' => false ]
                ])->hiddenInput([
                    'readonly'  => 'readonly'
                ])->label(false);

            echo $form
                ->field($model_doctors, 'password_hash', [
                    'options' => [ 'tag' => false ]
                ])->hiddenInput([
                    'readonly'  => 'readonly',
                    'value'     => Common::generate_token()
                ])->label(false);

            echo $form
                ->field($model_doctors, 'auth_key', [
                    'options' => [ 'tag' => false ]
                ])->hiddenInput([
                    'readonly'  => 'readonly',
                    'value'     => Common::generate_token()
                ])->label(false);
        ?>
        <?php
            echo Html::submitButton('<i class="' . Common::ICON_SUBMIT . '"></i> ' . Yii::t('app', 'Submit'), [
                'class' => 'buttons mini default pull-right'
            ])
        ?>
        <?php
            echo Html::button('<i class="' . Common::ICON_CLOSE . '"></i> ' . Yii::t('app', 'Cancel'), [
                'class' => 'js-cancel buttons mini pull-left ' . Common::LINK_CLOSE
            ])
        ?>
    </div>
<?php ActiveForm::end(); ?>

1 个答案:

答案 0 :(得分:1)

我认为$model->save(false)会跳过唯一索引验证。