Method và Properties trong laravel. 4 trường hợp không nên dùng Properties

Cách phân biệt Method Properties trong laravel eloquent model? Khi nào sử dụng method và khi nào sử dụng properties?

Chào anh em, chúng ta lại gặp nhau trong bài thứ 2 của seri laravel core rồi. Phần trước mình đã viết về vấn đề N+1 là gì và cách khắc phục. Cũng như cách hàm load() và hàm with() hoạt động ra sao, khi nào dùng load(), khi nào dùng with(), nếu anh em nào chưa xem thì có thể đọc lại tại đây nhé.

Bài viết hôm nay sẽ về cách phân biệt method properties trong laravel eloquent model, khi nào sử dụng method và khi nào sử dụng properties. Đây là 1 khái niệm không mới nhưng nhiều khi do thói quen code mà ae vô tình dùng sai cách và làm cho code của chương trình trở nên chậm chạp, kém tối ưu hơn. Hãy cùng mình tìm hiểu cơ chế hoạt động của methodproperties để tránh lỗi cơ bản không đáng có này nhé.

Ô CÊ LẸT GÔ…!

Method hay Properties

Method hay Properties Chúng khác nhau ra sao

Tiếp tục là 1 tuyệt chiêu liên quan đến quan hệ trong model Eloquent. Phải công nhận là trong laravel có nhiều cách code dẫn đến kết quả gần giống nhau khiến cho chúng mình lầm tưởng rằng những cách này hoàn toàn tương đồng. Chính vì vậy với những ae mà mới tiếp cận với laravel thì có thể sẽ lúng túng không biết nên sử dụng cái nào cho đúng. MethodProperties là 1 ví dụ điển hình cho việc này.

Vẫn sử dụng kịch bản quan hệ của bài trước : 1 Author có nhiều Post, mỗi Post thuộc về 1 Author.

Lần này sếp của bạn lại giao cho bạn viết 1 trang hiển thị danh sách tác giả và số lượng bài viết tương ứng với mỗi tác giả.

Để giải quyết vấn đề này ta có 2 cách code như sau:

// Bên trong Model của Author khai báo mối quan hệ với thằng post
public function posts() {
    return $this->hasMany(Post::class);
}
// ở View ta có thể sử dụng 2 lệnh bên dưới để lấy ra số lượng bài viết của tác giả
$author->posts->count();
// Hoặc
$category->posts()->count();

Như các bạn đã thấy ta có 2 cách để lấy ra số lượng bài viết theo tác giả là:

$author->posts->count(); $author->posts()->count(); 

Các bạn để ý 1 cái là có ngoặc () và 1 cái là không có ngoặc nhá

Có thể thấy cả 2 cách này đều đúng, nhưng sao laravel lại vẽ ra nhiều cách thế nhỉ? Chúng khác nhau ở chỗ nào?

Dễ thấy rằng $author->posts() là lời gọi hàm posts() từ 1 instance của Author trong khi đó $author->posts là lời gọi properties (thuộc tính) của class Author. Bây giờ chúng ta sẽ tìm hiểu bản chất của từng cái.

1. Gọi bằng method: $author->post() thì như định nghĩa của Eloquent Author Model thì sẽ trả về $this->hasMany(Post::Class) nghĩa là trả về 1 instance của thằng Builder.

Flow của code trong trường hợp này sẽ như sau: Khi gọi $author->posts() thì sẽ chưa có 1 lệnh SQL nào được thực thi cả mà $author->posts() chỉ trả về 1 instance của thằng Builder mà thôi (bạn hiểu nôm na là trả về 1 biến). Phải tới khi gọi tới count() thì laravel mới chạy lệnh SQL để lấy ra số lượng post theo tác giả. Điều này sẽ tương tự như việc chạy lệnh SELECT COUNT(*) trong mysql vậy. Mỗi lần chạy lệnh $author->posts()->count() này sẽ thực hiện SELECT Count(*) trong DB 1 lần.

