Làm Thế Nào Để Làm Việc Cơ Sở Dữ Liệu Trong Laravel?

Cũng tương tự như với các ngôn ngữ lập trình khác, các bạn cũng cần phải thao tác rất nhiều với cơ sở dữ liệu trong Laravel. Laravel có cung cấp 2 công cụ để các bạn làm việc.

Thao tác với cơ sở dữ liệu là một trong các thao tác mà các bạn lập trình phải làm thường xuyên. Nó cũng phần nào quyết định tốc độ tải trang web. Khi thực hiện trên Laravel, các bạn sẽ thấy nó có 2 công cụ để làm việc trên đó. Eloquent ORM và Query Builder. Hai công cụ này đều hoạt động trên các hệ thống cơ sở dữ liệu quan hệ thịnh hành như mysql, sqlite, pgsql và sqlsrv. 

 

Giới thiệu Model 

 

Khi các bạn sử dụng Eloquent ORM, mỗi bảng trong cơ sở dữ liệu đều có một model tương ứng để tương tác với bảng. Bảng người dùng (users) sẽ có model tương ứng là user. Bảng bài viết (posts) sẽ có model post tương ứng. Quy tắc đặt tên bảng là số nhiều và tên model là số ít.

 

Từ model, bạn sẽ dễ tạo truy vấn dữ liệu trong bảng. Bạn cũng có thể chèn hoặc cập nhật dữ liệu của bảng đó. Theo mặc định model, khi bạn tạo ra dữ liệu sẽ nằm trong thư mục app. Thông thường, các bạn nên chuyển tất cả các model vào thư mục Models để dễ quản lý hơn. Mỗi khi tạo model mới, bạn nên gõ lệnh php artisan make:model Models\\ModelName thay cho php artisan make:model ModelName.

 

Thuộc tính của Model 

 

Trong Model có nhiều thuộc tính. Do đó, các bạn nên cân nhắc dùng các thuộc tính phổ biến này: $table, $fillable, $hidden, $casts, $dates và $perPage. Ví dụ:

 

<?php
namespace App\Models;
use Illuminate\Notifications\Notifiable;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;
class User extends Authenticatable
{
    use Notifiable;
    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'name', 'email', 'password',
    ];
    /**
     * The attributes that should be hidden for arrays.
     *
     * @var array
     */
    protected $hidden = [
        'password', 'remember_token',
    ];
    /**
     * The attributes that should be cast to native types.
     *
     * @var array
     */
    protected $casts = [
        'email_verified_at' => 'datetime',
    ];
}

 

$table

 

$table là tên bảng. Các truy vấn sẽ được thực hiện trực tiếp vào bảng có tên khai báo. Nếu bảng không được khai báo, thì Laravel sẽ tự động lấy tên class (dưới dạng số nhiều của từ cuối và theo kiểu snake). 

 

Ví dụ bạn lấy tên class là PostCategory thì tên bảng sẽ là post_categories. Nếu tên bảng của bạn là dùng tiếng Anh, thì bạn cần biến đổi tên đúng dạng số nhiều. Nếu bạn tạo bảng theo đúng nguyên tắc và không sai chính tả, thì bạn sẽ không gặp phải rắc rối (khi bạn không khai báo thuộc tính $table). 

 

$fillable 

 

$fillable là các trường có thể gán giá trị. Nếu bạn tạo một bản ghi mới (create) hoặc cập nhật một bản ghi đã có (update), thì mảng giá trị truyền vào sẽ được lọc các dữ liệu có key thuộc mảng $fillable. Ví dụ:

 

 

Dữ liệu truyền vào sẽ bị bỏ các trường không có trong $fillable. Do đó, bạn sẽ thấy dữ liệu là [‘name’ => ‘Hien Ho’, ’email’ => ‘[email protected]’, ‘password’ => ‘12344321’]. Sau đó, bạn tạo câu truy vấn chèn vào bảng dữ liệu đã sửa. Nhiều bạn lập trình thường mắc lỗi này: các bạn thêm một cột mới vào bảng nhưng bỏ quên không cập nhật thuộc tính $fillable. Vì vậy, các bạn đó sẽ không tạo được giá trị cho trường mới thêm dù đã request gửi lên có giá trị. 

 

