0

I want to insert multiple record in my table using yii2 ActiveRecord. I already know that I can use this code

$connection->createCommand()->batchInsert('user', ['name', 'age'], [
    ['Tom', 30],
    ['Jane', 20],
    ['Linda', 25],
])->execute();

but by this approach my model validations are not executing. and I already have read this question ActiveRecord batch insert (yii2)

but also by doing validation in a tricky way, consider I want to fill created_at and updated_at columns using ActiveRecords events.

just like this

public function beforeSave($insert)
{
    if (parent::beforeSave($insert)) {
        if($insert)
            $this->created_at = date('Y-m-d H:i:s');
        $this->updated_at = date('Y-m-d H:i:s');
        return true;
    } else {
        return false;
    }
}
Community
  • 1
  • 1
Majid Abdolhosseini
  • 2,191
  • 4
  • 31
  • 59

1 Answers1

1

I think is not good idea to use beforeSave events (and similar stuff) because it will trigger for each model. However you want save multiple models at once. I recommend to use bulk methods.

In similar cases I use usually following "bulk" approach (code not tested, just for example):

namespace common\components;

class Model extends yii\base\Model {

    /**
     * Saves multiple models.
     *
     * @param ActiveRecord[] $models
     * @return bool
     */
    public static saveMultiple($models){

        if(count($models) > 0){

            $firstModel      = reset($models);
            $columnsToInsert = $firstModel->attributes();   // here you can remove excess columns. for example PK column.
            $modelsToInsert  = [];
            $rowsToInsert    = [];

            foreach($models as $model){
                if ($this->beforeSave(true)) {
                    $modelsToInsert[] = $model;
                }
            }

            foreach($modelsToInsert as $model){
                $rowsToInsert[] = array_values($model->attributes);     // here you can remove excess values
            }

            $numberAffectedRows = \Yii::$app->db->createCommand()
                ->batchInsert($firstModel->tableName(), $columnsToInsert, $rowsToInsert)
                ->execute();

            $isSuccess = ($numberAffectedRows === count($models));

            if($isSuccess){
                $changedAttributes = array_fill_keys($columnsToInsert, null); 

                foreach($modelsToInsert as $model){
                    $model->afterSave(true, $changedAttributes);
                }
            }

            return $isSuccess;

        } else {

            return true;
        }

    }

}

This class can be used:

use common\components\Model;

/**
 * @var SomeActiveRecord[] $models Array that contains array of active records (type SomeActiveRecord)
 */ 

// ...

if (Model::validateMultiple($models)){

    if(!Model::saveMultiple($models)){
        // ... some error handling
    }

} else {

    foreach($models as $model){
        if($model->hasErrors()){

            $errors = $model->getFirtsErrors();
            // ... some error handling

        }
    }

}

Additionally, for more convenient working with multiple models can be developed special Collection class that implements \ArrayAccess and \Iterator interfaces. This collection can iterated as simple array, however it contains special methods for bulk operations. Something like this:

foreach($modelCollection as $model){
    // ...
}

$modelCollection->validate();   // works similar to common\components\Model::validateMultiple()
$modelCollection->save();       // works similar to common\components\Model::saveMultiple()
IStranger
  • 1,868
  • 15
  • 23
  • thanks for your answer, if your model has an beforeSave Event which is triggering before insert new record so there is something happening tp all your records except batch inserts. what should we do whith this problem ? – Majid Abdolhosseini Sep 20 '16 at 21:40
  • Yes, I forgot about events. I added to example support of `beforeSave` and `aftreSave` events (based on `\yii\db\ActiveRecord::insertInternal`). Of course, described approach is not full alternative of `$model->save()`. At least we can't fill PKs after successful insertion. – IStranger Sep 21 '16 at 05:18