Laravel Tutorial

Bài 32: Eloquent ORM relationship trong Laravel – Phần 2

Trong bài này chúng ta cùng nhau tìm hiểu cách query với relationship và sự khác nhau giữ việc gọi method và gọi property

1. Gọi relationship

1.1. Gọi relationship như method

Khi chúng ta thực hiện gọi một relationship như một method trong model thì Laravel sẽ trả về một instance (query builder) chứa query của relationship đó mà chưa thực hiện một query đến database. Lúc này bạn có thể apply thêm các điều kiện khác vào relationship query đó

Ví dụ: Đối với relationship posts của User

app/Models/User.php

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

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

Lúc này mình có thể bổ sung điều kiện vào query relationship posts của user.

use App\Models\User;

$user = User::find(1);
// Lấy ra các post có active=1 của user đó. 
$user->posts()->where('active', 1)->get();

Vấn đề với orWhere

Có 1 số trường hợp sử dụng orWhere có thể sẽ làm câu query của bạn bị sai

Ví dụ: Lấy tất cả các posts của user có active=1 hoặc votes>=100

use App\Models\User;

$user = User::find(1);
$user->posts()
        ->where('active', 1)
        ->orWhere('votes', '>=', 100)
        ->get();

Câu lệnh trên tương ứng với

select * from posts
where user_id = 1 and active = 1 or votes >= 100

Lúc này bạn có thể thấy điều kiện bắt buộc “user_id = 1” sẽ không đúng nữa, vì nó bị điều kiện or kia ghi đè

Để fix lỗi này thì mọi người cần group điều kiện where kia vào

use App\Models\User;
use Illuminate\Database\Eloquent\Builder;

$user = User::find(1);
$user->posts()
        ->where(function (Builder $query) {
            return $query->where('active', 1)
                         ->orWhere('votes', '>=', 100);
        })
        ->get();

Câu lệnh SQL sẽ như sau:

select * from posts where user_id = ? and (active = 1 or votes >= 100)

Điệu kiện “user_id = 1” vẫn sẽ là bắt buộc

1.2. Gọi relationship như property

Khi bạn thực hiện việc gọi relationship như một thuộc tính trong eloquent model thì laravel sẽ thực thi query dữ liệu đó với database cho bản ghi tương ứng. Bạn có thể hiểu khi chúng ta gọi relationship dưới dạng thuộc tính thì bằng với cách gọi relationship như một phương thức không apply thêm điều kiện gì

$user = User::find(1)->post;
// Tương đương với
$user = User::find(1)->post()->get();

2. Truy vấn với relationship

Khi bạn sử dụng relationship, bạn cũng có thể sử dụng kết quả của nó để filter data một cách tiện lợi với phương thức has hoặc orHas

Ví dụ:

use App\Models\Post;

// Lấy ra các post có ít nhất một bình luận
$posts = Post::has('comments')->get();

// Lấy ra các post có ít nhất 3 comment
$posts = Post::has('comments', '>=', 3)->get();

Trong một số trường hợp bạn muốn thêm nhiều điều khiện khác vào trong câu lệnh của relationship, bạn có thể sử dụng whereHas hoặc orWhereHas

use Illuminate\Database\Eloquent\Builder;

// Lấy ra post có it nhất một bình luận, content có từ "code*".
$posts = Post::whereHas('comments', function (Builder $query) {
    $query->where('content', 'like', 'code%');
})->get();

// Lấy ra post có it nhất 10 bình luận, content có từ code*.
$posts = Post::whereHas('comments', function (Builder $query) {
    $query->where('content', 'like', 'code%');
}, '>=', 10)->get();

Đôi lúc bạn cần lấy ra các dữ liệu mà không tồn tại bản ghi nào matching với relationship, bạn có thể sử dụng phương thức doesntHave hoặc orDoesntHave.

Ví dụ: Lấy ra các posts không có comments

use App\Models\Post;

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

Cũng tương tự, bạn có thể sử dụng phương thức whereDoesntHave hoặc orWhereDoesntHave để thêm các điều kiện vào truy vấn relationship

Ví dụ: Lấy ra các post không có comment nào có nội dung chứa từ “code*

use Illuminate\Database\Eloquent\Builder;

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

3. Aggregate Related Models

# withCount

Đếm số lượng bản ghi của relation mà không cần phải gọi đến model đó. Khi sử dụng hàm withCount thì laravel sẽ thêm một attribute với tên có pattern “{relation}_count” vào trong  kết quả của model

Ví dụ: Lấy ra số lượng comment của post

use App\Models\Post;

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

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

Bạn cũng có thể sử dụng withCount với nhiều relation một lúc bằng cách truyền vào một mảng relation

use Illuminate\Database\Eloquent\Builder;

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

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

Hoặc bạn cũng có thể sử dụng alisas cho relation count

Illuminate\Database\Eloquent\Builder;

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

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

Trong một số trường hợp model cha của bạn đã được load sẵn rồi, bạn có thể sử dụng phương thức loadCount để lấy ra số lượng bản ghi của relation đó

$book = Book::first();
$book->loadCount('genres');

Tương tự bạn cũng có thể truyền thêm điều kiện vào trong relation query

$book->loadCount(['reviews' => function ($query) {     
    $query->where('rating', 5); 
}]);

Nếu bạn sử dụng phương thức withCount kết hợp với phương thức select, bạn cần phải gọi phương thức select trước khi gọi phương thức withCount thì nó mới hoạt động

$posts = Post::select(['title', 'body'])
    ->withCount('comments')
    ->get();

# withMin, withMax, withAvg, withSum

Các phương thức withMinwithMaxwithAvg và withSum dungf để lấy ra giá trị nhỏ nhất, lớn nhất, trung bình và tổng của dữ liệu trong relation với cú pháp:

withName($relation, $column);

Trong đó:

  • withName là một trong các phương thức withMin, withMax, withAvg hoặc withSum.
  • $relation là tên của relation các bạn muốn thực hiện query.
  • $column là column các bạn muốn query.

Khi thực hiện phương thức trên, Laravel sẽ thêm một attribute với pattern “{relation}_{function}_{column}” chứa dữ liệu của query đó

Ví dụ: Lấy ra tổng số lượng votes của post

use App\Models\Post;

$posts = Post::withSum('comments', 'votes')->get();

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

Tương tự bạn cũng có thể sử dụng hàm load để lấy ra các giá trị của relation query khi bạn đã có sẵn model instance

post = Post::first();

$post->loadSum('comments', 'votes');

 

Leave a Comment