فصل ۱۲-۲:‌ آموزش روابط Has Many Through و Polymorphic در لاراول

06 آبان 1397
درسنامه درس 16 از سری لاراول
Laravel-Main-has-many-through-polymorphic

درود خدمت شما عزیزان و همراهان گرامی، با مطالعه و بررسی فصل ۱۲-۱ با اسامی انواع روابط در مدل‌ها و جداول آشنا شدید. اما تنها سه نوع رابطه‌ی یک به یک، یک به چند، چند به چند را بررسی کردیم. حال در این فصل به ادامه روابط پرداخته و سه نوع دیگر را که تخصصی و پیشرفته‌تر هستند مورد ارزیابی قرار می‌دهیم. با ما همراه باشید.

رابطه‌ی Has Many Through

این رابطه یک میانبر کوتاه برای دسترسی به روابط دورتر توسط یک رابطه‌ی میانی، ایجاد می‌کند. برای توضیح این نوع رابطه یک مثال میزنیم. فرض کنید می‌خواهیم تعداد پستهایی که کاربران یک کشور قرار داده‌اند را در دسترس داشته باشیم. برای اینکار یک مدل به نام Country داریم که باید یک رابطه‌ی یک به چند با مدل Post برقرار کند اما این وسط یک مدل به نام User وجود دارد که تعداد پست‌های هر کاربر را مشخص می‌کند و یک کاربر دارای چندین پست می‌باشد. بنابراین یک رابطه‌ی یک به چند دیگر بین کاربر و پست‌ها برقرار است. برای روشن شدن این موضوع به عبارات زیر توجه کنید:

countries
    id - integer
    name - string

users
    id - integer
    country_id - integer
    name - string

posts
    id - integer
    user_id - integer
    title - string

بنابراین هر کاربر علاوه بر اینکه چندین پست دارد، متعلق به یک کشور است. در اینجا مدل User به عنوان یک مدل میانی یا رابطه‌ی میانی معرفی می‌شود. چون جدول posts ستونی به نام country_id ندارد بلکه به واسطه‌ی جدول users به این ستون دسترسی پیدا می‌کند. این رابطه توسط متد hasManyThrough معرفی می‌شود. که اجازه می‌دهد پست‌های یک کشور توسط دستور country->posts$ قابل دسترسی است. این کوئری بدین صورت اجرا می‌شود که Eloquent ابتدا ستون country_id را درون جدول users پیدا می‌کند سپس id هر کاربر را درون ستون user_id در جدول posts بررسی کرده و پس از تطبیق آن را نمایش می‌دهد. حال برای تکمیل کردن مثال فوق ابتدا فایل Country.php را ایجاد کرده و سپس درون آن متد posts را به صورت زیر می‌نویسیم:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Country extends Model
{
    /**
     * Get all of the posts for the country.
     */
    public function posts()
    {
        return $this->hasManyThrough('App\Post', 'App\User');
    }
}

همانطور که ملاحظه می‌کنید دو آرگومان به این متد ارسال شده است. اولی نمایانگر جدولی است که مدل در نهایت با آن رابطه خواهد داشت و دومین آرگومان نشان‌دهنده‌ی مدل واسطه است.

مشابه سایر روابط با اعمال یک سری تغییرات می‌توان کلید خارجی هر جدول و رابطه را به صورت دلخواه تغییر داد. برای اینکار باید آرگومان‌های سوم و چهارم متد hasManyThrough را پر کرد. آرگومان سوم نام کلید خارجی در مدل میانی است و آرگومان چهارم نام کلید خارجی در مدلی است که در نهایت با آن رابطه خواهیم داشت. همچنین کلید اصلی را هم می‌توان با ارسال آرگومان پنجم در مدل Country تغییر داد (این کلید به صورت پیش فرض id نام دارد)

class Country extends Model
{
    public function posts()
    {
        return $this->hasManyThrough(
            'App\Post', 'App\User',
            'country_id', 'user_id', 'id'
        );
    }
}

روابط یک به چند پلی مورفیک (Polmorphic)

ساختار جداول

