Laravel 編碼技巧 模型關(guān)聯(lián)

2023-02-16 17:10 更新

在 Eloquent 關(guān)系中使用 OrderBy

您可以在關(guān)聯(lián)關(guān)系中直接指定 orderBy()。

public function products()
{
    return $this->hasMany(Product::class);
}

public function productsByName()
{
    return $this->hasMany(Product::class)->orderBy('name');
}

在 Eloquent 關(guān)系中添加條件

假如你經(jīng)常在模型關(guān)聯(lián)關(guān)系中添加某些相同的 where 條件,可以創(chuàng)建一個(gè)獨(dú)立方法。

Model:

public function comments()
{
    return $this->hasMany(Comment::class);
}

public function approved_comments()
{
    return $this->hasMany(Comment::class)->where('approved', 1);
}

DB 原生查詢: havingRaw ()

你可以在很多地方使用原始數(shù)據(jù)庫(kù)查詢,比如在 groupBy() 后面調(diào)用 havingRaw()

Product::groupBy('category_id')->havingRaw('COUNT(*) > 1')->get();

Eloquent 使用 has () 實(shí)現(xiàn)多層調(diào)用查詢

你可以在關(guān)聯(lián)關(guān)系查詢中使用 has() 實(shí)現(xiàn)兩層關(guān)聯(lián)查詢。

// Author -> hasMany(Book::class);
// Book -> hasMany(Rating::class);
$authors = Author::has('books.ratings')->get();

一對(duì)多關(guān)系中獲取符合指定數(shù)量的信息

在一對(duì)多關(guān)系中,你可以通過(guò)條件過(guò)濾,獲取符合的數(shù)據(jù)。比如需要查找有哪些作者出版書的數(shù)量大于 5。

// Author -> hasMany(Book::class)
$authors = Author::has('books', '>', 5)->get();

返回默認(rèn)模型

你可以在 belongsTo 關(guān)系中設(shè)置返回一個(gè)默認(rèn)的模型,從而避免類似于使用 {{ $post->user->name }} 當(dāng) $post->user 不存在的時(shí)候,引起的致命的錯(cuò)誤。

public function user()
{
    return $this->belongsTo('App\User')->withDefault();
}

一對(duì)多關(guān)系中一次創(chuàng)建多條關(guān)聯(lián)數(shù)據(jù)

在一對(duì)多關(guān)系中,你可以使用 saveMany() 通過(guò)一次提交,保存多條關(guān)聯(lián)數(shù)據(jù)。

$post = Post::find(1);
$post->comments()->saveMany([
    new Comment(['message' => 'First comment']),
    new Comment(['message' => 'Second comment']),
]);

多層級(jí)渴求式加載

在 Laravel 中,你可以在一條語(yǔ)句中渴求式加載多個(gè)層級(jí),在這個(gè)例子中,我們不僅加載作者關(guān)系,而且還加載作者模型上的國(guó)家關(guān)系。

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

渴求式加載特定字段

你可以在 Laravel 中渴求式加載并指定關(guān)聯(lián)中的特定字段。

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

你同樣可以在深層級(jí)中這樣做,如第二層級(jí)關(guān)系:

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

輕松觸及父級(jí) updated_at

如果你想更新一條數(shù)據(jù)同時(shí)更新它父級(jí)關(guān)聯(lián)的 updated_at 字段 (例如:你添加一條帖子評(píng)論,想同時(shí)更新帖子的 posts.updated_at),只需要在子模型中使用 $touches = ['post']; 屬性。

class Comment extends Model
{
    protected $touches = ['post'];
}

一直檢查關(guān)聯(lián)是否存在

永遠(yuǎn)不要在不檢查關(guān)聯(lián)是否存在時(shí)使用 $model->relationship->field 。

它可能因?yàn)槿魏卧?,如在你的代碼之外,被別人的隊(duì)列任務(wù)等等被刪除。用 if-else,或者在 Blade 模板中 {{$model->relationship->field ? ? '' }},或者 {{optional($model->relationship)->field }} 。在 php8 中,你甚至可以使用 null 安全操作符 {{ $model->relationship?->field) }}

