eloquent relationships  آموزش

اصول آموزش Laravel

تاریخ : جمعه 15 دی 1396

جداول پایگاه داده معمولا به هم مرتبط هستند. برای مثال یک پست در وبلاگ می تواند تعداد زیادی دیدگاه (مرتبط) داشته باشد یا سفارشی با کاربری که آن را داده رابطه داشته باشد. Eloquent مدیریت و کار با این رابطه ها را به مراتب آسان می سازد. Eloquent از رابطه های زیر پشتیبانی می کند:


تعریف رابطه ها (relationships)

در Eloquent، رابطه ها به صورت توابعی داخل کلاس های مدل تعریف می شوند. از آنجایی که رابطه ها نیز همچون مدل های Eloquent، خود به عنوان یک query builder ایفای نقش می کنند، ویژگی تعریف رابطه ها به صورت توابع این امکان را فراهم می کند تا متدها را به صورت زنجیره ای صدا زده و قابلیت های پرس و جوی بیشتری را در کوئری گرفتن های خود لحاظ کنید. مثال:

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


رابطه ی یک به یک One To One

رابطه ی یک به یک ساده ترین نوع relationship است. به عنوان مثال یک مدل User ممکن است با تنها یک phone رابطه داشته باشد. برای تعریف این رابطه، یک متد phone در مدل User قرار می دهیم. متد phone بایستی خروجی متد hasOne را در کلاس base مدل Eloquentبرگرداند:

<?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');
    }
}

اولین آرگومان ارسالی به متد hasOne اسم مدل مربوطه هست. پس از اینکه رابطه تعریف شد، می توان رکورد مربوطه را با استفاده از امکانproperty های داینامیک Eloquent بازیابی کرد. property های داینامیک به شما اجازه می دهند به رابطه ها (که به صورت توابع تعریف می شوند) مانند property های تعریف شده در مدل دسترسی داشته باشید:

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

Eloquent اسم کلید خارجی (foreign key) رابطه را بر اساس اسم مدل مربوطه درنظر می گیرد. در این مثال، مدل Phone (با توجه به اسم مدل) باید دارای کلید خارجی به نام user_id باشد. برای شکستن و بازنویسی این قرارداد، می توانید اسم دلخواه برای کلید خارجی را به عنوان آرگومان دوم به متد پاس دهید:

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

علاوه بر آن Eloquent انتظار دارد کلید خارجی مقداری منطبق با مقدار ستون id (یا متغیر سفارشی primaryKey$) جدول پدر داشته باشد. به عبارت بهتر، Eloquent به دنبال مقدار ستون id کاربر در ستون user_id رکورد Phone می گردد . اگر می خواهید رابطه از مقداری غیر از id به عنوان کلید خارجی استفاده کند، بایستی یک آرگومان سوم به متد hasOne ارسال کرده و از طریق آن کلید سفارشی خود را مشخص کنید:

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


تعریف عکس رابطه (طرف دیگر رابطه)

حال می توان از طریق مدل User خود به Phone دسترسی داشت. در این بخش رابطه ای در مدل Phone تعریف می کنیم که به ما اجازه ی دسترسی به User ای که مالک یا مدل پدر phone هست را می دهد. طرف دیگر رابطه ی hasOne را با استفاده از متد belongsTo تعریف می کنیم:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Phone extends Model
{
    /**
     * Get the user that owns the phone.
     */
    public function user()
    {
        return $this->belongsTo('App\User');
    }
}

در نمونه ی بالا، Eloquent ستون user_id از مدل Phone را به id در مدل User متصل (match می کند). Eloquent اسم پیش فرض کلید خارجی را با در نظر گرفتن اسم متد (رابطه) و الصاق پسوند id_ تعریف می کند. حال اگر می خواهید کلید خارجی در مدل Phone اسمی غیر ازuser_id داشته باشد، می توانید اسم دلخواه کلید خارجی را به عنوان آرگومان دوم ارسال کنید:

/**
 * Get the user that owns the phone.
 */
public function user()
{
    return $this->belongsTo('App\User', 'foreign_key');
}

بنابراین :

 $user = Phone::where('phone', '0911*******')->first()->user;

اگر مدل پدر از id به عنوان کلید اصلی استفاده نمی کند، یا می خواهید مدل فرزند را به ستون دیگری وصل کنید، در آن صورت می توانید کلید سفارشی جدول پدر (اسم ستون کلید اصلی در جدول پدر) را به عنوان آرگومان سوم به متد belongsTo ارسال کنید:

/**
 * Get the user that owns the phone.
 */
public function user()
{
    return $this->belongsTo('App\User', 'foreign_key', 'other_key');
}


Default Models

The belongsTo relationship allows you to define a default model that will be returned if the given relationship is null. This pattern is often referred to as the Null Object pattern and can help remove conditional checks in your code. In the following example, the user relation will return an empty App\User model if no user is attached to the post:

