Các Mối Quan Hệ Trong Eloquent Của Laravel

Các Mối Quan Hệ Trong Eloquent Của Laravel
Bài này sẽ giúp các bạn tìm hiểu về các mối quan hệ Eloquent trong Laravel. Các bạn cũng sẽ bước đầu học cách sử dụng chúng.

Trong một dự án, các đối tượng luôn có mối quan hệ với nhau. Thậm chí các bảng trong database cũng có sự liên kết. Ví dụ 1 project ecom, 1 người có thể mua nhiều đơn hàng. Mỗi đơn hàng của họ cũng có đa dạng sản phẩm trong các đơn hàng đó. Đây chính là các mối quan hệ. Eloquent trong Laravel cũng có các mối quan hệ qua lại. 

 

Các mối quan hệ căn bản 

 

Eloquent trong Laravel có nhiều mối quan hệ. Các bạn sẽ thấy có 6 mối quan hệ cơ bản dưới đây:

 

  • One to one (1 - 1)
  • One To Many (1 - n)
  • Many To Many (n - n)
  • Has One Through
  • Has Many Through
  • Polymorphic 

 

One to one (1 - 1)

 

Quan hệ 1-1 hoặc 1-n được xác định khi một model của nó có mối quan hệ với nhiều model khác. Ví dụ 1 người dùng thì có nhiều đơn đặt hàng. Để ta có thể lấy ra các đơn hàng của một người dùng, bạn sẽ viết 1 function orders() trong model của người dùng đó. Kế tiếp, bạn sử dụng method hasMany() (vì 1 người có nhiều đơn hàng). Kết quả trả về sẽ có nhiều đơn hàng nên tên function nên đặt ở số nhiều. 

 

<?php

namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
    /**
     * Trả về danh sách các đơn hàng của user
     */
    public function orders()
    {
        return $this->hasMany('Order::class');
    }
}

 

Tương tự với method hasOne(), bạn có thể thêm vào tham số thứ nhì foreign_key và tham số thứ 3 locale_key để xác định khóa tương ứng trong một mối quan hệ. Ví dụ: 

 

return $this->hasMany('Order::class', 'foreign_key', 'local_key');

 

Sau khi bạn có thể xác định được mối quan hệ, bạn có thể gọi nó là một thuộc tính của model. Ví dụ: 

 

$orders = User::find(1)->orders;

 

Kết quả trả về là 1 collection các đơn hàng của người dùng. Dùng foreach để xử lý kết quả đó. 

 

foreach ($orders as $order) {
    // xử lý kết quả
}

 

Thêm các điều kiện truy vấn vào sau hàm quan hệ: 

 

$order = User::find(1)->orders()->where('total', 500000)->first();

 

Vậy là các bạn đã lấy được các đơn hàng của người dùng. Nếu có 1 đơn hàng thì các bạn có thể tìm ra người đã đặt đơn hàng đó không? Có thể được. Các bạn dùng method belongsTo() tương tự như mối quan hệ 1-1. Ví dụ: 

 

<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Order extends Model
{
    /**
     * Trả về người đặt đơn hàng
     */
    public function post()
    {
        return $this->belongsTo(User::class);
    }
}

 

Tương tự như mối quan hệ 1-1, bạn có thể thêm vào tham số thứ 2 và 3 để xác định foreign_key và locale_key. Ví dụ: 

 

return $this->belongsTo(User::class, 'foreign_key', 'local_key');

 

Sử dụng nó như một thuộc tính của model. Ví dụ: 

 

$user = Order::find(1)->user;

 

One to Many (1-n) 

 

Mối quan hệ One to Many (1-n) được xác định khi 1 model của nó có liên quan đến nhiều model khác. Ta cũng xem tiếp ví dụ một người dùng có nhiều đơn đặt hàng. Để tìm và lấy ra các đơn hàng đó, các bạn sẽ viết 1 function orders() trong model user. Tiếp theo, bạn dùng method hasMany() vì một người có nhiều đơn hàng. Kết quả trả về cũng sẽ nhiều đơn hàng nên tên function cũng nên để ở số nhiều. Ví dụ: 

 

