Toidicode.com

Toidicode.com

BASIC TO ADVANCE

Bài 33: Eloquent ORM relationship trong Laravel 8

Tiếp tục với chuỗi bài về Eoloquent ORM trong Laravel. Phần này mình sẽ giới thiệu với mọi người về Eloquent relationship trong Laravel 8.

1. Eloquent Relationship là gì?.

Eloquent Relationship là tính năng trong Laravel nó cho phép chúng ta định nghĩ ra các mối quan hệ (relationship) giữa các model với nhau. Từ đó có thể query, làm việc với các model được định nghĩa quan hệ một cách đơn giản.

Các mỗi quan hệ này thực ra là một bản config trên code về cấu trúc link giũa các table trong database.

VD: Một post sẽ có nhiều comment, hoặc một post chỉ thuộc về một tác giả.

2. Định nghĩa relationship trong model.

Các eloquent relationship được định nghĩ trong model nhưng một phương thức bình thường. Và chúng ta cũng có thể dùng nó để tạo ra các query builder khác một cách nhanh chóng và đơn giản bằng cách sử dụng chúng như method.

Dưới đây sẽ là các kiểu quan hệ trong eloquent.

One To One.

One to one (1-1) là một trong những mỗi quan hệ đơn giản nhất trong database.

VD: Mỗi một User thì chỉ có duy nhất một số điện thoại (phone).

Để định nghĩa mối quan hệ này chúng ta dùng phương thức hasOne với cú pháp như sau:

hasOne($relationModel, $foreignKey, $localKey');

Trong đó:

  • $relationModel là model sẽ được link với model hiện tại với mỗi quan hệ 1-1.
  • $foreignKey là khóa ngoại của table $relationModel ở trên dùng để liên kết giũa 2 bảng với nhau. Mặc định thì $foreignKey sẽ là tên bảng của model hiện tại cộng với '_id', ví dụ model User thì sẽ là users_id.
  • $localKey là cột chứa dữ liệu để liên kết với bẳng của $relationModel. Mặc định thì $localKey sẽ là primary key của model hiện tại.

VD: Chúng ta sẽ định nghĩa mối quan hệ này bằng cách định nghĩa thêm một phương thức có tên là phone trong User model như sau:

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    /**
     * Get the phone associated with the user.
     */
    public function phone()
    {
        return $this->hasOne(Phone::class);
    }
}

Khi mình định nghĩa như này thì $foreignKey ở đây sẽ là user_id$localKey sẽ là id.

Sau khi đã định nghĩa xong relationship cho Model lúc này bạn có thể query đến relation model bằng cách gọi đến phương thức định nghĩa relation như một thuộc tính trong model hiện tại.

VD: Truy vấn dữ liệu trong relation model.

// truy vấn data của table phone của user id = 1

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

Bạn cũng có thể định nghĩa được mối quan hệ đảo ngược của quan hệ 1-1 này. Ví dụ một số điện thoại (phone) thì sẽ chỉ thuộc về một User thôi.

Để định nghĩa mối quan hệ đảo ngược của quan hệ one to one này các bạn sủ dụng phương thức belong với cú pháp:

belongsTo($relatedModel, $foreignKey, $ownerKey);

Trong đó:

  • $relatedModel là model của bạn muốn liên kết.
  • $foreignKey là column của bảng hiện tại sẽ dùng để liên kết. Mặc định $foreignKey sẽ là tên của phương thức cộng với primary key của $relatedModel.
  • $ownerKey là column của bảng $relatedModel sẽ dùng để liên kết. Mặc định $ownerKey là khóa chính của $relatedModel model.

VD: Định nghĩa mối quan hệ đảo ngược cho model PhoneUser.

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Phone extends Model
{
    /**
     * Get the user that owns the phone.
     */
    public function user()
    {
        return $this->belongsTo(User::class);
    }
}

Lúc này query User trong Phone sẽ như sau:

// Lấy user có số điện thoại +84123456789

$user = Phone::where('number', '+84123456789')->user;

One To Many