روابط یک به چند پلی مورفیک به مدل اجازه می‌دهد که با بیش از یک مدل روابط belongsTo ایجاد کند. یعنی به صورت همزمان و در یک ارتباط با چندین مدل دیگر رابطه " ۱ " داشته باشد. مطابق روش‌های قبلی باید با یک مثال شما را آشنا کرده تا مفهوم را به دقت درک کنید. فرض کنید کاربران وب سایت شما می‌توانند در هر دو بخش پست‌ها و ویدیوها نظر بگذارند. ما اینجا چندین مدل داریم User, Comment, Post, Video. با استفاده از رابطه پلی مورفیک می‌توان یک جدول comments را در هر دو جدول posts و videos استفاده کرد. ابتدا ساختار جداول را می‌کشیم:

posts
    id - integer
    title - string
    body - text

videos
    id - integer
    title - string
    url - string

comments
    id - integer
    body - text
    commentable_id - integer
    commentable_type - string

جداول posts و videos که مشخص هستند و ابهامی در آنها وجود ندارد اما در جدول comments دو ستون بسیار ارزشمند و مهم تحت عناوین commentable_id و commentable_type وجود دارند. ستون commentable_id یک ID از ویدیو یا پست را در خود ذخیره می‌کند درحالیکه commentable_type شامل نام مدلی است که جدول comments با آن ارتباط برقرار می‌کند. مثلا اگر بخواهیم نظرات مرتبط با یک پست را که آی دی آن ۴۵۳ است را استخراج کنیم مقدار commentable_id برابر ۴۵۳ و مقدار commentable_type برابر Post خواهد بود.

ساختار مدل

بنابراین جدول comments را باید به صورت زیر تعریف کنیم:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Comment extends Model
{
    /**
     * Get all of the owning commentable models.
     */
    public function commentable()
    {
        return $this->morphTo();
    }
}

برای مدل Post خواهیم داشت:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

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

همچنین برای مدل Video داریم:

class Video extends Model
{
    /**
     * Get all of the video's comments.
     */
    public function comments()
    {
        return $this->morphMany('App\Comment', 'commentable');
    }
}

بازیابی اطلاعات از روابط پلی مورفیک

پس از اینکه مدل‌ها و جداول دیتابیس تعریف شدند دسترسی به این روابط و اطلاعات درون آنها بسیار ساده می‌شود. برای نمونه، جهت دستیابی به نظرات یک پست باید ابتدا id‌ پست را پیدا کرده و سپس از ویژگی پویای comments استفاده کنیم:

$post = App\Post::find(1);

foreach ($post->comments as $comment) {
    //
}

همچنین برای استخراج نام مدل موردنظر که از رابطه‌ی پلی مورفیک استفاده می‌کند می‌توان از متد commentable استفاده کرد. یعنی در حالت کلی متدی که از ساختار morphTo استفاده کرده است. بنابراین داریم:

$comment = App\Comment::find(1);

$commentable = $comment->commentable;

در مثال فوق بررسی کرده‌ایم که یک دیدگاه با id =1 با چه نوع مدل ذخیره شده است ( مثلا Post یا Video).

انواع دلخواه پلی مورفیک

در حالت پیش‌فرض، لاراول نام کلاس مدل را به عنوان type در جدول ذخیره می‌کند. به عنوان نمونه، در مثال بالا مدل Comment با مدل‌های Post و Video رابطه یک به چند داشته و به صورت پیش‌فرض ستون commentable_type دارای مقادیر App\Post و App\Video است. همانطور که ملاحظه می‌کنید در این حالت پایگاه داده وابسته به مسیر App است و اگر پوشه شما تغییری داشته باشد هیچ اطلاعاتی در دسترس نخواهد بود. بنابراین برای جداسازی پایگاه داده از مسیر نرم افزاری باید یک morph map تعریف کرد تا نام دلخواهی را به جای مدل‌ها در دیتابیس ذخیره کنیم:

use Illuminate\Database\Eloquent\Relations\Relation;

Relation::morphMap([
    'posts' => 'App\Post',
    'videos' => 'App\Video',
]);

همینطور که ملاحظه می‌کنید در کدهای بالا مسیرهای App\Post و App\Video به عناوین posts و videos توسط متد morphMap از Relation Facade تغییر کرده است. برای ثبت این کدها باید آنها را درون تابع boot موجود در کلاس AppServiceProvider ایجاد کرد.

روابط چند به چند پلی مورفیک (Polymorphic)