<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
    /**
     * Trả về danh sách các đơn hàng của user
     */
    public function orders()
    {
        return $this->hasMany('Order::class');
    }
}

 

Tương tự như method hasOne(), bạn có thể thêm tham số thứ nhì (foreign_key) và tham số thứ 3 (local_key) để xác định khóa tương ứng trong mối quan hệ. Ví dụ: 

 

return $this->hasMany('Order::class', 'foreign_key', 'local_key');
Sau khi mối quan hệ đã được xác định, bạn có thể gọi nó là một thuộc tính của model. Ví dụ:
$orders = User::find(1)->orders;

 

Kết quả trả về sẽ là 1 collection các đơn hàng của người dùng. Bạn dùng hàm foreach để xử lý kết quả. 

 

foreach ($orders as $order) {
    // xử lý kết quả
}

 

Thêm các điều kiện truy vấn vào sau hàm quan hệ thì bạn sẽ có: 

 

$order = User::find(1)->orders()->where('total', 500000)->first();

 

Vậy là bạn đã có được các đơn hàng của người dùng. Nếu có 1 đơn hàng, thì bạn có thể tìm ra người đã đặt hàng đó không? Bạn dùng hàm method belongsTo(), áp dụng tương tự như mối quan hệ 1-1. Ví dụ:

 

<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Order extends Model
{
    /**
     * Trả về người đặt đơn hàng
     */
    public function post()
    {
        return $this->belongsTo(User::class);
    }
}

 

Tương tự mối quan hệ 1-1, bạn có thể bổ sung tham số thứ 2 (foreign_key) và thứ 3 (local_key) để xác định rõ hơn. Ví dụ: 

 

return $this->belongsTo(User::class, 'foreign_key', 'local_key');

 

Sử dụng nó như một thuộc tính của model:

 

$user = Order::find(1)->user;

 

Many to Many (n-n)

 

Mối quan hệ Many to Many phức tạp hơn 2 mối quan hệ ở trên. Có 1 đơn hàng nhưng nhiều danh mục sản phẩm trong đó. 1 sản phẩm lại có nhiều đơn hàng. Do đó, các bạn cần có khoảng 3 bảng cho mối quan hệ này: orders, products và order_product khi thiết kế database.  Bảng order_product là bảng trung gian giữa sản phẩm và đơn hàng.

 

Chúng ta sẽ lưu 2 trường giá trị: order_id và products_id. Tên của bảng này được đặt theo thứ tự bảng chữ cái theo tên của 2 mối quan hệ chính. Để xác định 1 order có nhiều product, bạn sẽ viết 1 function products() (bạn nên để tên ở số nhiều vì kết quả sẽ chứa nhiều product). Ta sẽ viết 1 function products() và sử dụng hàm method belongsToMany(). Ví dụ: 

 

<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Order extends Model
{
    /**
     * Trả về các sản phẩm của đơn hàng
     */
    public function products()
    {
        return $this->belongsToMany(Product::class);
    }
}

 

Khác với method của hai mối quan hệ trên, method belongsToMany() nên chứa các tham số khác một chút. Tham số thứ nhất sẽ là tên của model có quan hệ. Tham số thứ nhì là tên bảng trung gian giữa hai mối quan hệ. 

 

Lấy ví dụ bảng trung gian là bảng order_product. Tham số thứ 3 sẽ là tên khóa ngoại của model mà bạn đang sử dụng (order_id). Tham số thứ 4 sẽ là tên khóa ngoại của model có mối quan hệ (product_id). Các bạn sẽ thực hiện dòng lệnh đầy đủ là:

 