/**
 * Get the author of the post.
 */
public function user()
{
    return $this->belongsTo('App\User')->withDefault();
}

To populate the default model with attributes, you may pass an array or Closure to the withDefault method:

/**
 * Get the author of the post.
 */
public function user()
{
    return $this->belongsTo('App\User')->withDefault([
        'name' => 'Guest Author',
    ]);
}

/**
 * Get the author of the post.
 */
public function user()
{
    return $this->belongsTo('App\User')->withDefault(function ($user) {
        $user->name = 'Guest Author';
    });
}


رابطه ی یک به چند (one to many)

relationship یک به چند برای تعریف رابطه ای بکار می رود که در آن یک مدل مالک چندین مدل دیگر است. برای مثال، یک پست در وبلاگ می تواندn تا دیدگاه داشته باشد. برای تعریف این نوع رابطه نیز یک متد در مدل Eloquent تعریف می کنیم:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Post extends Model
{
    /**
     * Get the comments for the blog post.
     */
    public function comments()
    {
        return $this->hasMany('App\Comment');
    }
}

یادآور می شویم که Eloquent خود ستون کلید خارجی را در مدل Comment تعیین می کند. بر اساس قراردادهای از پیش تعیین شده،Eloquent فرم snake case اسم مدل مالک (post) را انتخاب کرده و صرفا یک پسوند id_ به آن اضافه می کند. در این مثال Eloquent فرض را بر این می گذارد که کلید خارجی در مدل Comment ستونی به نام post_id هست.

پس از تعریف رابطه ی مورد نیاز، می توان با دسترسی به پراپرتی comments به مجموعه دیدگاه های پست مربوطه دسترسی داشت. همان طور که پیش تر گفته شد، Eloquent از ویژگی dynamic properties پشتیبانی می کند. بنابراین می توان به رابطه ها (relationship function) همانند property های تعریف شده در مدل دسترسی داشت:

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

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

و با توجه به اینکه رابطه ها نقش query builder را نیز ایفا می کنند، می توانید دیدگاه هایی که در خروجی واکشی می شوند را با اعمال constraintها محدود نمایید. برای این منظور متد comments را صدا زده و شرط ها را به صورت زنجیره ای به کوئری الحاق کنید:

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

برای تعیین اسم کلید خارجی دلخواه و کلید محلی که در رابطه استفاده می شود می توانید آن ها را به ترتیب به عنوان آرگومان های دوم و سوم به متد ارسال کنید:

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

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


تعریف طرف دیگر رابطه One To Many Inverse

پس از تعریف رابطه ی لازم برای دسترسی به تمامی دیدگاه های پست، رابطه ای تعریف می کنیم که اجازه ی دسترسی دیدگاه به پست مربوطه (پست پدر) را فراهم می کند. برای تعریف طرف دیگر رابطه ی hasMany، یک تابع relationship در مدل فرزند اعلان می کنیم که متد belongsTo را صدا می زند:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Comment extends Model
{
    /**
     * Get the post that owns the comment.
     */
    public function post()
    {
        return $this->belongsTo('App\Post');
    }
}

پس از تعریف رابطه، می توان با دسترسی به property های داینامیک post مدل Post مربوط به یک Comment را واکشی نمود:

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

echo $comment->post->title;

در مثال بالا، Eloquent ستون post_id از مدل Comment را به ستون id در مدل Post مچ می کند. همان طور که قبلا هم به آن اشاره شد،Eloquent اسم کلید خارجی را با درنظر گرفتن اسم رابطه و افزودن پسوند id_ به اسم متد تعیین می کند. حال اگر اسم ستون کلید خارجی در مدلComment، post_id نباشد در آن صورت یک اسم سفارشی به عنوان آرگومان دوم به متد belongsTo پاس می دهیم:

/**
 * Get the post that owns the comment.
 */
public function post()
{
    return $this->belongsTo('App\Post', 'foreign_key');
}

اگر اسم ستون کلید اصلی در جدول پدر id نباشد یا شما می خواهید مدل فرزند را به ستون دیگری (غیر از id) وصل کنید، در آن صورت می توانید اسم ستونی که کلید اصلی در جدول پدر هست را به عنوان آرگومان سوم به متد belongsTo ارسال نمایید:

/**
 * Get the post that owns the comment.
 */
public function post()
{
    return $this->belongsTo('App\Post', 'foreign_key', 'other_key');
}


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

relation های چند به چند کمی پیچیده تر از رابطه های hasOne و hasMany هستند. به عنوان مثال می توان به رابطه ای اشاره کرد که در آن یک کاربر می تواند چندین نقش داشته باشد و نقش ها هم می توانند به کاربرهای متعددی داده شوند.

