Polymorphic association and inverse relation

Hello Yii community!

First of all, big thanks to all of you for the great framework, the forum and the spirit.

I’m having an issue, which I think is not resolvable by design. Correct me if I’m wrong. Maybe nobody hasn’t even thought about it.

I have the following scenario:

Two sql tables: Product and Project. Both are of course totally different.

Another table: Asset. This one holds meta file information. To achieve a relation the table has following fields:

  • owner_type (product or project or whatever might follow)

  • owner_id (id of the record in the table given in owner_type)

This works like a charm when defining the relations. The product relation looks like this:


public function getAssets()

{

    $where = [

        'owner_type' => Asset::OWNER_TYPE_PRODUCT

    ];


    return $this->hasMany(Asset::className(), ['owner_id' => 'id'])

        ->where($where);

}

The Asset relation:


public function getOwner()

{

    switch ($this->owner_type) {

        case self::OWNER_TYPE_PRODUCT:

            return $this->hasOne(Product::className(), ['id' => 'owner_id']);

        case self::OWNER_TYPE_PROJECT:

            return $this->hasOne(Project::className(), ['id' => 'owner_id']);

    }

}

The issue comes when I add the inverse relation information to the product relation:


public function getAssets()

{

    $where = [

        'owner_type' => Asset::OWNER_TYPE_PRODUCT

    ];


    return $this->hasMany(Asset::className(), ['owner_id' => 'id'])

        ->where($where)->inverseOf('owner');

}

The inverseOf usually works totally fine. But since the Asset instance hasnt loaded the data yet when the relation is being built, owner_type is null and no relation is being returned.

Eventually it’s no big deal since computing power is no issue here.

My only workaround would be to define a dedicated relation to every single owner table. So the Asset model would hold (for now) getProduct and getProject. This doesn’t seem right though since it leaves dead code behind.

Edit: This proposed workaround would not work. In the asset model I have to use the owner relation. And of course it’s not the same instance as the product/project instance. Just tested:


public function getProduct()

{

    return $this->hasOne(Product::className(), ['id' => 'owner_id']);

}


public function getProject()

{

    return $this->hasOne(Project::className(), ['id' => 'owner_id']);

}


public function getOwner()

{

    switch ($this->owner_type) {

        case self::OWNER_TYPE_PRODUCT:

            return $this->getProduct();

        case self::OWNER_TYPE_PROJECT:

            return $this->getProject();

    }

}

Using inverseOf(‘product’) in the product model, the following test fails:


$product->assets[0]->owner === $product

I do prefer the getOwner relation since all asset owner models implement one common interface. This is good coding style for me.

Somebody has anything to say to my thought?

Thanks for the read!

For further reference, here is an actually working workaround:


public function getOwner()

{

    switch ($this->owner_type) {

        case self::OWNER_TYPE_PRODUCT:

            return $this->hasOne(Product::className(), ['id' => 'owner_id']);

        case self::OWNER_TYPE_PROJECT:

            return $this->hasOne(Project::className(), ['id' => 'owner_id']);

        default:

            return new ActiveQuery(null);

    }

}

So when owner_type is null, a blank dummy ActiveQuery instance is returned. This works since its being resolved by the actual owner model instance anyway.