Laravel Eloquent ORM: Relationships

Eloquent: Relationships

Hello anh em! đầu xuân năm mới viết bài khai xuân đây, tiện thể gửi lời chúc mừng năm mới đến tất cả anh em nào đang đọc bài của mình nhé 😃)) Trong bài viết này mình sẽ nói về các mối quan hệ cơ bản nhưng rất hay sử dụng trong Eloquent Relationships,đây có lẽ là kiến thức cơ bản nhất mà anh em nào bước vào lập trình với framework Laravel cũng đều phải biết. Tuy nhiên với những anh em đang ở mức beginer thì cũng khá hay nhầm lẫn hoặc hiểu chưa đúng về bản chất của những mối quan hệ này.Nên hi vọng, bài viết này có thể giúp anh em một phần nhỏ nào đó 😃).

Các bảng cơ sở dữ liệu thường có liên quan đến bảng khác. Vd: một bài post có nhiều comments hoặc một hóa đơn có thể liên quan đến một khách hàng,người đã đặt hóa đơn đó. Eloquent tạo sự quản lý và làm việc với các mối quan hệ đơn giản này,và hỗ trợ một vài kiểu quan hệ khác.chúng ta cùng đi tìm hiểu nhé.

1.Định nghĩa các mỗi quan hệ trong Eloquent Relationships

Eloquent Model định nghĩa các quan hệ bằng các phương thức trong Model, một ưu điểm là có thể sử dụng chuỗi phương thức và tăng cường khả năng truy vấn dữ liệu.Ví dụ dưới đây giúp bạn thấy được sức mạnh của khai báo relationship trong Eloquent Model:

$user = User::where('name', 'FirebirD')->first();
$posts = $user->post()->where('active', 1)->get();

1.1 Quan hệ 1 – 1

1.1.1 Định nghĩa quan hệ 1 – 1

Quan hệ một – một (còn gọi là quan hệ 1-1 hay one to one) là mối quan hệ cơ bản nhất, là mối quan hệ giữa hai tập thực thể mà mỗi thực thể thuộc tập này chỉ có duy nhất một mối quan hệ với một thực thể thuộc tập kia. Ví dụ User chỉ có một Phone, để định nghĩa mối quan hệ này, chúng ta sẽ đưa phương thức phone() vào model User. Trong phương thức phone() sẽ sử dụng phương thức hasOne() để trả về kết quả cho mối quan hệ.


<?php

namespace App;
use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    public function phone()
    {
        return $this->hasOne('App\Phone');
    }
}

Phương thức hasOne() có nhiều tham số:

Tham số thứ nhất chính là tên của Model có quan hệ, khi quan hệ được định nghĩa, chúng ta có thể lấy các bản ghi liên quan thông qua các thuộc tính động của Eloquent. Các thuộc tính động cho phép ta truy xuất vào phương thức quan hệ.

$phone = User::find(1)-

Tham số thứ hai là khóa ngoại của quan hệ, trong ví dụ trên model Phone giả sử rằng khóa ngoại là user_id, nếu tên trường khóa ngoại khác, bạn có thể đưa nó vào tham số thứ hai này.

return $this->hasOne('App\Phone', 'foreign_key');

Tham số thứ ba là trường khóa chính, mặc định sẽ sử dụng trường id hoặc trường được thiết lập trong biến $primaryKey của Model. Nếu sử dụng một giá trị khác với trường id, anh em có thể đưa vào tham số thứ ba này.

return $this->hasOne('App\Phone', 'foreign_key', 'local_key');

1.1.2 Định nghĩa chiều ngược lại trong quan hệ 1-1

Ở chiều ngược lại. Trong ví dụ trên Eloquent định nghĩa quan hệ 1-1 theo chiều ngược này bằng cách đưa phương thức user() vào trong Model Phone và sử dụng phương thức belongsTo() để thực hiện:

<?php
namespace App;
use Illuminate\Database\Eloquent\Model;

class Phone extends Model
{
    public function user()
    {
        return $this->belongsTo('App\User');
    }
}

Tương tự như phương thức hasOne(), phương thức belongsTo() cũng có ba tham số, tham số thứ nhất là tên Model có quan hệ ngược, tham số thứ hai là tên trường dùng làm khóa ngoại và tham số thứ ba là tên trường dùng làm khóa chính.

1.2. Quan hệ một – nhiều

1.2.1 Định nghĩa quan hệ một -nhiều

Quan hệ 1-n (còn gọi là quan hệ một – nhiều hay one to many) là quan hệ giữa hai tập thực thể mà mỗi thực thể thuộc tập này có quan hệ với nhiều thực thể thuộc tập kia. Ví dụ, một bài viết có thể có rất nhiều các bình luận. Eloquent cho phép định nghĩa mối quan hệ này bằng cách đưa phương thức comment() vào Model Post và sử dụng phương thức hasMany():

<?php

