Yii2 模型加载不按预期工作
Yii2 model load does not work as expected
我的问题可能类似于以下问题:
但是,这里的情况是不同的,因为通过联结table处理多对多关系。
例如,我有三个table,Jobs
,Eqtypes
和tableEqtype_jobs
。我想使用简单的 activeform
和多个 select dropDownList
将一些 Eqtypes
与当前的 Job
相关联。以下是我的代码,控制器和视图:
//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]);
}
这里是视图:
//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();
这里我有一个问题:var_dump($model->eqtypes)
的输出,就在控制器代码die()
之前,总是returns为空数组 array(0) { }
.
的确,是什么让我尝试使用 die()
进行调试,没有调试,我遇到了以下错误:
Indirect modification of overloaded property
app\modules\crud\models\Job::$eqtypes has no effect at line 149
在我的例子中,第 149 行是控制器代码中 foreach
语句之后的第一行:
$model->eqtypes->id = $eqt->id;
编辑
所有模型都是使用 yii2-giiant 创建的。然而,以下是工作模型的部分副本:
<?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']);
}
我不知道,确切地说,问题是由于 var_dump
的输出而导致从表单中加载 POST 数据失败,还是由于 var_dump
中缺少其他内容我的代码?!
所以,我想知道,为什么 load
没有按预期工作? 换句话说 var_dump($model->eqtypes)
打印连词 table 中的存储值而不是表单中提交的值,然后 如果我们能够解决这个问题,为什么会提示间接修改的错误信息?
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;
}
我找到了解决方案,但我不认为它是直接解决方案或更好的解决方案。它没有应用 ORM 或 activerecords 的假定好处。解决方案大纲如下:
1.collecting 来自多个 select 下拉列表的 eqytpes ids 直接来自 POST 就像控制器操作中的以下内容:
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]);
}
在上一步中,我必须向名为 eqtypesIds
的作业模型添加新的 属性 以在其中临时存储 ID。
在 Job 模型中,我必须使用 afterSave
回调来执行保存 selected eqtypes
连词中的 ID table eqtype_jobs
如下:
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);
}
- 为了避免在作业模型的任何其他保存过程中与作业模型关联的 eqtypes 的任何错误或错误删除,我必须在模型中使用
beforeSave
处理程序,如下所示:
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
}....
但是,正如我上面所看到的,这个解决方案非常肮脏。它可能会重复并消耗更多资源。例如,假设您只想编辑职位名称,这将导致 select 与职位 eqtypes 相关,然后再次重新更新它。
您遇到 "Indirect modification of overloaded property" 的错误是因为您试图修改“__get”魔术方法返回的 属性。看看 "yii\db\BaseActiveRecord" 的“__get”,简而言之,它首先检查您的模型是否有 属性 "eqtypes",如果没有,则检查您的模型关系。
正如您所理解的,您不能从 post 加载模型中的关系并简单地保存它。
我认为在您的解决方案中,您正在过度解决问题并使您的代码过于复杂。
我建议你使用这个模块:https://github.com/cornernote/yii2-linkall
安装它并将您的操作更改为:
$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]);
并添加您的工作模型:
public function behaviors()
{
return [
\cornernote\linkall\LinkAllBehavior::className(),
];
}
如果您不想使用此模块,您应该覆盖模型的默认 load() 和 save() 方法以包括加载和保存您的关系。
我的问题可能类似于以下问题:
但是,这里的情况是不同的,因为通过联结table处理多对多关系。
例如,我有三个table,Jobs
,Eqtypes
和tableEqtype_jobs
。我想使用简单的 activeform
和多个 select dropDownList
将一些 Eqtypes
与当前的 Job
相关联。以下是我的代码,控制器和视图:
//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]);
}
这里是视图:
//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();
这里我有一个问题:var_dump($model->eqtypes)
的输出,就在控制器代码die()
之前,总是returns为空数组 array(0) { }
.
的确,是什么让我尝试使用 die()
进行调试,没有调试,我遇到了以下错误:
Indirect modification of overloaded property app\modules\crud\models\Job::$eqtypes has no effect at line 149
在我的例子中,第 149 行是控制器代码中 foreach
语句之后的第一行:
$model->eqtypes->id = $eqt->id;
编辑
所有模型都是使用 yii2-giiant 创建的。然而,以下是工作模型的部分副本:
<?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']);
}
我不知道,确切地说,问题是由于 var_dump
的输出而导致从表单中加载 POST 数据失败,还是由于 var_dump
中缺少其他内容我的代码?!
所以,我想知道,为什么 load
没有按预期工作? 换句话说 var_dump($model->eqtypes)
打印连词 table 中的存储值而不是表单中提交的值,然后 如果我们能够解决这个问题,为什么会提示间接修改的错误信息?
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;
}
我找到了解决方案,但我不认为它是直接解决方案或更好的解决方案。它没有应用 ORM 或 activerecords 的假定好处。解决方案大纲如下:
1.collecting 来自多个 select 下拉列表的 eqytpes ids 直接来自 POST 就像控制器操作中的以下内容:
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]);
}
在上一步中,我必须向名为
eqtypesIds
的作业模型添加新的 属性 以在其中临时存储 ID。在 Job 模型中,我必须使用
afterSave
回调来执行保存 selectedeqtypes
连词中的 ID tableeqtype_jobs
如下:
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);
}
- 为了避免在作业模型的任何其他保存过程中与作业模型关联的 eqtypes 的任何错误或错误删除,我必须在模型中使用
beforeSave
处理程序,如下所示:
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
}....
但是,正如我上面所看到的,这个解决方案非常肮脏。它可能会重复并消耗更多资源。例如,假设您只想编辑职位名称,这将导致 select 与职位 eqtypes 相关,然后再次重新更新它。
您遇到 "Indirect modification of overloaded property" 的错误是因为您试图修改“__get”魔术方法返回的 属性。看看 "yii\db\BaseActiveRecord" 的“__get”,简而言之,它首先检查您的模型是否有 属性 "eqtypes",如果没有,则检查您的模型关系。
正如您所理解的,您不能从 post 加载模型中的关系并简单地保存它。 我认为在您的解决方案中,您正在过度解决问题并使您的代码过于复杂。
我建议你使用这个模块:https://github.com/cornernote/yii2-linkall 安装它并将您的操作更改为:
$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]);
并添加您的工作模型:
public function behaviors()
{
return [
\cornernote\linkall\LinkAllBehavior::className(),
];
}
如果您不想使用此模块,您应该覆盖模型的默认 load() 和 save() 方法以包括加载和保存您的关系。