تعداد بازدید: 5896

Eloquent Relationships

عملگر ها و کارکرد هایی که در درس Eloquent ORM – Model گفتیم در اینجا آن ها را با روابط میان model ها و جداول تامیم می دهیم.

نکاتی که در این درس بررسی میشه :

  • Defining Relationships : ساخت جدول با استفاده از migration و برقراری ارتباط میان جداول با استفاده از foreign key و primary key و ساخت model متناظر و در نهایت برقراری ارتباط میان model ها.
  • Querying Relations : نحوه استفاده از model که متصل به مدل دیگری است جهت دریافت اطلاعات.
  • Inserting & Updating Related Models : نحوه ساخت و بروز رسانی اطلاعات در مدل های متصل شده .

Migration Relationship

هر جدول در پایگاه داده یا مستقل است و یا مرتبط با یک یا چند جدول دیگر . ارتباط میان جداول به شرح زیر است:

  • One To One
  • One To Many
  • Many To Many

گام اول در پیاده سازی جداول در پایگاه داده migration می باشد. دقت داشته ارتباط میان نام های جدول و model را رعایت کنید. یعنی نام مدل مفرد نام جدول (مثلا model با نام product و migration و table با نام products). جهت برقراری ارتباط میان جداول باید از کلید اصلی (PK) و کلید خارجی (FK) استفاده کنید.

برای ارتباط One To One و One To Many می توانید از کلید خارجی برای دو جدول استفاده کنید.

مثال :

  • ارتباط One To One : جداول users , phones . کلید های خارجی user_id , phone_id. دو کلید خارجی.
  • ارتباط One To Many : جداول users , products . یک کلید خارجی user_id . یک کلید خارجی و عملگر ها از سمت user.
  • ارتباط Many To Many : ارتباط میان محصول و دسته بندی . در این نوع ما یک جدول میانی (intermediate table) خواهیم داشت. مثال جداول produtcts , categories , category_product و کلید های خارجی موجود در جدول میانی product_id , category_id.

نکته : برای انتخاب نام جدول میانی category_product از ترتیب حروف اول کلمه استفاده می کنیم. یعنی کاراکتر c که اول تر است در ابتدا می آید و p دوم است . و همچنین نام ها باید مفرد جدوال باشد.(دقت داشته باشید نام مدل را مفرد انتخاب کرده باشید)

Model Relationships

همان طور که جداول باید با هم ارتباط داشته باشند ، مدل ها نیز باید با هم ارتباط داشته باشند. جهت برقراری ارتباط میان مدل ها در هر یک از انواع ارتباط (One To One , One To Many , Many To Many) باید متدی با نام مدل مرتبط در مدل جاری ساخته شود (بر اساس نوع ارتباط جمع یا مفرد بودن آن مشخص می گردد).متد هایی که در این روابط تعیین می کنیم برای Insert , Update , Select از آن ها استفاده می کنیم حال چه به صورت Dynamic Property (Collection) و چه به صورت method (Builder).

One To One

برای برقراری ارتباط میان جداول از نوع one to one باید متدی در هر دو model ساخته شود . برای ارتباط در مدل از متد hasOne استفاده می کنیم که پارامتر های آن عبارتست از :

  • پارامتر اول نام مدل مرتبط
  • پارامتر دوم کلید خارجی مربوط به جدول مدل دیگر که به کلید اصلی مدل جاری اشاره دارد ، در این مثال یعنی user_id
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    /**
     * Get the phone record associated with the user.
     */
    public function phone()
    {
        return $this->hasOne('App\Phone');
    }
}

در صورتی که قوانین مربوط به naming convention در لاراول را رعایت کرده باشید نیازی به تعیین کلید های خارجی در متد hasOne نخواهید داشت.

این قوانین عبارتست از :

  • انتخاب نام مفرد برای مدل و نام جمع برای جدول در پایگاه داده.
  • انتخاب نام {modelName}_id برای کلید خارجی . مثلا در مدل phone برای مدل مرتبط user کلید خارجی user_id.

خوب اگر قوانین naming convention به درستی رعایت نشده بود باید به شکل زیر وارد می شد :

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

