فصل ۱۲-۱:‌ آموزش روابط یک به یک، یک به چند و رابطه‌ی چند به چند در لاراول

04 فروردین 1396
درسنامه درس 15 از سری لاراول
Laravel-Main-RELATIONSHIPS-PART1

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

مقدمه

همانطور که در مثال‌های قبلی مشاهده کردید برخی از جداول با جداول دیگر ارتباط داشتند. منظور از ارتباط وجود یک ستون به همراه کلید خارجی در جداول است. برای مثال یک رکورد موجود در جدول posts ممکن است چندین نظر (comments) داشته باشد. بنابراین یک ارتباط یک به چند بوجود می‌آید. حال لاراول و Eloquent مدیریت این ارتباط‌ها را بسیار ساده‌تر کرده تا شما بتوانید سریع‌تر به تولید نرم‌افزار خود بپردازید. انواع روابطی که به دقت آنها را بررسی می‌کنیم عبارتند از:

  • روابط یک به یک (One to One)
  • روابط یک به چند (One to Many)
  • روابط چند به چند (Many to Many)
  • روابط Has Many Through
  • روابط چند ریختی (Polymorphic)
  • روابط چند به چند پلی مورفیک (Many to Many Polymorphic)

در ادامه به توضیح هر یک از روابط فوق می‌پردازیم. اما قبل از مراجعه به این روابط نحوه‎‌ی تعریف ارتباط‌ها در لاراول را توضیح می‌دهیم.

تعریف ارتباط‌ها

روابط جداول دیتابیس‌ درون کلاس‌های مدل Eloquent تعریف می‌شوند. برای داشتن یک رابطه باید همواره یک کلید خارجی برای هر جدولی که طرف اصلی رابطه است، تعریف کرد. برای ایجاد یک کلید خارجی باید ابتدا فایل موردنظر در پوشه‌ی database/migrations را باز کرده و سپس به جدولی که مدنظرمان است عبارت زیر را اضافه کنیم:

Schema::table('posts', function (Blueprint $table) {
    $table->integer('user_id')->unsigned();

    $table->foreign('user_id')->references('id')->on('users');
});

در نمونه‌ی فوق همانطور که مشاهده می‌کنید برای جدول posts یک ستون به نام user_id تعریف کرده‌ایم سپس این ستون را با استفاده از دستور foreign به جدول users، ارتباط داده‌ایم. عبارت references به معنای مرجع این کلید خارجی است که در این مثال مرجع کلید خارجی user_id برابر ستون id در جدول users است.

روابط همواره توسط query builder ها یا سازنده‌ی کوئری‌ها به صورت زنجیره‌ای مورد استفاده قرار می‌گیرند. به عنوان مثال می‌توان روی رابطه‌ی posts در نمونه‌ی زیر متدهای مختلفی را اعمال کرد:

$user->posts()->where('active', 1)->get();

اما قبل از ورود به این مبحث اجازه دهید تمامی روابط را مورد بررسی قرار دهیم.

توجه: برای فهم بیشتر مطالب لطفا هر جدول را به صورت یک مستطیل روی یک کاغذ باطله کشیده و سپس متناسب با نوع ارتباطی که دارند با خطوط آن‌ها را بهم وصل کنید.

رابطه‌ی یک به یک (One to One)

ساده‌ترین نوع رابطه بین دو جدول رابطه‌ی یک به یک است. به عنوان مثال یک کاربر همواره یک شماره تلفن دارد. بنابراین جدول users با جدول phones رابطه‌ی یک به یک برقرار کرده است. برای تعریف این رابطه باید ابتدا در فایل مدل User موجود در پوشه‌ی app/User.php را باز کرده و دستور زیر را به آن اضافه کنید:

public function phone()
    {
        return $this->hasOne('App\Phone');
    }

با این کار یک متد phone به مدل User اضافه کرده‌ایم. این متد باید توسط متد hasOne فراخوانی شود. که از این متد برای روابط یک به یک استفاده می‌کنیم. آرگومانی که این متد می‌پذیرد نام کلاس مدلی است که با این کلاس رابطه دارد. هنگامی یک رابطه تشکیل شد، می‌توان رکوردهای مرتبط را با استفاده از ویژگی‌ها پویای Eloquent بازیابی کرد بنابراین داریم:

$phone = User::find(1)->phone;

