Eloquent ORM là gì? Hướng dẫn sử dụng Eloquent trong Laravel

1. Eloquent ORM là gì?

Eloquent ORM là hệ thống Object-Relational Mapping (ORM) được tích hợp trong Laravel, một framework phát triển ứng dụng Web bằng PHP. Eloquent cho phép lập trình viên tương tác với cơ sở dữ liệu thông qua các class và object, thay vì phải viết câu lệnh SQL thủ công cho mọi thao tác.

Trong Eloquent, mỗi model thường đại diện cho một bảng trong database. Model cung cấp các phương thức để truy vấn, thêm, cập nhật và xóa dữ liệu. Eloquent cũng hỗ trợ khai báo quan hệ giữa các model, quản lý các truy vấn liên quan và ánh xạ kết quả truy vấn thành object PHP.

Ví dụ, để lấy sản phẩm có ID bằng 1, có thể sử dụng:

$product = Product::find(1);

Thay vì tự viết câu SQL:

SELECT * FROM products WHERE id = 1;

Eloquent xây dựng và thực thi truy vấn tương ứng thông qua hệ thống truy vấn của Laravel, sau đó trả về model chứa dữ liệu nếu tìm thấy bản ghi.

Tuy nhiên, Eloquent không loại bỏ SQL. Nó cung cấp một lớp trừu tượng giúp lập trình viên thao tác với database bằng PHP dễ dàng hơn, trong khi các truy vấn vẫn được thực thi trên hệ quản trị cơ sở dữ liệu.

2. ORM giải quyết vấn đề gì?

Trong ứng dụng Backend, dữ liệu thường được lưu trữ dưới dạng bảng, hàng và cột trong database. Ngược lại, chương trình PHP làm việc với biến, array, object và class.

Sự khác biệt giữa hai cách biểu diễn này tạo ra một vấn đề: lập trình viên phải chuyển đổi dữ liệu giữa mô hình quan hệ của database và mô hình đối tượng trong chương trình.

Giả sử hệ thống có bảng products:

id name price
1 Keyboard 500000
2 Mouse 200000

Nếu sử dụng SQL thuần, lập trình viên viết truy vấn, thực thi truy vấn và xử lý kết quả trả về. Nếu sử dụng ORM, nhiều thao tác có thể được thực hiện thông qua các phương thức của model.

ORM giúp:

  • Giảm lượng mã lặp lại khi thực hiện các thao tác CRUD.
  • Tổ chức code theo các model tương ứng với dữ liệu nghiệp vụ.
  • Khai báo và truy xuất quan hệ giữa các đối tượng dữ liệu.
  • Hỗ trợ xây dựng truy vấn thông qua các phương thức dễ đọc.
  • Giúp việc bảo trì và mở rộng ứng dụng thuận tiện hơn.

Dù vậy, ORM không tự động khiến truy vấn nhanh hơn hoặc thiết kế database tốt hơn. Lập trình viên vẫn cần hiểu SQL, cấu trúc dữ liệu, index và cách database thực thi truy vấn.

3. Model trong Eloquent là gì?

Model là thành phần trung tâm của Eloquent, dùng để tương tác với dữ liệu trong database.

Một model thường đại diện cho một loại thực thể trong hệ thống, chẳng hạn:

  • User: người dùng.
  • Product: sản phẩm.
  • Order: đơn hàng.
  • Category: danh mục sản phẩm.

Tạo model

Laravel cung cấp Artisan CLI để tạo model:

php artisan make:model Product

Thông thường, Laravel tạo file Product.php trong thư mục app/Models.

Một model cơ bản có thể được khai báo như sau:

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Product extends Model
{
    protected $fillable = [
        'name',
        'price',
    ];
}

Trong ví dụ này:

  • Product là model dùng để tương tác với dữ liệu sản phẩm.
  • extends Model cho phép class sử dụng các chức năng của Eloquent.
  • $fillable xác định những thuộc tính được phép gán hàng loạt thông qua các phương thức như create() và fill().

Quy ước của Eloquent

Theo quy ước mặc định, Laravel sử dụng tên model để suy ra tên bảng:

Model Bảng mặc định
Product products
User users
Order orders
Category categories

Eloquent cũng mặc định sử dụng cột id làm khóa chính và hai cột created_at, updated_at để theo dõi thời điểm tạo và cập nhật bản ghi.

Nếu bảng không tuân theo các quy ước này, lập trình viên có thể cấu hình model tương ứng.

Ví dụ, nếu model Product sử dụng bảng store_items:

class Product extends Model
{
    protected $table = 'store_items';
}

Những quy ước này giúp giảm lượng cấu hình cần viết, nhưng không có nghĩa Eloquent bắt buộc mọi database phải sử dụng cùng một cách đặt tên.

4. Eloquent thực hiện CRUD như thế nào?

CRUD là viết tắt của Create, Read, Update và Delete — bốn thao tác cơ bản khi làm việc với dữ liệu.

4.1. Create — Thêm dữ liệu

Có thể tạo bản ghi mới bằng cách khởi tạo model:

$product = new Product();

$product->name = 'Keyboard';
$product->price = 500000;

$product->save();

Hoặc sử dụng create():

$product = Product::create([
    'name' => 'Keyboard',
    'price' => 500000,
]);

Phương thức save() lưu các thay đổi của model xuống database. Với model mới, nó thực hiện thao tác thêm bản ghi; với model đã tồn tại, nó có thể cập nhật bản ghi tương ứng.

Phương thức create() tạo và lưu model trong một bước. Tuy nhiên, những thuộc tính được truyền vào phải đáp ứng cấu hình mass assignment của model.

4.2. Read — Truy vấn dữ liệu

Lấy một bản ghi theo khóa chính:

$product = Product::find(1);

Nếu không tìm thấy bản ghi có ID bằng 1, find() trả về null.

Nếu muốn Laravel ném ngoại lệ khi không tìm thấy bản ghi, có thể sử dụng:

$product = Product::findOrFail(1);

Lấy tất cả bản ghi:

$products = Product::all();

Lọc dữ liệu theo điều kiện:

$products = Product::where('price', '>', 300000)
    ->get();

Sắp xếp và giới hạn kết quả:

$products = Product::orderBy('price', 'desc')
    ->limit(10)
    ->get();

Eloquent thường cho phép kết hợp nhiều phương thức để xây dựng truy vấn trước khi thực thi. Chẳng hạn, where() bổ sung điều kiện lọc, còn get() thực thi truy vấn và trả về một collection chứa các model phù hợp.

4.3. Update — Cập nhật dữ liệu

Có thể truy xuất model rồi thay đổi thuộc tính:

$product = Product::findOrFail(1);

$product->price = 550000;

$product->save();

Hoặc cập nhật trực tiếp những bản ghi thỏa mãn điều kiện:

Product::where('id', 1)
    ->update([
        'price' => 550000,
    ]);

Hai cách này có sự khác biệt. Cách đầu làm việc với một model cụ thể và lưu các thay đổi của model đó. Cách thứ hai thực hiện cập nhật theo điều kiện bằng một truy vấn hàng loạt.

Các thao tác cập nhật hàng loạt không khởi tạo từng model như cách cập nhật từng bản ghi. Vì vậy, những sự kiện Eloquent gắn với từng model có thể không được kích hoạt theo cách người mới sử dụng ORM thường kỳ vọng.

4.4. Delete — Xóa dữ liệu

Xóa một bản ghi theo ID:

$product = Product::findOrFail(1);

$product->delete();

Hoặc xóa theo điều kiện:

Product::where('price', '<', 100000)
    ->delete();

Phương thức delete() có thể xóa dữ liệu thực sự khỏi bảng. Nếu model được cấu hình sử dụng Soft Deletes, thao tác này sẽ đánh dấu bản ghi đã bị xóa thay vì xóa vật lý ngay lập tức.

Cần đặc biệt cẩn thận với thao tác xóa hàng loạt vì một điều kiện sai có thể tác động đến nhiều bản ghi ngoài dự kiến.

5. Query Builder trong Eloquent

Eloquent cung cấp một hệ thống xây dựng truy vấn theo dạng fluent interface, cho phép nối các phương thức để mô tả điều kiện truy vấn.

Ví dụ:

$products = Product::where('price', '>=', 200000)
    ->where('name', 'like', '%Key%')
    ->orderBy('price', 'desc')
    ->get();

Đoạn code trên yêu cầu lấy những sản phẩm có giá từ 200000 trở lên, tên chứa chuỗi Key, sau đó sắp xếp theo giá giảm dần.

Có thể hình dung truy vấn này tương ứng với SQL:

SELECT *
FROM products
WHERE price >= 200000
  AND name LIKE '%Key%'
ORDER BY price DESC;

SQL trên nhằm minh họa ý nghĩa của truy vấn; câu SQL thực tế được sinh ra có thể khác nhau tùy database driver và cách Laravel xây dựng truy vấn.

Một số phương thức thường gặp

Phương thức Chức năng
where() Thêm điều kiện lọc
orWhere() Thêm điều kiện OR
orderBy() Sắp xếp kết quả
limit() Giới hạn số lượng kết quả
select() Chọn các cột cần lấy
first() Lấy bản ghi đầu tiên phù hợp
get() Thực thi truy vấn và lấy collection
count() Đếm số bản ghi phù hợp
exists() Kiểm tra có bản ghi phù hợp hay không

Một điểm cần nhớ là các phương thức xây dựng truy vấn như where() thường chưa thực thi truy vấn ngay. Việc truy vấn thường xảy ra khi gọi phương thức lấy kết quả như get(), first() hoặc count().

Ví dụ:

$query = Product::where('price', '>', 300000);

// Truy vấn được thực thi tại đây.
$products = $query->get();

Cách xây dựng truy vấn theo từng bước giúp code dễ đọc và thuận tiện khi bổ sung điều kiện theo logic nghiệp vụ.

6. Relationships — Quan hệ giữa các model

Trong database, dữ liệu thường được liên kết với nhau thông qua khóa chính và khóa ngoại. Eloquent cho phép khai báo các quan hệ này bằng phương thức trong model.

Các loại quan hệ phổ biến gồm:

  • One to One — Một-một.
  • One to Many — Một-nhiều.
  • Many to Many — Nhiều-nhiều.
  • Belongs To — Một model thuộc về model khác.
  • Polymorphic Relationships — Quan hệ đa hình.

6.1. One to Many — Một-nhiều

Giả sử một danh mục có nhiều sản phẩm, nhưng mỗi sản phẩm chỉ thuộc về một danh mục.

Bảng products có thể chứa cột category_id làm khóa ngoại tham chiếu đến bảng categories.

Trong model Category:

public function products()
{
    return $this->hasMany(Product::class);
}

Trong model Product:

public function category()
{
    return $this->belongsTo(Category::class);
}

Sau khi khai báo quan hệ, có thể truy xuất sản phẩm thuộc một danh mục:

$category = Category::findOrFail(1);

$products = $category->products;

Hoặc lấy danh mục của một sản phẩm:

$product = Product::findOrFail(1);

$category = $product->category;

Eloquent sử dụng các quan hệ đã khai báo để xây dựng truy vấn tương ứng. Tên phương thức quan hệ không tự tạo ra khóa ngoại trong database; cấu trúc bảng và các cột liên quan vẫn phải được thiết kế đúng.

6.2. Many to Many — Nhiều-nhiều

Giả sử một sản phẩm có thể thuộc nhiều chương trình khuyến mãi và mỗi chương trình khuyến mãi có thể áp dụng cho nhiều sản phẩm.

Mô hình này thường cần một bảng trung gian, chẳng hạn product_promotion, chứa product_id và promotion_id.

Quan hệ trong model Product có thể được khai báo như sau:

public function promotions()
{
    return $this->belongsToMany(Promotion::class);
}

Sau đó, có thể lấy các chương trình khuyến mãi của sản phẩm:

$product = Product::findOrFail(1);

$promotions = $product->promotions;

Với quan hệ nhiều-nhiều, Eloquent hỗ trợ truy vấn và thao tác thông qua bảng trung gian mà không cần tự viết toàn bộ câu SQL cho từng trường hợp thông thường.

7. Eager Loading và vấn đề N+1 Query

Một trong những vấn đề hiệu năng phổ biến khi sử dụng ORM là N+1 Query.

Giả sử cần lấy 100 sản phẩm và hiển thị tên danh mục của từng sản phẩm. Nếu truy vấn danh mục riêng cho từng sản phẩm, ứng dụng có thể thực hiện:

  • Một truy vấn để lấy danh sách 100 sản phẩm.
  • Thêm một truy vấn cho mỗi sản phẩm để lấy danh mục.

Tổng cộng có thể lên tới 101 truy vấn. Đây là ví dụ điển hình của vấn đề N+1.

Lazy Loading

Khi truy cập một quan hệ chưa được tải, Eloquent thường thực hiện lazy loading:

$products = Product::all();

foreach ($products as $product) {
    echo $product->category->name;
}

Nếu quan hệ category chưa được tải trước, việc truy cập nó trong vòng lặp có thể tạo thêm truy vấn cho từng sản phẩm.

Eager Loading

Có thể sử dụng with() để tải trước quan hệ:

$products = Product::with('category')->get();

foreach ($products as $product) {
    echo $product->category->name;
}

Eager loading cho phép Eloquent lấy dữ liệu liên quan theo cách giảm đáng kể số truy vấn trong tình huống này.

Tuy nhiên, eager loading không phải lúc nào cũng tốt hơn. Nếu tải nhiều quan hệ hoặc quá nhiều cột không cần thiết, ứng dụng vẫn có thể tiêu tốn bộ nhớ và tài nguyên không cần thiết.

Một số nguyên tắc tối ưu gồm:

  • Chỉ truy vấn những cột cần sử dụng.
  • Sử dụng with() khi biết trước cần dữ liệu quan hệ.
  • Kiểm tra SQL được sinh ra và số lượng truy vấn thực tế.
  • Dùng phân trang hoặc xử lý theo từng nhóm khi dữ liệu lớn.
  • Sử dụng index phù hợp với điều kiện lọc và cách truy vấn.

8. Mass Assignment và bảo mật

Mass assignment là cơ chế cho phép gán nhiều thuộc tính cho model cùng lúc bằng một mảng dữ liệu.

Ví dụ:

$product = Product::create([
    'name' => 'Keyboard',
    'price' => 500000,
]);

Cơ chế này tiện lợi, nhưng có thể gây lỗ hổng bảo mật nếu ứng dụng gán trực tiếp dữ liệu không đáng tin cậy từ request mà không kiểm soát những trường được phép cập nhật.

Giả sử bảng users có cột is_admin. Nếu ứng dụng cho phép client truyền tùy ý thuộc tính vào model, người dùng có thể cố gửi thêm trường is_admin để nâng quyền tài khoản.

Sử dụng $fillable

Trong model, có thể chỉ định các trường được phép gán hàng loạt:

class Product extends Model
{
    protected $fillable = [
        'name',
        'price',
    ];
}

Với cấu hình này, chỉ những thuộc tính được cho phép mới có thể được gán thông qua các phương thức mass assignment.

Một lựa chọn khác là $guarded, dùng để chỉ định những thuộc tính không được phép gán hàng loạt. Cần hiểu rõ cấu hình này trước khi sử dụng, đặc biệt khi danh sách thuộc tính bị chặn có thể không đầy đủ.

Ngoài $fillable, ứng dụng cần xác thực dữ liệu đầu vào, kiểm tra quyền của người dùng và tránh đưa trực tiếp toàn bộ dữ liệu request vào create() hoặc update().

ORM không thay thế các biện pháp bảo mật của ứng dụng. Cấu hình model chỉ là một lớp bảo vệ trong quá trình gán thuộc tính.

9. Eloquent và Transaction

Trong các thao tác nghiệp vụ gồm nhiều bước, chỉ sử dụng save() cho từng model không đảm bảo toàn bộ quy trình thành công hoặc thất bại như một đơn vị.

Ví dụ, tạo đơn hàng có thể bao gồm:

  1. Tạo đơn hàng.
  2. Tạo các dòng chi tiết đơn hàng.
  3. Cập nhật số lượng tồn kho.

Nếu bước thứ ba thất bại sau khi hai bước đầu đã thành công, database có thể rơi vào trạng thái không mong muốn nếu không có cơ chế transaction phù hợp.

Laravel cung cấp transaction thông qua DB facade:

use Illuminate\Support\Facades\DB;

DB::transaction(function () {
    $order = Order::create([
        'user_id' => 1,
        'total' => 700000,
    ]);

    $order->items()->createMany([
        [
            'product_id' => 10,
            'quantity' => 1,
            'price' => 500000,
        ],
        [
            'product_id' => 11,
            'quantity' => 1,
            'price' => 200000,
        ],
    ]);
});

Ví dụ này minh họa việc tạo đơn hàng và các dòng chi tiết trong một transaction. Để chạy đúng, ứng dụng cần khai báo quan hệ items() cùng cấu trúc bảng và các trường phù hợp.

Khi callback hoàn tất thành công, transaction được commit. Nếu xảy ra exception, Laravel sẽ rollback transaction theo cơ chế của database.

Transaction giúp bảo đảm tính nguyên tử của các thao tác database trong phạm vi transaction đó. Tuy nhiên, nó không tự động giải quyết mọi vấn đề đồng thời, khóa dữ liệu hay giao dịch trải qua nhiều dịch vụ độc lập.

10. Ưu điểm và hạn chế của Eloquent ORM

Ưu điểm

1. Tăng khả năng đọc hiểu code

Các phương thức như Product::where(...)->get() giúp thể hiện ý định truy vấn trực tiếp trong code PHP.

2. Tổ chức dữ liệu theo model

Mỗi model tập trung vào một loại thực thể và cung cấp nơi khai báo quan hệ cũng như một số hành vi liên quan đến dữ liệu.

3. Hỗ trợ quan hệ giữa các bảng

Eloquent cung cấp các phương thức để truy cập dữ liệu quan hệ mà không phải tự viết SQL cho mọi truy vấn.

4. Tích hợp với Laravel

Eloquent hoạt động cùng các thành phần khác của Laravel, bao gồm migration, validation, transaction và hệ thống xử lý HTTP.

Hạn chế

1. Có thể che khuất chi phí truy vấn

Code ngắn không có nghĩa truy vấn hiệu quả. Lazy loading hoặc việc truy vấn quá nhiều dữ liệu có thể làm giảm hiệu năng.

2. Không loại bỏ nhu cầu hiểu SQL

Khi truy vấn phức tạp, cần tối ưu hoặc sử dụng tính năng đặc thù của database, lập trình viên vẫn phải hiểu SQL và cách database vận hành.

3. Có thêm chi phí xử lý

ORM cần tạo và quản lý object model, ánh xạ kết quả truy vấn và thực hiện một số công việc bổ sung. Với một số tác vụ xử lý dữ liệu lớn, query builder hoặc SQL trực tiếp có thể phù hợp hơn.

4. Cần chú ý đến hành vi của các phương thức

Các thao tác theo từng model và thao tác hàng loạt có thể khác nhau về sự kiện, cách xử lý model và lượng dữ liệu được tải.

Do đó, lựa chọn Eloquent hay SQL trực tiếp nên dựa trên yêu cầu về khả năng bảo trì, độ phức tạp của truy vấn và hiệu năng thực tế, thay vì cho rằng một cách luôn tốt hơn cách còn lại.

11. Eloquent ORM khác gì SQL thuần?

Tiêu chí Eloquent ORM SQL thuần
Cách thao tác Model và phương thức PHP Câu lệnh SQL
CRUD phổ biến Thường ít mã hơn Cần viết truy vấn trực tiếp
Quan hệ dữ liệu Có thể khai báo qua model Sử dụng JOIN hoặc truy vấn liên quan
Kiểm soát truy vấn Thông qua API của ORM Trực tiếp hơn
Truy vấn đặc thù Có thể cần query builder hoặc SQL thô Linh hoạt theo hệ quản trị database
Tối ưu hiệu năng Cần hiểu truy vấn ORM tạo ra Cần hiểu và tối ưu SQL

Eloquent phù hợp với nhiều thao tác dữ liệu thường gặp trong ứng dụng Laravel. SQL thuần vẫn hữu ích khi cần kiểm soát truy vấn chính xác hoặc sử dụng những tính năng đặc thù.

Trong thực tế, hai cách không loại trừ nhau. Một ứng dụng có thể sử dụng Eloquent cho phần lớn CRUD và sử dụng query builder hoặc SQL trực tiếp cho những truy vấn phù hợp hơn.

12. Eloquent ORM trong hệ sinh thái Backend

Eloquent là một ví dụ cụ thể về cách ORM hỗ trợ ứng dụng Backend làm việc với database. Tuy nhiên, ORM không phải là một khái niệm riêng của PHP hoặc Laravel.

Các hệ sinh thái khác cũng có những thư viện tương tự, chẳng hạn:

  • GORM: ORM phổ biến trong hệ sinh thái Go.
  • Entity Framework Core: ORM trong hệ sinh thái .NET.
  • Hibernate: framework ORM phổ biến trong Java.
  • SQLAlchemy: thư viện SQL và ORM trong Python.

Mỗi thư viện có API, quy ước và cách quản lý truy vấn khác nhau, nhưng chúng cùng hướng đến việc kết nối mô hình đối tượng trong chương trình với dữ liệu quan hệ trong database.

13. Kết luận

Eloquent ORM là một thành phần quan trọng của Laravel, giúp lập trình viên thao tác với database thông qua model và object PHP. Từ các thao tác CRUD, xây dựng truy vấn, khai báo quan hệ đến eager loading và transaction, Eloquent cung cấp nhiều công cụ để phát triển ứng dụng Backend một cách rõ ràng và thuận tiện hơn.

Tuy nhiên, sử dụng ORM hiệu quả không chỉ là ghi nhớ các phương thức như find(), where(), create() hay with(). Điều quan trọng là hiểu mỗi thao tác tạo ra loại truy vấn nào, dữ liệu được tải ra sao, những vấn đề đồng thời có thể xảy ra và cách bảo vệ dữ liệu khỏi các thao tác không hợp lệ.

Mục tiêu không phải là tránh viết SQL, mà là biết khi nào nên sử dụng ORM, khi nào cần kiểm soát truy vấn trực tiếp và làm thế nào để cân bằng giữa khả năng bảo trì, tính đúng đắn và hiệu năng của ứng dụng.

Nguồn tham khảo

Bài viết khác

Positive vs Negative Testing(Kiểm thử tích cực và Kiểm thử tiêu cực)

1.Positive vs Negative Testing là gì? Trong ngành công nghệ phần mềm, chất lượng sản phẩm không bao giờ là một khái niệm đơn chiều. Một ứng dụng được lập trình hoàn hảo trên lý thuyết vẫn có thể sụp đổ hoàn toàn ngay trong ngày đầu tiên phát hành nếu nó chỉ được chuẩn […]

CAP theorem

1. CAP Theorem là gì? CAP Theorem là một định lý nền tảng trong Distributed Systems, giải thích giới hạn của việc đồng thời đảm bảo ba thuộc tính: Consistency (C), Availability (A) và Partition Tolerance (P) trong một hệ thống dữ liệu phân tán. Định lý bắt nguồn từ nhận định của Eric Brewer […]

Series 5 – Next.js Fundamentals: Next.js, Project Structure, App Router, Static Routes, Dynamic Routes và Nested Routes

Series 5 – Next.js Fundamentals   Bài 1:Next.js, Project Structure và App Router 1. Mở đầu Sau khi học React, chúng ta đã biết cách xây dựng giao diện bằng Component, truyền dữ liệu qua Props và quản lý trạng thái bằng State. Tuy nhiên, một ứng dụng web thực tế thường có nhiều trang, […]

CAP Theorem

CAP Theorem là gì? Tìm hiểu Consistency, Availability và Partition Tolerance 1. CAP Theorem là gì? CAP Theorem (định lý CAP) là một nguyên lý quan trọng trong thiết kế hệ thống phân tán, mô tả sự đánh đổi giữa tính nhất quán (Consistency) và tính sẵn sàng (Availability) khi xảy ra phân vùng mạng […]

Saga Pattern

Saga Pattern là gì? Quản lý Transaction trong kiến trúc Microservices 1. Saga Pattern là gì? Saga Pattern là một mẫu thiết kế được sử dụng để quản lý các nghiệp vụ phân tán gồm nhiều bước xử lý, trong đó mỗi bước có thể thực hiện một local transaction tại một service hoặc Database […]

Two-Phase Locking (2PL)

Two-Phase Locking (2PL) là gì? Cơ chế khóa hai pha trong Database 1. Two-Phase Locking (2PL) là gì? Two-Phase Locking (2PL — khóa hai pha) là một giao thức quản lý khóa trong Database, quy định cách một transaction được phép lấy và giải phóng lock trong quá trình thực thi. Khi nhiều transaction cùng […]

Leave a Reply

Your email address will not be published. Required fields are marked *