return $this->belongsToMany(Product::class, order_product, order_id', 'product_id');

 

Để có thể lấy ra các sản phẩm trong đơn hàng, bạn có thể xem nó như là một thuộc tính của model. Kết quả trả về sẽ là 1 collection. Bạn dùng vòng lặp để xử lý nó (thao tác này cũng tương tự với 2 mối quan hệ ở trên). 

 

$order = Order::find(1);
foreach ($order->products as $product) {
    //xử lý kết quả.
}

 

Sau khi bạn đã lấy được các sản phẩm trong đơn hàng, bạn có thể lấy được luôn các đơn hàng có chứa sản phẩm đó. Bạn viết 1 function orders() trong model Product. Đừng quên sử dụng hàm method belongsToMany() và truyền vào các tham số tương ứng. Ví dụ:

 

<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Product extends Model
{
    /**
     * Trả về các đơn hàng có chứa sản phẩm
     */
    public function orders()
    {
        return $this->belongsToMany(Order::class);
    }
}

 

Bạn cũng có thể truyền vào tham số như thế này:

 

return $this->belongsToMany(Order::class, 'role_user', 'product_id', 'order_id');

 

Ở mối quan hệ Many to Many, bạn nên thêm bảng trung gian. Bảng trung gian thể hiện mối quan hệ giữa 2 đối tượng của model. Ngoài chuyện lưu id, bảng này cũng hỗ trợ bạn lưu một số thuộc tính khác. Ví dụ là lấy thời gian thêm product vào order. Bạn có thể tác động vào bảng này. Eloquent cung cấp một số cách tương tác hữu ích. Sau khi đã tạo mối quan hệ, bạn truy cập vào bảng trung gian bằng thuộc tính pivot. Ví dụ: 

 

$order = Order::find(1);
foreach ($order->products as $product) {
    echo $product->pivot->created_at;
}

 

Pivot chứa các key có 2 bảng mối quan hệ. Nếu bảng trung gian của bạn có nhiều thuộc tính khác nhau, bạn có thể khai báo nó bằng cách dùng method withPivot khi xác định mối quan hệ. Ví dụ:

 

return $this->belongsToMany(Product::class)->withPivot('column1', 'column2');

 

Nếu muốn pivot tự trả về timestamp, thì bạn dùng hàm method withTimestamps khi xác định mối quan hệ. Ví dụ:

 

return $this->belongsToMany(Product::class)->withTimestamps();

 

Khi muốn thay đổi tên pivot để phù hợp với mục đích dùng bảng trung gian, thì bạn có thể đổi tên. Ví dụ: 

 

return $this->belongsToMany(Product::class)
                ->as('contain')
                ->withTimestamps();
Thay vì dùng ->pivot, thì bạn chuyển thành ->contain. 

 

Has One Through 

 

Has One Through là mối quan hệ liên kết các bảng với nhau qua một bảng trung gian. Ví dụ: 

 

users
    id - integer
    supplier_id - integer
suppliers
    id - integer
history
    id - integer
    user_id - integer

 

Dù bảng history không có chứa supplier_id, nhưng các bạn vẫn có thể truy cập đến user’s history từ mối quan hệ Has One Through. 

 

<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Supplier extends Model
{
    public function userHistory()
    {
        return $this->hasOneThrough('App\History', 'App\User');
    }
}

 

Tham số thứ nhất truyền vào là tên của model mà bạn muốn truy cập. Tham số thứ nhì là model trung gian. Các bạn cũng có thể custom các key có liên quan đến mối quan hệ này. Chúng có thể lần lượt là các tham số vào sau hàm định nghĩa quan hệ. Ví dụ:

 

class Supplier extends Model
{
    public function userHistory()
    {
        return $this->hasOneThrough(
            'App\History',
            'App\User',
            'supplier_id', // Khóa ngoại của bảng trung gian user
            'user_id', // Khóa ngoại của bảng chúng ta muốn truy cập đến
            'id', // Khóa mà chúng ta muốn liên kết ở bảng supplier
            'id' // Khóa mà chúng ta muốn liên kết ở bảng user
        );
    }
}

 

Bảng user và history có định nghĩa như sau: 

 

// User.php
namespace App;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
    public function supplier()
    {
         return $this->belongsTo(Supplier::class);
    }
}
// Supplier.php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Supplier extends Model
{
    public function user()
    {
         return $this->hasOne(User::class);
    }
}

 

Has Many Through 

 

Mối quan hệ Has Many Through cho phép cập vào các mối quan hệ xa thông qua một mối quan hệ trung gian. Ví dụ một country model có thể có nhiều post model qua một user model trung gian. Bạn có thể lấy tất cả các blog post cho một country. 

 