使用 withCount () 來(lái)統(tǒng)計(jì)子關(guān)聯(lián)記錄數(shù)

如果你有 hasMany() 的關(guān)聯(lián),并且你想統(tǒng)計(jì)子關(guān)聯(lián)記錄的條數(shù),不要寫一個(gè)特殊的查詢。例如,如果你的用戶模型上有帖子和評(píng)論,使用 withCount()。

public function index()
{
    $users = User::withCount(['posts', 'comments'])->get();
    return view('users', compact('users'));
}

同時(shí),在 Blade 文件中,您可以通過(guò)使用 {relationship}_count 屬性獲得這些數(shù)量:

@foreach ($users as $user)
<tr>
    <td>{{ $user->name }}</td>
    <td class="text-center">{{ $user->posts_count }}</td>
    <td class="text-center">{{ $user->comments_count }}</td>
</tr>
@endforeach

也可以按照這些統(tǒng)計(jì)字段進(jìn)行排序:

User::withCount('comments')->orderBy('comments_count', 'desc')->get(); 

關(guān)聯(lián)關(guān)系中過(guò)濾查詢

假如您想加載關(guān)聯(lián)關(guān)系的數(shù)據(jù),同時(shí)需要指定一些限制或者排序的閉包函數(shù)。例如,您想獲取人口最多的前 3 座城市信息,可以按照如下方式實(shí)現(xiàn):

$countries = Country::with(['cities' => function($query) {
    $query->orderBy('population', 'desc');
    $query->take(3);
}])->get();

動(dòng)態(tài)預(yù)加載相關(guān)模型

您不僅可以實(shí)現(xiàn)對(duì)關(guān)聯(lián)模型的實(shí)時(shí)預(yù)加載,還可以根據(jù)情況動(dòng)態(tài)設(shè)置某些關(guān)聯(lián)關(guān)系,需要在模型初始化方法中處理:

class ProductTag extends Model
{
    protected $with = ['product'];

    public function __construct() {
        parent::__construct();
        $this->with = ['product'];

        if (auth()->check()) {
            $this->with[] = 'user';
        }
    }
}

使用 hasMany 代替 belongsTo

在關(guān)聯(lián)關(guān)系中,如果創(chuàng)建子關(guān)系的記錄中需要用到父關(guān)系的 ID ,那么使用 hasMany 比使用 belongsTo 更簡(jiǎn)潔。

// if Post -> belongsTo(User), and User -> hasMany(Post)...
// Then instead of passing user_id...
Post::create([
    'user_id' => auth()->id(),
    'title' => request()->input('title'),
    'post_text' => request()->input('post_text'),
]);

// Do this
auth()->user()->posts()->create([
    'title' => request()->input('title'),
    'post_text' => request()->input('post_text'),
]);

自定義 pivot 屬性名稱

如果你想要重命名「pivot」并用其他的什么方式來(lái)調(diào)用關(guān)系,你可以在你的關(guān)系聲明中使用 ->as('name') 來(lái)為關(guān)系取名。

模型 Model:

public function podcasts() {
    return $this->belongsToMany('App\Podcast')
        ->as('subscription')
        ->withTimestamps();
}

控制器 Controller:

$podcasts = $user->podcasts();
foreach ($podcasts as $podcast) {
    // instead of $podcast->pivot->created_at ...
    echo $podcast->subscription->created_at;
}

僅用一行代碼更新歸屬關(guān)系

如果有一個(gè) belongsTo() 關(guān)系,你可以在僅僅一條語(yǔ)句中更新這個(gè) Elquent 關(guān)系:

// if Project -> belongsTo(User::class)
$project->user->update(['email' => 'some@gmail.com']); 

Laravel 7+ 的外鍵

