laravel model relationship

世有因果知因求果發表於2016-09-10

laravel支援多種模型之間的relation,對應著模型間的one2one, one2many,many2many,hasManyThrough,Polymorphic, many2many polymorphic關係。

心法

1.所有relation都由model class上的方法來定義;

2. relationship和model本身都是以query builder作為基類的,因此對relation的操作也可以使用類似query builder的方法,比如:可以支援級聯;

3.Dynamic property of model:這是 model->relation方式引用的結果,這種方式直接返回relation model的value,不支援query builder的級聯操作,這是和model->relation()方法呼叫的本質區別。

也就是說model->relation = model->relation()->get()

關係的種類:

one 2 one(一對一)

例子User hasOne Phone, Phone belongsTo User這種關係。要使得這種關係work,則需要以下前提:

1. User模型

base table: users,

欄位預設: id

relation method: phone (){ return $this->hasOne('App\Phone') }

 

注意:按照laravel的命名規約,laravel會假設phones表中有一個user_id欄位作為users表中的id外來鍵,

我們可以通過在user()方法中傳入另外的引數來覆蓋這種規約,比如$this->hasOne('App\Phone','owner_id')

在這裡owner_id就是phones表中外來鍵引用users表的id的欄位名稱(預設為user_id);

 

2.Phone模型

base table: phones,

欄位預設: user_id

relation method: user(){ return $this->belongsTo('App\User')}

在這裡,laravel也會預設parent表(也就是users表)中有一個id欄位為primary key,如果你的parent表並不是使用id作為主鍵,則在上面的hasOne呼叫中,可以使用第三個引數來指定這個主鍵,比如

