MarBer
MarBer

Reputation: 595

Yii2 exist validation rule on database transaction

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." } }

Upvotes: 0

Views: 1823

Answers (1)

csminb
csminb

Reputation: 2382

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.

Upvotes: 0

Related Questions