Conditional relation

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?

Try this:

http://www.yiiframework.com/doc-2.0/guide-db-active-record.html#dynamic-relational-query

Well it’s not even very close to the thing I asked …

Please elaborate your question in details.




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

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

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']);

    }

}

Nice :)