Yii2 model load does not work as expected

六眼飞鱼酱① 提交于 2019-12-12 04:35:54

问题


My question may look like the following question:

yii2 - model load function does not set some model attributes

However, the case here is different due to dealing with Many to Many relation through junction table.

For instance, I have three tables, Jobs, Eqtypes and the junction table Eqtype_jobs. I want to relate some Eqtypes to a current Job using simple activeform using multiple select dropDownList. The following is the code that I have in, the controller and the view:

//the controller code
public function actionEqtypeCreate($id)
{
  $eqtypes = \app\modules\crud\models\Eqtype::find()->all();
  $model = Job::findOne(['id' => $id]);
  if ($model->load(Yii::$app->request->post())){    
    var_dump($model->eqtypes);
    die(); // for debuging <<<<<<<<<<*>>>>>>>>
    foreach ($eqtypes as $eqt){
      $model->eqtypes->id = $eqt->id;
      $model->eqtypeJobs->eqtype_id = $eqt->eqtype_id;
      $model->save();
    }
    return $this->redirect(['index']);
  }
  return $this->render('eqtype-create', ['model' => $model, 'eqtypes' => $eqtypes]);  

}

Here the view:

//The view code i.e Form
<?php
//use Yii;
use yii\helpers\Html;
use yii\widgets\ActiveForm;
use yii\helpers\ArrayHelper;

$form = ActiveForm::begin([
    'enableClientValidation' => true,
]);

echo $form->field($model, 'eqtypes')->dropDownList(ArrayHelper::map($eqtypes,'id','title'), ['multiple' => true]);

 echo Html::submitButton(
        '<span class="glyphicon glyphicon-check"></span> ' .
        ($model->isNewRecord ? Yii::t('cruds', 'Create') : Yii::t('cruds', 'Save')),
        [
        'id' => 'save-' . $model->formName(),
        'class' => 'btn btn-success'
        ]
        );      

$form->end();

Here I have a problem: the output of var_dump($model->eqtypes), just before the die() in the controller code, always returns empty array array(0) { }.

Indeed, what's making me try to debug using die(), without the debug, I have got the following error:

Indirect modification of overloaded property app\modules\crud\models\Job::$eqtypes has no effect at line 149

In my case line 149 is the first line after foreach statement in the controller code:

$model->eqtypes->id = $eqt->id;

Edit

All models are created using yii2-giiant. However the following is a partial copy of job model:

<?php
// This class was automatically generated by a giiant build task
// You should not change it manually as it will be overwritten on next build

namespace app\modules\crud\models\base;

use Yii;
abstract class Job extends \yii\db\ActiveRecord
{



    /**
     * @inheritdoc
     */
    public static function tableName()
    {
        return 'jobs';
    }


    /**
     * @inheritdoc
     */
    public function rules()
    {
        return [
            [['machine_id', 'product_id', 'title', 'qty'], 'required'],
            [['machine_id', 'product_id', 'qty', 'updated_by' ,'created_by'], 'integer'],
            [['created', 'started', 'completed'], 'safe'],
            [['desc'], 'string'],
            [['title', 'speed'], 'string', 'max' => 255],
            [['product_id'], 'exist', 'skipOnError' => true, 'targetClass' => \app\modules\crud\models\Product::className(), 'targetAttribute' => ['product_id' => 'id']],
            [['machine_id'], 'exist', 'skipOnError' => true, 'targetClass' => \app\modules\crud\models\Machine::className(), 'targetAttribute' => ['machine_id' => 'id']]
        ];
    }

    /**
     * @inheritdoc
     */
    public function attributeLabels()
    {
        return [
            'id' => Yii::t('app', 'ID'),
            'machine_id' => Yii::t('app', 'Machine ID'),
            'created' => Yii::t('app', 'Created'),
            'started' => Yii::t('app', 'Started'),
            'completed' => Yii::t('app', 'Completed'),
            'product_id' => Yii::t('app', 'Product ID'),
            'title' => Yii::t('app', 'Title'),
            'qty' => Yii::t('app', 'Qty'),
            'speed' => Yii::t('app', 'Speed'),
            'created_by' => Yii::t('app', 'Created By'),
            'desc' => Yii::t('app', 'Desc'),
        ];
    }
 public function getEqtypeJobs()
    {
        return $this->hasMany(\app\modules\crud\models\EqtypeJob::className(), ['job_id' => 'id']);
    }