從 Laravel 7 開始,你不需要在遷移(migration)中為一些關(guān)系字段寫兩行代碼 —— 一行是字段,一行是外鍵。你可以使用 foreignId() 方法。

// Laravel 7 之前
Schema::table('posts', function (Blueprint $table)) {
    $table->unsignedBigInteger('user_id');
    $table->foreign('user_id')->references('id')->on('users');
}

// 從 Laravel 7 開始
Schema::table('posts', function (Blueprint $table)) {
    $table->foreignId('user_id')->constrained();
}

// 或者你的字段不同于表中的引用的
Schema::table('posts', function (Blueprint $table)) {
    $table->foreignId('created_by_id')->constrained('users', 'column');
}

兩種 「whereHas」 組合使用

在 Eloquent 中,你可以在同一條語(yǔ)句中使用 whereHas() 和 orDoesntHave()。

User::whereHas('roles', function($query) {
    $query->where('id', 1);
})
->orDoesntHave('roles')
->get();

檢查關(guān)系方法是否已經(jīng)存在

如果你的 Eloquent 關(guān)系名是動(dòng)態(tài)的,那么你需要檢查項(xiàng)目中是否存在相同名稱的關(guān)系。你可以使用這個(gè) PHP 方法 method_exists($object, $methodName)

$user = User::first();
if (method_exists($user, 'roles')) {
    // 使用 $user->roles()-> 做其它事情...
}

獲取中間表中的關(guān)聯(lián)關(guān)系數(shù)據(jù)

在多對(duì)多關(guān)系中,您定義的中間表里面可能會(huì)包含擴(kuò)展字段,甚至可能包含其它的關(guān)聯(lián)關(guān)系。

下面生成一個(gè)中間表模型:

php artisan make:model RoleUser --pivot

然后,給 belongsToMany() 指定 ->using() 方法。下面就是見證奇跡的時(shí)刻:

// in app/Models/User.php
public function roles()
{
    return $this->belongsToMany(Role::class)
        ->using(RoleUser::class)
        ->withPivot(['team_id']);
}

// app/Models/RoleUser.php: 注意繼承的是 Pivot, 不是 Model
use Illuminate\Database\Eloquent\Relations\Pivot;

class RoleUser extends Pivot
{
    public function team()
    {
        return $this->belongsTo(Team::class);
    }
}

// 在控制器里面就可以直接使用如下方式獲取中間表 RoleUser 的 Team 信息:
$firstTeam = auth()->user()->roles()->first()->pivot->team->name;

便捷獲取一對(duì)多關(guān)系中子集的數(shù)量

除了可以使用 Eloquent 中的 withCount() 方法統(tǒng)計(jì)子集數(shù)量外,還可以直接用 loadCount() 更加便捷和快速獲取:

// if your Book hasMany Reviews...
$book = App\Book::first();

$book->loadCount('reviews');
// 使用 $book->reviews_count 獲取 Reviews 數(shù)量;

// 還可以在 loadCount 中添加額外的查詢條件
$book->loadCount(['reviews' => function ($query) {
    $query->where('rating', 5);
}]);

對(duì)關(guān)聯(lián)模型數(shù)據(jù)進(jìn)行隨機(jī)排序

您可以使用 inRandomOrder() 對(duì) Eloquent 的查詢結(jié)果進(jìn)行隨機(jī)排序,同時(shí)也可以作用于關(guān)聯(lián)關(guān)系中,實(shí)現(xiàn)關(guān)聯(lián)數(shù)據(jù)的隨機(jī)排序。

// If you have a quiz and want to randomize questions...

// 1. 獲取隨機(jī)答案:
$questions = Question::inRandomOrder()->get();

// 2. 獲取隨機(jī)問(wèn)題的隨機(jī)答案:
$questions = Question::with(['answers' => function($q) {
    $q->inRandomOrder();
}])->inRandomOrder()->get();

過(guò)濾一對(duì)多關(guān)聯(lián)

通過(guò)我項(xiàng)目中的一個(gè)代碼例子,展示了過(guò)濾一對(duì)多關(guān)系的可能性。