countries
    id - integer
    name - string
users
    id - integer
    country_id - integer
    name - string
posts
    id - integer
    user_id - integer
    title - string

 

Dù post không chứa cột country_id, mối quan hệ Has Many Through có cung cấp quyền truy cập vào post của country thông qua $country->posts. Để thực hiện các truy vấn, Eloquent sẽ kiểm tra các country_id trên bảng user trung gian. Sau khi đã tìm ra id của user thích hợp, bạn dùng nó để truy vấn bảng posts. Xem các cấu trúc bảng cho các mối quan hệ và định nghĩa nó trên country model. 

 

<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Country extends Model
{
    /**
     * Get all of the posts for the country.
     */
    public function posts()
    {
        return $this->hasManyThrough('App\Post', 'App\User');
    }
}

 

Đối số đầu tiên truyền cho phương thức Has Many Through thường là tên của model cuối cùng mà các bạn muốn truy cập. Trong khi đó, đối số thứ nhì là tên của model trung gian. Nếu muốn tùy chỉnh các foreign_key của mối quan hệ, bạn nên truyền vào các đối số thứ 3 và thứ 4 của phương thức Has Many Through. Đối số thứ 3 là foreign_key của model trung gian. Đối số thứ 4 là foreign_key của model cuối cùng và đối số thứ 5 sẽ là locale_key. Ví dụ: 

 

class Country extends Model
{
    public function posts()
    {
        return $this->hasManyThrough(
            'App\Post', 'App\User',
            'country_id', 'user_id', 'id'
        );
    }
}

 

Polymorphic 

 

Polymorphic là mối quan hệ đa hình trong Laravel. Nó cho phép ta lập 1 model với nhiều belongsTo từ nhiều model khác và chỉ cần dùng 1 associate. 

 

One to One Polymorphic

 

Mối quan hệ này cũng tương tự 1-1. Tuy nhiên, mục đích khởi tạo mối quan hệ này là 1 model có thể belongsTo 1 hoặc nhiều model khác nhau. 

 

Ví dụ một bài post có một image và một product, thì bạn phải tạo 2 bảng (product_image để lưu hình của post và product_image để lưu hình của product). Nếu có nhiều hơn 2 bảng mà cần tới image thì bạn phải tạo thêm bấy nhiêu bảng để lưu hình như vậy. Khi thực hiện như vậy, bạn sẽ thấy rất rối. Đó là lý do mối quan hệ polymorphic hiện hữu. 

 

posts
    id - integer
    name - string
products
    id - integer
    name - string
images
    id - integer
    url - string
    imageable_id - integer
    imageable_type - string

 

One to Many Polymorphic

 

Mối quan hệ này cũng có sự tương đồng với mối quan hệ One to Many. Ví dụ 1 user có thể comment ở bên dưới bài post và video, thì bạn chỉ cần tạo 1 bảng comments. 

 

posts
    id - integer
    title - string
    body - text
videos
    id - integer
    title - string
    url - string
comments
    id - integer
    body - text
    commentable_id - integer
    commentable_type - string

 

Many to Many Polymorphic

 

Mối quan hệ này có chút phức tạp hơn. Ví dụ 1 post hoặc 1 video có nhiều tags. Khi sử dụng mối quan hệ Many to Many Polymorphic, bạn sẽ truy vấn và lấy ra được các tags thuộc về post hoặc video đang cần.

 

posts
    id - integer
    name - string
videos
    id - integer
    name - string
tags
    id - integer
    name - string
taggables
    tag_id - integer
    taggable_id - integer
    taggable_type - string 

 

Vạn sự đều có sự liên kết với nhau. Trong cơ sở dữ liệu hoặc trong Laravel cũng có sự liên kết như vậy. Laravel cho phép các bạn tạo các mối quan hệ của các hàm để tiết kiệm thời gian và công sức thực hiện. 

 

 

 

 

Hồ Hữu Hiền

Mình là developer nên đôi khi viết bài không hay lắm mong các bạn thông cảm. Nếu muốn biết thêm thông tin về mình thì vui lòng vào website này để biết. https://huuhienqt.dev/

Bình luận (0)