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
استفاده کنید..
متد 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 |
سلام کاش برای جدول هاشم نمونه کد میذاشتین
ارتباط One To Many : جداول users , products . یک کلید خارجی user_id . یک کلید خارجی و عملگر ها از سمت user.
من اینو متوجه نشدم
شما هنرمند هستید و یک مجسمه می سازید . شما user و مجسمه product است . شما مجسمه درست می کنید.
user->products->create
به این ترتیب ما عمل می کنیم. از طریفی شما هنرمند یک تعداد مجسمه برای فروش دارید .user->products->get
برای گرفتن این محصولات . هر مجسمه را یک نفر در کارگاه ساخته است و متعلق به اوست برای در آمد فروشproduct->user
.متد ها و موجودیت ها به صورت نمادین برای فهم بهتر موضوع است.
بسیار عالی خیلی ممنون نکات کاربردی گفتید
فک کنم بار 4 باشه دارم این کامنت می نویسم که کپچا گیر میده _ خیلییی سخته این خیلی روی ux تاثییر میزاره