Mr.Singh
Mr.Singh

Reputation: 1781

Yii2 dynamic form constraint violation on unique index in database table

I am creating a form with dynamic fields in which 2 fields have unique indexes in the database table. 1st 'Landline' and 2nd 'Address'. If there is no duplicate value in any of the dynamically added fields then the form submits without any error, but if I enter Address or Landline which matches any previously added field then shows constraint violation instead of showing error message even when I have already added rules in the model.

For example: I have 1 record with address = 'address', landline = 123 and I create a new record with the address = 'address' or landline = 123 then there is no error but when I submit it shows:

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'

If I remove the indexes from the table then data saves successfully.

I have tried to submit this normally and via ajax but same problem every time, my main interest is submitting the form via ajax.

I already have googled for this issue, but couldn't find any solution, please help.

model/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;
    }
}

controller

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);
        }
    }

    ...
}

form

<?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(); ?>

Upvotes: 0

Views: 633

Answers (1)

tigrasti
tigrasti

Reputation: 342

I think that $model->save(false) skip unique index validation.

Upvotes: 1

Related Questions