برای مثال کاربران متعددی می توانند نقش Admin را داشته باشند. برای تعریف این رابطه به سه جدول نیاز داریم: 1. users 2. roles 3.role_user. اسم جدولrole_user بر اساس ترتیب حروف الفبا از کنار هم قرار گرفتن نام دو مدل مرتبط گرفته شده است. این جدول حاوی دو ستون به نام های user_id و role_id می باشد.

برای تعریف رابطه ی چند به چند یک متد تعریف می کنیم که خود متد belongsToMany را در کلاس پایه Eloquent صدا می زند. در مثال زیر متدroles را در مدل User فراخوانی می کنیم:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    /**
     * The roles that belong to the user.
     */
    public function roles()
    {
        return $this->belongsToMany('App\Role');
    }
}

پس از تعریف رابطه، کافی است از طریق property داینامیک roles به نقش های کاربر دسترسی پیدا کنید:

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

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

می توان متد roles را فراخوانی نموده و محدودیت هایی را جهت واکشی نتایج مد نظر در ادامه ی آن متد به صورت زنجیره ای به کوئری الحاق کرد:

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

همان طور که قبلا گفته شد، اسم جدول واسط با کنار هم قرار گرفتن اسم مدل های مرتبط به ترتیب حروف الفبا مشخص می شود. می توانید اسم دلخواه خود را برای جدول واسط تعریف کنید. برای این منظور بایستی آن را به عنوان آرگومان دوم به متد belongsToMany ارسال کنید:

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

همچنین می توانید اسم ستون های کلید خارجی جدول را توسط آرگومان سوم و چهارم سفارشی تنظیم نمایید. آرگومان سوم اسم کلید خارجی مدلی است که رابطه را در آن تعریف می کنید و آرگومان چهارم اسم کلید خارجی مدلی است که به آن وصل می شوید:

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


تعریف طرف دیگر رابطه Defining The Inverse Of The Relationship

برای تعریف طرف مقابل رابطه، متد belongsToMany را در مدل مربوطه صدا می زنیم. در ادامه ی مثال قبلی، یک متد users در مدل Roleتعریف می کنیم که متد belongsToMany را صدا می زند:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Role extends Model
{
    /**
     * The users that belong to the role.
     */
    public function users()
    {
        return $this->belongsToMany('App\User');
    }
}

همان طور که می بینید رابطه ی مقابل درست مانند همتای User آن تعریف شده است. با این تفاوت که در آن به مدل App\User ارجاع می دهیم. از آن جایی که متد belongsToMany را مجددا در این رابطه استفاده می کنیم، اسم جدول و کلیدهای خارجی که به صورت سفارشی تعریف کرده بودیم در زمان تعریف طرف دیگر رابطه ی چند به چند قابل استفاده خواهند بود.


بازیابی ستون های جدول واسط Retrieving Intermediate Table Columns

همان طور که پیش تر گفته شد، کار با رابطه های چند به چند لازمه ی وجود جدول واسط است. Eloquent روش های آسان و بهینه ای برای کار با این جدول فراهم می کند. برای مثال، فرض بگیرید شی User تعداد زیادی شی Role مرتبط دارد. پس از دسترسی به این رابطه، می توان با بهره گیری از اتریبیوت pivot در مدل ها به راحتی به جدول واسط دسترسی داشت:

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

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

همان طور که می بینید به ازای هر مدل Role که واکشی می کنیم، یک اتریبیوت pivot تخصیص می یابد. این attribute حاوی یک مدل است که بیانگر جدول واسط می باشد و می توان از آن مانند هر مدل دیگری استفاده کرد.

به صورت پیش فرض، شی pivot فقط کلیدهای مدل را شامل می شود. چنانچه قرار است جدول pivot دارای attribute هایی ورای کلید های مدل باشد، بایستی آن ها را در زمان تعریف رابطه مشخص نمایید:

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

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

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


Customizing The pivot Attribute Name

As noted earlier, attributes from the intermediate table may be accessed on models using the pivot attribute. However, you are free to customize the name of this attribute to better reflect its purpose within your application.

For example, if your application contains users that may subscribe to podcasts, you probably have a many-to-many relationship between users and podcasts. If this is the case, you may wish to rename your intermediate table accessor to subscription instead of pivot. This can be done using the as method when defining the relationship:

return $this->belongsToMany('App\Podcast')
                ->as('subscription')
                ->withTimestamps();

Once this is done, you may access the intermediate table data using the customized name:

$users = User::with('podcasts')->get();

foreach ($users->flatMap->podcasts as $podcast) {
    echo $podcast->subscription->created_at;
}


فیلتر کردن رابطه ها از طریق جداول واسط Filtering Relationships Via Intermediate Table Columns

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

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

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


Defining Custom Intermediate Table Models

If you would like to define a custom model to represent the intermediate table of your relationship, you may call the using method when defining the relationship. All custom models used to represent intermediate tables of relationships must extend the Illuminate\Database\Eloquent\Relations\Pivot class. For example, we may define a Role which uses a custom UserRole pivot model:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Role extends Model
{
    /**
     * The users that belong to the role.
     */
    public function users()
    {
        return $this->belongsToMany('App\User')->using('App\UserRole');
    }
}

