4

I have two models in Yii2 (masterTransaction and splitTransaction), where each masterTransactions can have multiple splitTransactions. Each splitTransaction has an attribute 'amount'. My problem is I need to validate if the sum over all 'amount' attributes is 0.

My first solution was to make another model called Transaction, in which I had an attribute where I saved an instance of the masterTransaction model and another attribute with an array of splitTransaction instances. I did the validation with a custom inline validatior, which work perfectly.

Transaction model

class Transaction extends Model
{
    public $masterTransaction;
    public $splitTransactions;

    public function init()
    {
        $this->masterTransaction = new MasterTransaction();
        $this->splitTransactions[] = new SplitTransaction();
    }

    public function rules()
    {
        return [
            ['splitTransactions', 'validateSplitTransactions'],
        ];
    }

    public function validateSplitTransactions($attribute, $params)
    {
        $sum = 0;
        foreach ($this->$attribute as $transaction) {
            $sum = bcadd($sum, $transaction->amount, 3);
        }
        if ($sum != 0) {
            $this->addError($attribute, 'The sum of the entries has to be 0');
        }
    }

    public function save()
    {
        $this->masterTransaction->save();
        foreach ($this->splitTransactions as $splitTransaction) {
            $splitTransaction->master_transaction_id = $this->masterTransaction->id;
            $splitTransaction->save();
        }
    }
}

Controller function to create the model

public function actionCreate()
{
    $transaction = new Transaction();
    $count = count(Yii::$app->request->post('SplitTransaction', []));
    for ($i = 1; $i < $count; $i++) {
        $transaction->splitTransactions[] = new SplitTransaction();
    }

    if ($transaction->masterTransaction->load(Yii::$app->request->post()) && Model::loadMultiple($transaction->splitTransactions, Yii::$app->request->post())) {
        $transaction->masterTransaction->user_id = Yii::$app->user->id;
        foreach ($transaction->splitTransactions as $splitTransaction) {
            $splitTransaction->user_id = Yii::$app->user->id;
        }
        if ($transaction->validate()) {
            $transaction->save();
        }
    }

    return $this->render('create', [
        'transaction' => $transaction,
    ]);
}

But when I tried building a form to input the data, I ran into a problem with the Ajax validation. The validation would work, but Yii didn't know where to put the error message, so it just deleted it.

I suspect that this is just not the preferred way in Yii2 model my data, but I don't really have another idea. Maybe someone has some ideas for me.

coldice4
  • 43
  • 2
  • 7

1 Answers1

1

Option 1. It depends on your view file codes. Does your form contains "splitTransactions" variable? If not, you can put it like this

<?= $form->field($model, 'splitTransactions')->hiddenInput(['maxlength' => true])->label(false); ?>

The variable will be hidden, but still show errors. In some case validation will not be fired because of empty value of "splitTransactions" variable. "splitTransactions" should contain some value to fire validation. You can put some value to if before pasting the form like this

$model->splitTransactions=1;

Option 2. You can add error to other variable (which form contains) like this

public function validateSplitTransactions($attribute, $params)
{
    $sum = 0;
    foreach ($this->$attribute as $transaction) {
        $sum = bcadd($sum, $transaction->amount, 3);
    }
    if ($sum != 0) {
        $this->addError('transaction_number', 'The sum of the entries has to be 0');
    }
}

Look, form should contain "transaction_number" variable. Error will be added to "transaction_number" input.


Option 3. In my experience. It is better to separate ajax validation from form action url a.g. create another controller action for ajax validation and use it.

Example

Create model FeedbackForm

class FeedbackForm extends Model
{
    public $name;
    public $email;
    public $text;


    /**
     * @inheritdoc
     */
    public function rules()
    {
        return [
            [['name', 'email', 'text'], 'required'],
            [['name', 'email'], 'string', 'max' => 128],
            [['email'], 'email'],
            [['text'], 'string', 'max' => 512],
        ];
    }

    public function attributeLabels()
    {
        return [
            'name' => \Yii::t('front', 'Name'),
            'email' => \Yii::t('front', 'Email'),
            'text' => \Yii::t('front', 'Message text'),
        ];
    }
}

put actions to SiteSontroller

public function actionFeedback()
{
    $model=  new \frontend\models\FeedbackForm;
    $model->load(Yii::$app->request->post());
    if($model->validate()) {
        $newFeed=new  \frontend\models\Feedback;
        $newFeed->create_time=new \yii\db\Expression('NOW()');
        $newFeed->name=$model->name;
        $newFeed->email=$model->email;
        $newFeed->is_new=1;
        $newFeed->text=$model->text;
        if($newFeed->save()) {
            \Yii::$app->session->setFlash('success', \Yii::t('front', 'Your message has accepted'));
        } else {
            \Yii::$app->session->setFlash('error', \Yii::t('front', 'Error on save'));
        }
    } else {
        \Yii::$app->session->setFlash('error', \Yii::t('front', 'Data error'));
    }
    return $this->redirect(['/site/index']);
}


public function actionFeedbackvalidate()
{   
     Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;

    $model=  new \frontend\models\FeedbackForm;

    $model->load(Yii::$app->request->post());


    return ActiveForm::validate($model);
}

And create form inside view

                <?php $model=new \frontend\models\FeedbackForm; ?>
                <?php $form = ActiveForm::begin([
                        'enableClientValidation' => true,
                        'enableAjaxValidation' => true,
                        'validationUrl'=>['/site/feedbackvalidate'],
                        'validateOnSubmit' => true,
                        'id' => 'form-feedback',
                        'action'=>['/site/feedback'],
                        'options'=>['class'=>'some class', 'autocomplete'=>'off']
                    ]); ?>
                        <?= $form->field($model, 'name')->textInput(['maxlength' => true, 'placeholder'=>$model->getAttributeLabel('name'), 'autocomplete'=>'off'])->label(false); ?>
                        <?= $form->field($model, 'email')->textInput(['maxlength' => true, 'placeholder'=>$model->getAttributeLabel('email'), 'autocomplete'=>'off'])->label(false); ?>
                        <?= $form->field($model, 'text')->textarea(['maxlength' => true, 'placeholder'=>$model->getAttributeLabel('text'), 'autocomplete'=>'off'])->label(false); ?>


                    <div class="form-group">
                        <input type="submit" class="btn btn-default" value="<?php echo Yii::t('front', 'Send') ?>">
                    </div>
                <?php ActiveForm::end(); ?>

That is it

user3410311
  • 582
  • 4
  • 6