Eloquent: relationships in laravel-Phần 3
Bài đăng này đã không được cập nhật trong 8 năm
Phần 1: https://viblo.asia/trung.nn.92/posts/PdbGnoEdeyA
Phần 2: https://viblo.asia/trung.nn.92/posts/aRBvXWEokWE
Querying Relations
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 một hệ thống blog, 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 Vs. 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 User
và Post
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 whereHas
và orWhereHas
đặ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 save
và saveMany
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 save
và create
là save
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, attach
và detach
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. Hy vọng bài viết này sẽ giúp ích được cho các bạn! ^^
All rights reserved