2. Cách gọi properties: Có bao giờ bạn tự hỏi là bên trong class Author không có bất kì 1 properties nào có tên là post cả, vậy mà chương trình vẫn chạy ngon mà không báo lỗi nhỉ? Sao laravel có thể làm được điều này là vì nó đã sử dụng Magic Method của PHP để biến việc gọi properties post trở lên hợp lệ. Sau đó nó sẽ trả về quan hệ posts của author, tức là $author->posts sẽ tương đương với $author->posts()->get(). Và đương nhiên khi đã gọi tới get() thì sẽ trả về 1 Collection rồi.

Tới đây thì chắc sẽ có ae bảo “ôi dào, vòng vo mãi thì thằng properties có khác mọe gì thằng method đâu”, vì cuối cùng cũng gọi tới $author->posts() thôi mà =)). Tuy nhiên có 1 điều hết sức đặc biệt là $author->posts chỉ query DB ở lần đầu tiên khi chúng gọi, sau khi nhận về kết quả thì sẽ lưu kết quả đó vào thằng properties posts mới tạo ra. Từ lần sau sẽ lấy trực tiếp giá trị của thằng posts này mà không cần query lại nữa. Đó chính là điều khác biệt với với method.

Flow của code trong trường hợp này sẽ như sau: Khi gọi $author->posts thì chương trình sẽ query để lấy tất cả các post, Việc này sẽ tương đương với lệnh SELECT * FROM posts trong SQL. Sau khi kết quả trả ra là 1 collection thì hàm count() khi này mới được gọi để đếm số lượng phần tử trên collection đó (giống như hàm count() của mảng trong php vậy). Và bạn có gọi lệnh này bao nhiêu lần đi chăng nữa nó cũng chỉ thực thi 1 lần đầu tiên và lưu vào property posts của class Author.

Bạn có thể dùng Laravel Debugbar để kiểm chứng lại nhé. Nếu chưa biết cài thì có thể tham khảo tại đây (Link chưa được cập nhật)

Khi nào nên sử dụng method, khi nào sử dụng properties?

Khi nào nên sử dụng method, khi nào sử dụng properties

Ta sẽ xét từng trường hợp cụ thể để biết được khi nào nên sử dụng cái nào nhé.

Với ví dụ lấy số lượng bài viết theo tác giả đã đề cập bên trên, do $author->posts chỉ query 1 lần và lưu vào collection, tuy nhiên 1 lần chạy này lại lấy hết thông tin của post ra nên là lần thực hiện đầu tiên này sẽ tốn thời gian hơn. Vì thế nếu ta có nhu cầu lấy tất cả thông tin của post như tiêu đề, thẻ tag, ngày đăng… của post thì ta nên sử dụng properties.

Mặt khác, nếu chỉ cần đếm số lượng bài viết mà không cần lấy thêm thông tin gì của post thì ta nên sử dụng cách gọi method vì tuy rằng mỗi lần gọi lại sẽ thực hiện query 1 lần nhưng chỉ là SELECT COUNT() nên sẽ tối ưu hơn.

Tuy nhiên có 1 điều đặc biệt là mình có thể kèm thêm điều kiện cho thằng method nghĩa là mình có thể tối ưu tốc độ truy vấn của thằng method để bù lại việc truy vấn nhiều lần. Ví dụ như $author->posts()-pluck(‘id’, ‘name’)->where(xxx)->get().

Cũng có nghĩa là nếu bạn chỉ cần lấy những thông tin cơ bản được lưu thẳng vào DB ra như: tiêu đề, description, số lượt thích… thì có thể query lấy ra trực tiếp luôn mà không cần thông qua collection?

Ngược lại, nếu như dữ liệu của chúng ta cần được xử lý trước ( ví dụ như ngày tháng đăng bài, làm tròn số…), lúc đó việc xử lý trong DB sẽ rất khó khăn thì hãy tận dụng các hàm mà Collection hỗ trợ, khi này sử dụng $author->posts() lại là lựa chọn đúng đắn.

Có 1 nguyên tắc không được quên là hãy tạo query dữ liệu một cách chi tiết nhất có thể. Ví dụ như, nếu như sau khi lấy về các post của một author, nếu bạn tiếp tục cần thêm điều kiện lọc dữ liệu nữa, thì thay vì dùng $author->post để rồi sau đó thực hiện hàm where trên collection, hãy dùng $author->post() và kèm thêm điều kiện where() vào trong chuỗi query.

