# Eloquent: Relationships trong Laravel (Phần 1)

### 1. Giới thiệu <a href="#id-1-gioi-thieu-0" id="id-1-gioi-thieu-0"></a>

Trong 1 project, các đối tượng luôn có mối quan hệ với nhau. Các bảng trong database cũng được liên kết với nhau. Ví dụ 1 project ecommerce, 1 người có thể mua nhiều đơn hàng, mỗi đơn hàng có nhiều sản phẩm khác nhau. Ở phần này, mình sẽ giới thiệu các mối quan hệ cơ bản và cách sử dụng nó trong Laravel.

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

### 2. Các quan hệ cơ bản <a href="#id-2-cac-quan-he-co-ban-1" id="id-2-cac-quan-he-co-ban-1"></a>

#### 2.1. One to one (1 - 1) <a href="#id-21-one-to-one-1---1-2" id="id-21-one-to-one-1---1-2"></a>

Đây là quan hệ rất cơ bản. Ví dụ 1 User có 1 địa chỉ xác định. Ta có 2 model là User và Address.

Để lấy ra address của user, ta viết function `address()` trong class model `User`, sử dụng method `hasOne()`. (1 user chỉ có 1 address)

```none
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    /**
     * return the user's address.
     */
    public function address()
    {
        return $this->hasOne(Address::class);
    }
}
```

Tham số đầu tiên truyền vào `hasOne()` là tên của model được liên kết. Ở ví dụ trên, khóa ngoại của quan hệ được đặt mặc định là `user_id`, tương ứng với cột `id` của bảng users. Nếu khóa ngoại của bạn khác, bạn cần truyền vào tham số thứ 2 là tên khóa ngoại tương ứng.

```none
 return $this->hasOne(Address::class, 'foreign_key');
```

Ngoài ra, mặc định giá trị khóa ngoại tương ứng với cột id trong quan hệ cha. Nếu quan hệ của bạn xác định qua 1 trường khác, bạn phải truyền vào tham số thứ 3 là tên cột tham chiếu.

```none
return $this->hasOne(Address::class, 'foreign_key', 'local_key');
```

Sau khi quan hệ này được xác định, bạn có thể gọi nó như 1 thuộc tính được định nghĩa trên model.

```none
$address = User::find(1)->address;
```

#### **Inverse** <a href="#inverse-3" id="inverse-3"></a>

Vậy là bạn đã lấy được địa chỉ của 1 user. Vậy nếu bạn có địa chỉ và muốn lấy ra user có địa chỉ đó thì làm cách nào? Để lấy ra user tương ứng với 1 địa chỉ, ta viết function `user()` trong class model `Address`, sử dụng method `belongsTo()`. (1 address thuộc về 1 user)

```none
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Address extends Model
{
    /**
     * Return user 
     */
    public function user()
    {
        return $this->belongsTo(User::class);
    }
}
```

Tương tự như phương thức `hasOne()`, bạn có thể truyền vào tham số thứ 2 và thứ 3 tương ứng với tên khóa ngoại và tên cột tham chiếu từ quan hệ cha.

```none
return $this->belongsTo('App\User', 'foreign_key', 'other_key');
```

Sau khi quan hệ này được xác định, bạn có thể gọi nó như 1 thuộc tính được định nghĩa trên model.

```none
$address = Address::find(1)->user;
```

#### 2.2. One To Many (1 - n) <a href="#id-22-one-to-many-1---n-4" id="id-22-one-to-many-1---n-4"></a>

Quan hệ 1 - n xác định khi 1 model của có quan hệ với nhiều model khác. Ví dụ 1 `user` sẽ có nhiều đơn hàng `order`. Để lấy ra các đơn hàng của 1 user, ta sẽ viết 1 function `orders()` trong model User, sử dụng method `hasMany()` (1 người có nhiều đơn hàng). Vì kết quả trả về gồm nhiều đơn hàng nên tên function nên đặt số nhiều nhé.

```none
<?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');
    }
}
```

Giống với method hasOne(), bạn có thể tuyền vào tham số thứ 2 `foreign_key` và tham số thứ 3 `local_key` để xác định khóa tương ứng trong quan hệ.

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

Sau khi quan hệ được xác định, bạn có thể gọi nó như 1 thuộc tính của model.

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

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

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

Bạn cũng có thể thêm các điều kiện truy vấn vào sau hàm quan hệ. Ví dụ:

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

#### **Inverse** <a href="#inverse-5" id="inverse-5"></a>

Vậy là chúng ta đã lấy được các đơn hàng của user. Vậy nếu có 1 đơn hàng, có thể tìm ra user đặt đơn hàng đó không? Tất nhiên có thể. Chúng ta sẽ dùng method `belongsTo()` giống như quan hệ 1 - 1.

