Học Laravel – Bài 10: Facades

Giới thiệu Facades trong Laravel:

Facades cung cấp 1 “static” interface đến những class mà có sẵn trong service container. Laravel có nhiều facade cung cấp tiếp cận tới hầu hết tính năng.

Facade hỗ trợ như một “static proxy” tới những class cơ bản trong service container, cung cấp cú pháp ngắn gọn mà vẫn linh hoạt hơn so với static method thông thường. Bạn không cần thiết thực sự hiểu hết cách mà facade hoạt động, chỉ cần làm theo flow của Laravel.

Tất cả facade được define trong namespace Illuminate\Support\Facades. Vì vậy bạn có thể dễ dàng tiếp cận facade như sau:

PHP
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Route;

Route::get('/cache', function() {
    return Cache::get('key');
});

Helper functions:

Để bổ sung cho facade, Laravel cung cấp nhiều global helper function, giúp dễ dàng hơn để tương tác với các tính năng thông thường của Laravel. Một số helper function thông thường như view, response, url, config

Ví dụ, thay vì dùng facade Response để tạo 1 Json response, chúng ta có thể đơn giản sử dụng hàm response. Bạn có thể gọi hàm này bất cứ ở đâu:

PHP
return Response::json();
// or:
return resonpse()->json();

Khi nào sử dụng facade:

Facade có nhiều lợi ích. Nó cung cấp cú pháp ngắn gọn, dễ nhớ giúp bạn sử dụng các tính năng của Laravel mà không cần nhớ class dài dòng. Tuy nhiên một số trường hợp cần lưu ý. Nguy hiểm của facade là class “scope creep”. Vì facade dễ dùng và không cần inject, nó giúp dễ dàng sử dụng nhiều facades trong 1 class. Sử dụng dependency injection giúp bạn quan sát trực quan rằng 1 constructor lớn cho thấy class của bạn đang phát triển quá lớn. Vì vậy khi sử dụng facade, đặc biệt lưu ý đến kích thước class, phạm vi trách nhiệm của nó nên nhỏ. Nếu class của bạn quá lớn, nên tách thành nhiều class nhỏ.

Facade Vs. Dependency injection

Một lợi ích của dependency injection là có thể hoán đổi implement của class được inject. Nó hiệu quả trong việc testing vì bạn có thể inject mock hoặc stub và xác nhận rằng nhiều phương thức đã được gọi trên stub.

Thông thường, không thể mock hoặc stub 1 phương thức static. Tuy nhiên nhờ facade sử dụng phương thức động để ủy quyền phương thức gọi đến những đối tượng được resolve từ service container, chúng ta có thể test facade như test 1 instance của class được inject. Ví dụ, với route dưới đây:

PHP
Route::get('/cache', function() {
    return Cache::get('key');
});

Sử dụng phương thức testing của facade, chúng ta có thể viết test dưới đây để xác minh phương thức Cache::get đã được gọi với tham số mà chúng ta mong muốn:

PHP
public function test_basic_example(): void
{
    Cache::shouldReceive('get')
        ->with('key')
        ->andReturn('value');
    $response = $this->get('/cache');
    $response->assertSee('value');
}

Facades vs. Helper Functions:

Ngoài Facades, Laravel cung cấp nhiều helper functions giúp thực hiện nhiều tác vụ như views, kích hoạt sự kiện, dispatch jobs, gửi responses … Nhiều helper thực hiện cùng chức năng với 1 facade tương ứng. Ví dụ facade và helper sau là tương đương:

return Illuminate\Support\Facades\View::make('profile');
 
return view('profile');

Facade hoạt động như thế nào?

Facade là 1 class cung cấp sự tiếp cận tới object trong container. Facade extend class Illuminate\Support\Facades\Facade.

Facade base class sử dụng magic-method __callStatic() để gọi tới 1 object được resolve từ container. Ở ví dụ dưới đây ta có thể thấy facade Cache đang gọi 1 phương thức tĩnh ::get():

<?php
 
namespace App\Http\Controllers;
 
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Cache;
use Illuminate\View\View;
 
class UserController extends Controller
{
    /**
     * Show the profile for the given user.
     */
    public function showProfile(string $id): View
    {
        $user = Cache::get('user:'.$id);
 
        return view('profile', ['user' => $user]);
    }
}

Ở đây ta import Cache facade. Facade này như 1 proxy để tiếp cận đến thực thi của Illuminate\Contracts\Cache\Factory. Mọi method gọi qua facade đều được pass đến instance của cache service.

Ở trong class Illuminate\Support\Facades\Cache bạn sẽ không thấy static method get mà chỉ có method getFacadeAccessor():

class Cache extends Facade
{
    /**
     * Get the registered name of the component.
     */
    protected static function getFacadeAccessor(): string
    {
        return 'cache';
    }
}

Method này trả về tên của service container binding. Khi user tham chiếu đến 1 static method của Cache facade, Laravel sẽ resolve cache binding từ service container và chạy method tương ứng của object này.

Real-time Facades:

Sử dụng real-time facades, bạn có thể coi bất kỳ class nào trong ứng dụng của bạn như là facade. Đầu tiên xem đoạn code sau không sử dụng real-time facade. Ví dụ Podcast model có method publish. Để publish podcast, ta cần inject 1 instance Publisher:

