0

One function in my model use the transaction to save a row in two distinct tables, table_1 and table_2. In table_2 one foreign key refers to table_1.id and the validation rule, auto generate by gii, is type "exist". When I need to save rows the first step is to begin a database transaction, the second is to set and save a table_1 row and at the end set and save the table_2 rows related with table_1 row, if both inserts are ok the transactions are committed otherwise they are rolled back.

The problem is when I pass to table_2 the id of table_1 row and the validation fails because the id of table_1 is not valid, but the id is generated in the same script, is this a problem with transaction?

Edit 1:

The operations which generates error:

$order = new OrdersToImport();
$transaction = OrdersToImport::getDb()->beginTransaction();
... //operations on $order
if($order->save()){
   $detail = new OrdersToImportD();
   ... //operations on $detail
   $detail->id_order = $order->id;
   if(!$detail->save()){
      $transaction->rollback();
      return -1;
   }
}

The code for data validation:

[['id_order'], 'exist', 'skipOnError' => true, 'targetClass' => OrdersToImport::className(), 'targetAttribute' => ['id_order' => 'id']]

Edit 2:

The result of:

if(!$detail->save()){
    echo "$order->id";
    echo "$detail->id_order";
    var_dump($detail->errors);
    die();
}

is:

187
187
array(1) { ["id_order"]=> array(1) { [0]=> string(20) "Id Order is invalid." } }
Jason Aller
  • 3,541
  • 28
  • 38
  • 38
MarBer
  • 535
  • 1
  • 5
  • 22
  • 1
    Can you share code of this transaction? – Yupik Jul 20 '17 at 10:45
  • can you post some code? are you making sure you're calling `Model1->save()` before you try to get it's id? – csminb Jul 20 '17 at 10:52
  • The code is on edit, @csminb yes I use the `save()` function otherwise the id isn't set in the model instance – MarBer Jul 20 '17 at 11:50
  • What's result of `var_dump($detail->id_order);die;` right after `$detail->id_order = $order->id;` ? – Yupik Jul 20 '17 at 11:57
  • I will post the result soon, one more detail... I have 2 connections at the database, in this case both the connections working on the same database, can my error have origin because the connection of the table where I want insert data is served by one connection and the one where Yii run validation use another? I hope my description is clear – MarBer Jul 20 '17 at 12:27
  • @MarBer it is possible for that to happen, if you have setup different databases on child classes (parent has validation rule - and child uses diferent db from parent). if that's your case, add more details about how where you are defining these these, perhaps also use fully namespaced classes for your example code, – csminb Jul 20 '17 at 12:35
  • All the process work on an indipendet module, this module have configured a database settings because it work every time with the same database, the applications wich loads the module have his own database settings, the validation is maded on a site class but transaction run on module connection wich I consider distinct from the one of the site. I think that post all parts interested by the problem is like post all project, if you can understand from my explanation is better otherwise I can try to post something more – MarBer Jul 20 '17 at 12:47
  • 1
    in your module try and overwrite the rules() method for `OrdersToImport` and `OrdersToImportD`, – csminb Jul 20 '17 at 14:13
  • 1
    @MarBer You have two diffrent connections in those 2 models? If yes - second connection won't see records before commiting them. – Yupik Jul 20 '17 at 14:16
  • @Yupik Updated the model for use same database connection but the behavior is the same. – MarBer Jul 21 '17 at 06:38
  • @csminb how I can overwrite rules? These was generated from gii, I suppose that all the rules thus generated are right – MarBer Jul 21 '17 at 06:38
  • @MarBer try to change your transaction definition to: `$transaction = \Yii::$app->db->beginTransaction();` – Yupik Jul 21 '17 at 06:40
  • @Yupik for Modules logics I have changed the `getDb()` of both models `OrdersToImport` and `OrdersToImportD` as `public static function getDb(){ $c = Yii::$app->getModule('carrello'); return $c->get('db'); }` do you think that this is like you suggests? – MarBer Jul 21 '17 at 06:48
  • Hmm, you can try, without testing it i can't say anything. If it won't work, try in way i told you, by getting db from `Yii::$app` (for testing purposes, you can relocate db config from module to main config and name it like `dbtest`, then use it: `$transaction = \Yii::$app->dbtest->beginTransaction();`. – Yupik Jul 21 '17 at 06:55

1 Answers1

0

These was generated from gii, I suppose that all the rules thus generated are right

Yes the rules are correct, and they are good in most use cases.
that does not mean that they will work in every situation;

i assume from the comments that your structure is something like this:
(and if i'm wrong please update your question with the appropiate details):
i'm gonna call them Order and OrderDetail for the sake of simplicity

  • generated models: these contain the existance rule you mentioned common\models\Order
    common\models\OrderDetail

  • models with custom db: these contain a different definition of getDb() and extend the two generated modules above
    common\modules\samplemodule\models\Order
    common\modules\samplemodule\models\OrderDetail

now the models in samplemodule will inherit the rules of the generated models.

note the targetClass of this generated rule in common\models\OrderDetail :

[['id_order'], 'exist', 'skipOnError' => true, 'targetClass' => Order::className(), 'targetAttribute' => ['id_order' => 'id']]

Order::className() means common\models\Order::className() that means that all child classes (regardless of namespace) will have a existance rule that refferences common\models\Order.
and in your case: modules\samplemodule\models\OrderDetail (which uses a different db) will validate against the existence of a common\models\Order (an order from the default database)


so here's my proposed solution:

for common\models\OrderDetail (the generated class) remove the existence rules, and define them in a separate method

namespace common\models;

class OrderDetail extends ActiveRecord {

    //.. 

    public function rules(){
        return ArrayHelper::merge([
            // ..
            // all the default generated rules except the existance ones

        ], static::existenceRules());
    }

    protected static function existenceRules(){
        return [
            [['id_order'], 'exist', 'skipOnError' => true, 
            // fqn not required it's just here to highlight the difference
            'targetClass' => common\models\Order::className(), 
            'targetAttribute' => ['id_order' => 'id']]
        ];
    }
    // ..
}

for common\modules\samplemodule\models\OrderDetail overwrite the existanceRules() method we created previously an link the correct target class

namespace common\modules\samplemodule\models;

class OrderDetail extends common\models\OrderDetail {

    //.. 

    // custom db:
    public static function getDb(){
       return Yii::$app->moduleDatabase;
    }

    // optional (if you need more rules here):
    public function rules(){
        return ArrayHelper::merge( parent::rules(), [
            // rules that apply only in this context (this db)
        ]);
    }

    // this is required if to reference the correct `targetClass`
    protected static function existenceRules(){
        return [
            [['id_order'], 'exist', 'skipOnError' => true, 
            'targetClass' => common\modules\samplemodule\models\Order::className(), 
            'targetAttribute' => ['id_order' => 'id']]
        ];
    }
    // ..
}

in both cases, i've used the full name of the target class to help highlight the difference, since they use different databases the same rule cannot work in both parent and child class.

hope this is helpfull to you. best of luck.

csminb
  • 2,382
  • 1
  • 16
  • 27
  • i recomend you use `getDb()` as a means to separate the two connections with `moduleDatabase` configuration specified in config (the first solution you were provided [here](https://stackoverflow.com/a/45198645/374509) seems the cleanest way to achieve this) – csminb Jul 21 '17 at 08:44
  • Thanks for your help, seems this solution works, now I need to run deep test about all site components and modules to be sure that all elements interacts like I hope. I want to still to thank you and @Yupik for the support! – MarBer Jul 21 '17 at 12:10