NTM Solutions

Thứ Tư, 22 tháng 7, 2026

🚀 Laravel 12 (2026) — Bài 16 — Relationship Has Many Through

Sau khi đã học One To One, One To ManyMany To Many, chúng ta sẽ tìm hiểu thêm một kiểu Relationship đặc biệt của Eloquent là Has Many Through.

Kiểu quan hệ này cho phép truy cập các bản ghi ở bảng cuối cùng thông qua một Model trung gian mà không cần tự viết câu lệnh JOIN.


Has Many Through là gì?

Has Many Through là quan hệ một - nhiều - nhiều.

Ví dụ:

  • Một Country có nhiều User.

  • Một User có nhiều Post.

Suy ra:

  • Một Country có nhiều Post thông qua User.

Sơ đồ:

Country
   │
   └──── User
            │
            ├── Post
            ├── Post
            └── Post

Country không liên kết trực tiếp với Post.

Laravel sẽ đi qua bảng User.


Ví dụ cơ sở dữ liệu

Bảng countries

idname
1Việt Nam
2Nhật Bản

Bảng users

idnamecountry_id
1An1
2Bình1
3Ken2

Bảng posts

idtitleuser_id
1Laravel1
2PHP2
3MySQL3

Quan hệ:

Country
      ↓
    Users
      ↓
    Posts

1. Tạo Country Model

php artisan make:model Country -m

2. Tạo bảng countries

Schema::create('countries', function (Blueprint $table) {
    $table->id();
    $table->string('name');
    $table->timestamps();
});

3. Thêm country_id vào bảng users

Nếu bảng users đã tồn tại từ các bài trước:

php artisan make:migration add_country_id_to_users_table --table=users

Migration:

Schema::table('users', function (Blueprint $table) {
    $table->foreignId('country_id')
          ->nullable()
          ->constrained()
          ->nullOnDelete();
});

4. Quan hệ trong các Model

Country

public function posts()
{
    return $this->hasManyThrough(
        Post::class,
        User::class
    );
}

Ý nghĩa:

  • Bắt đầu từ Country.
  • Đi qua User.
  • Lấy tất cả Post.

User

public function country()
{
    return $this->belongsTo(Country::class);
}

📋 Chạy lệnh php artisan migrate

Tạo Country Seeder

php artisan make:seeder CountrySeeder

📋 Copy

CountrySeeder.php

use App\Models\Country;

public function run(): void
{
    Country::insert([
        [
            'name' => 'Việt Nam',
        ],
        [
            'name' => 'Nhật Bản',
        ],
        [
            'name' => 'Hoa Kỳ',
        ],
        [
            'name' => 'Hàn Quốc',
        ],
    ]);
}

📋 Copy


Đăng ký trong DatabaseSeeder

public function run(): void
{
    $this->call([
        CountrySeeder::class,
    ]);

    // các seeder khác...
}

UserFactory

'country_id' => Country::query()->inRandomOrder()->value('id'),

Khi đó chỉ cần:

User::factory()->count(10)->create();

Mỗi User sẽ được chọn Country ngẫu nhiên.

Chạy

php artisan migrate:fresh --seed

Lấy dữ liệu

/* PHẦN THÊM VÀO routes/web.php*/
use App\Models\Post;
use App\Models\Country;

Route::get('/test', function () {

    $country = Country::first();

    if (!$country) {
        return 'Chưa có dữ liệu Country.';
    }

    $posts = $country->posts;

Hoặc:

foreach ($country->posts as $post) {

    echo $post->title;
    echo " - ";
    echo $country->name;
    echo "<br>";

}  

Laravel sẽ tự tạo các câu lệnh JOIN cần thiết.


SQL tương đương

Laravel sẽ sinh câu lệnh có ý tưởng tương tự:

SELECT posts.*

FROM posts

JOIN users
ON posts.user_id = users.id

WHERE users.country_id = 1;

Bạn không cần tự viết SQL.


eager loading

Có thể tải trước:

$countries = Country::with('posts')->get();
if ($countries->isEmpty()) {
        return 'Chưa có dữ liệu Country.';
    }

Sau đó:

foreach ($countries as $country) {

     echo "<h3>Quốc gia:". $country->name . "</h3>";

     foreach ($country->posts as $post) {

       echo $post->title . "<br>";

     }

}

Điều này giúp giảm số lượng truy vấn.

Nếu muốn lấy luôn User của từng Post

Khi đó eager loading nên viết:

$countries = Country::with('posts.user')->get();

và:

foreach ($country->posts as $post) {

    echo $post->title;
    echo " - ";
    echo "<i>Tác giả: " . $post->user->name . "</i>";
    echo "<br>";

}

Đánh giá


Một ví dụ thực tế

Giả sử hệ thống bán hàng:

Customer
      │
      ▼
    Order
      │
      ▼
 OrderItem

Một Customer có nhiều Order.

Một Order có nhiều OrderItem.

Ta có thể khai báo:

public function orderItems()
{
    return $this->hasManyThrough(
        OrderItem::class,
        Order::class
    );
}

Khi đó:

$customer->orderItems;

sẽ trả về toàn bộ sản phẩm mà khách hàng đã đặt.


Khi nào nên dùng?

Has Many Through phù hợp khi:

  • Muốn truy cập dữ liệu gián tiếp.

  • Không muốn tự viết JOIN.

  • Muốn giữ mã nguồn ngắn gọn và dễ đọc.

Ví dụ:

  • Country → Posts.

  • Customer → OrderItems.

  • Company → Employees thông qua Department.

  • School → Students thông qua ClassRoom.


So sánh

RelationshipVí dụ
One To OneUser → Profile
One To ManyUser → Posts
Many To ManyPost ↔ Tags
Has Many ThroughCountry → Posts → User

Tổng kết

Trong bài này chúng ta đã học:

  • Khái niệm Has Many Through.

  • Truy cập dữ liệu thông qua Model trung gian.

  • Khai báo hasManyThrough().

  • Lấy dữ liệu bằng Eloquent.

  • with() để tối ưu truy vấn.

Đây là một Relationship mạnh mẽ giúp giảm đáng kể số lượng câu lệnh JOIN thủ công trong các dự án lớn.


Bài tập

  1. Tạo model Country.

  2. Thêm khóa ngoại country_id vào bảng users.

  3. Khai báo hasManyThrough() trong model Country.

  4. Hiển thị tất cả Post của Country có id = 1.

  5. Sử dụng with('posts') để lấy danh sách Country cùng các Post liên quan.


Bài tiếp theo

Ở bài sau, chúng ta sẽ tìm hiểu Polymorphic Relationship, một kiểu quan hệ linh hoạt cho phép nhiều Model khác nhau cùng chia sẻ một bảng dữ liệu, chẳng hạn như PostVideo đều có thể sử dụng chung bảng comments.

x1

quay về MỤC LỤC

Không có nhận xét nào:

Đăng nhận xét

Facebook Youtube RSS