When defining the UserRole model, we will extend the Pivot class:

<?php

namespace App;

use Illuminate\Database\Eloquent\Relations\Pivot;

class UserRole extends Pivot
{
    //
}


Has Many Through

The "has-many-through" relationship provides a convenient shortcut for accessing distant relations via an intermediate relation. For example, a Country model might have many Post models through an intermediate User model. In this example, you could easily gather all blog posts for a given country. Let's look at the tables required to define this relationship:

countries
    id - integer
    name - string

users
    id - integer
    country_id - integer
    name - string

posts
    id - integer
    user_id - integer
    title - string

Though posts does not contain a country_id column, the hasManyThrough relation provides access to a country's posts via $country->posts. To perform this query, Eloquent inspects the country_id on the intermediate users table. After finding the matching user IDs, they are used to query the posts table.

Now that we have examined the table structure for the relationship, let's define it on the Country model:

<?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');
    }
}

The first argument passed to the hasManyThrough method is the name of the final model we wish to access, while the second argument is the name of the intermediate model.

Typical Eloquent foreign key conventions will be used when performing the relationship's queries. If you would like to customize the keys of the relationship, you may pass them as the third and fourth arguments to the hasManyThrough method. The third argument is the name of the foreign key on the intermediate model. The fourth argument is the name of the foreign key on the final model. The fifth argument is the local key, while the sixth argument is the local key of the intermediate model:

class Country extends Model
{
    public function posts()
    {
        return $this->hasManyThrough(
            'App\Post',
            'App\User',
            'country_id', // Foreign key on users table...
            'user_id', // Foreign key on posts table...
            'id', // Local key on countries table...
            'id' // Local key on users table...
        );
    }
}


رابطه های polymorphic

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

رابطه های Polymorphic به یک مدل اجازه می دهند در یک رابطه همزمان به چندین مدل دیگر تعلق داشته باشد (امکان رابطه ی یک مدل با چندین مدل دیگر از طریق یک رابطه را فراهم می آورد). به عنوان مثال، فرض کنید کاربران اپلیکیشن شما می خواهند برای پست ها و هم برای ویدئو ها، کامنت بگذارند. با بهره گیری از رابطه های polymorphic می توان به راحتی یک جدول واحد comments برای هر دو این سناریو بکار برد. در گام نخست ساختار جداولی که برای این رابطه لازم است را مورد بررسی قرار می دهیم:

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

دو ستون مورد توجه در این مثال، ستون های commentable_id و commentable_type در جدول comments هستند. ستون commentable_id حاوی مقدار ID پست یا ویدئو خواهد بود، در حالی که ستون commentable_type اسم کلاس مدل مالک (پدر) را نگه خواهد داشت. Eloquent در واقع به واسطه ی ستون commentable_type تشخیص می دهد در زمان دسترسی به رابطه ی commentable کدام مدل را بایستی برگرداند.


ساختار مدل Model Structure

در مرحله ی بعدی، تعریف مدل لازم برای ایجاد این رابطه را مورد بررسی قرار می دهیم:

<?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();
    }
}

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

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


بازیابی رابطه های polymorphic (Retrieving Polymorphic Relations)

پس از تعریف جداول و مدل های مورد نیاز، می توانید از طریق مدل ها به رابطه ها دسترسی داشته باشید.برای مثال، جهت دسترسی به کامنت های های یک پست، می توان از property داینامیک comments استفاده کرد:

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

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

می توان مالک یک رابطه ی polymorphic را از مدل polymorphic بازیابی کرد. برای این منظور بایستی به اسم متدی که متد morphTo را صدا می زند، دسترسی داشته داشت. در مثال ما، این همان متد commentable در مدل Like هست. حال کافی است به آن متد همانند یک dynamic property دسترسی داشته باشید:

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

$commentable = $comment->commentable;

فراخوانی (متد) رابطه ی commentable در مدل Comment ، بسته به مدلی که مالک آن comment می باشد یک نمونه از Post یا Video را در خروجی برمی گرداند.


نوع های اختصاصی polymorphic

به صورت پیش فرض، Laravel اسم کامل کلاس را برای ذخیره ی نوع (اسم) مدل مربوطه بکار می برد. به عنوان نمونه، در ادامه ی مثال فوق که یک comment ممکن بود به یک Post یا Video تعلق داشته باشد، commentable_type پیش فرضApp\Post یا App\Video خواهد بود.

شما می توانید این قرار داد را شکسته و پایگاه داده را از ساختار داخلی اپلیکیشن جدا نمایید. این کار را با تعریف یک relationship "morph map" انجام می دهیم. morph map به Eloquent اعلان می کند که بجای اسم کلاس، اسم جدول متناظر با آن مدل را بکار ببرد.

