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:
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:
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:
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:
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();
}
}