Yazan Qwaider

ال Polymorphic Relationships في Laravel

4 دقيقة - وقت القراءة
تاريخ النشر : قبل ٨ أشهر

ال relationships أو العلاقات في laravel من الميزات القوية التي تقدمها من خلال Eloquent, فإلى جانب ال One-To-One و One-To-Many و Many-to-Many, فإن هناك Ploymorphic relationship أيضاً, فلو شئنا ترجمتها حرفياً فإنها تعني (متعدد الأشكال) وهو تعريف ينطبق عليها فعلياً.


أحياناً قد يكون نفس ال model ينتمي لأكثر من model من خلال association واحد, دعنا نوضّح ذلك بشكل أفضل, على سبيل المثال:

لنفرض أن لدينا (Post و Comment, و Image), وال Post و Comment يملكان صورة أو أكثر, فهنا تتجلّى فكرة ال ploymorphic فيصبح ال Image Model متعدد الأشكال فقد تكون الصورة المخزنة بال Database في جدول images إما تابعة لل Post أو Comment.

* * *

One To One Polymorphic :

مثلاً لنفرض أن هناك بعض ال models والتي هي (Post, User, Image), وال post, user يملكان image فالبتالي ال image سوف تنتمي الى user أو post, لنرى الصورة والكود حتى تتضح المسألة أكثر :

Image

في Image Model سوف نكتب relation جديدة بإسم imagable وقد تختار أنت إسم آخر, ولكن حاول دائماً إتباع ال standard حتى في المسميات فتقوم بإضافة able مع الكلمة حتى تجعلها صفة لإنك لا تعرف بالضبط ماذا سترجع ال relation إما post أو user.

plain
class Image extends Model {
    /** Get the parent imageable model (user or post).   */
    public function imageable()
    {
        return $this->morphTo();
    }
}

ثم في Post Model سنقوم ببناء relation لتشير إلى أن ال post قد يملك صورة واحدة, فتقوم بذكر نفس إسم ال relation الذي قمت بتعريفه في Image Model في ال argument الثاني.

plain
class Post extends Model
{
    /** Get the post's image.   */
    public function image()
    {
        return $this->morphOne(Image::class, 'imageable');
    }
}

ونفس الأمر مع ال User Model.

plain
class User extends Model {
    /** Get the user's image. */
    public function image()
    {
        return $this->morphOne(Image::class, 'imageable');
    }
}

حيث أن جدول images سوف يتضمن عمودين وهما :

imageable_id :

وهو يشير إلى ال parent model id, وهو الذي قد يكون إما ال post او ال user.

imageable_type :

وهو النوع الخاص بال parent model class, وهو الذي قد يكون post او user, مثلاً :

App\Models\Post

App\Models\User

ولاحظ هنا أن مسميات الإعمدة هي الافتراضية طالما كان إسم ال relation هو imagable, أما إذا قمت بتغييره فعليك أن تجعل أسماء الأعمدة موافقة لإسم ال relation أيضاً.


ماذا لو أردت جعل إسم ال relation هو imagable ولكن أريد تغيير أسماء الإعمدة إلى أسماء أخرى, يمكنك ذلك من خلال :

plain
public function imageable() {
    return $this->morphTo(__FUNCTION__, 'imageable_type', 'imageable_id');
}

ال __FUNCTION__ هي من magic constants في php وتقوم بإرجاع إسم ال method.


للحصول على image من post مثلاً :

plain
$post = Post::find(1);
$image = $post->image;

للحصول على parent model من image مثلاً :

plain
$image = Image::find(1);
$imageable = $image->imageable;

أحياناً قد يكون ال parent model يملك many of child model, ولكن قد تحتاج إلى بناء relation تدل على استرجاع عنصر واحد منها بناءاً على latest أو oldest مثلاً :

plain
public function latestImage(): MorphOne
{
    return $this->morphOne(Image::class, 'imageable')->latestOfMany();
}

