天天看點

跟我一起學Laravel-EloquentORM進階部分

關聯關系

One To One

假設User模型關聯了Phone模型,要定義這樣一個關聯,需要在User模型中定義一個phone方法,該方法傳回一個

hasOne

方法定義的關聯

<?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

方法的第一個參數為要關聯的模型,定義好之後,可以使用下列文法查詢到關聯屬性了

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

複制

Eloquent會假定關聯的外鍵是基于模型名稱的,是以Phone模型會自動使用

user_id

字段作為外鍵,可以使用第二個參數和第三個參數覆寫

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

複制

定義反向關系

定義上述的模型之後,就可以使用User模型擷取Phone模型了,當然也可以通過Phone模型擷取所屬的User了,這就用到了

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');
        // return $this->belongsTo('App\User', 'foreign_key');
        // return $this->belongsTo('App\User', 'foreign_key', 'other_key');

    }
}           

複制

One To Many

假設有一個文章,它有很多關聯的評論資訊,這種情況下應該使用一對多的關聯,使用

hasMany

方法

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

複制

查詢操作

$comments = App\Post::find(1)->comments;
foreach ($comments as $comment) {
    //
}

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

複制

定義反向關聯

反向關聯也是使用

belongsTo

方法,參考One To One部分。

$comment = App\Comment::find(1);
echo $comment->post->title;           

複制

Many To Many

多對多關聯因為多了一個中間表,實作起來比

hasOne

hasMany

複雜一些。

考慮這樣一個場景,使用者可以屬于多個角色,一個角色也可以屬于多個使用者。這就引入了三個表:

users

,

roles

,

role_user

。其中

role_user

表為關聯表,包含兩個字段

user_id

role_id

多對多關聯需要使用

belongsToMany

方法

<?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', 'role_user');
        // 指定關聯表,關聯字段
        // return $this->belongsToMany('App\Role', 'role_user', 'user_id', 'role_id');

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

複制

上述定義了一個使用者屬于多個角色,一旦該關系确立,就可以查詢了

$user = App\User::find(1);
foreach ($user->roles as $role) {
    //
}

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

複制

反向關聯關系

反向關系與正向關系實作一樣

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

複制

檢索中間表的列值

對多對多關系來說,引入了一個中間表,是以需要有方法能夠查詢到中間表的列值,比如關系确立的時間等,使用

pivot

屬性查詢中間表

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

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

複制

上述代碼通路了中間表的

created_at

字段。

注意的是,預設情況下之後模型的鍵可以通過

pivot

對象進行通路,如果中間表包含了額外的屬性,在指定關聯關系的時候,需要使用

withPivot

方法明确的指定列名

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

複制

如果希望中間表自動維護

created_at

updated_at

字段的話,需要使用

withTimestamps()

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

複制

Has Many Through

這種關系比較強大,假設這樣一個場景:Country模型下包含了多個User模型,而每個User模型又包含了多個Post模型,也就是說一個國家有很多使用者,而這些使用者都有很多文章,我們希望查詢某個國家的所有文章,怎麼實作呢,這就用到了Has Many Through關系

countries
    id - integer
    name - string

users
    id - integer
    country_id - integer
    name - string

posts
    id - integer
    user_id - integer
    title - string           

複制

可以看到,posts表中并不直接包含country_id,但是它通過users表與countries表建立了關系

使用Has Many Through關系

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', 'country_id', 'user_id');

        return $this->hasManyThrough('App\Post', 'App\User');
    }
}           

複制

方法

hasManyThrough

的第一個參數是我們希望通路的模型名稱,第二個參數是中間模型名稱。

HasManyThrough hasManyThrough( 
    string $related, 
    string $through, 
    string|null $firstKey = null, 
    string|null $secondKey = null, 
    string|null $localKey = null
)           

複制

Polymorphic Relations (多态關聯)

多态關聯使得同一個模型使用一個關聯就可以屬于多個不同的模型,假設這樣一個場景,我們有一個文章表和一個評論表,使用者既可以對文章執行喜歡操作,也可以對評論執行喜歡操作,這樣的情況下該怎麼處理呢?

表結構如下

posts
    id - integer
    title - string
    body - text

comments
    id - integer
    post_id - integer
    body - text

likes
    id - integer
    likeable_id - integer
    likeable_type - string           

複制

可以看到,我們使用likes表中的likeable_type字段判斷該記錄喜歡的是文章還是評論,表結構有了,接下來就該定義模型了

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Like extends Model
{
    /**
     * Get all of the owning likeable models.
     */
    public function likeable()
    {
        return $this->morphTo();
    }
}

class Post extends Model
{
    /**
     * Get all of the product's likes.
     */
    public function likes()
    {
        return $this->morphMany('App\Like', 'likeable');
    }
}

class Comment extends Model
{
    /**
     * Get all of the comment's likes.
     */
    public function likes()
    {
        return $this->morphMany('App\Like', 'likeable');
    }
}           

複制

預設情況下,

likeable_type

的類型是關聯的模型的完整名稱,比如這裡就是

App\Post

App\Comment

通常情況下我們可能會使用自定義的值辨別關聯的表名,是以,這就需要自定義這個值了,我們需要在項目的服務提供者對象的

boot

方法中注冊關聯關系,比如

AppServiceProvider

boot

方法中

use Illuminate\Database\Eloquent\Relations\Relation;

Relation::morphMap([
    'posts' => App\Post::class,
    'likes' => App\Like::class,
]);           

複制

檢索多态關系

通路一個文章所有的喜歡