پس از این که قواعد بالا پیاده سازی شد. به ازای هر کاربر می تونیم با استفاده از متد phone که ساختیم به اطلاعات جدول خارجی phone دسترسی داشته باشیم.

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

کد بالا یک ردیف از اطلاعات phone مربوط به کاربر با شناسه 1 رو بر اساس Mass Assignment attributes باز میگردونه و چون از Dynamic Property استفاده شده به صورت collection می باشد.

نکته : برای دسترسی به phone از طریق user باید از طریق یک شئ اقدام کنید.

قواعد پیاده سازی برای مدل user رو می تونیم دقیقا به همین شکل برای مدل phone نگاشت بدیم و کاربر هر تلفن رو پیدا کنیم.

One To Many

در رابطه one to many یک مدل به چند مدل مرتبط است(1-n). مدل سمت 1 می تواند مولد مدل سمت n باشد. به طور مثال در رابطه user و product مدل user می تواند product تولید کند. و همچنین مدل product می تواند user خود را تعیین و عوض کند.کلید خارجی مربوط به مدل سمت رابطه n می باشد.

  •  user با شناسه 1 product می سازد که کلید خارجی user_id در محصول برابر با 1 است.
  • product سازنده (user) خود را عوض می کند (user_id).FK
  • همچنین از طریق user به product ها و از طریق product به user دسترسی داریم.

در رابطه one to many برای این که دسترسی های لازم را برای مدل ها به هم فراهم کنیم باید یک متد به صورت جمع در مدل سمت 1  (hasMany) و یک متد به صورت مفرد در مدل سمت n (belongTo) بسازیم.

<?php
namespace App;

class User extends Authenticatable
{
    public function products()
    {
        return $this->hasMany('App\product');
    }
}

متد hasMany پارامتر های زیر را به ترتیب می گیرند:

  • نام مدل دیگر برای برقراری ارتباط با مدل جاری.
  • کلید خارجی مربوط به مدل دیگر که مرتبط به کلید اصلی در آن متد می نویسیم.(در این رابطه تنها یک کلید خارجی وجود دارد)
  • کلید اصلی مدل دیگر .
return $this->hasMany('App\product','user_id','id');

کد بالا مربوط به مدل user و متد product می باشد. همچنین به وسیله کد زیر می توانید تمامی محصولات کاربر با شناسه $id را نمایش دهید. بلافاصله پس از تعریف رابطه میان user و product در مدل user کد زیر کار می کند.

dd(\App\User::find($id)->products);

در آن طرف ما باید هر product را به یک user ارتباط دهیم که برای متد داخلی مدل product از متد belongTo استفاده می کنیم.

<?php

namespace App;
use Illuminate\Database\Eloquent\Model;

class products extends Model
{
    protected $fillable = ['name','description','status','price','weight'];
    protected $hidden = [];

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

}

مثل موارد قبل اگر naming convention را رعایت نکرده باشیم باید اطلاعات را کامل تر در این متد belongsTo وارد کنیم.

  • پارامتر اول نام مدل دیگر از مدل جاری برای برقراری ارتباط.
  • پارامتر دوم کلید خارجی مدل جاری. (در این نوع رابطه یک کلید خارجی بیشتر نداریم)
$this->belongsTo('App\User', 'user_id');

دسترسی به اطلاعات product از طریق user :

  • اگر متد را صدا بزنید به شما یک Builder می دهد که می توانید متد های دیگری نظیر orderBy و … را به آن chain کنید.
  • اگر Dynamic Property را بخوانید برای شما یک collection از product می دهد.
\App\User::find($id)->products()->orderBy('id','desc')->get();
\App\User::find($id)->products

Many To Many

در این نوع رابطه سه جدول درگیر است. دو جدول اصلی و یک جدول میانی (Intermediate table). برای مثال ما جداول products , categories , product_category درگیر می باشند. به جدول product_category جدول میانی گفته می شود. مدل ها در مثال ما product , category می باشند.

نکته : برای انتخاب نام جدول میانی باید علاوه بر قواعد گفته شده برای انتخاب کلید اصلی و کلید خارجی قواعد جدید را نیز رعایت کنید :

  • جدول میانی از نام مدل ها ساخته می شود. لذا باید نام مدل را مفرد و نام جدول را جمع انتخاب کنید.
  • ترتیب نام جدول میانی از روی مدل ها ساخته می شود و باید به ترتیب حرف اول مدل باشد . به طور مثال c اول تر از p می آید. پس category_product.