TagType -> hasMany tags -> hasMany examples

如果你想查詢所有的標(biāo)簽類型,伴隨他們的標(biāo)簽,但只包含有實(shí)例的標(biāo)簽,并按照實(shí)例數(shù)量倒序。

$tag_types = TagType::with(['tags' => function ($query) {
    $query->has('examples')
        ->withCount('examples')
        ->orderBy('examples_count', 'desc');
    }])->get();

通過(guò)中間表字段過(guò)濾多對(duì)多關(guān)聯(lián)

如果你有一個(gè)多對(duì)多關(guān)聯(lián),你可以在中間表中添加一個(gè)額外字段,這樣你可以在查詢列表時(shí)用它排序。

class Tournament extends Model
{
    public function countries()
    {
        return $this->belongsToMany(Country::class)->withPivot(['position']);
    }
}
class TournamentsController extends Controller

public function whatever_method() {
    $tournaments = Tournament::with(['countries' => function($query) {
            $query->orderBy('position');
        }])->latest()->get();
}

whereHas 的一個(gè)更簡(jiǎn)短的方法

在 Laravel 8.57 中發(fā)布:通過(guò)包含一個(gè)簡(jiǎn)單條件的簡(jiǎn)短方法來(lái)寫 whereHas()。

// 以前
User::whereHas('posts', function ($query) {
    $query->where('published_at', '>', now());
})->get();

// 現(xiàn)在
User::whereRelation('posts', 'published_at', '>', now())->get();

提取一個(gè)重復(fù)回調(diào)作為變量

如果你有一個(gè)重復(fù)使用的回調(diào)函數(shù),你可以提取它作為變量。

// 你有一個(gè)很長(zhǎng)的包含重復(fù)的回調(diào)函數(shù)
$results = Model::with('relationships')
    ->whereHas('relationships', function($query) use ($var1, $var2) {
        return $query->where('field1', $var1)->where('field2', $var2);
    })
    ->withCount('relationships', function($query) use ($var1, $var2) {
        return $query->where('field1', $var1)->where('field2', $var2);
    })
    ->get();

// 你可以提取它作為變量
$callback = function($query) use ($var1, $var2) {
        return $query->where('field1', $var1)->where('field2', $var2);
    });
$results = Model::with('relationships')
    ->whereHas('relationships', $callback)
    ->withCount('relationships', $callback)
    ->get();

你可以為你的模型關(guān)聯(lián)添加條件

class User
{
    public function posts()
    {
        return $this->hasMany(Post::class);
    }

    // 添加一個(gè)獲取器
    public function getPublishedPostsAttribute()
    {
        return $this->posts->filter(fn ($post) => $post->published);
    }

    // 添加一個(gè)關(guān)聯(lián)
    public function publishedPosts()
    {
        return $this->hasMany(Post::class)->where('published', true);
    }
}

感謝 @anwar_nairi 提供

新的 Eloquent 查詢構(gòu)建器方法 whereBelongsTo()

Laravel 8.63.0 版本帶有一個(gè)新的 Eloquent 查詢構(gòu)建器方法 whereBelongsTo() 。

這允許你從你的查詢中刪除 BelongsTo 外鍵名稱,并使用關(guān)聯(lián)方法替代(該方法會(huì)根據(jù)類名自動(dòng)確定關(guān)聯(lián)與外鍵,也可以添加第二個(gè)參數(shù)手動(dòng)關(guān)聯(lián))。

// 以前:
$query->where('author_id', $author->id)

// 現(xiàn)在:
$query->whereBelongsTo($author)

// 輕松添加更多的過(guò)濾功能:
Post::query()
    ->whereBelongsTo($author)
    ->whereBelongsTo($cateogry)
    ->whereBelongsTo($section)
    ->get();

// 指定一個(gè)自定義的關(guān)系:
$query->whereBelongsTo($author, 'author')

感謝 @danjharrin 提供


以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)