Mối quan hệ một nhiều được sử dụng trong trường hợp một model (A) sẽ được link đến một hoặc nhiều model khác (B). Ví dụ một bài post sẽ có rất nhiều comment.

Dể định nghĩa mối quan hệ này chúng ta sử dụng phương thức hasMany với các tham số truyền vào tương tự như phương thức hasOne ở trên.

VD: Định nghĩa quan hệ một nhiều giữa model Post và model Comment.

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Post extends Model
{
    /**
     * Get the comments for the blog post.
     */
    public function comments()
    {
        return $this->hasMany(Comment::class);
    }
}

Ở mối quan hệ này thì khi query đến relationship nó sẽ trả về một collection với các item sẽ là model object.

VD: query đến comment trong post.

use App\Models\Post;

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

foreach ($comments as $comment) {
    //
}

Như mình đã nói ở trên, các mối quan hệ trong model này còn đóng vai trò như một query builder, nên bạn cũng có thể dùng cách này để apply thêm điều kiện cho query.

VD: Lấy các comment của post có title = foo.

$comment = Post::find(1)->comments()
                    ->where('title', 'foo')
                    ->first();

Nếu bạn muốn định nghĩa mối quan hệ đảo ngược cho comment với post trong trường hợp này thì có thể sử dụng như đối với one to one ở trên. Vì lúc này một comment vẫn thuộc về một post.

VD:

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Comment extends Model
{
    /**
     * Get the post that owns the comment.
     */
    public function post()
    {
        return $this->belongsTo(Post::class);
    }
}

Và query cũng tương tự như đối với one to one ở trên.

VD:

use App\Models\Comment;

$comment = Comment::find(1);

return $comment->post->title;

Has One Through

Đây thực ra cũng là mối quan hệ 1-1 tuy nhiên chúng phải link với nhau thông qua một model khác.

VD: Một bộ phận sẽ thuộc về một chiếc xe và một chiếc xe sẽ thuộc về một người. Trong trường hợp này bạn muốn kiểm tra xem một bộ phận thuộc về người nào đó thì bạn cần phải liên kết với một bảng trung gian trong trường hợp này thì chiếc xe chính là trung gian.

Để cho dễ hiểu bạn có thểm xem qua data struct và cách định nghĩa như sau:

Cấu trúc của các bảng.

mechanics
    id - integer
    name - string

cars
    id - integer
    model - string
    mechanic_id - integer

owners
    id - integer
    name - string
    car_id - integer

Lúc này để định nghĩa owner của mechanics các bạn có thể sử dụng phương thức hasOneThrough.

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Mechanic extends Model
{
    /**
     * Get the car's owner.
     */
    public function carOwner()
    {
        return $this->hasOneThrough(Owner::class, Car::class);
    }
}

Và nếu như các column name của các bạn không theo rule trên thì bạn có thể thiết lập thủ công vào phương thức hasOneThrough.

VD:

class Mechanic extends Model
{
    /**
     * Get the car's owner.
     */
    public function carOwner()
    {
        return $this->hasOneThrough(
            Owner::class,
            Car::class,
            'mechanic_id', // Foreign key on the cars table...
            'car_id', // Foreign key on the owners table...
            'id', // Local key on the mechanics table...
            'id' // Local key on the cars table...
        );
    }
}

Has Many Through

Mối quan hệ này cũng tương tự như mối quan hệ one to many, chỉ khác là nó phải cần thêm một model thứ 3 để xác định được.

VD: Một project sẽ có nhiều môi trường và một môi trường sẽ có nhiều lần deploy. Như vậy để có thể lấy ra được các deploy của project bạn phải cần thêm thông tin của môi trường. Ở đây môi trường đóng vai trò làm trung gian.

Các bạn có thể tham khảo ví dụ sau cho dễ hiểu hơn.

Cấu trúc các bảng.

projects
    id - integer
    name - string

environments
    id - integer
    project_id - integer
    name - string

deployments
    id - integer
    environment_id - integer
    commit_hash - string

Định nghĩa quan hệ:

Để định nghĩa quan hệ này các bạn sử dụng phương thức hasManyThrough.

