Giới thiệu
Theo Translate, Facade có nghĩa là “bề ngoài, mặt ngoài”.
Nó cho phép bạn truy cập đến các hàm static
bên trong các service (class) được khai báo trong Service Container.
Nói như vậy thì hơi khó hiểu và hình dung. Xuống phía dưới đi vào khúc khai vào này nọ thì sẽ hiểu ngay thôi. Và thực tế chúng ta sử dụng Facade nhiều mà không để ý đó thôi.
Cùng khám phá qua file config/app.php
một xíu.
Ở phần cuối file, Laravel đã khai báo sẵn hàng loạt các class aliases
để sau này ta có thể sử dụng trong project của mình dưới cái tên ngắn gọn, thay vì phải gọi đầy đủ namespace
của chúng. Tức là khi khai báo 'Auth' => Illuminate\Support\Facades\Auth::class
, thì khi chúng ta sử dụng class Auth
bên trong prject của mình, chúng ta thực tế đã gọi đến class Illuminate\Support\Facades\Auth
.
Nếu các bạn thắc mắc vì sao Laravel có thể hỗ trợ mạnh mẽ như vậy. Bởi bản thân PHP đã support cho chúng ta thông qua hàm class_alias. Chúng ta đơn giản chỉ cần khai báo dưới dạng key-value vào file config/app.php
.
Quay trở lại chủ đề hôm nay, việc register alias trong file config liên quan gì đến chủ đề Facade vậy?
Để ý một xíu tên đầy đủ của class, hầu hết chúng đều có namespace
là Illuminate\Support\Facades
và vài class không thuộc facade thì cùng class chứa những hàm static. Các Facade mặc định đều năm trong thư mục vendor\laravel\framework\src\Illuminate\Supports
.
Những alias mà chúng ta hay dùng như App, Auth, DB, Route
, … đều là Facade cả.
Ví dụ:
// routes.php file
Route::get('/', function () {
return view('welcome');
});
// Controller file or middleware file
if (Auth::check()) {
$user = Auth::user();
}
Việc sử dụng các hàm Route::get()
hay Auth::user()
đều là sử dụng Facade. Và kiểm tra thử các hàm đó có phải các hàm static như mình đã nói ở trên không nhé!
Đi vào file facade xem như thế nào. Ở đây mình dùng file Auth
và những gì bạn thấy ở class Illuminate\Support\Facades\Auth
namespace Illuminate\Support\Facades;
/**
* @see \Illuminate\Auth\AuthManager
* @see \Illuminate\Contracts\Auth\Factory
* @see \Illuminate\Contracts\Auth\Guard
* @see \Illuminate\Contracts\Auth\StatefulGuard
*/
class Auth extends Facade
{
/**
* Get the registered name of the component.
*
* @return string
*/
protected static function getFacadeAccessor()
{
return 'auth';
}
}
Trong class chỉ khai báo một phương thức duy nhất là getFacadeAccessor
, ngoài ra không có một static method nào cả. Vậy hàm Auth::check()
, Auth::user()
… lấy từ đâu ra???
Facade hoạt động như thế nào?
Tiếp tục tìm hiểu sâu vào. Vì sao có thể gọi các hàm check, user từ Facade.
Hãy để ý một chút, ta thấy class Auth
, hay các class Facade khác, đều kế thừa từ một abstract class
là Illuminate\Support\Facades\Facade
. Và trong class này đã khai báo sẵn method getFacadeAccessor()
/**
* Get the registered name of the component.
*
* @return string
*
* @throws \RuntimeException
*/
protected static function getFacadeAccessor()
{
throw new RuntimeException('Facade does not implement getFacadeAccessor method.');
}
Việc khai báo throw exception bắt buộc khi class kế thừa của bạn bắt buộc phải khai báo override hàm này, nếu không sẽ sinh ra lỗi. Do đó tất cả các Facade đều có một static method getFacadeAccessor()
và may mắn thay, Laravel đã chuẩn bị sẵn cho chúng ta, chỉ cần khai báo hàm này thôi thì nó sẽ “dọn” ra cho chúng ta ăn luôn =]].
Đọc kỹ nội dung abstract class Facade bạn sẽ thấy được rằng nội dung mà method getFacadeAccessor()
trả về sẽ được sử dụng để tạo ra Facade Instance
, mà instance này được resolve ra từ Application Instance $app
, hay nói cách khác chính là Service Container.
Như vậy làm việc với Auth
thực tế là làm việc với service auth trong Service Container. Việc gọi các hàm static
của Auth
thực tế sẽ được xử lý trong magic method __callStatic
được khai báo bên dưới, ròi chuyển lời gọi qua hàm bình thường từ một instance được resolve ra từ Service Container.
Như vậy, ta có thể thấy các cách gọi sau sẽ tương đương:
Auth::check();
// Bản chất của lời gọi hàm static qua Facade có thể được tóm tắt lại thành
$auth = app('auth');
$auth->check();
Facade - GOOD - BAD
Facade có thể nói là một trong những khái niệm hay tính năng gây tranh cãi nhiều nhất của Laravel. Bạn có thể google ra cả đống bài viết, ý kiến cho rằng Facade là một “Bad design”, hay “Anti-pattern”. Nhưng cũng có nhiều người vẫn yêu thích và sử dụng Facade hàng ngày. Nó đã và vẫn đang là một phần của Laravel.
Facade thuận tiện. Facade dễ dùng. Facade giúp bạn viết code rất ngắn gọn nhanh chóng. Trên document của Laravel, Facade được miêu tả với rất nhiều ưu điểm như providing the benefit of a terse, expressive syntax while maintaining more testability and flexibility than traditional static methods.
Facade được thiết kế như một cây cầu để người lập trình tiếp cận với “Service” một cách dễ dàng. Bạn có thể sử dụng Facade gần như mọi lúc mọi nơi mà không phải mất công khởi tạo, resolve các instance từ trong Service Container. Facade về bản chất cũng không phải là một class chứa đầy những static method, nên nó không nặng nề chiếm bộ nhớ như những class như vậy.
Tuy nhiên chính vì để có được những sự tiện lợi như thế mà ta cần một đống các “magic method” được xử lý ngầm “behind the scene”. Với một người mới bắt đầu tìm hiểu và sử dụng Laravel, có thể họ sẽ dễ dàng biết đến và sử dụng cách gọi Auth::user()
, nhưng họ sẽ không thể hiểu được bản chất của hàm đó từ đâu ra. Hay khi gặp vấn đề và cần vào trong code của Framework để kiểm tra thì cũng thật khó để bạn tìm thấy được nguồn gốc của chúng.
Hơn thế nữa, với việc quá tiện dụng của Facade, nếu không cẩn thận bạn sẽ có thể làm code trở nên phức tạp và khó đoán biết hơn. Ví dụ như mình muốn kiểm tra một class hoạt động dựa vào các dependencies nào thì mình thường sẽ xem ở trong constructor của nó. Hay trong Laravel có support method injection thì mình cũng xem qua cả trong khai báo method nữa. Thế nhưng, với việc gọi Facade ở bên trong hàm thì ta đã vô hình chung che đi việc hàm, hay class, đó cần một service dependency, thứ mà thay vì ta nên inject vào thì nay được resolve ra thông qua Facade.
Tự tạo Facade
Các bước để tạo Facade trong Laravel
- Tạo class extends từ
lluminate\Support\Facades\Facade
. - Tạo PHP Class File.
- Bind class đó vào Service Provider.
- Đăng ký class Facade vào aliases trong file
config\app.php
hoặc không cũng được, ta sẽ gọi hết cảnamespace
của nó ra vẫn được nhang.
Thực hiện
- Tạo mới một Facade
Tạo fileDemoFacade.php
trong folderapp/Demo/Facade
App/Demo/Facade/DemoFacade.php
<?php
namespace App\Demo\Facade;
use Illuminate\Support\Facades\Facade;
class DemoFacade extends Facade
{
protected static function getFacadeAccessor()
{
return 'demo';
}
}
- Tạo class chứa các hàm static
Tạo fileDemo.php
trong folderapp/Demo
App/Demo/Demo.php
<?php
namespace App\Demo;
class Demo
{
public function helloWorld()
{
echo "Hello World!";
}
}
- Binding class vào Service Provider
Mở fileapp/Providers/AppServiceProvider.php
và chỉnh sửa methodregister
:
use App\Demo\Demo;
public function register()
{
$this->app->singleton('demo', function () {
return new Demo();
});
}
Đăng ký Facade vào file
config/app.php
'aliases' => [ ... 'Demo' => App\Demo\Facade\DemoFacade::class, ]
Gọi hàm từ Facade
Mở fileroutes/web.php
và tạo route demo.Route::get('demo', function () { echo Demo::helloWorld(); });
Chạy serve lên và truy cập trình duyệt vào đường dẫn http://127.0.0.1:8000/demo
, ta sẽ có kết quả là dòng chữ “Hello World“.
Như vậy là ta đã thành công tạo một Facade. Từ đây, để sử dụng Demo
Facade, bạn chỉ cần viết các function mà mình cần vào file App\Demo\Demo.php
. Chúc các bạn thành công khi áp dụng vào dự án của mình!
Giải thích một xíu chỗ này
Như mình đã nói ở trên là Facade gọi đến các hàm static nhưng sao hàm helloWorld()
của class Demo
lại không khai báo static. Tới đây chắc không ít bạn thắc mắc điều đó nhỉ.
Hàm helloWorld()
được gọi không phải gọi trực tiếp từ class Demo
mà là được gọi từ Facade DemoFacade
và đã được binding với class Demo
. Nói tới đây chắc nhiều bạn vẫn chưa hiểu đâu. Vậy các bạn nên tìm hiểu thêm các khác niệm về Service Container
, Service Providers
, Binding
, resolve
.
Các bạn đường hiểu lầm Demo chúng ta gọi ở đây, Cái Demo là từ alias mà ra đó.
Bạn có thể đổi tên class Demo -> DemoManager là cập nhật lại file AppServiceProvider
. Và reload lại trang thì nó vẫn chạy bình thường. Hoặc các bạn không muốn sử dụng alias
thì có thể gọi trực tiếp từ Facade
Route::get('demo', function () {
echo \App\Demo\Facade\DemoFacade::helloWorld();
});
Kết luận
Bài viết này chỉ hy vọng có thể giúp các bạn có thể hiểu hơn về những gì xảy ra đằng sau những hàm static mà ta vẫn hay gọi thông qua Facade.
Vì đây là một khái niệm khác là khó cho bạn nào chưa tìm hiểu về service container hay service provider. Cần phải tìm hiểu và suy ngẫm nhiều hơn thì mới có thể hiểu hết được.
Còn với câu hỏi “Nếu không dùng Facade thì có thể dùng cái gì để thay thế?“ thì câu trả lời là mình sử dụng Dependency Injection.
Hy vọng qua bài viết này mọi người có thể hiểu thêm về một tính năng vốn rất quen thuộc của Laravel. Và xin hẹn gặp lại vào bài viết tới, với những đào sâu khác về Framework phổ biến này.
Nguồn tham khảo:
Rất mong được sự ủng hộ của mọi người để mình có động lực ra những bài viết tiếp theo.
{\__/}
( ~.~ )
/ > ♥️ I LOVE YOU 3000
JUST DO IT!