$hidden

 

$hidden quy định các trường được ẩn khi biến đổi Model thành kiểu dữ liệu mảng hoặc JSON (Laravel gọi đây là serialization). Lấy ví dụ trong model User ở trên thì sẽ có password và remember_token trong mảng $hidden. Do đó, khi bạn chuyển $user thành toArray() thì bạn sẽ không thấy các trường này. Điều tương tự cũng sẽ xảy ra khi bạn thực hiện toJSON(). 

 

$cats

 

$cats quy định kiểu dữ liệu của các trường. Giá trị của các trường sẽ được chuyển thành kiểu dữ liệu tương ứng khi bạn thao tác trực tiếp với nó. Ví dụ:

 

protected $casts = [
        'email_verified_at' => 'datetime',
    ];

 

Khi bạn gọi $user->email_verified_at thì kiểu dữ liệu của nó là Carbon object. Khi bạn gán $user->email_verified_at = Carbon::now(), Laravel sẽ chuyển Carbon object này thành date time string và tự lưu vào database khi bạn save. Nếu bạn truyền vào string $user-> email_verified_at = ‘1994-10-13 12:00:00’, dữ liệu vẫn sẽ là Carbon object khi bạn lấy ra. 

 

Laravel hỗ trợ tất cả dạng dữ liệu phổ biến của PHP. Nếu bạn truyền vào sai kiểu dữ liệu đã được định nghĩa, thì hệ thống Laravel sẽ tự động báo lỗi. Động thái này giúp bạn thao tác dễ hơn của các trường cơ sở dữ liệu. Bạn cũng không phải lo về chuyện dữ liệu bị sai, không parse khi gửi cho front-end. 

 

$dates

 

$dates quy định các trường có kiểu dữ liệu thuộc dạng date.

 

$perPage

 

$perPage số phần tử trong một trang khi bạn thực hiện hàm paginate() thay cho get().

 

Model còn có 2 thuộc tính quan trọng khác mà các bạn cần tìm hiểu: $attributes và $original. Đây là 2 thuộc tính chứa dữ liệu của một bản ghi (thường là một hàng trong bảng cơ sở dữ liệu) mà model đó lưu giữ. Cùng thuộc dữ liệu của một bản ghi, nhưng nó sẽ khác nhau. 

 

$original: chứa giá trị nguyên thủy của một bản ghi. Giá trị của nó giống với giá trị trong database. Giá trị này được lấy ra bằng hàm getOriginal($key).

 

$attributes: chứa giá trị với các trường đã được thay đổi đúng dạng dữ liệu mà bạn định nghĩa trong $dates hoặc $cats. Giá trị này được lấy ra bằng hàm getAttributes($key).

 

Ví dụ với trường email_verified_at của model User ở trên khi có khai báo $cast là datetime. Giả sử trong database có giá trị là 2019-10-13 12:00:00. Khi bạn gọi hàm $user->getOriginal(’email_verified_at’), thì bạn sẽ nhận được giá trị là 2019-10-13 12:00:00. Nếu bạn gọi hàm $user->getAttributes(’email_verified_at’) sẽ ra Carbon object.

 

Liên kết các bảng 

 

Vì model là mô hình của bảng nên mỗi bảng có các liên kết với nhau như thế nào thì model cũng sẽ liên kết giống vậy. 

 

Có nhiều mối quan hệ giữa các model: quan hệ 1-1, quan hệ 1-nhiều, quan hệ nhiều-nhiều (thông qua bảng trung gian), quan hệ 1-1 thông qua bảng khác,…Các mối quan hệ này sẽ được thể hiện bằng một hàm trong model. Ví dụ: Một người dùng có nhiều bài viết thì sẽ được biểu diễn mối quan hệ như thế này.

 

<?php
namespace App\Models;
use App\Models\Post;
use Illuminate\Notifications\Notifiable;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;
class User extends Authenticatable
{
    /* .................. */
    public function posts()
    {
        return $this->hasMany(Post::class);
    }
}

 

