问题
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]);
}
In the previous step, I had to add new property to Job model named
eqtypesIds
to store the ids temporary in it.In Job model I had to use
afterSave
callback to perform saving selectedeqtypes
ids in the conjunction tableeqtype_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);
}
- 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