Eloquent Relationships in Laravel 5.3 (Chap 2)
Bài đăng này đã không được cập nhật trong 8 năm
Index
Mở đầu
Khi có nguyên liệu đầy đủ và một quyển công thức dạy nấu ăn thì chắc chắn bạn sẽ làm được món ăn ngon một cách dễ dàng. Eloquent Relationships trong Laravel cũng thế, khi các bảng đã được nối với nhau, và có hướng dẫn cách truy vấn đến từng bảng thì việc lấy dữ liệu trong database trở nên đơn giản hơn bao giờ hết. Ở bài trước mình đã cung cấp cho các bạn định nghĩa về
relationships
, thế nên hôm nay bằng những kinh nghiệm của bản thân (mặc dù chưa có kinh nghiệm nhiều =))), mình xin đưa ra công thức đểquerying relations
.
Querying Relations
Khi tất cả các kiểu của quan hệ Eloquent được định nghĩa qua các hàm, bạn có thể gọi những hàm này để lấy những instance của quan hệ mà không cần thực thi các truy vấn quan hệ. Tất cả các kiểu quan hệ của Eloquent cũng có tại query builders
thế nên bạn hoàn toàn có thể gán thêm các ràng buộc trên các truy vấn.
Tiếp tục với ví dụ quan hệ giữa 1 Area - n Stations:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Area extends Model
{
/**
* Get all of the stations for the area.
*/
public function stations()
{
return $this->hasMany('App\Station');
}
}
Bạn có thể truy vấn quan hệ stations
và thêm các quan hệ như sau:
$area = App\Area::find(1);
$area->stations()->where('departure_time', '<', 10)->get();
Đoạn code trên dùng để lấy tất cả các nhà ga có giờ khởi hành trước 10h của vùng có id là 1.
Note
: Hãy nhớ rằng bạn có thể sử dụng bất kì phương thức query builders
trên các mối quan hệ để việc truy vấn trở nên đơn giản hơn.
Relationship Methods Vs. Dynamic Properties
Ở bài trước mình có nhắc rất nhiều đến Eloquent dynamic properties
như là $area->stations
, nhưng nếu bạn tìm trong model Area
bạn sẽ chẳng thể tìm thấy một properties nào tên là stations
(haha).
Nghe có vẻ thần kì, kì thực là Laravel sử dụng các Magic Method của PHP để làm cho việc gọi stations
trở nên hợp lệ và nó sẽ trả về kết quả của quan hệ stations
(tương đương với $area->stations->get()
).
Eloquent dynamic properties
chính là lazy loading
nghĩa là chúng chỉ load dữ liệu quan hệ của chúng khi bạn thực sự gọi.
Bởi vì điều này, lập trình viên thường sử dụng Eager loading
để pre-load các quan hệ mà họ biết sẽ được xử lý sau khi load model. Eager loading cung cấp một sự giảm bớt đáng kể trong các truy vấn SQL mà sẽ phải thực thi khi load các quan hệ của model.
Querying Relationship Existence
Khi truy cập các bản ghi một model, bạn có thể muốn giới hạn kết quả trả về dựa trên sự tồn tại của một quan hệ. Ví dụ, bạn muốn lấy tất cả các area
mà có ít nhất một house
. Để làm việc này, bạn phải truyền tên của quan hệ vào phương thức has
:
// Retrieve all areas that have at least one house...
$areas = App\Area::has('houses')->get();
Hoặc là bạn cũng có thể chỉ định thêm toán tử và số lượng vào truy vấn:
// Retrieve all areas that have one-hundered or more houses...
$posts = Post::has('houses', '>=', 100)->get();
Bạn cũng có thể lấy ra tất cả các areas
có tối thiểu 1 houses
và 1 stations
:
// Retrieve all houses that have at least one house and station...
$posts = Post::has('houses.stations')->get();
Nếu bạn vấn muốn có những truy vấn mạnh hơn, hãy sử dụng phương thức whereHas
và orWhereHas
để đặt các điều kiện where
bên trong truy vấn has
. Những phương thức này cho phép bạn thêm những ràng buộc tùy chọn, như là kiểm tra nội dung của 1 house
:
// Retrieve all areas with at least one houses containing house_number like %128A%
$areas = ::whereHas('houses', function ($query) {
$query->where('house_number', 'like', '%128A%');
})->get();
Querying Relationship Absence
Ngược lại với has
là doesntHave
có nghĩa là bạn sẽ lấy những kết quả trả về dựa trên sự không tồn tại của 1 quan hệ. Ví dụ như bạn muốn liệt kê những areas
không có station
nào thì bạn sẽ thực hiện đơn giản như sau:
// Retrieve all houses that doesnot have station...
$areas = App\Area::doesntHave('stations')->get();
Tương tự nếu bạn muốn truy vấn sâu hơn thì sử dụng phương thức whereDoesntHave
:
// Retrieve all areas doesnot have house containing house_number like %128A%
$areas = ::whereDoesntHave('houses', function ($query) {
$query->where('house_number', 'like', '%128A%');
})->get();
Counting Related Models
Nếu bạn muốn đếm số kết quả từ 1 quan hệ mà không muốn load chúng bạn có thể sử dụng phương thức withCount
, nó sẽ đặt một cột {relation}_count vào model kết quả. Ví dụ:
$areas = App\Area::withCount('houses')->get();
foreach ($areas as $area) {
echo $area->houses_count;
}
Bạn có thể lấy counts
cho nhiều quan hệ cũng giống như thêm ràng buộc vào truy vấn:
$areas = Ap\Area::withCount(['houses', 'stations' => function ($query) {
$query->where('name', 'like', '%A%');
}])->get();
//Retrieve the first area
echo $areas[0]->houses_count;
echo $areas[0]->stations_count;
Eager Loading
Khi bạn truy cập Eloquent relationships
như là các thuộc tính, các dữ liệu này là lazy loaded
. Điều này có nghĩa là dữ liệu không thực sự load cho đến khi bạn truy cập lần đầu tiên tới thuộc tính. Tuy nhiên, Eloquent có thể eager load
các quan hệ tại thời điểm bạn truy vấn vào model cha. Eager loading
làm giảm bớt vấn đề truy vấn N + 1. Để giải thích vấn đề truy vấn N + 1, ta xét model House
có quan hệ với model Area
:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class House extends Model
{
/**
* Get the area that contain the house.
*/
public function area()
{
return $this->belongsTo('App\Area');
}
}
Và bạn cần lấy toàn bộ houses
và các area
của chúng:
$houses = App\House::all();
foreach ($houses as $house) {
echo $house->area->name;
}
Vòng lặp sẽ thực hiện 1 truy vấn để lấy tất cả các houses
trong bảng, sau đó là mỗi truy vấn cho 1 house
để lấy area
. Vì vậy nếu chúng ta có 100 houses
, vòng lặp sẽ thực hiện 101 truy vấn: 1 để lấy house
, 100 để lấy area
của mỗi house.
Như các bạn đã biết việc thực hiện nhiều truy vấn làm cho hiệu suất của trang web giảm đáng kể. Nhưng rất may, chúng ta có thể sử dụng eager loading
để giảm thiểu tính toán chỉ với 2 truy vấn. Khi thực hiện truy vấn, bạn chỉ định quan hệ này sẽ được eager load
bằng cách dùng phương thức with
:
$houses = App\House::with('area')->get();
foreach ($houses as $house) {
echo $house->area->name;
}
Với tính toán này chỉ có 2 truy vấn sẽ được thực thi đó là:
select * from houses
select * from areas where id in (1, 2, 3, 4, 5, ...)
Eager Loading Multiple Relationships
Có những lúc bạn sẽ cần eager load
vài mối quan hệ khác nhau trong 1 tính toán. Để làm việc này, chỉ cần truyền thêm các tham số vào phương thức with
:
$houses = App\House::with('area', 'user')->get();
Nested Eager Loading
Để lồng các eager load
, bạn có thể sử dụng dấu "."
. Ví dụ eager load
tất cả area
và tất cả stations
trong một cú pháp Eloquent:
$houses = App\House::with('area.stations')->get();
Constraining Eager Loads
Đôi khi bạn có thể muốn eager load
một quan hệ, nhưng cũng muốn chỉ định những ràng buộc thêm vào trong truy vấn của eager load
như sau:
//Retrieve all houses have area contain the word 'Ha'
$houses = App\House::with(['areas' => function ($query) {
$query->where('name', 'like', '%Ha%');
}])->get();
Lazy Eager Loading
Có lúc bạn muốn eager load
một quan hệ sau khi model cha đã được lấy ra. Điều này có thể hữu ích nếu bạn muốn quyết định sẽ load model liên quan nào:
$houses = App\House::all();
if ($someCondition) {
$houses->load('area', 'user');
}
Nếu bạn muốn thêm các ràng buộc vào truy vấn của eager load
, bạn có thể truyền một Closure
vào phương thức load
:
$houses->load(['area' => function ($query) {
$area->orderBy('name', 'asc');
}]);
Bài này mình đã giới thiệu cho các bạn cách Querying Relations
, bài sau mình sẽ tiếp tục nói về việc Inserting & Updating Related Models
. Cảm ơn các bạn đã đón đọc! (lay)
Tham khảo Query Relations: https://laravel.com/docs/5.3/eloquent-relationships#querying-relations
All rights reserved