    /**
     * @return \yii\db\ActiveQuery
     */
    public function getEqtypes()
    {
        return $this->hasMany(\app\modules\crud\models\Eqtype::className(), ['id' => 'eqtype_id'])->viaTable('eqtype_jobs', ['job_id' => 'id']);
    }

I don't know, exactly, is the problem due to the miss loading of the POST data from the form due to the output of the var_dump or there is another thing missing in my code?!

So, I would like to know, why load does not works as expected? In other words var_dump($model->eqtypes) prints the stored values in the conjunction table not the submitted values from the form, and then if we could able to solve this, why the error message about the indirect modification?


回答1:


The error you are having "Indirect modification of overloaded property" is due to the fact that you are trying to modify a property returned by "__get" magic method. Take a look at the "__get" of "yii\db\BaseActiveRecord", in short it first checks if your model has a property "eqtypes" and if not checks your model relations.

As you understood, you cannot load relations in a model from a post and simply save it. I think in your solution, you are overshooting the problem and make your code too complex.

I suggest you use this module: https://github.com/cornernote/yii2-linkall Install this and change your action to:

$model = Job::findOne(['id' => $id]);
    if ($model->load(Yii::$app->request->post())){
        $postData = Yii::$app->request->post();
        $eqtypesIds = $postData['Job']['eqtypes'];
        $eqtypes = \app\models\Eqtype::findAll($eqtypesIds);
        $transaction = Yii::$app->db->beginTransaction();
        $extraColumns = []; // extra columns to be saved to the many to many table
        $unlink = true; // unlink tags not in the list
        $delete = true; // delete unlinked tags
        try {
            $model->linkAll('eqtypes', $eqtypes, $extraColumns, $unlink, $delete);
            $model->save();

            $transaction->commit();
        } catch (Exception $e) {

            $transaction->rollBack();

        }
        return $this->redirect(['index']);
    }
    $eqtypes = \app\models\Eqtype::find()->all();
    return $this->render('eqtype', ['model' => $model, 'eqtypes' => $eqtypes]);

and add in your Job model:

public function behaviors()
{
    return [
        \cornernote\linkall\LinkAllBehavior::className(),
    ];
}

If you do not want to use this module, you should override the default load() and save() methods of your model to include loading and saving your relations.




回答2:


Check your model validation rulse and post parameter, is your model validate rulses?

if($model->load(Yii::$app->request->post()) && $model->validate())
{
   echo "<pre>";
   print_r($_POST); // check the post parameter
   exit;    
}
else {
   echo "<pre>";
   print_r($model->getErrors());
   exit;
}



回答3:


I found a solution, but I don't think it is straight solution or the better one. It not applying the supposed benefits of ORM or activerecords. The solution outline is like the following:

1.collecting the eqytpes ids from the multiple select dropdown directly from POST like the following in the controller's action:

 public function actionEqtypeCreate($id)
    {
      $eqtypes = \app\modules\crud\models\Eqtype::find()->all();
      $model = Job::findOne(['id' => $id]);  
      if ($model->load(Yii::$app->request->post())){
 // <<<<<<<<<<<<<<<<<<<<<<,*>>>>>>>>>>>>>>>>>>> 
 // Direct Collect the IDs      
        $model->eqtypesIds = Yii::$app->request->post()['Job']['eqtypes'];
 // <<<<<<<<<<<<<<<<<<<<<*>>>>>>>>>>>>>>>>

          $model->save();

        return $this->redirect(['index']);
      }
      return $this->render('eqtype-create', ['model' => $model, 'eqtypes' => $eqtypes]);


    }
  1. In the previous step, I had to add new property to Job model named eqtypesIds to store the ids temporary in it.

  2. In Job model I had to use afterSave callback to perform saving selected eqtypes ids in the conjunction table eqtype_jobs as follows:

8787

  public function afterSave($insert, $changedAttributes)
        {
         // recal the saved values eqtype_id list to check the absence items of it from the submited and then delete the absence
            $ee = $this->hatEqtypeIdsArray(); // method returns avaialble eqtype_id as array
            //die(var_dump($ee));
            foreach ($ee as $a){
              if (!in_array($a, $this->eqtypesIds)){
                      $eq = \app\modules\crud\models\EqtypeJob::findOne(['job_id' => $this->id, 'eqtype_id' => $a]);
                      $eq->delete();
              }
            }
    // adding the ids submitted to the conjunction table.
            foreach ($this->eqtypesIds as $eqtype) {
                $tag = \app\modules\crud\models\EqtypeJob::findOne(['job_id' => $this->id, 'eqtype_id' => $eqtype]);            
                if ($tag) {

                }
                else{
                  $tag = new \app\modules\crud\models\EqtypeJob();
                  $tag->job_id = $this->id;
                  $tag->eqtype_id = $eqtype;
                  $tag->save();

                }
            }

            parent::afterSave($insert, $changedAttributes);
        }
  1. To void any error or mistaken remove for eqtypes associated to the job model in any other saving process for the Job model, I had to use beforeSave handler in the model like the following:

545

 public function beforeSave($insert) {
          parent::beforeSave($insert);
          if(is_null($this->eqtypesIds)){// i.e it has no values.
            $this->eqtypesIds = $this->hatEqtypeIdsArray();// get stored values for it
          }....

However, as I regarded above, this solution is very dirty. It may repeat and consume more resources. For example, suppose, you want to just edit the job title, this will lead to select the job eqtypes related and then re update it again.



来源:https://stackoverflow.com/questions/42591365/yii2-model-load-does-not-work-as-expected

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!