Renka
(Renkas)
May 8, 2016, 11:33am
1
I have three models:
Product
Dealer
Comment
Product and Dealer have Comments. I dont want to split up the comments to different models/tables.
I have a ‘type’ and ‘entity_id’ fields in comment table.
Now I can easily create a relation in Comment model that returns me either Product or Dealer model - according to what is set in the ‘type’ field.
But the problem is that I can’t “eager load” data with this relation. For admin views I would like to eager load the needed Dealer/Product models.
Is there some good way for it?
softark
(Softark)
May 8, 2016, 10:34pm
2
Renka
(Renkas)
May 17, 2016, 10:33pm
3
Well it’s not even very close to the thing I asked …
softark
(Softark)
May 18, 2016, 12:24pm
4
Please elaborate your question in details.
Renka
(Renkas)
May 18, 2016, 7:32pm
5
class Comment extends CommentBase
{
public function getEntity()
{
if ($this->type == self::TYPE_DEALER) {
return $this->getDealer();
} else if ($this->type == self::TYPE_PRODUCT) {
return $this->getProduct();
}
}
public function getDealer()
{
return $this->hasOne(Dealer::className(), ['id' => 'entity_id']);
}
public function getProduct()
{
return $this->hasOne(Product::className(), ['id' => 'entity_id']);
}
}
I have this Comment model.
I want to eager load Dealer and Product models when querying Comments. How can I achieve this?
->with(‘product’) etc will not work - as the relation does not know anything about the ‘type’ field. So it will try to eager load also Dealer ID’s as Products
softark
(Softark)
May 19, 2016, 1:03pm
6
The following is not tested and I’m not sure if it really works as expected or not.
The idea is introducing virtual properties of "dealerId" and "productId", with which the relations are defined.
class Comment extends CommentBase
{
public $dealerId;
ppublic $productId;
public function getDealer()
{
return $this->hasOne(Dealer::className(), ['id' => 'dealerId']);
}
public function getProduct()
{
return $this->hasOne(Product::className(), ['id' => 'productId']);
}
// ...
}
And the eager loading can be written like the following:
$comments = Comment::find()
->select([
'{{coment}}.*', // select all comment fields
'IF({{comment}}.type=1, entity_id, 0) AS dealerId',
'IF({{comment}}.type=2, entity_id, 0) AS productId',
])
->with(['dealer', 'product'])
->where( ... )
->all();
The properties of "dealerId" and "productId" can be implemented with getters and setters in order to handle the lazy loading scenarios.
class Comment extends CommentBase
{
private $_dealerId;
public function setDealerId($id)
{
$this->_dealerId = $id;
}
public function getDealerId()
{
if (empty($this->entity_id) || empty($this->type)) {
return null;
}
if ($this->_dealerId === null) {
if ($this->type === self::TYPE_DEALER) {
$this->setDealerId($this->entity_id);
} else {
$this->setDealerId(0);
}
}
return $this->_dealerId;
}
private $_productId;
public function setProductId($id)
{
$this->_productId = $id;
}
public function getProductId()
{
if (empty($this->entity_id) || empty($this->type)) {
return null;
}
if ($this->_productId === null) {
if ($this->type === self::TYPE_PRODUCT) {
$this->setProductId($this->entity_id);
} else {
$this->setProductId(0);
}
}
return $this->_productId;
}
// ...
}
Please take a look at "ActiveRecord - Selecting extra fields" section of the guide.
http://www.yiiframework.com/doc-2.0/guide-db-active-record.html#selecting-extra-fields
Renka
(Renkas)
May 20, 2016, 8:14am
7
Thanks - this concept works. But I think it can be made much simpler by overriding find() method. Here’s my working model:
class Comment extends CommentBase
{
public $dealer_id;
public $product_id;
/**
* @inheritdoc
*/
public static function find()
{
$activeQuery = parent::find();
$activeQuery->addSelect([
'IF(type=' . self::TYPE_PRODUCT . ', entity_id, null) AS product_id',
'IF(type=' . self::TYPE_DEALER . ', entity_id, null) AS dealer_id'
]);
return $activeQuery;
}
/**
* @return \yii\db\ActiveQuery
*/
public function getEntity()
{
if ($this->type == self::TYPE_DEALER) {
return $this->getDealer();
} else if ($this->type == self::TYPE_PRODUCT) {
return $this->getProduct();
}
}
/**
* @return \yii\db\ActiveQuery
*/
public function getDealer()
{
return $this->hasOne(Dealer::className(), ['id' => 'dealer_id']);
}
/**
* @return \yii\db\ActiveQuery
*/
public function getProduct()
{
return $this->hasOne(Product::className(), ['id' => 'product_id']);
}
}