إذا أردت عند استرجاع العنصر الواحد الذي أشرنا إليه سابقاً بطريقة ترتيب مختلفة عن latest أو oldest, يمكنك الاعتماد على ofMany method لبناء ال sort الذي تريد حيث تستقبل هذه الدالة two arguments, الأول هو إسم العمود والثاني هو aggregate function إما max أو min.

plain
public function bestImage(): MorphOne
{
    return $this->morphOne(Image::class, 'imageable')->ofMany('likes', 'max');
}
* * *

One To Many Polymorphic :

هي نفس الـ one-to-many الاعتيادية ولكن في polymorphic قد ينتمي child model لأكثر من parent model بأكثر من row .

لو افترضنا مثلا أن هناك بعض ال models وهي (Post, Video, Comment), وال comment قد يكون على post او على video وقد يكون أكثر من comment ايضا على نفس post أو نفس video فإذن سوف نستخدم one-to-many :

Image
plain
class Comment extends Model {
    /** Get the parent commentable model (post or video). */
    public function commentable()
    {
        return $this->morphTo();
    }
}

plain
class Post extends Model {
    /**  Get all of the post's comments.  */
    public function comments()
    {
        return $this->morphMany(Comment::class, 'commentable');
    }
}

plain
class Video extends Model {
    /** Get all of the video's comments.   */

    public function comments()
    {
        return $this->morphMany(Comment::class, 'commentable');
    }
}

* * *

Many To Many Relationship :

لنفرض مثلا أن هناك بعض ال models وهي (Post, Video, Tag), وكل post أو video قد ينتمي الى أكثر من tag وجدول tags يحتوي على unique tags تنتمي الى أكثر من post وأكثر من video.

ففي هذه الحالة سيتم إنشاء جدول منفصل إسمه taggables, وهكذا شكل ال schema سوف يكون :

Image

نقوم بتعريف ال Tag Model في البداية, وفيه ال relations التالية :

plain
class Tag extends Model {
    /** Get all of the posts that are assigned this tag. */
    public function posts()
    {
        return $this->morphedByMany(Post::class, 'taggable');
    }

    /** Get all of the videos that are assigned this tag. */
    public function videos()
    {
        return $this->morphedByMany(Video::class, 'taggable');
    }
}

يحتوي على ال posts و videos, بحيث يستقبل في argument الثاني إسم ال relationship اعتماداً على الجدول الوسيط فيكون taggable.

ثم في Post Model ببني relation بإسم tags.

plain
class Post extends Model {
    /** Get all of the tags for the post. */
    public function tags()
    {
        return $this->morphToMany(Tag::class, 'taggable');
    }
}

ثم في Video Model ببني relation بإسم tags.

plain
class Video extends Model {
    /** Get all of the tags for the video. */
    public function tags()
    {
        return $this->morphToMany(Tag::class, 'taggable');
    }
}

تقوم laravel بوضع ploymorphic types افتراضياً, والتي هي ال model class لتشير إلى ال model مباشرة, وتقوم ببناء ال query الخاص بال relation, ولكن يمكنك تغيير ذلك من خلال عمل alias لذلك, قم بإضافة الكود التالي في boot method في AppServiceProvider :

plain
use Illuminate\Database\Eloquent\Relations\Relation;

Relation::enforceMorphMap([
    'post' => 'App\Models\Post',
    'video' => 'App\Models\Video',
]);

بحيث تقوم ال enforceMorphMap بجعل ال keys والتي هي post و video ك aliases لل models classes.

بعد ذلك يمكنك جعل ال type في database عند تخزينه ك post أو video بدلاً من ال classes.


الخاتمة:

أتمنى أنه كان شرحاً موفّقاً ل Polymorphic Relationships, ولا تنسى دائماً أن ترجع إلى Documentation لتقرأ وتتعلم الجديد عن الميزات التي قد تحدث على relationships والتي بدورها ستجعل من ال project الذي تعمل عليه أكثر قوةً ووضوحاً.