Tech Blog: [Khái niệm Laravel] Giải mã Facade
Trong loạt bài về các khái niệm Laravel, chúng ta sẽ cùng tìm hiểu những kỹ thuật được xem là cốt lõi của framework Laravel: Service Container, Service Provider, Facade, v.v.
Khái niệm và lợi ích
Tổng quan
Nếu đã từng làm việc với Laravel, chắc hẳn bạn không còn xa lạ với đoạn code sau:
Cache::set(‘access_token’, \Illuminate\Support\Facades\Request::get(‘token’));
Hãy chú ý đến “Cache::set(), Request::get(), DB::table(), Http::get()“, đây chính là Facade.
Bây giờ, hãy cùng tìm hiểu xem Facade thực chất là gì.
Facade cung cấp một lớp proxy tĩnh (static-proxy) cho các class trong container, giúp ta có thể gọi các hàm non-static theo cách tĩnh (static).
Nghe có vẻ phức tạp phải không? Hãy cùng xem ví dụ dưới đây để hiểu rõ hơn.
Đây là cách gọi thông thường đối với một hàm non-static:
$cache = new CacheManager(app());
Còn đây là cách gọi cache theo kiểu tĩnh (static):
Cache::set(‘access_token’, $token);
Với kiến thức PHP cơ bản, chúng ta đều biết toán tử :: giúp gọi một hàm tĩnh mà không cần khởi tạo đối tượng của class.
Lợi ích và trường hợp sử dụng của Facade
- Cú pháp ngắn gọn, dễ nhớ: nhìn lại ví dụ Cache Facade ở trên, nếu không dùng facade, ta sẽ phải nhớ tên class CacheManager. Trong những tình huống khác nhau, tên class này có thể hoàn toàn khác nhau như CacheService, CacheDriver, v.v. Tuy nhiên, khi dùng cái tên Cache, nó rất gần với khái niệm gốc (chính là bộ nhớ đệm – cache) nên cực kỳ dễ nhớ. Bên cạnh đó, cách gọi tĩnh cũng ngắn gọn hơn hẳn (có lẽ 1 dòng vẫn ngắn hơn 2 dòng?)
- Không cần quan tâm đến dependency hay cấu hình: nhìn vào new CacheManager(app()), có thể thấy rõ khi khởi tạo class này, chúng ta cần cung cấp đầy đủ các dependency hoặc giá trị cấu hình cho nó. Và nếu trong dependency đó lại còn có dependency khác, việc khai báo đầy đủ sẽ trở thành cơn ác mộng thực sự. Tuy nhiên, với Facade, chúng ta có thể hoàn toàn bỏ qua vấn đề này vì mọi thứ đã được framework xử lý sẵn.
- Ẩn đi phần triển khai thực tế bên dưới: khi gọi Http::get(), chúng ta không cần biết bên trong nó làm gì, chỉ cần gọi và nhận lấy kết quả ?. Hay như ví dụ InvoiceFacade::send(‘target@gmail.com’, ‘2022-02’, ‘month’), chỉ cần đọc qua là có thể hiểu ngay logic: gửi hóa đơn đến target@gmail.com, dữ liệu được tổng hợp theo tháng, cụ thể là tháng 2/2022. Hàm send() phía sau có thể chứa rất nhiều logic khác nhau như lấy dữ liệu, lọc dữ liệu, kiểm tra email, tạo file, tải lên s3, v.v… nhưng khi đã tạo facade, ta chỉ cần gọi hàm send() là đủ. Đây chỉ là ví dụ minh họa đơn giản, trong thực tế, tùy vào từng bài toán cụ thể mà chúng ta cần vận dụng linh hoạt.
- Dễ dàng thực hiện kiểm thử (testing)
Cách Facade hoạt động
Được rồi, giờ là lúc chúng ta cùng tìm hiểu xem Facade hoạt động như thế nào.
Hãy cùng xem ví dụ sau:
use Illuminate\Support\Facades\URL;
Ở trên là ví dụ sử dụng URL Facade. Có thể thấy chúng ta đã dùng class Illuminate\Support\Facades\URL theo cách thông thường. Thế nhưng, khi mở class này ra xem, bất ngờ thay, ta chỉ thấy một class với duy nhất một hàm:
/**
Khoan đã, class Cache lại đang kế thừa từ class Facade. Thử mở class Facade ra xem, biết đâu hàm set() được định nghĩa ở đó? Nhưng sau khi mở ra, ơ kìa, chẳng có gì cả ?.
Đây chính là lúc Facade thể hiện phép màu của mình.
Quay trở lại với PHP, khi một hàm tĩnh được gọi nhưng không tồn tại trong class, một magic method có tên __callStatic sẽ được gọi thay thế. Hãy cùng xem đoạn code dưới đây:
<?php
Sao chép đoạn code trên và chạy thử. Có thể dễ dàng thấy rằng khi gọi hàm get(), chúng ta sẽ nhận được kết quả “Get function”. Điều này khá dễ hiểu.
Trong class Test không hề có hàm set(). Lúc này, hàm __callStatic() sẽ được gọi cùng với tên hàm đầy đủ và các tham số truyền vào. Khi xem lại kết quả, ta sẽ có:
__callStatic: set function [].
Mở lại class Facade và tìm đến hàm __callStatic, ta có thể hiểu rằng khi gọi hàm URL::route(), __callStatic sẽ được thực thi mà không hề ném ra Exception.
/**
Nhưng thực sự thì hàm route() trong URL::route() nằm ở đâu, và được viết như thế nào?
Dưới đây là các bước Facade thực hiện khi gọi __callStatic:
- Lấy giá trị binding của instance thông qua hàm getFacadeAccessor(). Trong class Illuminate\Support\Facades\URL.php, ta thấy hàm này trả về giá trị ‘url’
- Tìm trong service container giá trị được binding với facade accessor: ở đây chính là ‘url’ (tham khảo class Illuminate\Routing\RoutingServiceProvider.php tại dòng 64)
- Sau khi resolve được giá trị binding từ service container, ta có một instance của class Illuminate\Routing\UrlGenerator.php. Đây chính là nơi hàm route() được viết. Sau khi resolve xong, tạm gọi kết quả này là $instanceResolved
- Gọi hàm từ $instanceResolved theo cách non-static
Thực hành tạo một Facade
Tạo class Invoice.php
<?php
Binding class Invoice vào Container
Trong class AppServiceProvider, thêm đoạn code sau:
public function register(): void
Tạo class InvoiceFacade
<?php
Lưu ý rằng giá trị trả về của getFacadeAccessor() phải khớp với giá trị đã binding vào container, ở đây là ‘invoice’.
Sử dụng Invoice Facade
public function Test()
Như vậy, chúng ta có thể thấy mình đã gọi được hàm send() của Invoice Facade mà không cần biết cách cấu hình để khởi tạo hay triển khai nó.
Facade Aliases
Quay trở lại với ví dụ Route::URL(), điều gì sẽ xảy ra nếu chúng ta xóa dòng code sau:
use Illuminate\Support\Facades\URL;
Câu trả lời là đoạn code vẫn hoạt động bình thường. Điều này là nhờ một cơ chế gọi là facade alias.
Mở lại class Illuminate\Support\Facades\Facade.php và tìm đến hàm defaultAliases(), nơi định nghĩa các alias cho những facade mặc định, giúp chúng ta không cần phải use class tương ứng.
Trong ví dụ thực hành, nếu chúng ta cũng muốn sử dụng alias thì sao? Hãy làm theo các bước dưới đây:
Tạo alias
Thêm đoạn code sau vào configs/app.php:
‘aliases’ => Facade::defaultAliases()->merge([
Xóa dòng sau tại nơi facade đang được sử dụng:
use App\InvoiceFacade;
Hãy thử chạy lại, kết quả vẫn tương tự
Tổng kết
Vậy là chúng ta đã cùng tìm hiểu về Facade trong Laravel và cơ chế hoạt động kỳ diệu của nó. Trong bài viết tiếp theo, chúng ta sẽ đi sâu vào kỹ thuật Service Container. Cảm ơn tất cả mọi người đã theo dõi bài viết này.
Rất mong nhận được góp ý của các bạn ở phần bình luận bên dưới. ?