use Illuminate\Database\Eloquent\Relations\Relation;

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

می توانید morphMap را در تابع boot از AppServiceProvider معرفی کنید یا در صورت تمایل یک service provider مجزا تعریف کنید.


رابطه های polymorphic چند به چند (Many To Many Polymorphic Relations)

Table Structure

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

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


Model Structure

اکنون زمان آن فرا رسیده تا رابطه ها را در مدل تعریف کنیم. مدل های Post و Video هر دو متد tags را خواهند داشت. متد tags متدmorphToMany را در کلاس پایه ی Eloquent فراخوانی می کند

<?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');
    }
}


تعریف طرف دیگر رابطه Defining The Inverse Of The Relationship

سپس در مدل Tag می بایست یک متد برای هر یک از مدل های مربوطه تعریف نمایید. در این مثال، یک متد 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');
    }
}


بازیابی رابطه (Retrieving The Relationship)

پس از تعریف جداول و مدل های مربوطه، می توان از طریق رابطه ها به مدل ها دسترسی داشت. برای مثال، جهت دسترسی به تمامی تگ های یک پست، کافی است property داینامیک tags را بکار ببرید:

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

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

می توانید مالک یک رابطه ی polymorphic را از مدل polymorphic بازیابی کنید. کافی است به اسم متدی که تابع morphedByMany را صدا می زند، دسترسی داشته باشید. در این مثال منظور دو متد posts یا videos در مدل Tag هست. بنابراین می بایست به متدهای نام برده همانندproperty های داینامیک دسترسی پیدا کنید:

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

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


پرس و جو و گرفتن کوئری از رابطه ها (Querying Relations)

همان طور که می دانید رابطه های Eloquent به صورت توابعی تعریف می شوند. شما می توانید با فراخوانی این توابع بدون اینکه لازم باشد خود کوئری relationship را واقعا اجرا کنید، نمونه ای از آن رابطه را دریافت نمایید. بعلاوه همان طور که قبلا نیز گفته شد، تمامی رابطه های Eloquentخود مانند query builder عمل می کنند و به شما اجازه می دهند شرط ها و قیودی را برای محدود نمودن نتیجه به صورت زنجیره ای به کوئریrelationship (پیش از اجرای نهایی کوئری بر روی پایگاه داده) اعمال کنید:

به عنوان مثال، یک سیستم وبلاگ را در نظر بگیرید که در آن مدل User تعداد زیادی مدل Post مرتبط دارد:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

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

می توانید از رابطه ی posts کوئری گرفته و محدودیت های بیشتری را مانند زیر به رابطه اعمال کنید:

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

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

می توانید تمامی متدهای query builder را بر روی رابطه بکار ببرید.


متدهای relationship در برابر property های Dynamic

اگر نیازی به اعمال قیود (constraint) اضافی بر سازمان بر روی کوئری relationship نیست، می توانید به راحتی به آن رابطه مانند یک propertyدسترسی داشته باشید. در ادامه ی مثال قبلی (مدل های User و Post)، می توان به تمامی پست های کاربر مانند زیر دسترسی داشت:

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

foreach ($user->posts as $post) {
    //
}

property های dynamic بار گذاری را با تاخیر انجام می دهند (lazy loading). بدین معنی که داده های relationship خود را تنها زمانی بارگذاری می کنند که شما به آن ها دسترسی پیدا می کنید. به همین خاطر توسعه دهندگان اغلب از eager loading استفاده کرده و بدین وسیله رابطه هایی که می دانند پس از بارگذاری مدل مورد دسترسی قرار می گیرند را از قبل لود می کنند. eager loading تعداد کوئری های SQL که باید برای بارگذاری رابطه های یک مدل اجرا شوند را به طور قابل توجهی کاهش می دهد.


محدود کردن نتایج یک کوئری بر اساس وجود یا عدم وجود یک رابطه Querying Relationship Existence

در زمان دسترسی به رکورد های یک مدل، می توانید نتایج را بر اساس وجود یک رابطه محدود نمایید. برای مثال، فرض کنید می خواهید تمامی پست های وبلاگ که دارای حداقل یک دیدگاه یا comment مرتبط هستند را واکشی نمایید. برای این منظور، اسم رابطه را به عنوان آرگومان به متد hasپاس دهید:

// Retrieve all posts that have at least one comment...
$posts = App\Post::has('comments')->get();

می توانید یک عملگر به همراه تعداد دیدگاه ها (تعداد comment هایی که پست ها باید داشته باشد تا در خروجی لحاظ شوند) برای تنظیم بیشتر کوئری مطابق نیاز تعریف کنید:

// Retrieve all posts that have three or more comments...
$posts = Post::has('comments', '>=', 3)->get();

با استفاده از عملگر نقطه می توانید دستورات has تودرتو بسازید. در نمونه ی زیر تمامی پست هایی که حداقل یک comment و vote دارند را واکشی می کنیم:

// Retrieve all posts that have at least one comment with votes...
$posts = Post::has('comments.votes')->get();

اگر می خواهید نتایج دقیق تری را در خروجی داشته باشید، می توانید متدهای whereHas و orWhereHas را برای اعمال شرط های where بر روی کوئری های has فراخوانی کنید. این متدها به شما امکان می دهند قیود اختصاصی (constraint) خود را به constraint های رابطه اعمال کنید، مانند بررسی محتویات یک دیدگاه (comment). در زیر تمامی پست هایی که حداقل یک دیدگاه مرتبط دارند و علاوه بر آن دربردارنده ی کلماتی همچون foo می باشد را استخراج می کنیم:

// Retrieve all posts with at least one comment containing words like foo%
$posts = Post::whereHas('comments', function ($query) {
    $query->where('content', 'like', 'foo%');
})->get();


Querying Relationship Absence

When accessing the records for a model, you may wish to limit your results based on the absence of a relationship. For example, imagine you want to retrieve all blog posts that don't have any comments. To do so, you may pass the name of the relationship to the doesntHave and orDoesntHave methods:

$posts = App\Post::doesntHave('comments')->get();

If you need even more power, you may use the whereDoesntHave and orWhereDoesntHave methods to put "where" conditions on your doesntHave queries. These methods allows you to add customized constraints to a relationship constraint, such as checking the content of a comment:

$posts = Post::whereDoesntHave('comments', function ($query) {
    $query->where('content', 'like', 'foo%');
})->get();


Counting Related Models

If you want to count the number of results from a relationship without actually loading them you may use the withCount method, which will place a {relation}_count column on your resulting models. For example:

$posts = App\Post::withCount('comments')->get();

foreach ($posts as $post) {
    echo $post->comments_count;
}

You may add the "counts" for multiple relations as well as add constraints to the queries:

$posts = Post::withCount(['votes', 'comments' => function ($query) {
    $query->where('content', 'like', 'foo%');
}])->get();

echo $posts[0]->votes_count;
echo $posts[0]->comments_count;

You may also alias the relationship count result, allowing multiple counts on the same relationship:

$posts = Post::withCount([
    'comments',
    'comments as pending_comments_count' => function ($query) {
        $query->where('approved', false);
    }
])->get();

echo $posts[0]->comments_count;

echo $posts[0]->pending_comments_count;


Eager loading (بارگذاری زود هنگام)

زمانی که به (توابع) رابطه های Eloquent مانند property دسترسی پیدا می کنید، داده های رابطه در واقع با تاخیر بارگذاری می شوند (به اصطلاحlazy load می شوند). بدین معنی که داده های رابطه تنها زمانی به معنای واقعی لود می شوند که شما (برای اولین بار) به property دسترسی پیدا کنید. Eloquent می تواند رابطه ها را در زمانی که از مدل پدر کوئری می گیرید، به صورت زود هنگام بارگذاری (eager load) کند. Eager loading در حقیقت برای برطرف ساختن مشکل کوئری N + 1 در نظر گرفته شد. برای فهم بهتر این مشکل یک مدل Book را در نظر بگیرید که با مدل Author مرتبط است:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Book extends Model
{
    /**
     * Get the author that wrote the book.
     */
    public function author()
    {
        return $this->belongsTo('App\Author');
    }
}

حال تمامی کتاب ها و نویسندنگان مرتبط آن ها بازیابی می کنیم:

$books = App\Book::all();

foreach ($books as $book) {
    echo $book->author->name;
}

این حلقه یک کوئری برای واکشی تمامی کتاب های داخل جدول اجرا کرده سپس یک کوئری دیگر به ازای هر کتاب تا نویسنده ی مربوطه ی آن نیز واکشی شود. بنابراین اگر 25 کتاب داشته باشیم، حلقه ی مزبور در کل 26 کوئری اجرا می کند: 1 کوئری برای کتاب اصلی و 25 کوئری جهت استخراج نویسندگان مربوط به هر کتاب.

با بهره گیری از امکان eager loading می توان این عملیات و تعداد کوئری را به تنها دو کوئری کاهش داد. به هنگام کوئری گرفتن، می توان مشخص کرد کدام رابطه ها بایستی زود هنگام بارگذاری شوند. این کار با فراخوانی متد with امکان پذیر خواهد بود:

$books = App\Book::with('author')->get();

foreach ($books as $book) {
    echo $book->author->name;
}

برای این عملیات دو کوئری بیشتر اجرا نمی شود:

select * from books

select * from authors where id in (1, 2, 3, 4, 5, ...)


بارگذاری زود هنگام چندین رابطه Eager Loading Multiple Relationships

گاهی لازم می شود چندین رابطه ی مختلف را در طی تنها یک عملیات به صورت زود هنگام بارگذاری کنید. برای نیل به این هدف کافی است رابطه ها را به عنوان آرگومان به متد with ارسال نمایید:

