Laravel & Lumen之Eloquent ORM使用速查-進階部分

關聯關係

One To One

假設User模型關聯了Phone模型,要定義這樣一個關聯,須要在User模型中定義一個phone方法,該方法返回一個hasOne方法定義的關聯php

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class User extends Model
{

    public function phone()
    {
        return $this->hasOne('App\Phone');
    }
}

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

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

Eloquent會假定關聯的外鍵是基於模型名稱的,所以Phone模型會自動使用user_id字段做爲外鍵,可使用第二個參數和第三個參數覆蓋sql

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
{

    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
{

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

查詢操做ide

$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

多對多關聯由於多了一箇中間表,實現起來比hasOnehasMany複雜一些。post

考慮這樣一個場景,用戶能夠屬於多個角色,一個角色也能夠屬於多個用戶。這就引入了三個表: users, roles, role_user。其中role_user表爲關聯表,包含兩個字段user_idrole_idthis

多對多關聯須要使用belongsToMany方法spa

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class User extends Model
{

    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
{

    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_atupdated_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
{

    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
{

    public function likeable()
    {
        return $this->morphTo();
    }
}

class Post extends Model
{

    public function likes()
    {
        return $this->morphMany('App\Like', 'likeable');
    }
}

class Comment extends Model
{

    public function likes()
    {
        return $this->morphMany('App\Like', 'likeable');
    }
}

默認狀況下,likeable_type的類型是關聯的模型的完整名稱,好比這裏就是App\PostApp\Comment

一般狀況下咱們可能會使用自定義的值標識關聯的表名,所以,這就須要自定義這個值了,咱們須要在項目的服務提供者對象的boot方法中註冊關聯關係,好比AppServiceProviderboot方法中

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會根據該記錄的類型返回帖子或者評論。

多對多的多態關聯

多對多的關聯使用方法morphToManymorphedByMany,這裏就很少廢話了。

關聯關係查詢

在Eloquent中,全部的關係都是使用函數定義的,能夠在不執行關聯查詢的狀況下獲取關聯的實例。假設咱們有一個博客系統,User模型關聯了不少Post模型:

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

若是須要更增強大的功能,可使用whereHasorWhereHas方法,把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
{
    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 關係

中間表查詢條件

當查詢時須要對使用中間表做爲查詢條件時,可使用wherePivotwherePivotInorWherePivotorWherePivotIn添加查詢條件。

$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();

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

$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
{

    protected $touches = ['post'];

    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

相關文章
相關標籤/搜索