Eloquent Relationships in Laravel 5.3 (Chap 2)

Index

  1. Eloquent Relationships in Laravel 5.3 (Chap 1)

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 buildersthế 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 whereHasorWhereHas để đặ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 hasdoesntHave 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