namespace App;
use Illuminate\Database\Eloquent\Model;

class Post extends Model
{
    public function comments()
    {
        return $this->hasMany('App\Comment');
    }
}

Phương thức hasMany() cũng như hasOne() sẽ có ba tham số, nếu không đưa vào các tham số 2 và 3 thì nó sẽ sử dụng các giá trị mặc định.

return $this->hasMany('App\Comment', 'foreign_key', 'local_key');

Khi quan hệ giữa Post và Comment được định nghĩa, bạn có thể lấy các Comment của một Post dễ dàng như sau:

$comments = App\Post::find(1)->comments;

foreach ($comments as $comment) {
    // Xử lý từng comment của post
}

1.2.2 Định nghĩa chiều ngược lại trong quan hệ một – nhiều

Tại chiều ngược lại của quan hệ 1-n.Ta định nghĩa ham quan hệ trên model con gọi là phương thức belongsto().

<?php

namespace App;
use Illuminate\Database\Eloquent\Model;

class Comment extends Model
{
    public function post()
    {
        return $this->belongsTo('App\Post');
    }
}

Khi đó, để lấy các thuộc tính của Post khi biết Comment cụ thể chúng ta chỉ việc thực hiện như sau:

$comment = App\Comment::find(1);

echo $comment->post->title;

1.3 Quan hệ nhiều – nhiều

1.3.1 Định nghĩa quan hệ nhiều – nhiều

Quan hệ nhiều – nhiều hay quan hệ many to many là mối quan hệ giữa hai tập thực thể mà mỗi thực thể thuộc tập này có thể có quan hệ với nhiều thực thể của tập kia và ngược lại. Ví dụ, một Product có thể nằm trong nhiều Order khác nhau và một Order có thể có nhiều Product khác nhau, do đó mối quan hệ Product-Order là quan hệ n-n. Định nghĩa quan hệ này trong Eloquent sử dụng phương thức belongsToMany().

<?php

namespace App;
use Illuminate\Database\Eloquent\Model;

class Product extends Model
{
    public function order()
    {
        return $this->belongsToMany('App\Order');
    }
}

1.3.2 Định nghĩa chiều ngược lại trong quan hệ nhiều – nhiều

Ở chiều ngược lại, chúng ta gọi tới phương thức belongsToMany trên model liên quan.

<?php

namespace App;
use Illuminate\Database\Eloquent\Model;

class Order extends Model
{
    public function product()
    {
        return $this->belongsToMany('App\Product');
    }
}

1.3.3 Lấy dữ liệu bảng trung gian trong quan hệ nhiều – nhiều

Như chúng ta đã biết, quan hệ n-n cần có bảng trung gian, trong ví dụ trên đó chính là bảng product_order. Eloquent ORM cung cấp sẵn một số phương thức hữu hiệu để làm việc với các bảng trung gian này. Để truy xuất các cột trong bảng trung gian chúng ta sử dụng thuộc tính pivot trên đối tượng.

$product = App\Product::find(1);

foreach ($product->order as $order) {
    // In ngày tạo đơn hàng trong cột created_at nằm trong bảng product_order
    echo $order->pivot->created_at;
}

Mặc định, chỉ các khóa của Model có trong đối tượng pivot, nếu bảng pivot của bạn chứa các thuộc tính khác nữa, bạn cần khai báo chúng khi định nghĩa quan hệ.

return $this->belongsToMany('App\Role')->withPivot('column1', 'column2');

Với các cột created_at, updated_at nếu muốn pivot tự động quản lý sử dụng phương thức withTimestamps():

return $this->belongsToMany('App\Role')->withTimestamps();

1.3.4 Lọc kết quả quan hệ trong qua bảng trung gian

Đôi khi chúng ta muốn trả về các bản ghi trong mối quan hệ hạn chế bởi việc lọc qua một số cột trong bảng trung gian có thể sử dụng các phương thức wherePivot() hoặc wherePivotIn() khi định nghĩa quan hệ.

return $this->belongsToMany('App\Product')->wherePivot('review', 1);

return $this->belongsToMany('App\Product')->wherePivotIn('priority', [1, 2]);

1.4 Quan hệ 3 ngôi

Quan hệ 3 ngôi (Has many through) là quan hệ giữa 3 tập thực thể mà mỗi thực thể tập A quan hệ với thực thể tập C thông qua mối quan hệ của thực thể tập B. Ví dụ sau giúp anh em hiểu rõ hơn về mối quan hệ 3 ngôi, một Country có nhiều cầu thủ thông qua câu lạc bộ(club). Như vậy club sẽ là trung gian của mối quan hệ giữa Country và players.

Khi đó muốn lấy tất cả các players của một Country cho trước, chúng ta cần định nghĩa mối quan hệ 3 ngôi này.

<?php

namespace App;
use Illuminate\Database\Eloquent\Model;

class Country extends Model
{
    public function players()
    {
        return $this->hasManyThrough('App\Players', 'App\Club');
    }
}