$books = App\Book::with(['author', 'publisher'])->get();


بارگذاری زود هنگام رابطه های تودرتو Nested Eager Loading

برای بارگذاری زود هنگام رابطه های تودرتو کافی است عملگر نقطه را بکار ببرید. در نمونه ی زیر تمامی نویسندگان کتاب و نیز کلیه ی تماس های شخصی نویسنده را در قالب یک دستور Eloquent به صورت زودهنگام بارگذاری می کنیم:

$books = App\Book::with('author.contacts')->get();


Eager Loading Specific Columns

You may not always need every column from the relationships you are retrieving. For this reason, Eloquent allows you to specify which columns of the relationship you would like to retrieve:

$users = App\Book::with('author:id,name')->get();

When using this feature, you should always include the id column in the list of columns you wish to retrieve.


اعمال محدودیت بر روی بارگذاری زود هنگام Constraining Eager Loads

گاهی لازم می شود رابطه ای را به صورت زود هنگام بارگذاری کرده و این میان محدودیت هایی نیز را بر روی کوئری اعمال کنید. مثال:

$users = App\User::with(['posts' => function ($query) {
    $query->where('title', 'like', '%first%');
}])->get();

در این مثال، Eloquent تنها پست هایی را بارگذاری می کند که عنوان (مقدار ستون title) آن ها دربردارنده ی کلمه ی first باشد. البته می توانید دیگر متدهای query builder را برای تنظیم دقیق تر عملیات eager loading بکار ببرید:

$users = App\User::with(['posts' => function ($query) {
    $query->orderBy('created_at', 'desc');
}])->get();


Lazy Eager Loading

Sometimes you may need to eager load a relationship after the parent model has already been retrieved. For example, this may be useful if you need to dynamically decide whether to load related models:

$books = App\Book::all();

if ($someCondition) {
    $books->load('author', 'publisher');
}

If you need to set additional query constraints on the eager loading query, you may pass an array keyed by the relationships you wish to load. The array values should be Closure instances which receive the query instance:

$books->load(['author' => function ($query) {
    $query->orderBy('published_date', 'asc');
}]);

To load a relationship only when it has not already been loaded, use the loadMissing method:

public function format(Book $book)
{
    $book->loadMissing('author');

    return [
        'name' => $book->name,
        'author' => $book->author->name
    ];
}


Inserting & Updating Related Models


متد save

Eloquent متدهای فراوانی برای افزودن مدل های جدید به رابطه ها فراهم می کند. برای مثال، ممکن است لازم شود یک مدل جدید Commentبرای مدل Post درج کنید. بجای اینکه attribute(ستون) post-id را در مدل Comment به صورت دستی مقداردهی کنید، می توانید این مدل را به صورت مستقیم از متد save رابطه وارد نمایید:

$comment = new App\Comment(['message' => 'A new comment.']);

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

$post->comments()->save($comment);

همان طور که در مثال بالا می بینید به رابطه ی comments به صورت یک dynamic property دسترسی پیدا نکردیم. بلکه صرفا متدcomments را برای بدست آوردن نمونه ای از رابطه فراخوانی نمودیم. متد save خود به صورت اتوماتیک مقدار post_id را به مدل جدیدComment اضافه می کند (فیلد مزبور را در مدل Comment مقداردهی می کند).

برای ذخیره ی چندین مدل مرتبط، کافی است متد saveMany را بکار ببرید:

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

$post->comments()->saveMany([
    new App\Comment(['message' => 'A new comment.']),
    new App\Comment(['message' => 'Another comment.']),
]);


متد Create

علاوه بر متدهای save و saveMany، می توانید متد create را فراخوانی کنید. این متد آرایه ای از attribute ها را به عنوان آرگومان دریافت کرده، یک مدل جدید ایجاد می کند و سپس آن مدل را داخل پایگاه داده درج می نماید. تفاوت بین دو متد save و create در این است که save یک نمونه ی کامل از مدل Eloquent را به عنوان آرگومان می پذیرد، در حالی که create صرفا یک آرایه ی ساده و متعارف PHP را به عنوان پارامتر ورودی دریافت می کند:

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

$comment = $post->comments()->create([
    'message' => 'A new comment.',
]);

Before using the create method, be sure to review the documentation on attribute mass assignment.

You may use the createMany method to create multiple related models:

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

$post->comments()->createMany([
    [
        'message' => 'A new comment.',
    ],
    [
        'message' => 'Another new comment.',
    ],
]);


بروز رسانی رابطه های "Belongs To" (مرتبط کردن مدل ها با متد associate)

به هنگام بروز رسانی رابطه ی belongsTo، می توانید متد associate را فراخوانی کنید. این متد کلید خارجی (foreign key) را در مدل فرزند مقداردهی می کند:

$account = App\Account::find(10);

$user->account()->associate($account);

$user->save();

