X

Eloquent: relationship trong Laravel - Phần 2

 

Querying Relations trong laravel là gì

Vì tất cả các mối quan hệ của Eloquent được định nghĩa qua các function, bạn có thể gọi những function để có được một thể hiện của mối quan hệ mà không thực sự thực hiện các truy vấn về quan hệ. Ngoài ra, tất cả các loại của các mối quan hệ Eloquent cũng phục vụ truy vấn, cho phép bạn tiếp tục hạn chế chuỗi vào truy vấn relationship cuối cùng trước khi SQL thi hành trong cơ sở dữ liệu của bạn.

Ví dụ, hãy tưởng tượng khi bạn thiết kế web nhà hàng, trong đó mỗi User model có nhiều Post model liên quan.

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

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

Bạn có thể truy vấn các posts trong mối quan hệ và thêm các giằng buộc cho relationship như sau:

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

$user->posts()->where('active', 1)->get();

Bạn có thể sử dụng bất kỳ query builder methods nào trong relationship.

Relationship Methods Với Dynamic Properties

Nếu bạn không cần phải thêm vào các giằng buộc cho 1 truy vấn Eloquent relationship, bạn có thể truy cập vào 1 relationship như là 1 property. Ví dụ, tiếp tục sử dụng UserPost model, chúng ta có thể truy cập đến tất cả các posts của user như sau:

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

foreach ($user->posts as $post) {
    //
}

Dynamic properties là "lazy loading", có nghĩa là nó sẽ chỉ tải dữ liệu của relationship khi bạn thực sự truy cập chúng. Bởi vì điều này, các developers thường sử dụng eager loading để truy cập vào các relationship. Eager loading cung cấp một sự giảm đáng kể trong các truy vấn SQL mà cần để thực hiện tải các relationship của model.

Querying Relationship Existence

Khi truy cập vào các record của 1 model, bạn muốn giới hạn kết quả của bạn dựa trên sự tồn tại của các relationship. Ví dụ, hãy tưởng tượng bạn muốn lấy tất cả các bài viết blog mà có ít nhất 1 comment. Để làm như vậy, bạn có thể vượt qua tên của các relationship với has method:

// Retrieve all posts that have at least one comment...
$posts = App\Post::has('comments')->get();

Bạn cũng có thể chỉ định 1 operator và đếm để tùy chỉnh cho truy vấn:

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

Các câu lệnh lồng nhau cũng có thể được xây dựng bằng các dấu ".", Ví dụ, bạn có thể lấy hết các posts mà có ít nhất 1 comment và vote:

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

Nếu bạn cần nhiều hơn nữa, bạn có thể sử dụng các method whereHasorWhereHas đặt "where" trong điều kiện query của bạn. Những method này cho phép bạn thêm các ràng buộc tùy chỉnh cho 1 relationship, chẳng hạn như kiểm tra nội dung của 1 comment:

// Retrieve all posts with at least one comment containing words like foo%
$posts = Post::whereHas('comments', function ($query) {
    $query->where('content', 'like', 'foo%');
})->get();

Querying Relationship Absence

Khi truy cập vào các record của 1 model, bạn muốn giới hạn kết quả dựa trên sự vắng mặt của 1 relationship. Ví dụ, hãy tưởng tượng bạn muốn lấy tất cả các posts mà không có bất kỳ comment nào, Để làm như vậy, bạn có thể sử dụng method doesntHave.

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

Nếu bạn cần nhiều hơn nữa, bạn có thể sử dụng method whereDoesntHave đặt "where" trong điều kiện query của bạn. Method này cho phép bạn thêm các ràng buộc tùy chỉnh cho 1 relationship, chẳng hạn như kiểu tra nội dung của 1 comment:

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

Counting Related Models

Nếu bạn muốn đếm số lượng các kết quả từ 1 relationship mà không load chúng, bạn có thể sử dụng withCount method, bạn sẽ đặt cột {relation}_count trên result model của bạn. Ví dụ:

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

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

Bạn có thể thêm "counts" cho nhiều relations cũng như thêm ràng buộc cho các truy vấn:

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

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

Eager Loading

Khi truy cập vào Eloquent relationship như property, các dữ liệu relationship là "lazy loaded". Điều này có nghĩa là các dữ liệu relationship không thực sự được load cho đến khi bạn truy cập vào property. Tuy nhiên, Eloquent có thể "eager load" các relationship vào thời điểm bạn truy vấn vào parent model. Eager loading làm giảm bớt các vấn đề truy vấn N+1. Để minh họa vấn đề truy vấn N+1, ta có Book model có liên quan đến Author model:

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

Bây giờ, chúng ta hãy lấý tất cả các books và author của nó:

$books = App\Book::all();

foreach ($books as $book) {
    echo $book->author->name;
}

Vòng lặp này sẽ thực hiện 1 truy vấn để lấy ra tất cả các books trên bàn, sau đó 1 truy vấn cho từng book để lấy ra author. Vì vậy, nếu chúng ta có 25 books, vòng lặp này sẽ chạy 26 truy vấn: 1 truy vấn để lấy ra các books và 25 truy vấn để lấy ra author cho các books.

Rất may, chúng ta có thể sử dụng eager loading để giảm thiểu số truy vấn này chỉ còn 2 truy vấn. Khi truy vấn, bạn có thể chỉ định các relationship được load bằng with method:

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

foreach ($books as $book) {
    echo $book->author->name;
}

Với cách này, chỉ có 2 câu truy vấn được thực thi:

select * from books

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

 

Eager Loading Multiple Relationships

Đôi khi bạn có thể cần phải eager load nhiều mối quan hệ khác nhau, Để làm như vậy, chỉ cần thêm argument cho with method:

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

Nested Eager Loading

