26

In yii2 how can i save multiple related records into db into single save call and single transaction. I have two tables:

User - id, name
UserAddress - id , user_id , city

User table has one to many relation to UserAdress table

What i want to do is :

UserAddress ua = new UserAddress();
ua.city = "fff"

User u = new User();
u.name = "test";
u.userAddress = new Array(ua);
u.save();

calling save on user should save user and useraddress with user_id set to user.id as well

Gaurav Singla
  • 1,405
  • 1
  • 17
  • 17

2 Answers2

33
 // You need create hasMany relation 'userAddress' (look guide relations)

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

try {

    $user = new User();
    $user->name = 'Name';
    $user->save();

    $ua = new UserAddress();
    $ua->city = 'City';

    $user->link('userAddress', $ua); // <-- it creates new record in UserAddress table with ua.user_id = user.id

    $transaction->commit();

} catch (Exception $e) {

    $transaction->rollBack();

}
  • would both the insert be in single transnational? – Gaurav Singla Mar 12 '15 at 11:03
  • 1
    You're using [`link()`](http://www.yiiframework.com/doc-2.0/yii-db-baseactiverecord.html#link()-detail) method in your example on two newly created active records. However, in "[Saving Relations](http://www.yiiframework.com/doc-2.0/guide-db-active-record.html#saving-relations)" article in Yii2 docs there's an info: "_Note: You cannot link two newly created Active Record instances_" and the example shows only linking new sub-model to an existing master model. It seems, that your solution won't work or there's a bug in Yii2 docs. Have you tested your answer? – trejder Apr 30 '15 at 08:51
  • 2
    @trejder after '$user->save()' it's not new actually. Yes, it works for me. – Andrey S. Rabchevsky Aug 12 '15 at 09:23
  • 2
    Running ```$user->save();``` like that can cause a problem here because it can fail due to validation. Beware, *save()* **does not throw an exception** for validation errors when validation checking is enabled (default). It will just skip saving $user and move on. I suggest calling ```$user->save(false);``` instead to disable validation and ensure an exception is thrown in case something goes wrong. – mae Dec 24 '15 at 05:39
  • user1132363 It can use a $user->load($data) first which returns false in case of not validating only calling save() if load() returns true; – Murilo Jan 14 '16 at 22:38
  • 1
    @user1132363 I agree with you. I added below my variant with handling of validation errors. – IStranger Aug 31 '16 at 19:42
6

Additionally to previous answer I propose variant that works without preliminarily defined relations and with explicit handling of validation errors.

Yii::$app->db->transaction(function(){

    $user = new User();
    $user->name = 'Name';

    if( !$user->save() ){
        throw new Exception('Can\'t be saved user model. Errors: '. join(', ', $user->getFirstErrors()));
    }

    $userAddress = new UserAddress();
    $userAddress->city      = 'City';
    $userAddress->user_id   = $user->id;

    if( !$userAddress->save() ){
        throw new Exception('Can\'t be saved user address model. Errors: '. join(', ', $userAddress->getFirstErrors()));
    }

});

This code strictly ensures that both records will be saved. If one of model can't be saved, will be thrown exception with validation error.

IStranger
  • 1,868
  • 15
  • 23