آموزش رابطه ها در لاراول
در این مقاله درمورد آموزش رابطه ها در لاراول بحث خواهیم کرد. معمولا جدول ها در دیتابیس یا پایگاه داده، با هم در ارتباط هستند. مثلا در یک وبلاگ، یک نوشته می تواند چندین دیدگاه یا کامنت داشته باشد. در عین حال همین نوشته با کاربری که آن را نوشته و ثبت کرده است نیز ارتباطی دیگر دارد. Eloquent نوشتن این روابط و کار با آنها را بسیار آسان تر کرده است. مخصوصا که از انواع ارتباط به خوبی پشتیبانی می کند. برای آموزش رابطه ها در لاراول باید تمام انواع رابطه ها را مطالعه نمایید:
- One To One رابطه یک به یک
- One To Many رابطه یک به چند
- Many To Many رابطه چند به چند
- Has One Through رابطه یک به یک با واسطه
- Has Many Through رابطه یک به چند با واسطه
- One To One (Polymorphic)
- One To Many (Polymorphic)
- Many To Many (Polymorphic)
آموزش رابطه ها در لاراول
رابطه های Eloquent با نوشتن متد ها در داخل Model های Eloquent تعریف می شوند. به عبارتی برای تعریف هر رابطه شما باید در داخل کلاس مدل هایتان، اقدام به نوشتن متد یا تابع یا فانکشن نمایید. دقیقا همانند مدل های Eloquent، رابطه ها نیز خود به عنوان یک query builder عمل می کنند، تعریف رابطه ها به صورت توابع یا همان متد ها این امکان را فراهم می کند تا اطلاعات را به صورت زنجیره ای از پایگاه داده صدا زده و قابلیت های پرس و جوی بیشتری را در جستجوهای خود به کار بگیرید. مثلا :
$user->posts()->where('active', 1)->get();
در مثال بالا مشاهده می کنید که توانستیم جستجوی پیشرفتهای در بین پست های یک کاربر انجام دهیم. به عبارتی باید گفت که می توانیم از متد های Eloquent در روابط لاراول بهره برده و پرس و جوی دقیق تری انجام دهیم.
آموزش رابطه ها در لاراول را با مطالعه روابط ساده شروع می کنیم :
رابطه یک به یک یا One To One در لاراول
ساده ترین و اساسی ترین رابطه در لاراول، رابطه یک به یک می باشد. مثلا هر کاربری فقط یک شماره موبایل دارد و هر شماره موبایلی می تواند فقط متعلق به یک کاربر باشد نه بیشتر.
پیاده سازی
اگر جدول users یا کاربران به این صورت باشد :
$table->bigIncrements('id'); $table->string('name');
جدول mobiles نیز باید اینگونه باشد :
$table->bigIncrements('id'); $table->string('number'); $table->unsignedBigInteger('user_id')->index(); $table->foreign('user_id')->references('id')->on('users') ->onDelete('cascade')->onUpdate('cascade');
در سطر سوم از دستورات بالا، به عبارت user_id کلید خارجی یا foreign key می گویند. مقدار این عبارت برابر خواهد بود با id کاربر. با استفاده از مقدار کلید خارجی مشخص می شود که یک شماره تلفن متعلق به کدام کاربر است.
در مدل User :
public function phone() { return $this->hasOne('App\Http\Models\Phone'); }
در مدل Phone :
public function user() { return $this->belongsTo('App\User'); }
بعد از انجام مراحل بالا کافیست جدول را نهایی کنیم :
php artisan migrate
حال برای فراخوانی شماره موبایل یک کاربر می توانیم دستور زیر را بکار ببریم :
return User::first()->phone;
و یا :
$user = User::find(3); return $user->phone;
و همچنین برای فراخوانی کاربر مربوط به یک شماره تلفن :
return Phone::first()->user;
و یا :
$phone = Phone::find(5); return $phone->user;
نکته :
کلید خارجی یا foreign key را می توانید چیزی به جز user_id نیز قرار دهید اما باید در متد phone() در مدل User و نیز در متد user() در مدل Phone به این مطلب اشاره کنید. فرض کنید ما از عبارت person_id استفاده کرده ایم :
در مدل User :
public function phone() { return $this->hasOne('App\Http\Models\Phone', 'person_id'); }
و در مدل Phone :
public function user() { return $this->belongsTo('App\User', 'person_id'); }
جهت آموزش کامل و کاربردی رابطه یک به یک، به این آدرس مراجعه بفرمایید.
رابطه یک به چند یا One To Many در لاراول
رابطه یک به چند زمانی تعریف می شود که یک مقدار از یک مدل Model دارای چند مقدار از مدل های دیگر است. به طور مثال در یک سایت خبری، هر پست یا خبر، چندین و چندین comment یا دیدگاه دارد و در عین حال دیدگاه ، فقط متعلق به یک خبر است.
پیاده سازی :
اگر جدول posts یا مطالب به این صورت باشد :
$table->bigIncrements('id'); $table->string('title');
جدول comments یا دیدگاه ها نیز باید اینگونه باشد :
$table->bigIncrements('id'); $table->string('body'); $table->unsignedBigInteger('post_id')->index(); $table->foreign(' post _id')->references('id')->on('posts') ->onDelete('cascade')->onUpdate('cascade');
در سطر سوم از دستورات بالا، به عبارت post_id کلید خارجی یا foreign key می گویند. مقدار این عبارت برابر خواهد بود با id مطلب یا post مربوطه. با استفاده از مقدار کلید خارجی مشخص می شود که یک دیدگاه یا کامنت متعلق به کدام پست است.
در مدل Post :
public function comments() { return $this->hasMany('App\Comment); }
در مدل Comment :
public function post() { return $this->belongsTo('App\Post'); }
بعد از انجام مراحل بالا کافیست جدول را نهایی کنیم :
php artisan migrate
حال برای فراخوانی کامنت ها یا دیدگاه های یک پست یا مطلب می توانیم دستور زیر را بکار ببریم :
$comments = Post::first()->comments ;
و یا :
$comments = Post::find(3)->comments ;
و همچنین برای فراخوانی مطلب مربوط به یک دیدگاه :
return Comment::first()->post;
و یا :
$comment = Comment::find(5); return $comment ->post;
نکته اول :
دقت کنید که در مدل Post نام متد را comments به صورت اسم جمع دادیم، زیرا هر پست یا مطلب می تواند بیش از یک دیدگاه یا کامنت داشته باشد.
نکته دوم :
کلید خارجی یا foreign key را می توانید چیزی به جز post_id نیز قرار دهید اما باید در متد comments() در مدل Post و نیز در متدpost() در مدل Comment به این مطلب اشاره کنید. فرض کنید ما از عبارت relation_id استفاده کرده ایم :
در مدل Post :
public function comments() { return $this->hasMany('App\Comment ', 'relation_id'); }
و در Comment.php :
public function post() { return $this->belongsTo('App\Post', ' relation_id'); }
جهت آموزش کامل و کاربردی رابطه یک به چند، به این آدرس مراجعه بفرمایید.
Many To Many رابطه چند به چند در لاراول
فرض کنید ما جدولی داریم به نام tags که بالطبع نام مدل آن Tag است و جدولی به نام posts که اسم مدل آن Post می باشد. حال فرض کنید یک post نوشته ایم به نام “روابط در لاراول” که می تواند این تگ ها را به خود اختصاص دهد : php , laravel , برنامه نویسی , …
حال هر یک از تگ های بالا می توانند در پست های زیادی باشند. مثلا پستی با عنوان “سیستم احراز هویت لاراول” هم می تواند تگ php یا laravel را بگیرد. پس هر post میتواند چندین tag داشته باشد و هر tag می تواند متعلق به چندین post باشد. بدین ترتیب دو جدول posts و tags می توانند رابطه many to many داشته باشند.
پیاده سازی
در رابطه Many To Many به غیر از جداول posts و tags ، به جدول واسطه دیگری نیازمندیم که به آن pivot table نیز گفته می شود. در واقع در هیچ یک از جداول posts و tags نیاز به تعریف کلید خارجی نداریم و این کار را تنها در جدول واسطه انجام خواهیم داد. ابتدا جداول posts و tags را تعریف می کنیم :
جدول posts :
$table->bigIncrements('id'); $table->string('title'); $table->longText('body'); $table->timestamps();
جدول tags :
$table->bigIncrements('id'); $table->string('name'); $table->timestamps();
نوشتن جداول واسطه مهم ترین بخش از آموزش رابطه ها در لاراول می باشد.حال نوبت به طراحی و تعریف جدول واسطه یا pivot table می رسد.
- اولین نکته در طراحی این جدول نام آن است. نام جدول واسطه باید از ترکیب نام دو جدول که رابطه many to many دارند ساخته شود.
- نام جدول واسطه باید ترکیب اسم مفرد جدول اول و اسم مفرد جدول دوم با یک آندرلاین (خط زیرین یا underscores) در میان آنها باشد. مثلا post_tag . درواقع اسم posts_tags برای نام pivot table صحیح نیست.
- در انتخاب نام باید ترتیب الفبایی رعایت شود. بدین صورت که اسم جدولی در ابتدا نوشته می شود که حرف اول آن نسبت به حرف اول اسم جدول دیگر اولویت داشته باشد. مثلا اگر اسم جدول اول post باشد و اسم جدول دوم tags باشد با مقایسه دو حرف p و t به این نتیجه می رسیم که حرف p نسبت به حرف t مقدم است. پس ابتدا باید post نوشته شود و سپس علامت _ و بعد tag ، پس می شود : post_tag
مثال دیگر : نام جدول واسطه بین دو جدول users و roles خواهد شد :
role_user
مقادیر این جدول تنها id های مقادیر دو جدول مربوط به هم و کلید های خارجی خواهند بود :
$table->bigInteger('post_id')->unsigned()->index(); $table->bigInteger('tag_id')->unsigned()->index(); $table->foreign('post_id')->references('id')->on('posts') ->onDelete('cascade')->onUpdate('cascade'); $table->foreign('tag_id')->references('id')->on('tags') ->onDelete('cascade')->onUpdate('cascade');
در مدل Post :
public function tags() { return $this->belongsToMany('App\ Tag'); }
در مدل Tag :
public function posts() { return $this->belongsToMany('App\ Post'); }
مجددا به جمع بودن اسم هر دو متد و عبارت belongsTo در هر دوی آنها دقت نمایید .
جهت فراخوانی تگ های پست با id = 3 می توان نوشت :
$tags = Post::find(3)->tags;
و برای فراخوانی پست های تگ با id=4 داریم :
$posts =Tag::find(3)->posts;
Attach ، Dettach و Sync در روابط many to many
الوکوئنت Eloquent ، متد ها و helper هایی در اختیارمان گذاشته تا کار با رابطه many to many آسان تر شود. یکی از این متد ها attach() می باشد. Attach در لغت به معنای جسباندن و الصاق است و در لاراول جهت افزودن مدار به رابطه یک مقدار دیگر به کار می رود . مثلا :
$post = Post::first(); $post->tags()->attach($tagId);
مشاهده می فرمایید که ورودی متد attach() باید id یا آرایه ای از id ها باشد. این به خاطر این است که در pivot table تنها id می توان ذخیره کرد. در مثال دیگری می خواهیم چند tag را به تگ های یک post اضافه نماییم :
$post->tags()->attach([1,2,3,]);
و یا مثلا در تکه کد زیر :
$tags = $request->tags; $post->tags()->attach($tags);
متد detach() نیز درست نقطه مقابل attach() و برعکس آن عمل می کند. مثلا برای حذف یک یا چند مقدار از روابط یک مقدار دیگر. مثلا برای حذف tag هایی با id های 1و2 از یک post معین می توانیم بنویسیم :
$post->tags()->attach([1,2]);
چنانچه از متد sync() استفاده نماییم، ابتدا تمام مقادیر حذف شده و سپس مقادیر ورودی متد sync() اضافه خواهد شد. مثلا فرض کنید post با id=3 تگ ها با id های 1و3 را دارد. چنانچه از دستور زیر استفاده کنیم:
$post ->tags() -> sync(2);
تنها تگ با id=2 جزو تگ های این پست خواهد بود و تگ های 1و3 از جدول واسطه حذف خواهند شد. این helper می تواند یک آرایه را نیز به عنوان ورودی بپذیرد.
کی اط قسمت های مهم آموزش روابط در لاراول روابط با واسطه است :
Has One Through رابطه یک به یک با واسطه در لاراول
رابطه hasOneThrough مدل ها را از طریق یک واسطه پیوند می دهد به عبارتی ایجاد ارتباط بین دو جدول یا دو مدل از طریق جدول یا مدل دیگری می تواند برقرار شود. مثلا فرض کنید در یک آژانس تلفنی جدولی به نام drivers یا راننده ها داریم. جدول دیگری هم داریم به نام cars یا ماشین ها. جدول سومی نیز وجود دارد به نام car_infos که مشخصات ماشین ها از قبیل شماره پلاک، رنگ و … را ثبت می کند و نام مدل مربوط به آن CarInfo می باشد. ارتباط بین دو جدول drivers و cars از نوع One To One است. و ارتباط بین دو جدول cars و car_infos نیز One To One است.
یعنی هر راننده ای یک ماشین دارد و هر ماشینی یک اطلاعاتی و بالعکس هر اطلاعاتی متعلق به یک ماشین است و هر ماشینی متعلق به یک راننده. اما ارتباطی بین drivers و car_infos وجود ندارد. برای ارتباط بین این دو جدول، در نسخه های قبل از 5.8 بایستی تغییراتی در هر دو جدول می دادیم و از کلید خارجی استفاده می کردیم اما از نسخه 5.8 به بعد این کار لازم نیست و ما میتوانیم با جدول های موجود ارتباط برقرار کنیم :
پیاده سازی
جدول drivers :
$table->bigIncrements('id'); $table->string('name');
جدول cars :
$table->bigIncrements('id'); $table->unsignedBigInteger('driver_id')->index(); $table->foreign('driver_id')->references('id')->on('drivers') ->onDelete('cascade')->onUpdate('cascade');
جدول car_infos :
$table->bigIncrements('id'); $table->string('color'); $table->integer('car_number'); $table->unsignedBigInteger('car_id')->index(); $table->foreign('car_id')->references('id')->on('cars') ->onDelete('cascade')->onUpdate('cascade');
مشاهده می کنید که ارتباطی بین drivers و car_infos وجود ندارد .
برای ایجاد ارتباط بین این دو کافیست در مدل Driver :
public function info() { return $this->hasOneThrough('App\CarInfo', 'App\Car'); }
این دستور می گوید: جدول drivers با جدول car_infos ارتباط One To One دارد اما از طریق جدول cars .
برای فراخوانی شماره پلاک مربوط به راننده اول داریم :
$driver = \App\Driver::first(); $info = $driver->info;
ولی تا تاریخ تحریر این مقاله، لاراول برای این رابطه، رابطه برعکسی نگذاشته است به عبارتی نمی توان در این رابطه از روی اطلاعات یک ماشین، راننده آن را پیدا کرد. اما می توان در مدل CarInfo این کار را انجام داد :
public function driver() { return $this->car->driver(); }
و برای فراخوانی راننده یک شماره ماشین :
$info = \App\CarInfo::where('number', '47693')->first(); $driver = $info->driver;
Has Many Through رابطه یک به چند با واسطه در لاراول
رابطه hasManyThrough مدل ها را از طریق یک مدل واسطه ارتباط می دهد به عبارتی ایجاد ارتباط بین دو جدول یا دو مدل از طریق جدول یا مدل دیگری اتفاق می افتد. فرض کنید یک جدول داریم به نام countries یا کشور ها با مدل Country .جدول دیگری داریم به نام users یا کاربران با مدل User. جدول سوم هم متعلق به posts یا نوشته هاست که نام مدلش Post می باشد.
ارتباط بین Country و User به صورت One To Many است یعنی هر کشوری چندین کاربر دارد ولی هر کاربر تنها متعلق به یک کشور است. ارتباط بین User و Post نیز چنین است. هر کاربر چندین پست دارد ولی هر پست تنها متعلق به یک کاربر است.
رابطه has many through می گوید می توانیم بین دو مدل Country و Post ارتباط برقرار کرده و همهpost های یک country را فراخوانی نمود. تنها کافیست در مدل Country :
public function posts() { return $this->hasManyThrough('App\Post', 'App\User'); }
این دستور می گوید : جدول countries با جدول posts ارتباطی از نوع One To Many دارد اما از طریق جدول users .
حال برای فراخوانی همه post های یک کشور مشخص چنین عمل می کنیم:
$country = \App\Country::first(); $posts = $country->posts;
ولی برای فراخوانی عکس این رابطه فعلا راهی جز نوشتن متد دستی وجود ندارد. مطابق مثال بالا می توان در مدل Post نوشت:
public function country() { return $this->user->country(); }
در این قسمت از مقاله آموزش روابط در لاراول نوبت به بررسی روابط Polymorphic می رسد :
(One To One Polymorphic) رابطه پلی مورفیک یک به یک در لاراول
آموزش روابط در لاراول مستلزم درک صحیحی از روابط پلی مورفیک است. رابطه پلی مورفیک یک به یک مشابه رابطه ساده یک به یک است که در ابتدای مقاله بحث شد با این تفاوت که مدل هدف می تواند متعلق به چند مدل باشد و یا با چند مدل رابطه یک به یک داشته باشد.
فرض کنید مدلی داریم به نام Post با این شرط که به دلیل محدودیت دیتابیس، در هر پست تنها قادر خواهیم بود از یک عکس استفاده کنیم. مدل دیگری نیز به نام User داریم با این شرط که هر کاربر مجاز است تنها یک عکس داشته باشد. پس نیاز به مدل دیگری داریم به نام Image.
به عبارتی Image هم با Post رابطه یک به یک دارد هم با User. بنابراین می توانیم برای این سه مدل رابطه One To One از نوع Polymorphic بنویسیم :
پیاده سازی
جدول users :
$table->bigIncrements('id'); $table->string('name');
جدول posts :
$table->bigIncrements('id'); $table->string('title');
جدول images :
$table->bigIncrements('id'); $table->string('path'); $table->rememberToken(); $table->timestamps();
در مدل User :
public function image() { return $this->morphOne('App\Image', 'imageable'); }
در مدل Post :
public function image() { return $this->morphOne('App\Image', 'imageable'); }
در مدل Image :
public function imageable() { return $this->morphTo(); }
برای فراخوانی عکس یک کاربر :
$user = User::first(); $image = $user->image;
یا برای فراخوانی پست مربوط به یک عکس :
$image = Image::first(); $user = $image->imageable;
(One To Many Polymorphic) رابطه یک به چند پلی مورفیک
این رابطه شبیه رابطه One To Many یک به چند ساده است با این تفاوت که مدل هدف، می تواند می تواند با چند مدل ارتباط یک به چند برقرار کند.
فرض کنید در یک سایت هم تعدادی post به صورت متن و تکست داریم و هم تعدادی video که کاربران می توانند برای هر دوی اینها دیدگاه یا کامنت comment بگذارند. به قولی دیگر مدل Comment با هر دو مدل Post و Video ارتباط one to many برقرار می کند.
به جای نوشتن چندین متد و کلید خارجی می توان از رابطه یک به چند پلی مورفیک استفاده کرده و از شلوغی در مدل ها و جدول ها جلوگیری کرد.
پیاده سازی :
جدول comments :
$table->bigIncrements('id'); $table->longText('body'); $table->integer('commentable_id'); $table->string('commentable_type');
جدول posts :
$table->bigIncrements('id'); $table->string('title'); $table->longText('body');
جدول videos :
$table->bigIncrements('id'); $table->string('title'); $table->string('path');
مدل Comment :
public function commentable() { return $this->morphTo(); }
مدل Post :
public function comments() { return $this->morphMany('App\ Comment', 'commentable'); }
مدل Video :
public function comments() { return $this->morphMany('App\ Comment', 'commentable'); }
حال برای فراخوانی دیدگاه های یک پست :
$post = App\Post::find(1); foreach ($post->comments as $comment) { // }
و یا برای فراخوانی ویدیوی مربوط به یک دیدگاه :
$comment = App\Comment::find(1); $commentable = $comment->commentable;
(Many To Many Polymorphic) رابطه چند به چند پلی مورفیک
رابطه چند به چند پلی مورفیک اندکی پیچیده تر از بقیه رابطه هاست. از این رابطه زمانی استفاده می کنیم که یک مدل با چند مدل ارتباط چند به چند داشته باشد. مثلا مدل Tag با هر دو مدل Post و Video رابطه چند به چند دارد. به عبارتی هر ویدیو و پست تکست می تواند چندین تگ داشته باشد و در عین حال هر تگ به صورت همزمان می تواند متعلق به چندین پست و ویدیو باشد.
اگر از رابطه Many To Many Polymorphic استفاده نکنیم جدول ها و مدل هایمان کثیف و شلوغ خواهند شد. برای این رابطه علاوه بر سه جدول posts و videos و tags بایستی جدول دیگری به نام taggables نیز بنویسیم. این جدول چیزی مشابه جدول واسطه است.
پیاده سازی
جدول posts :
$table->bigIncrements('id'); $table->string('title'); $table->longText('body');
جدول videos :
$table->bigIncrements('id'); $table->string('title'); $table->string('path');
جدول tags :
$table->bigIncrements('id'); $table->string('name');
و اما جدول taggables :
$table->integer('tag_id'); $table->integer('tagable_id'); $table->string('tagable_type');
در مدل Tag متدهای زیر را می نویسیم :
public function posts() { return $this->morphedByMany('App\Post', 'taggable'); } public function videos() { return $this->morphedByMany('App\Video', 'taggable'); }
در هر دو مدل Video و Post متد زیر را می نویسیم :
public function tags() { return $this->morphToMany('App\ Tag', 'taggable'); }
برای فراخوانی تگ های یک پست :
$post = Post::first(); $tags = $post->tags;
یا جهت فراخوانی تگ های یک ویدیو :
$video = \App\Video::first(); $tags = $video->tags;
ولی برای فراخوانی ویدیوهای مربوط به یک تگ :
$tag = Tag::first(); $videos = $tag->videos;
آموزش رابطه ها در لاراول به صورت دقیق و مستحکم مستلزم مشاهده ویدیوهای آموزشی و تمرین و تکرار است. جهت آموزش کلی به آموزش صفر تا صد لاراول در همین سایت مراجعه فرمایید.
مراجع
https://laravel.com/docs/6.x/eloquent-relationships
https://medium.com/swlh/a-guide-on-laravel-relationships-1febfac430f6
https://appdividend.com/2018/05/16/laravel-model-relationships-example/
https://www.w3resource.com/laravel/eloquent-relationships.php
درباره سالار عباپور
فارغ التحصیل رشته مهندسی برق و مدرس دوره های طراحی سایت - ایشان در زمینه طراحی سایت با زبان های برنامه نویسی PHP و فریمورک لاراول Laravel فعال هستند و همچنین یکی از تحلیل گران خبره در حوزه بازارهای مالی بین المللی و بورس هستند.
اقا انصافا دمتون گرم ایول