oxyaction/yii2-polymorphic-relation-behavior

Establishes polymorphic relations

1.0.0 2017-03-12 11:06 UTC

This package is not auto-updated.

Last update: 2025-01-04 21:52:50 UTC


README

This behavior helps to establish polymorphic behaviors easily. Forget about endless dictionary tables in your storage.

Build Status Latest Stable Version Latest Unstable Version License

Problem

Often you have some entity related to multiple entities in your system. For example Comment entity can belong to both Article, Photo, Post etc. You can create a comment table for each of this entities (ArticleComment, PhotoComment, PostComment), but this solution is not so good and not DRY at all.

The best solution will be creating the single Comment entity table and establishing polymorphic relations. The comment table is required to have type column that will indicate to which entity type comment belongs and external_id column that will be used as foreign key column.

class Article extends ActiveRecord
{
    const ARTICLE = 1;
    
    public function behaviors()
    {
        return [
            'polymorphic' => [
                'class' => RelatedPolymorphicBehavior::className(),
                'polyRelations' => [
                    'comments' => Comment::className()
                ],
                'polymorphicType' => self::TYPE_ARTICLE
            ]
        ];
    }
}

class Photo extends ActiveRecord
{
    const PHOTO = 2;

    public function behaviors()
    {
        return [
            'polymorphic' => [
                'class' => RelatedPolymorphicBehavior::className(),
                'polyRelations' => [
                    'comments' => Comment::className()
                ],
                'polymorphicType' => self::TYPE_PHOTO
            ]
        ];
    }
}

Then you can query relations as always

$article->comments;
$photo->comments;

Options

Has many

Short:

...
public function behaviors()
{
    return [
        'polymorphic' => [
            'class' => RelatedPolymorphicBehavior::className(),
            'polyRelations' => [
                'comments' => Comment::className()
            ],
            'polymorphicType' => 'article'
        ]
    ];
}
...

Same as

public function behaviors()
{
    return [
        'polymorphic' => [
            'class' => RelatedPolymorphicBehavior::className(),
            'polyRelations' => [
                'comments' => [
                    'type' => RelatedPolymorphicBehavior::HAS_MANY,
                    'class' => Comment::className(),
                    'pkColumnName' => 'id',
                    'foreignKeyColumnName' => 'external_id',
                    'typeColumnName' => 'type',
                    'deleteRelated' => false,
                ]
            ],
            'polymorphicType' => 'article'
        ]
    ]
}

Many to many

Often we need to establish polymorphic relation with many to many. in this case type and external_id columns will be in junction table.

public function behaviors()
{
    return [
        'polymorphic' => [
            'class' => RelatedPolymorphicBehavior::className(),
            'polyRelations' => [
                'tags' => [
                    'type' => RelatedPolymorphicBehavior::MANY_MANY,
                    'class' => Tag::className(),
                    'viaTable' => 'taggable_tag',
                ]
            ],
            'polymorphicType' => 'article'
        ]
    ]
}

All available options:

public function behaviors()
{
    return [
        'polymorphic' => [
            'class' => RelatedPolymorphicBehavior::className(),
            'polyRelations' => [
                'tags' => [
                    'type' => RelatedPolymorphicBehavior::MANY_MANY,
                    'class' => Tag::className(),
                    'viaTable' => 'taggable_tag',
                    'pkColumnName' => 'id',
                    'foreignKeyColumnName' => 'external_id',
                    'otherKeyColumnName' => 'tag_id',
                    'typeColumnName' => 'type',
                    'relatedPkColumnName' => 'id',

                ]
            ],
            'polymorphicType' => 'photo'
        ]
    ]
}

Some options can be set on per behavior or per relation level. For example polymorphicType from example above is set on behavior level, while typeColumnName on relation level. In case if you set option on behavior level you won't need to duplicate it for multiple relations while relation option will take precedence.

Final example

class Article extends ActiveRecord {
    public function behaviors()
    {
        return [
            'polymorphic' => [
                'class' => RelatedPolymorphicBehavior::className(),
                'polyRelations' => [
                    'tags' => [
                        'type' => RelatedPolymorphicBehavior::MANY_MANY,
                        'class' => Tag::className(),
                        'viaTable' => 'taggable_tag',
                    ],
                    'images' => [
                        'type' => RelatedPolymorphicBehavior::HAS_MANY,
                        'class' => Image::className(),
                    ],
                    'comments' => [
                        'type' => RelatedPolymorphicBehavior::HAS_MANY,
                        'class' => Comment::className(),
                    ],
                ],
                'polymorphicType' => 'article',
                'pkColumnName' => 'ID',
                'foreignKeyColumnName' => 'some_external_id',
                'typeColumnName' => 'entity_type',
            ]
        ]
    }
}

This will assume that taggable_tag, image and comment tables have some_external_id and entity_type columns, for Article entity it will be filled with article value and Article primary key column name is ID.

Warning

Since external_id of the polymorphic target entity links to multiple entities, you can not use foreign key constraints and need to check data consistency on application level.