Vì Post không có cột country_id nên phải thông quan mối quan hệ 3 ngôi với User làm trung gian. Sau khi định nghĩa, để lấy các post của một country chúng ta thực hiện đơn giản như sau:

$country = Country::find('VN');
foreach($country->Players as $Club){
    echo $player->name;
}

Chú ý, phương thức hasManyThrough() cũng có thêm các tham số để xác định tên cột làm khóa ngoại nếu như các bảng được đặt tên không theo chuẩn.

return $this->hasManyThrough('App\players', 'App\Club', 'country_id', 'club_id', 'id');
  • Tham số thứ nhất là Model cần quan hệ.
  • Tham số thứ hai là Model trung gian.
  • Tham số 3 là khóa ngoại của Model trung gian.
  • 4 là khóa ngoại của Model cần quan hệ.
  • 5 là khóa chính của Model đang định nghĩa quan hệ.

2. Truy vấn dữ liệu trong Eloquent Relationship

Sau khi các dạng quan hệ được định nghĩa trong Eloquent thông qua các phương thức, anh em có thể gọi các phương thức để lấy dữ liệu về như các phương thức sử dụng trong query builder, nó cho phép chúng ta gọi các phương thức theo chuỗi trước khi thực thi câu lệnh SQL. Ví dụ, các anh em muốn lấy về các bài viết của một user đã được đăng:

$user = App\User::find(1);

$user->posts()->where('active', 1)->get();

Khi truy xuất các bản ghi của một Model, bạn có thể loại bỏ bớt các kết quả dựa trên một quan hệ có trước, ví dụ bạn muốn lấy tất cả các bài viết có ít nhất một bình luận:

$posts = App\Post::has('comments')->get();

Bạn cũng có thể đưa vào các toán tử trong truy vấn

$posts = Post::has('comments', '>=', 3)->get();

Ngoài ra, Eloquent cung cấp các phương thức whereHas() và orWhereHas() cho phép thêm các điều kiện where vào trong truy vấn. Các phương thức này cũng cho phép anh em thêm các rằng buộc, ví dụ kiểm tra nội dung các bình luận:

$posts = Post::whereHas('comments', function ($query) {
    $query->where('content', 'like', 'foo%');
})->get();

Trái ngược với phương thức has(), whereHas(), orWhereHas() Eloquent cung cấp phương thức doesntHave(), whereDoesntHave()

$posts = App\Post::doesntHave('comments')->get();
$posts = Post::whereDoesntHave('comments', function ($query) {
    $query->where('content', 'like', 'foo%');
})->get();

Eloquent cung cấp thêm phương thức withCount() giúp đếm kết quả trả về và đặt vào một cột có tên mặc định là {relation}_count. Ví dụ:

$posts = App\Post::withCount('comments')->get();

foreach ($posts as $post) {
    echo $post->comments_count;
}

Thậm chí, có thể thực hiện đếm kết quả cho nhiều các quan hệ khác nhau:

$posts = Post::withCount(['votes', 'comments' => function ($query) {
    $query->where('content', 'like', 'foo%');
}])->get();

echo $posts[0]->votes_count;
echo $posts[0]->comments_count;
và có thể đặt tên cho các cột đếm kết quả trả về:

$posts = Post::withCount([
    'comments',
    'comments AS pending_comments' => function ($query) {
        $query->where('approved', false);
    }
])->get();

echo $posts[0]->comments_count;

echo $posts[0]->pending_comments_count;

3. Chèn và cập nhật các Model có quan hệ

Phương thức save()

Ví dụ các anh em muốn thêm một comment cho một bài viết, thay vì thiết lập thủ công thuộc tính post_id trên Comment, anh em có thể thêm Comment trực tiếp từ phương thức save dựa trên quan hệ:

$comment = new App\Comment(['message' => 'A new comment.']);

$post = App\Post::find(1);

$post->comments()->save($comment);

saveMany() giúp thêm nhiều model một lúc:

$post = App\Post::find(1);

$post->comments()->saveMany([
    new App\Comment(['message' => 'A new comment.']),
    new App\Comment(['message' => 'Another comment.']),
]);

Phương thức create()

Giống như chức năng của save() nhưng đầu vào của create() là một mảng thay vì một instance của Model.

$post = App\Post::find(1);

$comment = $post->comments()->create([
    'message' => 'A new comment.',
]);

4. Lời kết

Xử lý mối quan hệ trong cơ sở dữ liệu là kiến thức tương đối rối rắm, nếu như anh em nào chưa được học qua các kiến thức về chuẩn hóa cơ sở dữ liệu thì quả thật sẽ khó để tiếp cận. Hi vọng qua bài này anh em sẽ hiểu rõ hơn về relationships trong laravel.đây cũng là bài viết đầu tiên của mình.có gì chưa ok lắm thì cũng mong anh góp ý và thông cảm 😄