我对Yii2活动记录处理关系属性的方式感到困惑。是否可以将活动记录对象链接到另一个对象而不先保存它?
例如,我想将图像作为徽标添加到公司记录中,但尚未决定是否应保存这两个记录。鉴于一家公司
/**
* @property integer $logo_id
*/
class Company extends \yii\db\ActiveRecord
{
public function getLogo()
{
return $this->hasOne(Image::className(), ['id' => 'logo_id']);
}
}
和图片
class Image extends \yii\db\ActiveRecord
{
…
}
是否有一种简单的方法来设置$company->logo = new Image();
,以便下次调用$company->save()
时图像与公司一起保存,但在此之前不是?换句话说:我正在寻找Yii2相当于做@logo = @company.build_image()
in rails。
就目前而言,我正在Company
模型中定义徽标设定器功能。
public function setLogo(Image $image)
{
// `link` expects the primary key to be present
$image->id || $image->save();
$this->link('logo', $image);
}
这显然违反了最不惊讶的原则,因为setter不仅会修改当前对象,而且会以可能意外的方式影响(这里:保存)参数。这似乎是一个糟糕的主意。
如果不违反良好的设计原则,我该如何做$company->logo = new Image
?
答案 0 :(得分:1)
这是需要以任何方式发送到数据库的2个连续请求。
不违反良好的设计原则?
link()和unlink()旨在通过在 db 请求中设置其相对外键(在 >> <或> 连接表内)来关联或不关联2个已存在的模型)。如果 $ delete 属性设置为true,unlink()甚至可以删除相关记录。因此,必须首先使用有效的 id 创建相关数据,然后才能在第二个请求中进行关联。
如果无法链接模型,link()方法将抛出yii\base\InvalidCallException
。这很好,但在保存受影响的Active Record实例时不会执行任何数据验证。另一方面,save()方法默认执行验证,但如果出现问题则不会抛出http异常。
因此,如果我必须使用link()方法,我会在确定相关模型被保存(或通过尝试检索存在)之后链接 CONTROLLER 操作中的模型而不是模型类它)然后我确定如果某些东西不能按预期工作,将会抛出异常:
if ($image->save() === false && !$image->hasErrors()) {
throw new ServerErrorHttpException('Failed for some reason.');
}
$company->link('logo', $image);
否则我可能会保存相关模型并将其新的 id 属性分配给当前模型。我没有看到在模型类中使用link()方法的任何需要,如果我知道我的模型将在以后以任何方式保存,则再向 db 发送一个请求通过调用其save()
方法:
public function setLogo(Image $image)
{
if (!$image->id && $image->save() === false){
throw new yii\base\UserException('Failed for some reason');
}
$this->logo_id = $image->getPrimaryKey();
}
或者在您的控制器中,您可以简单地执行以下操作:
$image = new Image();
if ($image->save()) // or maybe throw exceptions
$company->logo_id = $image->getPrimaryKey();
无需为其编写任何额外功能。调用$company->save()
是否可以将活动记录对象链接到另一个对象 没先保存它?
另一种方法是将图像实例保存在某个位置,例如公共或私有属性,直到您决定是否要根据未来的逻辑来保存它。在这种情况下,在插入或更新之前触发的beforeSave方法可能是这样做的好地方:
private $_image;
public function getImage()
{
return $this->_image;
}
public function setImage(Image $image)
{
$this->_image = $image;
}
public function beforeSave($insert)
{
if ($this->_image->id){
$this->logo_id = $this->_image->id;
}
// otherwise you may save it as previous examples
// before assigning its returned id to $this->logo_id
// it will be good to have also plans for unexpected errors
return parent::beforeSave($insert);
}
然后,在执行$company->image = new Image
图像实例的任何位置都将使用它,直到调用$company->save()
或$company->update()
为止。