با این روش Eloquent یک کلید خارجی بر اساس نام مدل مشخص می‌کند. در این مثال مدل Phone به صورت خودکار حدس زده که کلید خارجی درون خودش برابر user_id است. اگر می‌خواهید کلید خارجی را از حالت پیش فرض خارج کنید باید متد hasOne را به صورت زیر ویرایش کنید و به آن آرگومان دوم بدهید:

return $this->hasOne('App\Phone', 'foreign_key');

 

در حالت پیش فرض یک رابطه کلید اصلی برابر id در نظر گرفته می‌شود حال در صورتیکه بخواهیم کلید اصلی primary key دیگری برای جدول خود تعریف کنیم باید آرگومان سومی به متد hasOne ارسال کرد و نام آن ستون را که به عنوان کلید اصلی مشخص می‌شود ارسال کنیم:

return $this->hasOne('App\Phone', 'foreign_key', 'local_key');

در عبارت بالا foreign_key کلید خارجی است و local_key کلید اصلی‌ مدل والدی‌ست که تعریف می‌کنیم، می‌باشد.

به این مثال توجه کنید:

return  $this->hasOne('Phone','user_id', 'id'); // default     
 
return  $this->hasOne('Phone','melicode_id', 'melicode');

حال رابطه‌ی ما یک طرفه است یعنی فقط جدول users به phones متصل شده است اما این رابطه باید دو طرفه باشد. بنابراین فایل Phone.php‌ را از پوشه‌ی app/Phone.php باز کرده و متد زیر را به آن اضافه می‌کنیم:

public function user()
    {
        return $this->belongsTo('App\User');
    }

در مثال فوق، Eloquent تلاش می‌کند که مقدار user_id موجود در مدل Phone را با یک id در مدل User مطابقت دهد. همانطور که مشاهده می‌کند متدی به نام belongsTo ایجاد شده است که آرگومان آن برابر مدل User است.

همچنین درصورتیکه کلید اصلی و کلید خارجی برای یک جدول تغییر کند باید آن را به عنوان آرگومان دوم و سوم به متد belongsTo به صورت زیر ارسال کرد:

public function user()
{
    return $this->belongsTo('App\User', 'foreign_key', 'other_key');
}

در عبارت فوق foreign_key کلید خارجی و other_key به عنوان کلید اصلی موجود در مدل والد است.

مثال زیر را مشاهده کنید:

public function phone()
{
    return  $this->belongsTo('User','user_id', 'id'); // default        
}

اگر بخواهیم این مثال را به بیان عامیانه ارائه دهیم:

مدل User توسط متد hasOne یک رابطه با مدل Phone و مدل Phone توسط متد belongsTo یک رابطه معکوس با مدل User به صورت یک به یک برقرار کرده است.

رابطه‌ی یک به چند (One to Many)

از رابطه‌ی یک به چند برای تعریف روابطی که در آنها یک مدل واحد با چندین مدل دیگر در ارتباط است، استفاده می‌شود. به عنوان مثال، یک پست وبلاگ ممکن است تعداد نامحدودی نظر داشته باشد. همانند تمامی روابط Eloquent، رابطه‌ی یک به چند نیز درون یک مدل و تابع موجود در آن تعریف می‌‍شود. به عنوان مثال دو مدل Post و Comment را در نظر داشته باشید، آنگاه داریم:

ابتدا مدل Post.php را باز کرده و سپس متد comments را درون آن ایجاد می‌کنیم و مقداری که باز می‌گردانیم رابطه‎ای‌ست که با مدل دیگری برقرار می‌کند. این رابطه به صورت زیر تعریف می‌شود:

public function comments()
{
    return $this->hasMany('App\Comment');
}

همانطور که ملاحظه می‌کند برای برقراری رابطه‌ای با عنوان «چند نظر» باید از متد hasMany استفاده کرد که داخل آن آرگومانی با مقدار نام مدل Comment ارسال نمود. در این مرحله جهت ایجاد ساختاری نظام‌مند در کدهای خود، بد نیست یک سری قوانین را رعایت کنید. هنگام تعریف یک کلید خارجی بهتر است از روش snake case استفاده کنید. مثلا اگر قرار است در جدول comments یک ستون از نوع کلید خارجی از ستون id جدول posts ایجاد شود، بهتر است این ستون با نام post_id ارائه گردد.

پس از تکمیل شدن این رابطه می‌توان برای دستیابی به تمام نظرات یک پست از ویژگی‌های داینامیک یا پویا استفاده کرد:

$comments = App\Post::find(1)->comments;

foreach ($comments as $comment) {
    //
}

