Hi các bạn,
Xài Laravel với Eloquent thì cũng như là ăn bánh mì phải có pate. Hai combi không thể thiếu.
Mà để tận dụng tốt khả năng của Eloquent, chúng ta phải tìm đến những Best Practices.
Cùng dạo qua những cái Best Practices nào.
1/ Sử dụng created_at / updated_at built-in của Eloquent
Record sinh ra khi nào và sửa lúc nào, ta đều phải log lại hết. Built-in của Eloquent giúp chúng ta làm điều đó cực kỳ đơn giản.
Chỉ đơn giản là lúc tạo 1 Migration mới, “$table->timestamps()” là xong :))
2/ Sử dụng deleted_at / Soft Deletes của Eloquent
Dành cho những tables mà data rất là quan trọng, ai đời lại đi hard delete các bạn nhỉ?
Để tìm hiểu về Soft Deletes, mình có viết 1 bài ở đây:
====
Note, các phần dưới đây, tôi đoán dc là bạn đã biết về Eloquent Relationships. Nếu chưa hãy visit trang này nhé:
====
3/ Eager Loading before Accessing
Như các bạn đã biết về Eloquent Relationships – chúng ta sẽ define các relationships thông qua Eloquent Entity.
Tuy nhiên, Eloquent thông minh đến nỗi, mọi Relationship Access sẽ được lazy-load (chỉ load/Query database khi bạn thực sự access vào).
Vậy nên, problems ở đây: Với 1 bài viết có 100 comments, relationship là 1 article – n comments thì ta hay dùng như sau:
foreach ($article->comments as $comment) {
...
}
Và thực tế Query sẽ chạy là:
SELECT * from `article` where `id` = ...;
SELECT * from `comment` where `id` = ...
SELECT * from `comment` where `id` = ...
SELECT * from `comment` where `id` = ...
SELECT * from `comment` where `id` = ...
....
Có nghĩa là, độ phức tạp của nó đang là O(n+1)
Vậy nên, để giảm thiểu dc vụ này, ta sẽ cần Eager Loading của Laravel, sử dụng đơn giản như sau với method with:
$article = Article::with(['comments'])->find($id);
Queries Eloquent sẽ generate ra là:
SELECT * from `article` WHERE `id` = ...;
SELECT * from `comments` WHERE `article_id` = ...;
Rất đơn giản phải ko nào? Problems của chúng ta đã dc giảm xuống còn O(2)
4/ Depth-Accessing Eager Loading
Với ví dụ ở trên, nếu tôi muốn lấy users đã posted comment(s) thì sao?
Đơn giản, với dot notation, bạn có thể làm việc đó dễ dàng như sau:
Article::with([
'comments',
'comments.user'
])
->find($id);
Rất đơn giản phải không nào, giờ thì cùng access như sau:
foreach ($article->comments as $comment) {
$user = $comment->user; // usable now
}
Hehehe
5/ With
with special actions
Giả sử bạn muốn sort (orderBy) những cái comments theo ngày dc đăng mới nhất hay là 1 condition nào đó.
Hoặc chỉ select những columns nhất định (nhẹ bộ nhớ), thì ta sẽ đơn giản làm việc như sau, tạo ra 1 closure và thêm query thoy:
Article::with([
'comments' => function($qComment) {
$qComment->select(['id', 'user_id', 'created_at', 'comment']);
$qComment->orderBy('created_at', 'DESC');
}
])
->find($id);
Và thế, comments chỉ lấy ra id,user_id,comment,created_at cũng như là sort theo ý bạn muốn.
Đây là 1 best practices khá tốt, không nên select * ra hết vì chắc gì bạn đã xài hết chúng đâu 😀
6/ whereHas
/ whereDoesntHave
Pro MAX
With cũng như Join, là các bạn sẽ ra những điều kiện gì bạn muốn join vào.
Where sẽ là nơi bạn phân loại/filter data khi đã joined tất cả.
whereHas
hay `whereDoesntHave` là 2 methods dùng để where trên relationships.
Và 2 methods này cũng có thể dùng dot notation để các bạn có thể where trên 1 relationship nhất định 😀
// vd, get comments of a specific user
Article::whereHas('comments', function($q) {
$q->where('user_id', Auth::user()->id);
});
7/ Viết scope viết scope và viết scope
Scope
là một cách tạo ra những set conditions nhất định giúp chúng ta query nhanh gọn hơn.
Đọc thêm về scope: https://laravel.com/docs/8.x/eloquent#query-scopes
Ví dụ, bạn muốn lấy ra tất cả comments của 1 user nào đó, thông thường bạn sẽ viết Query = Eloquent như sau:
$commentsOfUser = Comment::where('user_id', $userId)->get();
Nhưng nếu có scope, nó sẽ như sau:
// in Comment.php
public function scopeByUser($query, $userId) {
return $query->where('user_id', $userId);
}
// in your app
$commentsOfUser = Comment::byUser($userId)->get();
Elegant hơn, phải không? Ngoài ra nó còn có 1 opportunity nữa là những gì related tới database, bạn sẽ để trong Model file.
8/ Tận dụng tối đa Eloquent Accessor
Assessor (hay còn dc gọi là Getter) là một feature built-in trong Eloquent ORM. Nó giúp chúng ta format raw data từ database thành một formatted-data theo ý ta muốn.
Tận dụng dc Accessor sẽ giúp ích được mình rất nhiều từ công đoạn develop tới maintain.
Ví dụ, access lấy tên của user trong comment với cách thông thường:
foreach ($article->comments as $comment) {
$userName = $comment->user->name;
}
Thông qua accessor:
// in Comment.php
public function getCommentedUserNameAttribute() {
return $this->user->name;
}
// In your code
$userName = $comment->commentedUserName;
Cool, right?
Thêm các ví dụ tiếp theo như sau:
// in Article.php
public function getcountAllCommentsAttribute() {
return $this->comments->count();
}
// in User.php
public function getNameAttribute() {
return "{$this->first_name} {$this->last_name}";
}
//...
Bạn có thể đọc thêm về Accessor tại đây: https://laravel.com/docs/8.x/eloquent-mutators
9/ Data-Casting
Eloquent có đi kèm Data-Casting cho các bạn sử dụng (ép kiểu). Với data-casting, những field nào có những data-type nào bạn muốn, đơn giản là define vào $cast
trong Eloquent Model:
Ngoài ra, nếu bạn có thêm date field nào nữa (ngoài created_at, updated_at, deleted_at) thì bạn cũng nên define casting, Eloquent sẽ tự động parse date từ database vào tạo ra 1 Object Carbon, từ đó giúp bạn làm việc với Date/DateTime/Timestamp thoải mái hơn.
Đọc thêm tại: https://laravel.com/docs/8.x/eloquent-mutators#attribute-casting
10/ Dynamic Appends
$appends
là một configuration mà khi chúng ta convert Eloquent qua Assoc-Array hay JSON-string, nó sẽ tự động kèm theo Accessor mà chúng ta đã tạo ra.
Mặc định thì Eloquent sẽ không thêm bất kỳ Accessor nào của bạn vào converted data.
Cách thông thường ta hay define:
class Comment extends Model {
//...
protected $appends = [
'commentedUserName',
'formattedCreatedAt',
//...
];
}
Bình thường là vậy, nhưng nó ko tốt chút nào, bởi vì khi càng develop lâu dài, kiểu gì cũng sinh ra nhiều Accessors, rồi lại càng nhiều append attributes.
Và tất nhiên, không phải lúc nào ta cũng xài hết tất cả các accessors (nhất là API request), nó phát sinh ra vấn đề dư thừa dữ liệu, vừa tốn performance mà cũng tốn space.
Để tránh dc trường hợp này, ta cần phải dùng setAppends
:
$article->setAppends([
'countAllComments'
]);
foreach ($article->comment as $comment) {
$comment->setAppends([
'commentedUserName',
'formattedCreatedAt',
]);
}
$article->toJSON();
Đơn giản nhưng lại là 1 practice good!
Conclusion
Một dự án mà đã xác định đi lâu và dài, thì các bạn nên chú tâm tìm best practices xung quanh framework & libraries mà bạn xài để các bạn có thể tận dụng hết:
- Feature
- Upgrade
- Maintain
Chúc các bạn code vui vẻ =))