VD:

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Project extends Model
{
    /**
     * Get all of the deployments for the project.
     */
    public function deployments()
    {
        return $this->hasManyThrough(Deployment::class, Environment::class);
    }
}

Nếu trong trường hợp tên các cột của bạn không theo rule trên thì bạn có thể xác định thủ công vào trong phương thức hasManyThrough với các biến truyền vào như sau:

class Project extends Model
{
    public function deployments()
    {
        return $this->hasManyThrough(
            Deployment::class,
            Environment::class,
            'project_id', // Foreign key on the environments table...
            'environment_id', // Foreign key on the deployments table...
            'id', // Local key on the projects table...
            'id' // Local key on the environments table...
        );
    }
}

Many To Many

Đây là mối quan hệ thường gặp trong các chức năng phân quyền của ứng dụng.

Để cho dễ hiểu mình sẽ đi thẳng vào ví dụ.

Cấu trúc các bảng.

users
    id - integer
    name - string

roles
    id - integer
    name - string

role_user
    user_id - integer
    role_id - integer

Ở đây các bạn có thể thấy thì một chú ta sẽ có nhiều users và roles và để lấy ra được các roles của user thì chúng ta sẽ lấy ra các role_user của user_id đó và query trong roles table. Mối quan hệ như thế là many to many.

Lúc này để định nghĩa quan hệ này trong model các bạn sử dụng phương thức belongsToMany.

VD:

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    /**
     * The roles that belong to the user.
     */
    public function roles()
    {
        return $this->belongsToMany(Role::class);
    }
}

Tương tự, bạn có thể định nghĩa mối quan hệ đảo ngược tương tự như trên.

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Role extends Model
{
    /**
     * The users that belong to the role.
     */
    public function users()
    {
        return $this->belongsToMany(User::class);
    }
}

Trong một số trường hợp, bạn muốn lấy ra thêm data của bảng trung gian, bạn có thể sử dụng attribute pivot.

VD: lấy ra created_at của user_role.

use App\Models\User;

$user = User::find(1);

foreach ($user->roles as $role) {
    echo $role->pivot->created_at;
}

3. Default Model.

Trong các phương thức belongsTo, hasOne, hasOneThroughmorphOne sử dụng để định nghĩa kiểu quan hệ giữa các model với nhau nó sẽ trả về null nếu như dữ liệu của quan hệ không tồn tại trong database. Trong trường hợp này laravel có cung cấp cho mọi người định nghĩa thêm giá trị default với phương thức withDefault.

Nếu không truyền gì vào phương thức này thì Laravel sẽ trả về model object với không có giá trị attribute được thêm vào model object.

VD:

/**
 * Get the author of the post.
 */
public function user()
{
    return $this->belongsTo(User::class)->withDefault();
}

Để xác định thêm attribute mặc định được truyền vào model bạn có thể truyền thêm mảng attribute vào phương thức hoặc một closure trả về mảng giá trị attribute.

VD:

/**
 * Get the author of the post.
 */
public function user()
{
    return $this->belongsTo(User::class)->withDefault([
        'name' => 'Guest Author',
    ]);
}

// hoặc

/**
 * Get the author of the post.
 */
public function user()
{
    return $this->belongsTo(User::class)->withDefault(function ($user, $post) {
        $user->name = 'Guest Author';
    });
}

4. Lời kết.

Phần này mình chỉ giới thiệu với mọi người các mối quan hệ cơ bản trong Eloquent ORM và cách query cơ bản trong các mối quan hệ. Ngoài ra, trong eloquent ORM Laravel còn hộ trợ rất nhiều các mối quan hệ phức tạp hơn mà mình không trình bày ở đây vì nó có thể khó khá hiểu với các bạn tại thời điểm này (bạn nào cần có thể tham khảo tại đây).

Đăng ký nhận tin.

Chúng tôi chỉ gửi tối đa 2 lần trên 1 tháng. Tuyên bố không spam mail!

Vũ Thanh Tài

About author
The best way to learn is to share
Xem tất cả bài đăng

0 Comments

Bài viết chưa có ai bình luận, hãy là người đầu tiên đi bạn!

Bình luận

Captcha