$post = App\Post::find(1);  
foreach ($post->likes as $like) {
    //
}           

複制

通路一個喜歡的文章或者評論

$like = App\Like::find(1);   
$likeable = $like->likeable;           

複制

上面的例子中,傳回的likeable會根據該記錄的類型傳回文章或者評論。

多對多的多态關聯

多對多的關聯使用方法

morphToMany

morphedByMany

,這裡就不多廢話了。

關聯關系查詢

在Eloquent中,所有的關系都是使用函數定義的,可以在不執行關聯查詢的情況下擷取關聯的執行個體。假設我們有一個部落格系統,User模型關聯了很多Post模型:

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

複制

你可以像下面這樣查詢關聯并且添加額外的限制

$user = App\User::find(1);
$user->posts()->where('active', 1)->get();           

複制

如果不需要對關聯的屬性添加限制,可以直接作為模型的屬性通路,例如上面的例子,我們可以使用下面的方式通路User的Post

$user = App\User::find(1);
foreach ($user->posts as $post) {
    //
}           

複制

動态的屬性都是延遲加載的,它們隻有在被通路的時候才會去查詢資料庫,與之對應的是預加載,預加載可以使用關聯查詢出所有資料,減少執行sql的數量。

查詢關系存在性

使用

has

方法可以基于關系的存在性傳回結果

// 檢索至少有一個評論的所有文章...
$posts = App\Post::has('comments')->get();

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

複制

如果需要更加強大的功能,可以使用

whereHas

orWhereHas

方法,把where條件放到

has

語句中。

// 檢索所有至少存在一個比對foo%的評論的文章
$posts = Post::whereHas('comments', function ($query) {
    $query->where('content', 'like', 'foo%');
})->get();           

複制

預加載

在通路Eloquent模型的時候,預設情況下所有的關聯關系都是延遲加載的,在使用的時候才會開始加載,這就造成了需要執行大量的sql的問題,使用預加載功能可以使用關聯查詢出所有結果

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

複制

上面的查詢将會執行一個查詢查詢出所有的書,然後在周遊的時候再執行N個查詢查詢出作者資訊,顯然這樣做是非常低效的,幸好我們還有預加載功能,可以将這N+1個查詢減少到2個查詢,在查詢的時候,可以使用

with

方法指定哪個關系需要預加載。

$books = App\Book::with('author')->get();
foreach ($books as $book) {
    echo $book->author->name;
}           

複制

對于該操作,會執行下列兩個sql

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

複制

預加載多個關系

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

複制

嵌套的預加載

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

複制

帶限制的預加載

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

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

複制

延遲預加載

有時候,在上級模型已經檢索出來之後,可能會需要預加載關聯資料,可以使用

load

方法

$books = App\Book::all();
if ($someCondition) {
    $books->load('author', 'publisher');
}

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

複制

關聯模型插入

save方法

儲存單個關聯模型

$comment = new App\Comment(['message' => 'A new comment.']);
$post = App\Post::find(1);
$post->comments()->save($comment);           

複制

儲存多個關聯模型

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

複制

save方法和多對多關聯

多對多關聯可以為save的第二個參數指定關聯表中的屬性

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

複制

上述代碼會更新中間表的expires字段。

create方法

使用

create

方法與

save

方法的不同在于它是使用數組的形式建立關聯模型的

$post = App\Post::find(1);
$comment = $post->comments()->create([
    'message' => 'A new comment.',
]);           

複制

更新 “Belongs To” 關系

更新

belongsTo

關系的時候,可以使用

associate

方法,該方法會設定子模型的外鍵

$account = App\Account::find(10);
$user->account()->associate($account);
$user->save();           

複制

要移除

belongsTo

關系的話,使用

dissociate

方法

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

複制

Many to Many 關系

中間表查詢條件

當查詢時需要對使用中間表作為查詢條件時,可以使用

wherePivot

wherePivotIn

orWherePivot

orWherePivotIn

添加查詢條件。

$enterprise->with(['favorites' => function($query) {
    $query->wherePivot('enterprise_id', '=', 12)->select('id');
}]);           

複制

Attaching / Detaching

$user = App\User::find(1);
// 為使用者添加角色
$user->roles()->attach($roleId);
// 為使用者添加角色,更新中間表的expires字段
$user->roles()->attach($roleId, ['expires' => $expires]);

// 移除使用者的單個角色
$user->roles()->detach($roleId);
// 移除使用者的所有角色
$user->roles()->detach();           

複制

attach

detach

方法支援數組參數,同時添加和移除多個

$user = App\User::find(1);
$user->roles()->detach([1, 2, 3]);
$user->roles()->attach([1 => ['expires' => $expires], 2, 3]);           

複制

更新中間表(關聯表)字段

使用

updateExistingPivot

方法更新中間表

$user = App\User::find(1);
$user->roles()->updateExistingPivot($roleId, $attributes);           

複制

同步中間表(同步關聯關系)

使用

sync

方法,可以指定兩個模型之間隻存在指定的關聯關系

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

複制

上述兩個方法都會讓使用者隻存在1,2,3三個角色,如果使用者之前存在其他角色,則會被删除。

更新父模型的時間戳

假設場景如下,我們為一個文章增加了一個新的評論,我們希望這個時候文章的更新時間會相應的改變,這種行為在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');
    }
}           

複制

現在,更新評論的時候,文章的

updated_at

字段也會被更新

$comment = App\Comment::find(1);
$comment->text = 'Edit to this comment!';
$comment->save();           

複制

參考: Eloquent: Relationships