Toidicode.com

Toidicode.com

BASIC TO ADVANCE

Bài 34: Eloquent ORM relationship trong Laravel 8 (Phần 2)

Tiếp tục với bài trước, sau khi đã định nghĩa được các mối quan hệ trong Eloquent Model bài này mình sẽ giới thiệu với mọi người 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  method với property.

Như mình đã nói ở bài trước: "chúng ta hoàn toàn có thể gọi một relationship như một methos hoặc như một thuộc tính trong model".

Gọi relationship 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 đó.

VD: Đố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

Tuy nhiên khi bạn cần thêm các câu lệnh orWhere vào trong relationship thì bạn cần phải chú ý. Vì khi bạn sử dụng orWhere có thể sẽ làm câu query của bạn bị sai.

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

Ví dụ trên sẽ generate ra câu lệnh SQL tương ứng như sau (bạn có thể sử dụng phương thức toSql để parser câu lệnh của mình):

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.

VD: Fix lỗi orWhere ở trên.

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

Lúc này 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.

Gọi relationship như thuộc tính.

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ì.

VD:

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

VD: Lấy ra các post có ít nhất một bình luận.

use App\Models\Post;

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

Bạn cũng có thể apply các điều kiện khác vào phương thức has.

VD: 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.

VD:

use Illuminate\Database\Eloquent\Builder;

// Lấy ra post có it nhất một bình luận có 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 có 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.

VD: 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.

VD: 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.

Eloquent cũng hỗ trợ các bạn cũng aggregate relation query một cách đơn giản.

withCount

Các bạn có thể sử dụng phương thức 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.

VD: 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

VD:

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.

VD:

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 đó.

VD:

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

VD:

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

withMin, withMax, withAvg, withSum

Bạn có thể sử dụng các phương thức withMin, withMax, withAvgwithSum để 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 đó.

VD: 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.

VD:

post = Post::first();

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

4. Lời kết.

Như vậy phần này mình đã giới thiệu với mọi người về query relation trong Eloquent model rồi. Tuy nhiên còn một phần nữa rất là quan trọng liên quan đến performance của ứng dụng khi sử dụng eloquent ORM mình sẽ trình bài vào bài sau.

Đăng ký nhận tin.

Chúng tôi chỉ gửi tối đa 2 lần trên 1 tháng. Tuyên bố không spam mail!

Vũ Thanh Tài

About author
The best way to learn is to share
Xem tất cả bài đăng

2 Comments

1 like cho người anh chăm chỉ

Luân

3 năm trước

Cảm ơn anh vì đã share, em đang học laravel, bài viết của anh rất tâm huyết nhưng em đọc vẫn hơi khó hiểu. Nếu được anh hãy ví dụ thực tế và sát hơn nữa ở các bài viết sau được ko ạ, em cảm ơn

Hải

3 năm trước

Bình luận

Captcha