Để eager load các mối quan hệ lồng nhau, bạn có thể sử dụng dấu ".". Ví dụ, hãy eager load tất cả các author của các books và tất cả các contact của author trong 1 Eloquent statement:

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

Constraining Eager Loads

Đôi khi bạn có thể muốn eager load 1 relationship, nhưng cũng thêm 1 số ràng buộc cho câu truy vấn eager loading. Dưới đây là 1 ví dụ:

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

Trong ví dụ này, Eloquent sẽ chỉ eager load các posts với điều kiện cột title của nó có chứa chữ "first". Dĩ nhiên, bạn có thể gọi các query builder method khác cho tùy chỉnh của bạn.

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

Lazy Eager Loading

Đôi khi bạn có thể cần phải eager load 1 relationship theo parent model đã được lấy ra. Ví dụ, điều này có thể hữu ích nếu bạn cần phải tự động quyết định để load các model liên quan:

$books = App\Book::all();

if ($someCondition) {
    $books->load('author', 'publisher');
}

Nếu bạn cần phải thiết lập các truy vấn rằng buộc vào các truy vấn eager loading, bạn có thể vượt qua một mảng mới quan hệ bạn muốn load. Các giá trị mảng nên là thể hiện của Closure:

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

Inserting & Updating Related Models

The Save Method

Eloquent cung cấp phương thức thuận tiện cho việc thêm các models tới 1 relationships. Ví dụ, có lẽ bạn cần phải chèn thêm 1 Comment cho 1 Post model. Thay vì tự thiết lập attribute post_id vào Comment, bạn có thể chèn Comment trực tiếp từ method save của relationships:

$comment = new App\Comment(['message' => 'A new comment.']);

$post = App\Post::find(1);

$post->comments()->save($comment);

Chú ý rằng chúng ta đã không truy cập comments relationships như một thuộc tính động (dynamic property). Thay vào đó, chúng ta gọi comments method để có được một thể hiện của relationships. Phương thức save sẽ tự động thêm giá trị post_id phù hợp với Comment model.

Nếu bạn cần save nhiều models có liên quan, bạn có thể sử dụng saveMany method:

$post = App\Post::find(1);

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

The Create Method

Ngoài savesaveMany methods, bạn có thể sử dụng create method, mà chấp nhận 1 mảng các thuộc tính, tạo ra 1 model và chèn nó vào cơ sở dữ liệu. Một lần nữa, sự khác biệt giữa savecreatesave chấp nhận một thể hiện của Eloquent model đầy đủ trong khi create chấp nhận 1 mảng PHP:

$post = App\Post::find(1);

$comment = $post->comments()->create([
    'message' => 'A new comment.',
]);

Belongs To Relationships

Khi cập nhật 1 belongsTo relationship, bạn có thể sử dụng associate method. Method này sẽ set foreign key trên model con.

$account = App\Account::find(10);

$user->account()->associate($account);

$user->save();

Khi loại bỏ belongsTo relationship, bạn có thể sử dụng dissociate method. Method này sẽ thiết lập các foreign key thành null.

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

$user->save();

Many To Many Relationships

Eloquent cũng cung cấp cấp thêm một vài helper method để làm việc với các model liên quan 1 cách thuận tiện. Ví dụ, hãy tưởng tượng 1 user có thể có nhiều roles và 1 role có thể có nhiều users. Để gắn 1 role cho 1 user bằng cách chèn 1 bản ghi trong bảng trung gian, sử dụng attach method:

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

$user->roles()->attach($roleId);

Khi gắn 1 relationship với 1 model , bạn cũng có thể thêm vào 1 mảng dữ liệu bổ sung sẽ được chèn vào bảng trung gian:

$user->roles()->attach($roleId, ['expires' => $expires]);

Dĩ nhiên, đôi khi nó có thể là cần thiết để loại bỏ 1 role từ 1 user. Để loại bỏ 1 quan hệ many-to-many record, sử dụng detach method. Phương thức detach sẽ loại bỏ các bản ghi phù hợp ra khỏi bảng trung gian. Tuy nhiên, cả 2 model vẫn còn lại trong cơ sở dữ liệu.

// Detach a single role from the user...
$user->roles()->detach($roleId);

// Detach all roles from the user...
$user->roles()->detach();

Để thuận tiện, attachdetach cũng chấp nhận đầu vào là mảng:

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

$user->roles()->detach([1, 2, 3]);

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

Syncing Associations

Bạn cũng có thể sử dụng sync method để xây dựng many-to-many associations. Phương thức sync chấp nhận 1 mảng các ID để đặt trên các bảng trung gian. Bất kỳ ID mà không phải là trong mảng sẽ được đưa ra khỏi bảng trung gian. Vì vậy, sau khi hoạt động này hoàn tất, chỉ có các ID có trong mảng sẽ tồn tại trong bảng trung gian.

$user->roles()->sync([1, 2, 3]);

Bạn cũng có thể thêm giá trị bảng trung gian với các ID:

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

Nếu bạn không muốn detach ID đang tồn tại, bạn có thể sử dụng syncWithoutDetaching method.

$user->roles()->syncWithoutDetaching([1, 2, 3]);

Saving Additional Data On A Pivot Table

Khi làm việc với 1 mối quan hệ many-to-many, save method chấp nhận 1 mảng của bảng trung gian thêm thuộc tính như là đối số thứ 2 của nó.

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

Updating A Record On A Pivot Table

Nếu bạn cần update 1 hàng hiện có trong pivot table của bạn, bạn có thể sử dụng phương thức updateExistingPivot. Phương thức này chấp nhận các pivot record foreign key và 1 mảng các thuộc tính để update:

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

$user->roles()->updateExistingPivot($roleId, $attributes);

Như vậy là chúng ta đã tìm hiểu xong về Relationship trong Eloquent rồi.