همچنین می‌توان به صورت کاملا زنجیره‌ای دستورهای محدودیت‌های مختلفی برای بازگردانی نظرات یک پست اعمال کرد که با استفاده از سیستم سازنده کوئری انجام می‌شود:

$comments = App\Post::find(1)->comments()->where('title', 'foo')->first();

همانطور که در این مثال ملاحظه می‌کنید تمام نظراتی یک پست با id = 1 که عنوان آنها برابر با foo‌ است استخراج شده و با دستور first اولین مقدار بازیابی شده، درون متغییر comments ذخیره می‌گردد.

همانند رابطه‌‎ی hasOne، می‌توان کلید خارجی و کلید اصلی یک رابطه را به صورت دستی تغییر داد که مثلا در از حالت پیش فرض post_id خارج شود. برای اینکار باید آرگومان دوم را به متد hasMany ارسال کرد:

return $this->hasMany('App\Comment', 'foreign_key');

return $this->hasMany('App\Comment', 'foreign_key', 'local_key');

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

public function post()
{
    return $this->belongsTo('App\Post');
}

با این دستور ثابت کردیم که هر نظر تنها به یک پست تعلق دارد و این جمله با متد belongsTo اعمال می‌شود. هنگامیکه این رابطه تکمیل شد می‌توان پستی که به آن نظری ارسال شده است را به صورت زیر پیدا کرد:

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

echo $comment->post->title;

در این مثال مقدار ستون post_id در جدول comments به دنبال مقدار موجود در ستون id در جدول posts می‌گردد و هر جا که این مقادیر برابر بود پست موردنظر را پیدا می‌کند. همانطور که در جریان هستید برای تغییر کلید خارجی و کلید اصلی پیش فرض در جداول باید آرگومان دوم متد belongsTo را به صورت زیر اضافه کنید:

public function post()
{
    return $this->belongsTo('App\Post', 'foreign_key');
}

در صورتیکه مدل والد (Posts) از id به عنوان کلید اصلی primary key استفاده نکرده بود یا نیاز داریم رابطه را بر اساس ستون دیگری تعیین کنیم باید همواره یک آرگومان سوم به متد belongsTo اضافه کرده که کلید دلخواه مدل والد را نمایش می‌دهد.

public function post()
{
    return $this->belongsTo('App\Post', 'foreign_key', 'other_key');
}

رابطه‌ی چند به چند (Many to Many)

این رابطه به نسبت دو رابطه قبلی کمی پیچیده‌تر است. بهتر است آن را با یک مثال توضیح دهیم. فرض کنید یک کاربر (user) تعداد زیادی نقش (roles) در یک سایت دارد. از طرفی یک نقش ممکن است به چندین کاربر داده شود. مثلا چند کاربر نقش نویسندگی دارند، دیگری مدیر. برای نوشتن این رابطه باید سه جدول ایجاد کرد. جدولی به نام users، جدولی با عنوان roles و در نهایت یک جدول دیگر به نام role_user. دو جدول اول که خیلی معمولی هستند و بارها تولید شده اند اما جدول سوم به Pivot Table معروف است.

Pivot در لغت به معنای محور است. در اینجا می‌توانیم عبارت معادل جدول محوری یا جدول رابطه‌ای برای آن برگزینیم. این جدول به عنوان یک واسطه بین کاربران و نقش‌ها ایجاد می‌شود و در روابط چند به چند حتما حضور دارد. قواعد نامگذاری این جداول با حروف الفبا و به صورت مفرد تنظیم می‌شود. در مثال فوق r جلوتر از u است و جدول با ساختار role_user مورد استفاده قرار می‌گیرد. درون این جدول دو ستون به نام‌های user_id و role_id قرار می‌گیرد.

حال نوبت به نوشتن رابطه‌هاست. برای نوشتن رابطه‌ها باید از متد belongsToMany برای هر دو مدل User و Role استفاده کرد. بنابراین در فایل User.php خواهیم داشت:

public function roles()
{
    return $this->belongsToMany('App\Role');
}

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

$user = App\User::find(1);

foreach ($user->roles as $role) {
    //
}

همانند بسیاری از مدل‌های دیگر نیز می‌توان از ساختار زنجیره‌ای برای متد roles استفاده کرد:

$roles = App\User::find(1)->roles()->orderBy('name')->get();

در نظر دارید که لاراول هیچگونه اجباری را برای مخاطبان خود اعمال نمی‌کند. شما می‌توانید با پر کردن آرگومان دوم متد belongsToMany نام جدول میانی یا pivot table را به راحتی تغییر دهید و نام دلخواه خود را به مدل اطلاع دهید:

return $this->belongsToMany('App\Role', 'role_user');

درصورتیکه بخواهیم ستون‌های موجود در جدول میانی را با نام دلخواه تغییر دهیم می‌توان از آرگومان‌های سوم و چهارم متد belongsToMany استفاده کرد. آرگومان سوم برای کلید خارجی مدلی است که رابطه در آن تعریف شده است مثلا در مدل User این آرگومان به صورت user_id است و آرگومان چهارم نام کلید خارجی‌ای است که مدل با آن ارتباط دارد. در مثال فوق اگر بخواهیم این رابطه را در مدل User تعریف کنیم به صورت زیر خواهد بود:

return $this->belongsToMany('App\Role', 'role_user', 'user_id', 'role_id');

مانند سایر روابط همانطور که در جریان هستید باید همواره یک رابطه دو طرفه برقرار باشد. چون نوع رابطه ما اینجا چند به چند است بنابراین همین متد belongsToMany در مدل دیگری که در این مثال Role.php نام دارد تعریف می‌شود.

public function users()
{
    return $this->belongsToMany('App\User');
}

در نظر دارید که در صورت استفاده از تمام گزینه‌های آرگومان‌های دوم و سوم و چهارم باید آنها را در مدل Role.php تعریف کرد.

حال برای بازیابی اطلاعات موجود در جداول میانی یا pivot table می‌توان از صفت pivot استفاده کرد. مثلا برای دستیابی به تاریخ ایجاد یک نقش برای یک کاربر خاص از دستور زیر استفاده کرد:

$user = App\User::find(1);

foreach ($user->roles as $role) {
    echo $role->pivot->created_at;
}

به صورت پیش‌فرض تنها کلید‌های مدل درون شیء pivot در دسترس هستند. درصورتیکه جدول pivot شما دارای فیلدها یا ستون‌های متفاوتی بود باید آن را با استفاده از دستور withPivot در مدل تعریف کرد:

return $this->belongsToMany('App\Role')->withPivot('column1', 'column2');

اگر می‌خواهید جدول pivot شما ستون‌هایی با عنوان created_at و updated_at داشته باشد باید متد withTimestamps را به هنگام تعریف رابطه بکار برد:

return $this->belongsToMany('App\Role')->withTimestamps();

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

گاهی می‌توان خروجی و نتایج بازگشتی متد belongsToMany را با استفاده از متدهای wherePivot و wherePivotIn هنگام تعریف یک رابطه فیلتر کرد:

return $this->belongsToMany('App\Role')->wherePivot('approved', 1);

return $this->belongsToMany('App\Role')->wherePivotIn('priority', [1, 2]);

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

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

دیدگاه‌های شما (10 دیدگاه)

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

mohsen
23 آبان 1399
سلام. ممنون از شما به خاطر وقت و انرژیی که میذارید برای ارائه مطالب به صورت روان و شیوا.

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

voicerecordist
01 اسفند 1398
سلام تفاوت بین hasone و belongsto چیه؟ هر کدوم رو کجا باید استفاده کرد؟

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

محمد
14 مهر 1398
سلام راهی وجود نداره در رابطه های چند به چند بصورت خودکار در جدول واسط مقادیر اضافه بشه؟ مثلا وقتی یک فیلم با یک ژانر انتخاب میکنم خودکار آیدی ها این دو در جدول واسط ثبت بشه؟

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

روکسو
14 مهر 1398
سلام وقت شما بخیر برای انجام این کار کافیست از دستورهای attach یا sync استفاده کنید.

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

سامان
27 بهمن 1397
عالی بود دمتون گرم . هر وقت جایی واسم مبهمه سایت شما واقعا کمک میکنه

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

hosein
29 آبان 1397
thanks

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

افسانه
03 مهر 1397
سلام مطالب مفیدی بود فقط روابط morph-many و ... رو تعریفشو بزارین ممنون

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

محمد
21 مرداد 1397
بسیار ممنون - مفید بود

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

امید احمدیانی
25 اردیبهشت 1397
سلام وقتتون بخیر لطفا رابطه Model Relationship to Itself را نیز توضیح بفرمایید که خیلی کاربردی است. با تشکر

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

عرفان
14 بهمن 1396
حرف نداشت ، لذت بردم .

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

مرتضی
18 خرداد 1396
سلام .. واقعا عالی بود ... لطفا قسمت های جدید لاراول هم بزارید مثل Broadcasting و browser tests و کلا قسمت های دیگش ... بازم ممنون...

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