برای لینک کردن مدل ها در این روش از متد داخلی blongToMany استفاده می کنیم.

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class products extends Model
{
    protected $fillable = ['name','description','status','price','weight'];
    protected $hidden = [];

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

در مدل product اگر قواعد naming convention را رعایت نکرده باشیم باید در متد blongToMany به شکل زیر عمل کنیم :

$this->belongsToMany('App\category','category_product','product_id','category_id');

خوب متقابلا برای استفاده از product های هر category نیز باید این رابطه را در مدل category نیز تعریف کنیم.

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class category extends Model
{
    protected $fillable = ['name'];

    public function products()
    {
        return $this->belongsToMany('App\product', 'category_product', 'category_id', 'product_id');
    }
}

به وسیله کد زیر تمامی محصولات مربوط به دسته بندی با شناسه $id برمیگرداند:

<?php
dd(\App\category::find($id)->products);

چون در کد بالا از Dynamic Property استفاده کرده ایم خروجی collection خواهد بود . در صورت نیاز به Builder باید متد را از شئ فراخوانی کنید.

<?php
\App\category::find($id)->products()->orderBy('id','DESC')->get()

هر ردیف از جدول میانی بیانگر یک رابطه میان یک ردیف از جدول اصلی و یک ردیف از جدول دیگر می باشد. حال این رابطه می تواند خود نیز حاوی یک سری اطلاعات باشد.collection که برمی گردد شامل pivot attribute نیز می باشد که به صورت پیش فرض تنها کلید ها را شامل می شود.

برای این که اطلاعات دیگری از ستون های جدول میانی غیر از موارد پیش فرض (کلید اصلی و کلیدهای خارجی) را نیز به همراه اطلاعات مدل دیگر بیاوریم باید زمان تعریف رابطه از متد withPivot استفاده کنیم.

<?php
public function categories() {
    return $this->belongsToMany('App\category')->withPivot('column1', 'column2');
}

کد بالا یعنی اگر از یک محصول خواستیم به اطلاعات رابطه دسترسی پیدا کنیم می توانیم به روش زیر عمل کنیم (زیر ساخت این کار در کد بالا انجام شده)

<?php
$product = App\product::find(1);

foreach($product->categories as $category){
	$category->pivot->column1;
}

به عنوان چکیده مطالب بالا می توانیم روابط و متدهایشان را در جدول زیر بیاوریم.

Relation Methods
One To One (1-1) hasOne
One To Many (1-n) belongTo , hasMany
Many To Many (n-m) belongToMany

Lazy Loading & Eager Loads

دریافت اطلاعات دو جدول به ازای یک شئ نیازمند بکارگیری JOIN در SQL می باشد. زمانی که یک شئ از مدل را می گیریم در واقع یک ردیف از یک جدول را Query زده ایم.

در صورتی که ما مشخص نکنیم به اطلاعات جدول دیگر نیاز داریم لاراول برای ما JOIN نمی زند. ولی اگر در زمان استفاده از شئ در view ما از dynamic property مدل دیگر استفاده کنیم این JOIN در آنجا زده می شود که به آن Lazy Loading گفته می شود.

<?php
public function single($id)
{
    $products = products::find($id);
    return view('single-product',compact('products'));
}

و در view به Dynamic Property دسترسی پیدا کنیم. در حقیقت JOIN در view اتفاق می افتد.

{{ $product->user }}

ما می توانیم در قسمت کنترلر و زمانی که داریم شئ را می سازیم درخواست JOIN یا همان دریافت اطلاعات مدل دیگر را بدهیم تا لازم نباشد به ازای هر شئ JOIN زده شود. به این روش Eager Loading گفته می شود.

<?php
public function archive()
{
    $products = products::with('user')->orderBy('name','asc')->get();
    $title = "Product List";
    return view('archive-product',compact('products','title'));
}

مثلا در جایی که ما قرار است اشیا را لیست کنیم بهتر است که همان ابتدا در زمان دریافت اشیا درخواست JOIN را بدهیم. در اینجا از روش Eager Loading استفاده می کنیم که سریعتر است.

اما جایی که تنها یک شئ داریم می توان از روش Lazy Loading استفاده کرد . چون در نهایت یک JOIN میخورد.

Inserting & Updating Related Models

در رابطه های میان مدل ها ما یک متد میساختیم که از طریق آن می توانستیم به اطلاعات جانبی شئ ساخته شده دسترسی داشته باشیم . همچنین به موازات متد ساخته شده یک Dynamic Property هم نام با همان متد نیز ساخته می شد.

برای insert , update , get اطلاعات جانبی باید یک شئ از مدل اصلی داشته باشیم که بتوانیم به متد ساخته شده دسترسی پیدا کنیم.

مثلا در مورد post , comment ابتدا ما باید یک شئ از مدل post می ساختیم (حال با استفاده از create یا find) سپس با استفاده از شئ ساخته شده comment یا comment هایی را به آن اضافه می کردیم .

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

One To One

برای insert , update ما متد های مختلفی داریم که برخی مشترک میان تمامی روابط می باشد و برخی اختصاصا برای یک نوع رابطه خاص است.

در رابطه one to one ما ابتدا باید یک شئ از یک مدل داشته باشیم و سپس با استفاده از متد متصل به مدل دیگر شئ مورد نظر را به آن متصل کنیم. پس ما در اینجا با دو شئ سروکار داریم.

برای ساخت شئ از مدل دیگر می توانیم از متد های save و create استفاده کنیم.

  • save : این متد به عنوان ورودی یک شئ از مدل دیگر می گیرد.
  • create : این متد به عنوان ورودی یک آرایه انجمنی مقدار دهی شده از پارامتر های مدل می گیرد. پارامتر ها در fillabel و hidden مشخص شده اند.
<?php
$comment = new App\Comment(['message' => 'A new comment.']);
$post = App\Post::find(1);
$post->comments()->save($comment);
<?php
$post = App\Post::find(1);
$comment = $post->comments()->create([
    'message' => 'A new comment.',
]);

One To Many

از متد های create و save در رابطه One To Many نیز می توان استفاده کرد. همچنین متد associate و dissociate برای طرف belongTo نیز داریم که در ادامه به آن می پردازیم.

نکته : همان طور که save , create داریم saveMany , createMany هم داریم که می توان با آن ها اشیا زیادی یک جا در روابطی که یک حداقل یک طرف آن many است اضافه کرد.

در رابطه one to many به طور مثال میان user , product که هر user می تواند دارای چندین product باشد. مثلا بخواهیم user یک product را تغییر دهیم . برای این کار به دو شئ user و product نیاز داریم و ازطریق مدل product و متد user و همچنین associate می توان این کار را کرد.

<?php
$product = App\Product::find(10);
$product->user()->associate($user);
$product->save();

نکته : توجه داشته باشید که استفاده از متد associate در پایان نیازمند متد save می باشد. یعنی متد associate شی را تغییر می دهد و برای ذخیره سازی شی در پایگاه داده باید از متد save استفاده کنید..

1(user) hasManyProduct n(product) BelongToUser

متد dessociate عملکردی عکس عملکرد associate دارد. یعنی مقدار کلید خارجی را برابر null می کند.

Many To Many

رابطه many to many کمی متفاوت تر از سایر روابط از جهت وجود یک جدول میانی است. هر سطر از جدول میانی نشانگر یک رابطه برقرار شده میان دو شئ از مدل یا دو سطر از دو جدول می باشد. هر رابطه می تواند شامل اطلاعات مجزایی باشد که طبیعتا در جدول میانی ذخیره می شود. به جدول میانی pivot می گوییم.

به طور مثال جدول products , shops , product_shop را در نظر بگیرید. در این رابطه هر product در shop می تواند قیمت متفاوتی داشته باشد. قیمت prodduct در shop یکی از این ویژگی هایی که گفته شده می باشد(ویژگی رابطه).

در این مثال مدل ها product , shop و جداول products , shops و جدول میانی product_shop می باشد. کلید های خارجی shop_id , product_id موجود در جدول میانی است.

برای برقراری رابطه از attach استفاده می کنیم. یعنی مثلا موقع ساخت product باید shop هایی که در آن موجود است را به آن اضافه کنیم.

<?php
$product = APP\Product::create(['name'=>'lamy safari']);
$shop_ids_array = [1,2,3];
$product->shops()->attach($shop_ids_array);

shops متدی است که ما در مدل product ساخته ایم و روابط را در آن برقرار ساخته ایم.

تابع detach عکس تابع attach عمل می کند یعنی رابطه را از بین می برد. ورودی این تابع همان آرایه محصول است.

برای این که بتوانیم اطلاعات میانی را هم مقدار دهی کنیم کافی است آرایه آن را هم به همراه نام در متد attach استفاده کنیم.

نکته : اگر در اطلاعات جدول میانی timestamps یا همان created_at , updated_at وجود داشته باشد ما باید در زمان تعریف relation در مدل از متد withTimeStamp استفاده کنیم تا در زمان attach کردن timestamp جدول میانی هم پر بشود.

<?php
public function categoris()
{
     return $this->belongsToMany('App\Product')->withTimeStamps();
}

متد withTimeStamp را باید برای هر دو resource فراخوانی کنیم که در مثال ما می شود product , category .

مثلا ما مدل product و shop را داریم. هر محصول در هر فروشگاه یک قیمت دارد. پس از اینکه روابط مدل ها و migration ها را برقرار کردیم زمان وارد کردن یک شئ جدید می توان اطلاعات رابطه که در این جا قیمت (price) و مقدار (amount) هر product در shop می باشد.

<?php
$product = App\proudct::create($product_params);
$product->shops()->attach($shop_ids,['price'=>$prices,'amount'=>amounts]);

گفتیم که هر ردیف از جدول میانی نشانگر یک رابطه است. در بروزرسانی در این نوع از روابط (many to many) باید یه سری ردیف ها رو حذف و یک سری رو بدون تغییر و یه سری رو اضافه کنیم. فارغ از انجام هر گونه محاسبه با استفاده از متد sync می توان این کار را کرد.

<?php
$product = App\product::find(1);
$product->shops()->sync([1,2,3,4]);

برای بروزرسانی اطلاعات میانی در هر رابطه هم می توانیم به ازای هر id یک آرایه انجمنی از نام ستون و مقدار بدیهم و در آن اطلاعات را وارد کنیم.

<?php
$product = App\product::find(1);
$product->shops()->sync([1 => ['price' => 1000,'amount' => 5], 2, 3]);

هر رابطه چه متد هایی برای Insert و Update را داراست:

Relation Methods
One To One create,save
One To Many create,save,associage,dessociate
Many To Many create,save,attach,detach

 

اشتراک گذاری :

مدیر وب سایت گنوتک . برنامه نویسی رو با زبان C در هفده سالگی شروع کردم . در حال حاضر به برنامه نویسی php برپایه معماری MVC , HMVC و همچنین سیستم مدیریت محتوای WordPress و فریم ورک محبوب لاراول علاقه مند هستم و دوست دارم اطلاعاتم رو با شما به اشتراک بگذارم.

3 دیدگاه برای Eloquent Relationships

  1. سلام کاش برای جدول هاشم نمونه کد میذاشتین
    ارتباط One To Many : جداول users , products . یک کلید خارجی user_id . یک کلید خارجی و عملگر ها از سمت user.
    من اینو متوجه نشدم

    • شما هنرمند هستید و یک مجسمه می سازید . شما user و مجسمه product است . شما مجسمه درست می کنید. user->products->create به این ترتیب ما عمل می کنیم. از طریفی شما هنرمند یک تعداد مجسمه برای فروش دارید . user->products->get برای گرفتن این محصولات . هر مجسمه را یک نفر در کارگاه ساخته است و متعلق به اوست برای در آمد فروش product->user .
      متد ها و موجودیت ها به صورت نمادین برای فهم بهتر موضوع است.

  2. بسیار عالی خیلی ممنون نکات کاربردی گفتید
    فک کنم بار 4 باشه دارم این کامنت می نویسم که کپچا گیر میده _ خیلییی سخته این خیلی روی ux تاثییر میزاره

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *