Laravel 25 :Thao tác với database qua Eloquent Model

0
18

ORM là gì?

Kỹ thuật ORM

ORM (Object Relational Mapping) là một kỹ thuật lập trình dùng để chuyển đổi dữ liệu giữa một hệ thống không hướng đối tượng như cơ sở dữ liệu sang hệ thống hướng đối tượng như lập trình hướng đôi tượng trong PHP. Kỹ thuật này tạo ra các đối tượng CSDL ảo có thể được lập trình trong mã nguồn và có nhiều ưu điểm như mã nguồn trở lên rõ ràng và dễ bảo trì, dễ dàng thao tác với dữ liệu và thực hiện việc tối ưu hệ thống thông qua việc sử dụng bộ đệm… Các công việc khó hoặc không thể xử lý ở database layer sẽ được đưa lên lớp ứng dụng.

Mô hình MVC là gì?

Khái niệm này đã được nhắc đến trong Laravel Routing, định tuyến người dùng, ở đây chúng ta sẽ chỉ sơ lược lại đôi chút. MVC (Model – View – Controller) là mô hình phân chia ứng dụng thành 3 thành phần, mỗi thành phần có nhiệm vụ riêng:

  • Model chứa các logic nghiệp vụ và các thao tác với cơ sở dữ liệu.
  • View thực hiện các công việc hiển thị và tương tác với người dùng.
  • Controller làm nhiệm vụ điều hướng giữa các đối tượng tham gia hệ thống như điều hướng một yêu cầu từ người dùng (HTTP request chẳng hạn) đến cho một Model tương ứng xử lý sau đó kết quả trả về sẽ chuyển đến cho View để thực hiện các hiển thị và tương tác với người dùng cuối.
Mô hình MVC

Áp dụng mô hình MVC giúp ứng dụng có thể module hóa rất rõ ràng, phát triển ứng dụng nhanh chóng và đặc biệt mã nguồn dễ bảo trì. Laravel là framework PHP áp dụng mô hình MVC, bài viết này của chúng ta sẽ tập trung ở phần Model là một trong những phần chính trong hệ thống core Laravel.

Các thuật ngữ liên quan đến cơ sở dữ liệu

Trong bài viết sẽ cố gắng dùng các thuật ngữ bằng tiếng Việt, tuy nhiên có một số thuật ngữ nếu viết bằng tiếng Việt khá dài hoặc không có một thuật ngữ tương tự trong tiếng Việt nên có lúc sử dụng cả thuật ngữ tiếng Anh. Danh sách các thuật ngữ cơ bản liên quan đến cơ sở dữ liệu:

  • Relational Database Management System (RDBMS): hệ quản trị cơ sở dữ liệu quan hệ là các ứng dụng được phát triển bởi các nhà cung cấp, giúp tạo ra các cơ sở dữ liệu quan hệ và quản trị chúng. Ví dụ: MySQL, SQL Server là các hệ quản trị cơ sở dữ liệu quan hệ.
  • database: cơ sở dữ liệu là kho chứa dữ liệu, trong một hệ thống ứng dụng có thể sử dụng nhiều các database khác nhau.
  • table: bảng dữ liệu
    record: các bản ghi dữ liệu
  • relationship: mối quan hệ giữa các bảng dữ liệu.
  • index: tạo chỉ mục cho dữ liệu giúp truy vấn dữ liệu được nhanh chóng.
  • CRUD: viết tắt của Create, Read, Update, Delete là 4 thao tác cơ bản với một bản ghi.

Laravel Eloquent ORM

Với các thuật ngữ ở trên, chúng ta phần nào mường tượng được Laravel Eloquent ORM là gì?, vâng nó nói lên rằng Laravel đã sử dụng kỹ thuật ORM giúp lập trình viên thao tác dễ dàng hơn với cơ sở dữ liệu. Trong phần này chúng ta sẽ nói nhiều đến Laravel Eloquent Model (gọi tắt là Model) là một phần trong mô hình MVC ở trên, các Model này sẽ thao tác trực tiếp với cơ sở dữ liệu, xử lý các logic nghiệp vụ và trả dữ liệu về cho các Controller. Chúng ta sẽ lần lượt tìm hiểu cách thức tạo ra các Model và sử dụng chúng.

Tạo Model

Trong Laravel thông qua ORM mỗi bảng trong cơ sở dữ liệu sẽ là một Model, các hành động tương tác với CSDL sẽ được làm trên Model như truy vấn dữ liệu hay insert các bản ghi vào bảng… Các Model có thể được tạo ra thủ công bằng cách tạo file có phần mở rộng php hoặc tự động thông qua các câu lệnh artisan:

Khi đó nó sẽ tạo ra một lớp Product trong file Product.php nằm trong thư mục app.

Tạo Model cùng với file migrate

Một vấn đề đặt ra là khi bạn tạo Model nhưng bảng trong cơ sở dữ liệu chưa tồn tại thì sao? Ok, chúng ta sẽ phải thực hiện tạo một bảng mới thông qua Laravel Migration, Laravel cũng hỗ trợ bạn một cách tự động luôn là tạo Model kèm với file migrate luôn thông qua việc thêm tham số –migrate hoặc -m trong câu lệnh tạo Model:

Một số quy ước ngầm định của Eloquent Model

Ngầm định tên bảng

Mỗi Model sẽ được mapping với một bảng dưới cơ sở dữ liệu, Laravel ngầm định một Model sẽ được map với một bảng có tên chính là tên Model với dạng số nhiều trong tiếng Anh (thêm s hoặc ies) còn được gọi là tiêu chuẩn đặt tên Snake case. (Xem them Các tiêu chuẩn đặt tên trong lập trình).

Snake case ở đây sẽ biến một tên lớp dạng PascalCase sang tên bảng dạng underscore (gạch chân) và thêm số nhiều vào. Ví dụ Model Product ở trên sẽ tương ứng với bảng products. Nếu bạn muốn bỏ ngầm định này để map Model sang một bảng khác như my_products cũng rất đơn giản là sử dụng thuộc tính $table để khai báo.

Ngầm định khóa chính

Eloquent Model ngầm định sử dụng trường có tên id là khóa chính (primary key), nếu bảng trong cơ sở dữ liệu sử dụng một trường khác là khóa chính chúng ta có thể khai báo thông qua thuộc tính $primaryKey, với trường khóa chính này nếu không phải là dạng dữ liệu số nguyên Integer và tăng tự động, chúng ta cần thiết lập thuộc tính $incrementing thành false.

Log thời gian tương tác bản ghi dữ liệu

Mặc định, Eloquent Model yêu cầu các bảng trong CSDL phải có các trường created_at và updated_at lưu giữ thông tin thời gian tạo bản ghi và thời gian bản ghi này được cập nhật. Tính năng này giúp bạn quản lý các bản ghi trong CSDL rất tốt, đặc biệt trong các bảng liên quan đến giao dịch như đặt hàng, thanh toán… Tuy nhiên, nếu bạn không muốn tính năng này, thiết lập thuộc tính $timestamps thành false.

Như vậy chúng ta có thể thấy là Eloquent Model thiết lập ngầm định một số cấu hình, chúng ta nên tuân thủ theo một tiêu chuẩn lập trình, như vậy sẽ giảm được thời gian viết mã và cũng dễ bảo trì mã nguồn sau này.

Sử dụng Eloquent Model tương tác với database

Với mỗi một Model chúng ta sẽ có 4 hành động mà chúng ta quan tâm, nó tương ứng với 4 hành động với các bản ghi dữ liệu (record):

  • Create: tạo ra một Model, tương ứng với việc tạo ra một bản ghi dữ liệu trong bảng.
  • Read: truy vấn dữ liệu từ database và map lên Model
  • Update: chỉnh sửa một Model hay tương ứng là chỉnh sửa một bản ghi.
  • Delete: xóa một Model và tương ứng là xóa một bản ghi dữ liệu.

Insert dữ liệu

Tạo một bảng ghi dữ liệu

Như đã nói ở trên, việc thêm một bản ghi dữ liệu sẽ tương ứng với tạo một thực thể hay một cài đặt (instance) từ Model.

Tạo một thực thể bằng câu lệnh new Ten_model, ở đây tạo ra một thực thể là $product từ Model Product, sau đó gán giá trị cho các thuộc tính của chúng. Khi muốn lưu record này vào database chúng ta gọi đến phương thức save(). Chú ý, các trường created_at, updated_at sẽ được cập nhật các giá trị thời gian một cách tự động do đó bạn chỉ cần quan tâm đến các trường này khi sử dụng giá trị của chúng.

Chúng ta cùng xem lại nếu như không sử dụng Laravel Eloquent chúng ta cũng có thể sử dụng Query Builder để thực hiện, và khi đó chúng ta phải tự chủ động xử lý các công việc mà Laravel Eloquent đã ngầm định như đưa vào giá trị thời gian cho created_at, updated_at chẳng hạn.

Model Mass Assignment

Mass Assignment là gì? Mass Assignment xuất phát từ ngôn ngữ Ruby on Rails, là tính năng cho phép lập trình một cách tự động gán các tham số của một HTTP request vào các biến hoặc đối tượng trong lập trình. Ví dụ: chúng ta có một form đăng ký người dùng như sau, các tên trường nhập liệu trùng với tên cột trong bảng users trong CSDL.

Khi đó form này POST dữ liệu lên chúng ta có thể ghi dữ liệu này vào CSDL bằng đoạn code sau:

Thật ngắn gọn và đơn giản đúng không, tính năng này gọi là Mass Assignment. Tuy nhiên, có một lỗ hổng bảo mật xảy ra, nếu một kẻ xấu gửi thêm dữ liệu user_type = ‘admin’, khi đó user mới được tạo sẽ có quyền admin, việc gắn thêm dữ liệu gửi lên server là rất đơn giản có thể thực hiện bằng các công cụ có sẵn trên trình duyệt như Chrome Developer Tools…

Để xử lý vấn đề lỗ hổng trong Mass Assignment, Laravel đưa ra thêm hai thuộc tính cho Model là $fillable và $guarded. Ví dụ:

$fillable cho phép thiết lập các cột trong một bảng có thể sử dụng tính năng Mass Assignment, khi đó ta có thể thực hiện:

Khi đó nếu kẻ xấu gửi thêm user_type là trường không có trong $fillable, các câu lệnh trên sẽ phát sinh một exception ngay. Như vậy lỗ hổng trong Mass Assignment đã được xử lý.

Trái ngược với $fillable, ta có thể định nghĩa các trường được bảo vệ khỏi Mass Assignment thông qua thuộc tính $guarded. Chú ý, không khai báo cả hai thuộc tính này đồng thời.

Chú ý: một vấn đề nữa là $fillable và $guarded chỉ có tác dụng với các phương thức của Eloquent Model, với các phương thức của Query Builder nó không có tác dụng. Ví dụ sau minh chứng cho điều này:

Khi đó nếu kẻ xấu có tình chèn thêm user_type = ‘admin’ thì đoạn code đầu sẽ phát sinh exception còn đoạn code sau vẫn chạy bình thường.

Một số phương thức tạo bản ghi khác

Có hai phương thức tạo bản ghi mới sử dụng Mass Assignment khác là firstOrCreate và firstOrNew. Phương thức firstOrCreate() thử tìm các bản ghi sử dụng cặp cột và giá trị, nếu không tìm thấy, một bản ghi sẽ được tạo ra với các thuộc tính này. firstOrNew thì khác chút là nó không ghi dữ liệu vào CSDL mà trả về một instance của model, chỉ ghi dữ liệu xuống CSDL khi gọi phương thức save().

Một phương thức nữa cũng rất hay gặp trong thực tế là updateOrCreate, nó sử dụng để update hoặc tạo mới một entry, ví dụ

Truy vấn dữ liệu bằng Model

Bạn đã tạo ra các record, giờ là lúc đọc các record này và xử lý chúng. Trong database, chúng ta thường viết các câu truy vấn dữ liệu để lấy dữ liệu, ở trên lớp trên chúng ta cũng thực hiện các truy vấn thông qua Model. Để lấy tất cả các record trong table mà Model thể hiện tương ứng sử dụng phương thức all():

Đoạn mã trên lấy tất cả các sản phẩm trong bảng products và duyệt qua các thực thể đó rồi in ra màn hình thông tin về sản phẩm. Chúng ta có thể thêm các điều kiện vào truy vấn:

Các phương thức sử dụng để thêm điều kiện trong truy vấn như where, orderBy, take… bạn có thể xem thêm trong phần Xây dựng truy vấn database với Laravel Query Builder. Kết quả trả về của các phương thức get(), all() là một instance của lớp Illuminate\Database\Eloquent\Collection. Laravel Collection là một trong những phần cực hay về xử lý dữ liệu dạng tập hợp, nó xây dựng sẵn hàng trăm các hàm giúp bạn viết code cực nhanh với những yêu cầu tổng hợp dữ liệu phức tạp. Ví dụ: bạn muốn tính tổng theo các nhóm thỏa mãn một điều kiện trong một tập hợp chẳng hạn, chỉ cần 1 dòng code là bạn có thể thực hiện được. Khó tin nhỉ, xem phần Laravel Collection để kiểm chứng nhé. Bạn nào đã từng làm quen với Lodash một thư viện Javascript tuyệt vời cho xử lý các tập hợp thì Laravel Collection cũng tương tự như vậy.

Hơi dài dòng một chút vì có nhiều điều cần viết quá nhưng thôi chúng ta lại tập trung vào chủ đề của bài viết. Tình huống tiếp theo, chúng ta muốn tìm một sản phẩm khi biết id của sản phẩm hoặc muốn lấy một sản phẩm bất kỳ có giá 300k chẳng hạn, dễ dàng thực hiện với Eloquent Model:

Chú ý, phương thức find() có thể truyền vào một mảng các id của sản phẩm

Các truy vấn cũng có thể đưa vào các hàm tổng hợp dữ liệu như với Laravel Query Builder như count(), max(), min()…

Cập nhật dữ liệu

Khi muốn update giá trị cột nào đó trong bảng, cũng đơn giản là tạo instance của Model tương ứng và sử dụng phương thức save() như ở trên:

Trên đây chúng ta update một bản ghi, vậy update cùng lúc nhiều bản ghi thì làm thế nào trong Eloquent Model? Ví dụ, tất cả các sản phẩm TENDA hiện đang hết hàng và chúng ta muốn chuyển chúng sang chế độ không đăng bán active = 0, chúng ta thực hiện như sau:

Xóa bản ghi dữ liệu

Xóa bản ghi dữ liệu đơn giản bằng cách gọi phương thức delete() trên thực thể của Model:

Hoặc chúng ta có thể truy vấn dữ liệu và xóa dựa trên kết quả truy vấn: Xóa tất cả các sản phẩm đang không active.

Xử lý bản ghi đã xóa

Trước đây, khi dung lượng và bộ xử lý trong máy chủ là một cái gì đó xa xỉ, các bản ghi cần được xóa triệt để khỏi cơ sở dữ liệu, hiện nay thì đã khác nhiều, chúng ta không cần quan tâm nhiều đến dung lượng lưu trữ. Các bản ghi cũng vì vậy chỉ được xóa mềm (soft delete), thực chất là thêm một trường đánh dấu là bản ghi này đã xóa. Các dữ liệu đã xóa là rất cần thiết cho việc phân tích hành vi người dùng hoặc kiểm tra debug các ứng dụng. Laravel hỗ trợ xóa mềm một bản ghi bằng cách thêm một thuộc tính deleted_at vào Model cũng như ở table trong database. Để cho phép một Model có thể thực hiện được đánh dấu bản ghi đã xóa, chúng ta sử dụng trait Illuminate\Database\Eloquent\SoftDeletes và thêm deleted_at vào thuộc tính $dates của nó:

Khi đó, nếu bạn thực hiện phương thức delete() thay vì nó sẽ xóa record đó đi thì nó sẽ cập nhật thời gian hiện tại vào trường deleted_at, và như vậy bản ghi này đã được đánh dấu là đã xóa. Chúng ta cũng có thể kiểm tra xem một thực thể của Model là được xóa mềm hay không bằng phương thức trashed()

Truy vấn các bản ghi được xóa “mềm”

Các bản ghi được đánh dấu là đã xóa sẽ không có kết quả trong các truy vấn dữ liệu thông thường, để thêm vào các kết quả từ các bản ghi đã xóa “mềm” chúng ta sử dụng phương thức withTrashed():

Nó sẽ trả về tất cả các bình luận của người dùng có id là 1 với cả các bình luận thông thường và bình luận đã được xóa “mềm”. Ngược lại nếu muốn chỉ truy vấn các kết quả trong các record đã được xóa mềm, sử dụng phương thức onlyTrashed():

Khôi phục các bản ghi đã xóa “mềm”

Xóa mềm là một cách rất hay do khi cần thiết chúng ta hoàn toàn có thể khôi phục được dữ liệu đã “xóa”, với phương thức restore(), bản ghi đã được khôi phục hoàn toàn:

Xóa vĩnh viễn bản ghi