user(){return $this->hasOne('App\User','owner_id','u_id'}. 這裡owner_id為phones表中對users表主鍵(u_id)的外來鍵引用欄位名稱

One 2 Many(一對多)

例子: Post hasMany Comments, Comment belongsTo Post,這就是典型的一對多關係模型

1. Post模型

base table: posts

欄位預設:id標示行主鍵

relation method: comments(){return $this->hasMany('App\Comment')}

如果需要覆蓋預設命名規約,我們需要這樣呼叫:

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

注意這裡:foreign_owner_postid_key就是要在comments表中具有的欄位名;

local_postid_key就是posts本表中必須具有的主鍵欄位名

2. Comment模型

base table: comments

欄位預設: post_id 作為posts表的外來鍵

relation method: post(){return $this->belongsTo('App\Post')}

如果需要覆蓋預設命名規約,同樣我們需要這樣呼叫:

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

注意:這裡foreign_owner_postid_key就是在本comments表中必須具有的引用posts表的外來鍵欄位名;

other_local_postid_key就是必須在posts表中具有的主鍵名稱。之所以加local,other就是代表的是本表的還是關聯表的。

另外,上面也已經提到由於relationship本身就是query builder因此可以增加約束級聯,比如:

Post::find(1)->comments()->where('title','foo')->first()就只取title為foo的第一條post對應的comment

other_local_postid_key就是

注意按照laravel預設命名規約,一對多的關係模型,child model中的owning model

many 2 many(多對多)

例子: User belongsToMany Role, Role belongsToMany User

1. User模型

base table: users

欄位預設: id主鍵

relation method: roles(){return $this->belongsToMany('App\Role')}

其他預設: role_user pivot表(即兩個小寫關係之間按照字母排序使用 _ 連線起來)

2. Role模型

base table: roles

欄位預設: id主鍵

relation method: users(){return $this->belongsToMany('App\User')}

其他預設: role_user pivot表(即兩個小寫關係之間按照字母排序使用 _ 連線起來)

上面的兩個model中都有預設pivot表,我們可以通過傳參來覆蓋它(注意這裡只需要在一個方向上呼叫即可,不用像上面one 2 many需要兩邊都這樣呼叫才能work!):

public function roles(){return $this->belongsToMany('App\Role','user_role','user_pivot_ziduan_name','role_ziduan_name_in_pivot')}

再看一個使用query builder特性的例子:$roles=App\User::find(1)->roles()->orderBy('name')->get()

也可以在訪問relation的同時訪問pivot表資料:

public function roles(){$this->belongsToMany('App\Role')->withPivot('column1_in_pivot','c2_in_pivot')}

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

上面程式碼中獲取relation的同時,也返回pivot表格的對應資料,並且可以作為pivot relation來運算元據

 

has Many through(通過xx一對多)

例子:由於Country hasMany User,同時User hasMany Post,所以Country可以hasMany Post via User(注意同時User belongsTo Countery, Post belongsTo User)

1. Country模型

base table: countries

欄位預設: id, name

relation methods: 

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

public function posts(){return $this->hasManyThrough('App\Post')}

2.User模型

base table: users

欄位預設: id, country_id, username

relation method: country(){return $this->belongsTo('App\Country')}

3.Post模型

base table: posts

欄位預設: id, user_id, title,description

relation method: user(){return $this->belongsTo('App\User')}

Polymorphic relation(多型一對多)

例子:Photo可以morphTo Staff, 同時Photo也可以morphTo Product, Product可以morphMany Photo, Staff也可以morphMany Photo,也就是說一個model可以belongsTo多種parentmodel,多種parentmodel都可以hasMany這個model時,非常適合這種polymorphic relation

1. Staff模型:

base table: staffs

欄位預設: id, name

relation method: photos(){return $this->morphMany('App\Photo','imagable')} //注意imagable是Photo模型的一個relation method

2.Product模型

base table: products

欄位預設: id, name, price

relation method: photos(){return $this->morphMany('App\Photo','imagable')}//注意imagable是Photo模型的一個relation method

3. Photo模型

base table: photos

欄位預設: id,path,imageable_id,imageable_type //注意imageable就是本模型中的relation method名稱,這裡imageable_id和imageable_type非常關鍵,也是laravel根據photos表找到對應photos屬於哪種模型的關鍵,也就是說一張圖片到底是staff的圖片還是產品product的圖片,就看這個type和id了,由type找到是哪張表,id再找到這張表中第幾條資料。預設情況下laravel會使用Model的class全稱作為type,但是有時你可能並不希望如此,這時,一個可行的方案是建立一個types表,來指定這種對應關係,同時在AppServiceProvider booted方法中呼叫

Relation::morphMap($morphMapArray);

來完成這種人性化的對映關係

Many 2 Many Polymorphic Relations

例子: Post可以打上多個Tag標籤, Video也可以同樣打上多個Tag,也就是說Tag可以同時打在video或者post上。

如何在model中增加一個不存在於base table欄位中的欄位?

很多時候存在這樣的場景:你很難找到一個適合的relation來描述兩個表格之間的關係,或者你不需要建立複雜的relation,你只需要在你返回的model中新增一條額外的資訊,比如,User model中你可能需要一條額外的欄位'isAdmin'來方便判斷該使用者是否admin使用者,這時可以這樣操作:(但是好像動態訪問不是很好用)

https://laravel.com/docs/5.1/eloquent-serialization#appending-values-to-json

class User extends Model
{
    protected $appends = ['is_admin'];
    /**
     * Get the administrator flag for the user.
     *
     * @return bool
     */
    public function getIsAdminAttribute()
    {
        return $this->attributes['admin'] == 'yes';
    }
}

 laravel relation相關簡單除錯手段

雖然laravel的relation非常強大,只要你稍加熟悉,你就能花非常少的effort構建出非常強大的web後端應用來。但是有時候,一些高階的sql查詢,laravel可能並未按照我們的預期工作,這時,我們就希望eloquent最終到底對映成了什麼sql,有幾種方法獲取這方面的資訊:

1. get()替換為toSql()直接列印出來;

$results = User::where(function($q) use ($request) {
    $q->orWhere('email', 'like', '%john@example.org%');
    $q->orWhere('first_name', 'like', '%John%');
    $q->orWhere('last_name', 'like', '%Doe%');
})->toSql();

 

2. listen事件

\DB::listen(function($sql) {
    var_dump($sql);
});

https://scotch.io/tutorials/debugging-queries-in-laravel

查詢relation時針對relation實現限制條件:(has, doesnotHave)

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

$posts = App\Post::doesntHave('comments')->get(); // 簡化版
$posts = Post::whereDoesntHave('comments', function ($query) {
    $query->where('content', 'like', 'foo%');
})->get();

hasManyThrough 多對多?

http://laravel.io/forum/03-04-2014-hasmanythrough-with-many-to-many

 

相關文章