Truy vấn cơ sở dữ liệu 

 

Truy vấn cơ sở dữ liệu của bảng mà nó đại diện là chuyện cơ bản và đơn giản nhất khi các bạn dùng model. Lấy ví dụ truy vấn tất cả users có id>10. 

 

$users = App\Models\User::where('id', '>', 10)->get();

 

Câu lệnh tưởng như đơn giản nhưng quá trình thực hiện sẽ phải qua nhiều giai đoạn. Bạn nào đã từng đọc code của abstract class Illuminate\Database\Eloquent\Model  để xem nó có các phương thức nào. Khi đó, bạn sẽ không thấy được cái phương thức where đó ở đâu.  

 

Bạn cần biết luồng hoạt động khi gọi một hàm trong PHP. Khi gọi 1 hàm từ bên ngoài của 1 object, hàm đó nếu có tồn tại với tầm nhìn là public thì bạn sẽ gọi hàm đó dễ dàng. Nhưng nếu hàm đó không có tồn tại thì phải làm sao? Khi đó, bạn dùng hàm magic __call($method, $parameters) thay thế.

 

Một hàm static cũng tương tự. Hàm __callStatic($method, $parameters) được gọi thay thế khi hàm không tồn tại. Đối với câu lệnh trên, thì PHP sẽ gọi hàm __callStatic của User (là hàm __callStatic của Model đó). Bạn có thể vào xem thực chất Model đã thực hiện điều gì với lời gọi hàm của bạn.

 

public static function __callStatic($method, $parameters)
{
    return (new static)->$method(...$parameters);
}

 

Hàm này sẽ tạo ra 1 object mới của Model và gọi hàm $method trong object đó. Param truyền vào thì sẽ có param tương tự như khi bạn truyền từ bên ngoài vào. Tuy nhiên, hàm where không có trong Model. Do đó, bạn cần sử dụng __call($method, $parameters). Ví dụ:

 

public function __call($method, $parameters)
{
     if (in_array($method, ['increment', 'decrement'])) {
        return $this->$method(...$parameters);
     }
     return $this->forwardCallTo($this->newQuery(), $method, $parameters);
}

 

Khi đó, bạn sẽ thấy có 2 trường hợp xảy ra. 

 

Nếu tên hàm là increment hoặc decrement, thì bạn sẽ gọi hàm đó trong model. Vì sao chúng ta không gọi trực tiếp ở bên ngoài mà phải thông qua hàm __call? Bạn nên kéo lên xem hàm đó. Nó chính là các hàm protected sẽ không gọi được từ bên ngoài.

 

Bạn sẽ cần gọi hàm protected theo kiểu gọi hàm static thông qua một loạt các magic function (User::increment()). Bạn cũng có thể gọi hàm theo kiểu bình thường bên ngoài như gọi hàm public: ($user->increment()).

 

Nếu tên hàm mà khác với các tên bên trên, thì bạn cần chạy lệnh return.

 

$this->forwardCallTo($this->newQuery(), $method, $parameters);

 

Lệnh này gồm có 2 phần:

 

  • Tạo 1 object truy vấn mới $this->newQuery() và gọi hàm $method của truy vấn đó với tham số truyền vào là $parameters

  • Hàm forwardCallTo có nhiệm vụ gọi $method trong object truy vấn mới và trả về Exception (trong trường hợp không gọi được)

 

Hàm newQuery() sẽ tạo ra 1 EloquentBuilder mới nếu bạn truy xuất nhiều lần. Cuối cùng, nó sẽ thực hiện các truy vấn trên đó. Hàm where này cũng chính là hàm where của Illuminate\Database\Eloquen\Builder.

 

Sau khi bạn đã tạo 1 EloquentBuilder và thêm where vào, dữ liệu nhận được sẽ là 1 EloquentBuilder. Khi đã thao tác xong hàm get(), nó sẽ được thực hiện trên EloquentBuilder đã where đó để thực hiện câu truy vấn theo đúng như mục đích của bạn đã đề ra trước đó.