Chúng ta có xóa “mềm” thì cũng phải có xóa “cứng” hay còn gọi là xóa vĩnh viễn bản ghi, tức là khi thực hiện bản ghi đó sẽ không còn trong database và như vậy các truy vấn thường lẫn truy vấn xóa “mềm” không còn kết quả gì liên quan. Thực hiện xóa “cứng” bằng phương thức forceDelete():

Query Scope – Phạm vi truy vấn

Phạm vi truy vấn xuất phát từ một vấn đề khi chúng ta muốn thực hiện cùng một số điều kiện ràng buộc truy vấn với một hoặc nhiều các truy vấn, chúng ta không cần phải lặp lại chúng cho tất các truy vấn mà chỉ cần định nghĩa các Scope và sử dụng lại chúng trong định nghĩa Model. Tính năng xóa “mềm” (Soft delete) ở phần trên là một ví dụ, các ràng buộc về trường deleted_at được lặp lại cho tất cả các truy vấn, thay vì truy vấn nào chúng ta cũng đưa thêm các ràng buộc với trường deleted_at thì chúng ta tạo ra các Scope.

Phạm vi toàn cục

Với phạm vi toàn cục, định nghĩa Scope này sẽ được áp dụng cho một Model và tất cả các truy vấn liên quan đến Model đó sẽ được áp dụng thêm ràng buộc. Ví dụ chúng ta tạo ra một Scope có tên là AgeScope.php nằm trong thư mục app\scopes (nếu chưa có bạn tự tạo ra):

Đây là một lớp kế thừa lại Illuminate\Database\Eloquent\Scope và sử dụng phương thức apply() để khai báo các ràng buộc thêm vào. Ở đây, chúng ta thêm vào một ràng buộc là tuổi phải lớn hơn 18. Khi muốn áp dụng Scope với phạm vi toàn cục này, thực hiện ghi đè phương thức boot với việc khai báo thêm phương thức addGlobalScope().

Khi đó, mọi truy vấn bằng Model User sẽ được ràng buộc thêm là người dùng phải đủ 18 tuổi, khi đó nếu bạn sử dụng User::all() thì nó sẽ build ra truy vấn có dạng:

Laravel cũng cho phép sử dụng Closure, là một cách định nghĩa hàm nâng cao định nghĩa hàm trong một hàm:

Nếu muốn truy vấn mà không bị ảnh hưởng bởi các Scope đã được áp dụng vào Model sử dụng phương thức withoutGlobalScopes(), phương thức này nếu không truyền tham số nào vào nó sẽ bỏ qua tất cả các Scope được áp dụng hoặc chúng ta có thể chỉ muốn bỏ các Scope nào đó thì truyền vào mảng tên các Scope đó.

Phạm vi cục bộ

Các Scope phạm vi cục bộ cho phép bạn định nghĩa các tập ràng buộc để dễ dàng sử dụng cho chính Model đó, do vậy các Scope phạm vi cục bộ được định nghĩa ngay trong Model.

 

Chú ý các Scope phạm vi cục bộ được định nghĩa bởi các hàm trong Model với tên bắt đầu bằng scope (scopePopular, scopeActive…). Khi đó bạn có thể sử dụng các Scope đã được định nghĩa này trong các truy vấn như sau:

Lấy tất cả các user có lượng bình chọn lớn hơn 100 và đang hoạt động.

Phạm vi truy vấn động

Đôi khi bạn muốn các phạm vi truy vấn chấp nhận một tham số đầu vào, cùng xem ví dụ sau:

 

Sau đó, chúng ta có thể truyền tham số khi gọi các Scope này:

Các sự kiện khi thao tác với cơ sở dữ liệu

Eloquent Model sẽ tạo ra các sự kiện khi thao tác với cơ sở dữ liệu, các sự kiện này bao gồm: creating, created, updating, updated, saving, saved, deleting, deleted, restoring, restored. Với các sự kiện này, bạn dễ dàng thực hiện được các công việc khác trước khi thực hiện một thao tác nào đó với database. Để khai báo sử dụng các sự kiện này với một Model, chúng ta sử dụng thuộc tính $events:

Khi đó Model User sẽ tạo ra các event saved và deleted khi các bản ghi được lưu và các bản ghi được xóa khỏi cơ sở dữ liệu. Công việc còn lại là lắng nghe các sự kiện và xử lý chúng khi sự kiện xảy ra.

BÌNH LUẬN

Vui lòng nhập bình luận của bạn
Vui lòng nhập tên của bạn ở đây