Chào anh em cộng đồng Viblo!
Nếu anh em có thói quen rảnh rỗi sinh nông nổi, hay Ctrl + Click (hoặc Cmd + Click) vào các class của Laravel để đọc source code giống mình, chắc chắn anh em đã từng lướt qua file Illuminate\Http\Request.php và khựng lại ở một đoạn code trông có vẻ... cực kỳ ngớ ngẩn:
/**
* Return the Request instance.
*
* @return $this
*/
public function instance()
{
return $this;
}
Khoan đã! Một function tên là instance(), nằm bên trong class Request, và nội dung của nó chỉ có đúng 1 dòng là trả về chính bản thân nó (return $this;).
Nhiều anh em fresher nhìn vào sẽ cười thầm: "Ủa Taylor Otwell (cha đẻ Laravel) bị ngáo à? Đã cầm cái object trên tay rồi thì gọi hàm này làm gì nữa cho tốn thêm 1 bước xử lý?".
Nhưng ở đẳng cấp của một Framework hàng đầu thế giới, không có một dòng code nào là thừa thãi. Đằng sau hàm instance() trông có vẻ vô dụng này là cả một hệ tư tưởng về Design Pattern. Hôm nay, chúng ta cùng bóc trần nó nhé!
1. Nghịch lý bề nổi: Khi bạn cầm Vàng trên tay
Nếu bạn đang đứng ở Controller, và gọi hàm này thông qua Dependency Injection, thì đúng là nó... vô dụng thật:
public function store(Request $request)
{
// Cả 2 dòng này đều trả về CÙNG MỘT object trong bộ nhớ
$obj1 = $request;
$obj2 = $request->instance();
// So sánh: true (Giống hệt nhau)
dd($obj1 === $obj2);
}
Ở ngữ cảnh này, việc gọi $request->instance() đúng là việc làm "vẽ rắn thêm chân". Vậy tác giả viết nó ra để làm gì? Câu trả lời nằm ở "Tấm gương ma thuật" mang tên Facade.
2. Bản chất thật: Chiếc "Chìa khóa" mở khóa Facade
Trong Laravel, chúng ta rất hay dùng Facade để code ngắn gọn hơn. Thay vì inject Request vào mọi nơi, bạn có thể gọi tĩnh (static):
use Illuminate\Support\Facades\Request;
// Gọi hàm url() thông qua Facade
$url = Request::url();
Bí mật của Facade: Request facade thực chất chỉ là một cái "vỏ bọc" (Proxy). Lớp vỏ bọc này KHÔNG chứa hàm url(). Khi bạn gọi Request::url(), Facade sẽ dùng magic method __callStatic() chặn lại, chui vào Service Container (IoC) tìm cái object Request thật đang nằm trong RAM, và truyền lệnh url() cho object đó xử lý.
Vậy rắc rối xảy ra khi nào?
Giả sử bạn đang viết một hàm Service hoặc truyền data sang một thư viện bên thứ 3 (third-party package), và hàm đó bắt buộc (type-hint) tham số truyền vào phải là một object thật (Illuminate\Http\Request). Nhưng xui thay, ở chỗ bạn đang đứng, bạn không có object thật, bạn chỉ có cái Facade thôi!
Bạn KHÔNG THỂ truyền Facade vào được vì Facade là class Illuminate\Support\Facades\Request (vỏ bọc), còn hàm kia đòi class Illuminate\Http\Request (ruột).
Lúc này, hàm instance() tỏa sáng rực rỡ:
use Illuminate\Support\Facades\Request;
// LỖI! Class không khớp type-hint
$myService->process(Request::class);
// THÀNH CÔNG!
// Facade sẽ đẩy lệnh gọi instance() xuống object thật.
// Và object thật vui vẻ "return $this;" (Trả về chính nó) xuyên qua Facade để ra ngoài!
$realRequestObject = Request::instance();
$myService->process($realRequestObject);
Hàm instance() đóng vai trò là một "Cái móc", giúp bạn thọc tay xuyên qua lớp vỏ bọc Facade và lôi cái object thật bên trong ra ngoài ánh sáng.
3. Một ví dụ khác: Vượt ngục từ Helper
Tương tự như Facade, Laravel cung cấp global helper request().
Nếu gọi request() không có tham số, nó sẽ trả về object. Nhưng nếu bạn đang chain (chuỗi) các method liên tiếp, đôi khi cú pháp có thể hơi lấn cấn trong một số version PHP cũ hoặc khi viết Macro.
// Helper request() trả về instance, sau đó gọi instance() để lấy chính nó.
// Ở đây nó giúp đảm bảo tính nhất quán (Consistency) trong các chuỗi API tự build.
$req = request()->instance();
Thực chất, trong mã nguồn Core của Laravel, bạn sẽ thấy framework tự sử dụng hàm instance() rất nhiều khi nó cần truyền request gốc cho các thành phần như: Router, Validation, hay Exception Handler. Nó giúp Core code chắc chắn 100% rằng nó đang cầm object thật chứ không phải bị nhầm lẫn với một Wrapper hay Proxy nào khác.
4. Design Pattern: "Unwrapper" (Kẻ lột đồ)
Trong kiến trúc phần mềm, pattern này được xếp vào nhóm thiết kế Unwrapper hoặc Escape Hatch (Cửa thoát hiểm).
Khi bạn tạo ra các lớp ảo (Proxy, Facade, Decorator) để làm cho API dễ dùng hơn, bạn vô tình "nhốt" object thật vào trong một cái lồng. Một kiến trúc sư phần mềm giỏi (như Taylor) biết rằng: Dù cái lồng có đẹp đến đâu, sẽ luôn có lúc developer cần chạm vào cái object nguyên thủy bên trong.
Hàm instance() { return $this; } chính là cái cửa sổ nhỏ trên cái lồng đó.
Lời kết
Chỉ 3 dòng code đơn giản, ngỡ như một trò đùa, nhưng lại giải quyết triệt để bài toán kiến trúc giữa Static Proxy (Facade) và Object thật. Đó chính là sự tinh tế của việc xây dựng Framework dùng cho hàng triệu dự án.
Lần tới, nếu bạn có viết một package hay một class thiết kế theo dạng Wrapper/Decorator, đừng quên chừa lại một "cửa thoát hiểm" bằng hàm instance() (hoặc getWrappedObject()) nhé! Đội ngũ maintainer sau này sẽ thầm cảm ơn bạn đấy.
Anh em đã bao giờ bị dính lỗi Type-hint vì truyền nhầm Facade thay vì Object thật chưa? Cùng bình luận phía dưới nhé!
Nếu thấy bài mổ xẻ này hay, đừng tiếc 1 Upvote và Bookmark lại nha. Hẹn gặp anh em ở những bài soi Core tiếp theo!