```none
<?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ư quan hệ 1 - 1, bạn có thể truyền vào tham số thứ 2 và thứ 3 để xác định foreign\_key và local\_key.

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

Và sử dụng nó như 1 thuộc tính của model

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

#### 2.3. Many To Many (n - n) <a href="#id-23-many-to-many-n---n-6" id="id-23-many-to-many-n---n-6"></a>

Đây là quan hệ phức tạp hơn 2 quan hệ trước. Ví dụ như 1 đơn hàng có nhiều sản phẩm, 1 sản phẩm có thể có trong nhiều đơn hàng. Vì vậy, khi thiết kế databse, chúng ta cần có 3 bảng cho mối quan hệ này là `orders`, `products` và `order_product`. Bảng `order_product` là bảng trung gian giữa sản phẩm và đơn hàng, sẽ lưu 2 trường giá trị là `order_id` và `products_id`. Tên của bảng này được đặt theo thứ tự bảng chữ cái của tên 2 quan hệ chính. Đầu tiên, để xác định 1 order có nhiều product, ta sẽ viết 1 function `products()` (tên nên đặt số nhiều vì kết quả chứ nhiều product), sử dụng method `belongsToMany()`.

```none
<?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 các method của quan hệ trước, method `belongsToMany()` có các tham số truyền vào khác 1 chút. Tham số thứ 1 là tên của model có quan hệ. Tham số thứ 2 là tên bảng tung gian giữa 2 quan hệ. Ở ví dụ của mình, bảng trung gian là bảng `order_product`. Tham số thứ 3 là tên khóa ngoại của model bạn đang sử dụng (ở đây là `order_id`). Tham số thứ 4 là tên khóa ngoại của model có quan hệ (ở đây là `product_id`).

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

Để lấy ra các sản phẩm trong đơn hàng, bạn có thể gọi nó như 1 thuộc tính của model. Kết quả trả về là 1 colection và bạn có thể sử dụng vòng lặp để xử lý nó. (giống như các quan hệ trước thôi ![😃](https://twemoji.maxcdn.com/v/14.0.2/72x72/1f603.png))))

```none
$order = Order::find(1);

foreach ($order->products as $product) {
    //xử lý kết quả.
}
```

#### **Inverse** <a href="#inverse-7" id="inverse-7"></a>

Sau khi lấy được các sản phẩm trong đơn hàng, chúng ta có thể lấy các đơn hàng có chứa sản phẩm đó. Bạn có thể viết 1 function `orders()` trong model `Product`, sử dụng method `belongsToMany()` và truyền vào các tham số tương ứng nhé.

```none
<?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);
    }
}
```

hoặc truyền vào tham số

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

#### **Pivot** <a href="#pivot-8" id="pivot-8"></a>

Ở quan hệ **Many to many** chúng ta có thêm bảng trung gian (ở ví dụ là order\_product). Bảng này thể hiện mối quan hệ giữa 2 đối tượng của model. Ngoài việc lưu id, bạn có thể dùng bảng này để lưu lại 1 vài thuộc tính khác. Ở đây mình lấy ví dụ là thời gian thêm product vào order. Vậy chúng ta có thể tác động vào bảng này được không và làm như thế nào?

Eloquent cung cấp một số cách tương tác rất hữu ích với bảng này. Sau khi tạo quan hệ, chúng ta có thể truy cập vào bảng trung gian bằng thuộc tính `pivot`.

```none
$order = Order::find(1);

foreach ($order->products as $product) {
    echo $product->pivot->created_at;
}
```

Mặc định thì `pivot` chứa các key của 2 bảng có quan hệ. Nếu bảng trung gian của bạn có nhiều thuộc tính khác, bạn có thể khai báo nó bằng cách dùng method `withPivot` khi xác định quan hệ.

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

Nếu bạn muốn pivot tự động trả về timestamp thì bạn có thể dùng method `withTimestamps` khi xác định quan hệ.

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

Nếu bạn muốn thay đổi tên pivot để phù hợp với mục đích của bảng trung gian, hay đơn giản bạn không thích cái tên pivot, bạn cũng có thể thay đổi tên này. Với ví dụ của mình, mình muốn đổi pivot thành contain, mình sẽ làm như thế này:

```none
return $this->belongsToMany(Product::class)
                ->as('contain')
                ->withTimestamps();
```

Khi sử dụng, thay vì `->pivot`, mình có thể `->contain`. Thế thôi.

Còn 1 cách để tương tác với bảng trung gian nữa nhưng mình sẽ mói ở những phần sau nhé.

### Tổng kết nhẹ <a href="#tong-ket-nhe-9" id="tong-ket-nhe-9"></a>

Tưởng không dài mà dài không tưởng. Mình định viết thêm mà thôi để giành phần sau nhé. Mong là nó giúp ích cho các bạn. Đây là phần 1 nên toàn kiến thức đơn giản, các bạn hãy đón đợi phần 2 nhé.

Cảm ơn các bạn.

#### Tài liệu tham khảo <a href="#tai-lieu-tham-khao-10" id="tai-lieu-tham-khao-10"></a>

<https://laravel.com/docs/6.x/eloquent-relationships>