Túm cái váy lại: Bạn cần hiểu rõ bản chất của 2 thanh niên này để áp dụng hợp lý vào từng trường hợp cụ thể, không nên áp dụng 1 cách máy móc.

Các trường hợp không nên sử dụng properties

Các trường hợp không nên sử dụng properties

1. Không sử dụng collection để đếm số lượng bản ghi của đối tượng có quan hệ. Rõ ràng thao tác trong database nhanh hơn trên tầng application rất nhiều, vì thế nên khi bạn đơn thuần chỉ muốn đếm số lượng bản ghi, đừng dùng cách viết properties mà hãy sử dụng cách viết method.

2. Đừng sử dụng properties nếu như bạn chỉ cần lấy một phần tử của nó, trường hợp này xảy ra khá thường xuyên. Bạn cần lấy ra bài viết đầu tiên, hoặc bài viết cuối cùng hoặc một bài viết có id cụ thể nào đó. Thì đừng quen tay mà viết $author->posts->first() mặc dù nó có vẻ thuận mắt và nhìn chuyên nghiệp hơn =)). Hãy dùng $author->posts()->first(). Sử dụng 1 thằng thì lấy 1 thằng thôi, đừng lôi cả họ hàng nhà nó lên xong lấy có 1 thằng, tội nghiệp chúng nó lắm =)).

3. Đừng WHERE trên Collection, hãy WHERE trên Builder: Ví dụ như khi ta viết $author->posts->where(‘id’, 1) thì điều gì xảy ra? nó sẽ lấy ra tất cả các bản ghi posts có quan hệ với bản ghi Author, ném chúng vào collection, sau đó mới where trên collection này, kết quả trả về lại là một collection khác nữa. Tới đây đã thấy lằng nhằng rồi, còn chưa kể việc where được tiến hành trên tầng application => việc mình đánh index ở DB không còn ý nghĩa nữa.

Thay vào đó, khi ta viết $author->post()->where(‘id’, 1), tất cả việc tìm kiếm đều được thực hiện trong tầng database, sẽ thực thi câu lệnh SELECT * FROM posts where id = 1, mọi chuyện nhanh chóng và đơn giản hơn rất nhiều.

4. Đừng PLUCK trên Collection, hãy PLUCK trên Builder. Pluck là 1 hàm dùng để xử lý data được sử dụng khá thường xuyên trong laravel. Ví dụ như khi ta cần lấy về một mảng có format [‘id’ => ‘name’] của các bài viết chẳng hạn. Cách viết $author->post->pluck(‘name’, ‘id’) sẽ làm 1 loạt các công việc tương tự như như lấy hết ra, xong ném vào collection, xong pluck trên collecttion đó… Trong khi đó cách viết $author->posts()->pluck(‘name’, ‘id’) sẽ thêm điều kiện vào trực tiếp câu query, khi này (SELECT name, id) thay vì (SELECT *), performance sẽ tăng lên đáng kể.

Lưu ý: Nói vậy không phải việc sử dụng collection cũng là không tốt. Laravel đã xây dựng lên các method rất mạnh mẽ cho collection. Nếu việc xử lý ở DB phức tạp và dễ có bug tiền ẩn thì việc sử dụng điểm mạnh có sẵn của các mutator trong collection là điều cần thiết. Để tránh phát sinh những bug không đáng có sau này.

Tổng kết

Bài viết này mình đã đề cập tới cách phân biệt method và properties cũng như từng trường hợp cụ thể nên sử dụng cái nào. 1 số lưu ý cần tránh sử dụng properties trong thực tế. Cái này tùy vào trường hợp cụ thể anh em sẽ lựa chọn cách viết cho hợp lý. Hãy nhớ đừng vì thấy cú pháp đẹp hoặc có vẻ chuyên nghiệp mà sử dụng, nó có thể vô tình làm cho cái bụng của chương trình bạn viết phình to và trở nên chậm chạp.

Nếu bạn thấy hay thì hãy chia sẻ cho bạn bè của mình biết nhé…! Ở bài viết sau mình sẽ đề cập tới “Eloquent và Query Builder – Nên sử dụng cái nào?” Mời các bạn cùng tìm đọc.


Bài viết liên quan

Để lại một bình luận

Email của bạn sẽ không được hiển thị công khai. Các trường bắt buộc được đánh dấu *