علاوه بر روابط یک به چند که در بالا بررسی کردیم. روابط چند به چند پلی مورفیک وجود دارند. مجددا ادامه‌ی این آموزش را با مطرح کردن یک مثال پیش می‌بریم. یک وبلاگ مدل‌هایی با عناوین Post‌ و Video دارد. حال هر پست و ویدیو دارای چندین تگ یا برچسب است و متقابلا هر برچسب به چندین پست و ویدیو اختصاص دارد. در اینجا یک رابطه‌ی چند به چند بین مدل Tag با هر یک از مدل‌های Video و Post وجود دارد. بنابراین برای ایجاد همچنین رابطه‌ای باید و باید یک جدول تحت عنوان taggables ایجاد کرده که لیستی از تگ‌ها را که به پست یا ویدیوی مشخصی ارتباط دارند مشخص کند. بنابراین ساختار جداول برای این سه مدل به صورت زیر خواهد بود:

posts
    id - integer
    name - string

videos
    id - integer
    name - string

tags
    id - integer
    name - string

taggables
    tag_id - integer
    taggable_id - integer
    taggable_type - string

همانطور که مشاهده می‌کنید جدولی به نام taggables ایجاد شده است که شامل ستون‌های tag_id (آی دی هر تگ که از جدول tags استخراج می‌شود)، ستون taggable_id (آی دی هر پست یا ویدیو که می‌خواهیم تگ را به آن اختصاص دهیم) و ستون taggable_type (نوع مدلی که تگ به آن اتصال پیدا می‌کند).

ساختار Model

حال به تعریف مدل‌ها می‌پردازیم. مدل‌های Post و Video هر دو متدی به عنوان tags خواهند داشت که درون آنها متد morphToMany بازگردانده می‌شود:

در مدل Post.php خواهیم داشت:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

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

همچنین درون مدل Video:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

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

حال درون مدل Tag,php باید رابطه‌ی معکوس و دو طرفه را برقرار کنیم. بنابراین باید برای هر یک از مدل‌های Post و Video متدهای posts و videos را ایجاد کنیم:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

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

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

بازیابی اطلاعات از این روابط

پس از تعریف جداول و مدل‌ها برای بازیابی اطلاعات از این روابط اقدام می‌کنیم:

$post = App\Post::find(1);

foreach ($post->tags as $tag) {
    //
}

همچنین می‌توان تمام تگ‌های موجود در یک پست و یا ویدیو را با استفاده از دستور زیر بدست آورد:

$tag = App\Tag::find(1);

foreach ($tag->videos as $video) {
    //
}

 

بسیار عالی! مباحث مربوط به روابط Eloquent به اتمام رسید و شما با مطالعه‌ی فصل ۱۲ می‌توانید ۶ نوع رابطه‌ای که برای جداول هر پایگاه داده نیاز است را ایجاد کنید. در جلسه‌ی بعدی به توضیح مختصری درباره انواع کوئری این روابط و همچنین نحوه‌ی بارگذاری آنها می‌پردازیم.

تمام فصل‌های سری ترتیبی که روکسو برای مطالعه‌ی دروس سری لاراول توصیه می‌کند:
نویسنده شوید
دیدگاه‌های شما (8 دیدگاه)

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.

farhadfery
09 آذر 1399
تشکر

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.

دانیال
15 خرداد 1399
سلام ، من دارم بلاگ شخصی خودمو با لاراول می‌سازم ، خواستم بدونم الان ما رابطه بین مثلا پست بلاگ و تصویر و محصولات بلاگ داریم ، حالا سوال اینجاست که آیا این جدول مثلا photos نباید چیزی تحت عنوان یوزر آیدی داشته باشه به طوری که مثلا بتونیم تمامی تصاویری که کاربر ثبت کرده به دست بیاوریم ؟ یا مثلا مثال دیگه اینکه ، بتونیم ببینیم چه کاربری ، چه محصولات یا مطالبی دنبال کرده ( flow) ?

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.

رضا
26 مهر 1398
واقعا فوق العاده بود. سپاس فراوان

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.

لیلا
13 شهریور 1398
بسیار عالی بود. سپاس

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.

محمدعلی
21 مهر 1397
با سلام واقعا دوره عالی بود.ممنون و سپاس گذار

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.

محمد حسین افضلی
12 مهر 1397
با سلام، خیلی زیبا و روان توضیح دادید، انشاالله که همیشه مؤید و موفق باشید

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.

مجید
05 تیر 1397
عالی بود .

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.

فاطمه
03 خرداد 1397
بهترین هستید بدون اغراق بگم، کلی گشتم تا بتونم سایتی را پیدا کنم که morph را توضیح بده، اما هیچ سایتی به این خوبی نبود، حتی خود سایت Laravel. سپاس از زحماتتون

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.