Facade trong Laravel


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ó namespaceIlluminate\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 classIlluminate\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

  1. Tạo mới một Facade
    Tạo file DemoFacade.php trong folder app/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';
    }
}
  1. Tạo class chứa các hàm static
    Tạo file Demo.php trong folder app/Demo

App/Demo/Demo.php

<?php
namespace App\Demo;

class Demo 
{
   public function helloWorld()
   {
      echo "Hello World!";
   }
}
  1. Binding class vào Service Provider
    Mở file app/Providers/AppServiceProvider.php và chỉnh sửa method register:
use App\Demo\Demo;

public function register()
{
    $this->app->singleton('demo', function () {
        return new Demo();
    });
}
  1. Đăng ký Facade vào file config/app.php

     'aliases' => [
         ...
         'Demo' => App\Demo\Facade\DemoFacade::class,
     ]
    
  2. Gọi hàm từ Facade
    Mở file routes/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!


Reprint policy: All articles in this blog are used except for special statements CC BY 4.0 reprint policy. If reproduced, please indicate source Nguyễn Quang Đạt !
Comments
  TOC