<?php
 
namespace App\Models;
 
use App\Contracts\Publisher;
use Illuminate\Database\Eloquent\Model;
 
class Podcast extends Model
{
    /**
     * Publish the podcast.
     */
    public function publish(Publisher $publisher): void
    {
        $this->update(['publishing' => now()]);
 
        $publisher->publish($this);
    }
}

Inject 1 publisher implement vào method cho phép dễ dàng test method riêng biệt vì bạn có thể mock publisher được inject. Tuy nhiên nó yêu cầu bạn luôn phải pass 1 publisher mỗi lần gọi method publish. Sử dụng real-time facades, bạn có thể duy trì cùng khả năng có thể test (testability) và không yêu cầu phải pass tường minh 1 Publisher instance. Để khởi tạo real-time facade, đặt tiền tố namespace của class là Facades:

<?php
 
namespace App\Models;
 
use App\Contracts\Publisher;

// khai báo publisher
use Facades\App\Contracts\Publisher;
use Illuminate\Database\Eloquent\Model;
 
class Podcast extends Model
{
    /**
     * Publish the podcast.
     */
    public function publish(): void
    {
        $this->update(['publishing' => now()]);
 
        Publisher::publish($this);
    }
}

Khi real-time facade được sử dụng, publisher implement sẽ được resolve ngoài service container sử dụng phần tên interface hoặc class sau tiền tố Facades. Khi test, bạn có thể dùng testing helper để mock method call:

<?php
 
namespace Tests\Feature;
 
use App\Models\Podcast;
use Facades\App\Contracts\Publisher;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
 
class PodcastTest extends TestCase
{
    use RefreshDatabase;
 
    /**
     * A test example.
     */
    public function test_podcast_can_be_published(): void
    {
        $podcast = Podcast::factory()->create();
 
        Publisher::shouldReceive('publish')->once()->with($podcast);
 
        $podcast->publish();
    }
}

Các Class Facades:

FacadeClassService Container Binding
AppIlluminate\Foundation\Applicationapp
ArtisanIlluminate\Contracts\Console\Kernelartisan
AuthIlluminate\Auth\AuthManagerauth
Auth (Instance)Illuminate\Contracts\Auth\Guardauth.driver
BladeIlluminate\View\Compilers\BladeCompilerblade.compiler
BroadcastIlluminate\Contracts\Broadcasting\Factory 
Broadcast (Instance)Illuminate\Contracts\Broadcasting\Broadcaster 
BusIlluminate\Contracts\Bus\Dispatcher 
CacheIlluminate\Cache\CacheManagercache
Cache (Instance)Illuminate\Cache\Repositorycache.store
ConfigIlluminate\Config\Repositoryconfig
CookieIlluminate\Cookie\CookieJarcookie
CryptIlluminate\Encryption\Encrypterencrypter
DateIlluminate\Support\DateFactorydate
DBIlluminate\Database\DatabaseManagerdb
DB (Instance)Illuminate\Database\Connectiondb.connection
EventIlluminate\Events\Dispatcherevents
FileIlluminate\Filesystem\Filesystemfiles
GateIlluminate\Contracts\Auth\Access\Gate 
HashIlluminate\Contracts\Hashing\Hasherhash
HttpIlluminate\Http\Client\Factory 
LangIlluminate\Translation\Translatortranslator
LogIlluminate\Log\LogManagerlog
MailIlluminate\Mail\Mailermailer
NotificationIlluminate\Notifications\ChannelManager 
PasswordIlluminate\Auth\Passwords\PasswordBrokerManagerauth.password
Password (Instance)Illuminate\Auth\Passwords\PasswordBrokerauth.password.broker
Pipeline (Instance)Illuminate\Pipeline\Pipeline 
ProcessIlluminate\Process\Factory 
QueueIlluminate\Queue\QueueManagerqueue
Queue (Instance)Illuminate\Contracts\Queue\Queuequeue.connection
Queue (Base Class)Illuminate\Queue\Queue 
RedirectIlluminate\Routing\Redirectorredirect
RedisIlluminate\Redis\RedisManagerredis
Redis (Instance)Illuminate\Redis\Connections\Connectionredis.connection
RequestIlluminate\Http\Requestrequest
ResponseIlluminate\Contracts\Routing\ResponseFactory 
Response (Instance)Illuminate\Http\Response 
RouteIlluminate\Routing\Routerrouter
SchemaIlluminate\Database\Schema\Builder 
SessionIlluminate\Session\SessionManagersession
Session (Instance)Illuminate\Session\Storesession.store
StorageIlluminate\Filesystem\FilesystemManagerfilesystem
Storage (Instance)Illuminate\Contracts\Filesystem\Filesystemfilesystem.disk
URLIlluminate\Routing\UrlGeneratorurl
ValidatorIlluminate\Validation\Factoryvalidator
Validator (Instance)Illuminate\Validation\Validator 
ViewIlluminate\View\Factoryview
View (Instance)Illuminate\View\View 
ViteIlluminate\Foundation\Vite 

Để 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 *