و در زمان حذف یک رابطه ی belongsTo متد dissociate را بکار ببرید. این متد علاوه بر بازگردانی کلید خارجی، رابطه ی موجود در مدل فرزند راreset می کند:

$user->account()->dissociate();

$user->save();


Many To Many Relationships


درج (attach) / حذف (detach) در رابطه های چند به چند

به هنگام کار با رابطه های چند به چند، Eloquent تعداد زیادی متد کمکی (helper method) ارائه می کند که کار با مدل های مرتبط را به مراتب آسان ساخته. سناریویی را در نظر بگیرید که در آن یک کاربر می تواند نقش های متعددی داشته باشد و یک نقش نیز در مقابل به کاربران زیادی تعلق داشته باشد. برای اینکه بتوان یک نقش جدید را با درج یک رکورد در جدول واسطه (که مدل ها را به هم وصل می کند) به کاربر مورد نظر اضافه کرد، بایستی متد attach را بکار برد (می توان با درج یک رکورد در جدول واسطه که مدل ها را به هم پیوند می دهد، یک نقش جدید به کاربر مورد نظر اضافه کرد. برای این منظور کافی است متد attach را فراخوانی کنید):

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

$user->roles()->attach($roleId);

به هنگام اضافه کردن یک رابطه جدید به مدل، همچنین می توانید آرایه ای از داده های جدید برای درج در جدول واسطه به متد attach ارسال کنید:

$user->roles()->attach($roleId, ['expires' => $expires]);

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

// Detach a single role from the user...
$user->roles()->detach($roleId);

// Detach all roles from the user...
$user->roles()->detach();

هر دو متدهای attach و detach آرایه ای از شناسه ها را به عنوان ورودی می گیرند:

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

$user->roles()->detach([1, 2, 3]);

$user->roles()->attach([
    1 => ['expires' => $expires],
    2 => ['expires' => $expires]
]);


Syncing Associations

You may also use the sync method to construct many-to-many associations. The sync method accepts an array of IDs to place on the intermediate table. Any IDs that are not in the given array will be removed from the intermediate table. So, after this operation is complete, only the IDs in the given array will exist in the intermediate table:

$user->roles()->sync([1, 2, 3]);

You may also pass additional intermediate table values with the IDs:

$user->roles()->sync([1 => ['expires' => true], 2, 3]);

If you do not want to detach existing IDs, you may use the syncWithoutDetaching method:

$user->roles()->syncWithoutDetaching([1, 2, 3]);


Toggling Associations

همچنین می توانید از متد sync برای ساخت رابطه های چند به چند استفاده نمایید. این متد آرایه ای از شناسه ها (ID) را به عنوان آرگومان دریافت کرده و در جدول واسطه قرار می دهد. هر ID ای که در آرایه ی ورودی موجود نباشد، از جدول واسطه حذف خواهد شد. پس از اتمام این عملیات، تنهاID های حاضر در آرایه ی ورودی داخل جدول واسطه موجود خواهند بود:

$user->roles()->toggle([1, 2, 3]);


Saving Additional Data On A Pivot Table

When working with a many-to-many relationship, the save method accepts an array of additional intermediate table attributes as its second argument:

App\User::find(1)->roles()->save($role, ['expires' => $expires]);


بروز رسانی یک رکورد در جدول Pivot

برای بروز رسانی سطر موجود در جدول pivot، متد updateExistingPivot را بکار ببرید:

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

$user->roles()->updateExistingPivot($roleId, $attributes);


بروز رسانی Timestamp مدل پدر

زمانی که مدلی به یک (belongsTo) یا چند (belongToMany) مدل دیگر تعلق دارد، مانند مدل Comment که به یک مدل Post تعلق دارد، بروز رسانی timestamp مدل پدر پس از بروز رسانی مدل فرزند می تواند مفید واقع شود. برای مثال زمانی که یک مدل Comment بروز رسانی می شود، ممکن است بخواهید تایم استمپ updated_at مدل مالک (Post) نیز بروز آوری شود. Eloquent این کار را برای شما آسان کرده است. کافی است پراپرتی touches که با اسم رابطه ها مقداردهی شده را به مدل فرزند اضافه نمایید:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Comment extends Model
{
    /**
     * All of the relationships to be touched.
     *
     * @var array
     */
    protected $touches = ['post'];

    /**
     * Get the post that the comment belongs to.
     */
    public function post()
    {
        return $this->belongsTo('App\Post');
    }
}

حال زمانی که شما یک Comment را بروز رسانی می کنید، مقدار ستون های updated_at در مدل پدر مدل پدر (Post) نیز بروز آوری می شود:

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

$comment->text = 'Edit to this comment!';

$comment->save();

منابع مورد مطالعه جهت جمع آوری این مطلب:
https://laravel.com/docs/5.5/eloquent-relationships
www.tahlildadeh.com/ArticleDetails/آموزش-رابطه-ها-در-Eloquent


نظرات