Фасади · Laravel
У документації Laravel вам постійно траплятимуться приклади коду, який працює з можливостями фреймворку через «фасади». Фасади дають «статичний» інтерфейс до класів, доступних у контейнері сервісів. Laravel постачається з великою кількістю фасадів, які відкривають доступ майже до всіх його можливостей.
Фасади Laravel працюють як «статичні проксі» до класів у контейнері сервісів: синтаксис стислий і виразний, а тестованість і гнучкість вищі, ніж у традиційних статичних методів. Якщо ви поки не до кінця розумієте, як влаштовані фасади, нічого страшного - рухайтеся далі й вивчайте Laravel.
Усі фасади Laravel визначені в просторі імен Illuminate\Support\Facades. Тож звернутися до фасаду просто:
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Route;
Route::get('/cache', function () {
return Cache::get('key');
});
У документації Laravel більшість прикладів використовує фасади для демонстрації різних можливостей фреймворку.
Хелпер-функції
На додачу до фасадів Laravel пропонує набір глобальних «хелпер-функцій», які ще спрощують роботу з типовими можливостями фреймворку. Серед найпоширеніших - view, response, url, config та інші. Кожна хелпер-функція описана разом із відповідною можливістю, а повний перелік є в окремій документації по хелперах.
Наприклад, замість фасаду Illuminate\Support\Facades\Response для генерації JSON-відповіді можна просто скористатися функцією response. Хелпер-функції доступні глобально, тому імпортувати класи для їх використання не потрібно:
use Illuminate\Support\Facades\Response;
Route::get('/users', function () {
return Response::json([
// ...
]);
});
Route::get('/users', function () {
return response()->json([
// ...
]);
});
Коли використовувати фасади
У фасадів багато переваг. Вони дають стислий синтаксис, який легко запамʼятати, і позбавляють потреби тримати в голові довгі імена класів, які треба впроваджувати чи налаштовувати вручну. До того ж завдяки нестандартному використанню динамічних методів PHP їх легко тестувати.
Проте певна обережність потрібна. Головна небезпека фасадів - «розповзання» відповідальності класу. Оскільки фасади дуже прості у використанні й не потребують впровадження, клас легко розростається і починає тягнути до себе десяток фасадів. При впровадженні залежностей великий конструктор сам візуально сигналізує, що клас став завеликим. Тож використовуючи фасади, окремо стежте за розміром класу, щоб його зона відповідальності лишалася вузькою. Якщо клас надто розрісся, розбийте його на кілька менших.
Фасади проти впровадження залежностей
Одна з головних переваг впровадження залежностей - можливість підмінити реалізацію впровадженого класу. Це стає в пригоді під час тестування, бо можна підставити mock чи stub і перевірити, що на ньому викликалися потрібні методи.
Зазвичай справді статичний метод класу підмінити mock- або stub-обʼєктом неможливо. Але оскільки фасади використовують динамічні методи, щоб проксіювати виклики до обʼєктів, отриманих із контейнера сервісів, ми можемо тестувати фасади так само, як і впроваджений екземпляр класу. Наприклад, для такого маршруту:
use Illuminate\Support\Facades\Cache;
Route::get('/cache', function () {
return Cache::get('key');
});
Використовуючи методи Laravel для тестування фасадів, ми можемо написати такий тест і переконатися, що метод Cache::get був викликаний з очікуваним аргументом:
use Illuminate\Support\Facades\Cache;
test('basic example', function () {
Cache::shouldReceive('get')
->with('key')
->andReturn('value');
$response = $this->get('/cache');
$response->assertSee('value');
});
use Illuminate\Support\Facades\Cache;
/**
* Базовий приклад функціонального тесту.
*/
public function test_basic_example(): void
{
Cache::shouldReceive('get')
->with('key')
->andReturn('value');
$response = $this->get('/cache');
$response->assertSee('value');
}
Фасади проти хелпер-функцій
Крім фасадів, Laravel містить набір «хелпер»-функцій для типових завдань: згенерувати представлення (view), кинути подію, відправити завдання (job) у чергу чи повернути HTTP-відповідь. Багато з цих функцій роблять те саме, що й відповідний фасад. Наприклад, цей виклик фасаду і виклик хелпера рівнозначні:
return Illuminate\Support\Facades\View::make('profile');
return view('profile');
Жодної практичної різниці між фасадами й хелпер-функціями немає. Хелпери тестуються так само, як і відповідний фасад. Наприклад, для такого маршруту:
Route::get('/cache', function () {
return cache('key');
});
Хелпер cache викличе метод get на класі, який стоїть за фасадом Cache. Тож навіть використовуючи хелпер-функцію, ми можемо написати такий тест і перевірити, що метод був викликаний з очікуваним аргументом:
use Illuminate\Support\Facades\Cache;
/**
* Базовий приклад функціонального тесту.
*/
public function test_basic_example(): void
{
Cache::shouldReceive('get')
->with('key')
->andReturn('value');
$response = $this->get('/cache');
$response->assertSee('value');
}
Як працюють фасади
У застосунку Laravel фасад - це клас, який дає доступ до обʼєкта з контейнера. Уся механіка, що це забезпечує, живе в класі Facade. Фасади Laravel, як і будь-які власні фасади, які ви створите, успадковують базовий клас Illuminate\Support\Facades\Facade.
Базовий клас Facade використовує магічний метод __callStatic(), щоб передати виклики з вашого фасаду обʼєкту, отриманому з контейнера. У прикладі нижче виклик іде до системи кешу Laravel. Побіжно глянувши на цей код, можна вирішити, що статичний метод get викликається на класі Cache:
<?php
namespace App\Http\Controllers;
use Illuminate\Support\Facades\Cache;
use Illuminate\View\View;
class UserController extends Controller
{
/**
* Показати профіль вказаного користувача.
*/
public function showProfile(string $id): View
{
$user = Cache::get('user:'.$id);
return view('profile', ['user' => $user]);
}
}
Зверніть увагу, що вгорі файлу ми «імпортуємо» фасад Cache. Цей фасад слугує проксі для доступу до реалізації інтерфейсу Illuminate\Contracts\Cache\Factory. Будь-які виклики через фасад передаються екземпляру сервісу кешу Laravel.
Якщо заглянути в клас Illuminate\Support\Facades\Cache, ніякого статичного методу get там немає:
class Cache extends Facade
{
/**
* Отримати зареєстроване імʼя компонента.
*/
protected static function getFacadeAccessor(): string
{
return 'cache';
}
}
Натомість фасад Cache успадковує базовий клас Facade і визначає метод getFacadeAccessor(). Завдання цього методу - повернути імʼя привʼязки в контейнері сервісів. Коли хтось звертається до будь-якого статичного методу фасаду Cache, Laravel дістає привʼязку cache з контейнера сервісів і виконує запитаний метод (у цьому випадку get) на отриманому обʼєкті.
Фасади в реальному часі
З фасадами в реальному часі (real-time facades) будь-який клас застосунку можна використовувати так, ніби він є фасадом. Щоб показати, як це працює, спершу розгляньмо код без них. Припустимо, у моделі Podcast є метод publish. Але щоб опублікувати подкаст, нам потрібно впровадити екземпляр Publisher:
<?php
namespace App\Models;
use App\Contracts\Publisher;
use Illuminate\Database\Eloquent\Model;
class Podcast extends Model
{
/**
* Опублікувати подкаст.
*/
public function publish(Publisher $publisher): void
{
$this->update(['publishing' => now()]);
$publisher->publish($this);
}
}
Впровадження реалізації publisher у метод дозволяє легко тестувати його ізольовано, бо впроваджений publisher можна підмінити mock-обʼєктом. Але це змушує щоразу передавати екземпляр publisher під час виклику publish. З фасадами в реальному часі тестованість лишається такою самою, а явно передавати екземпляр Publisher більше не потрібно. Щоб згенерувати фасад у реальному часі, додайте до простору імен імпортованого класу префікс Facades:
<?php
namespace App\Models;
use App\Contracts\Publisher; // [tl! remove]
use Facades\App\Contracts\Publisher; // [tl! add]
use Illuminate\Database\Eloquent\Model;
class Podcast extends Model
{
/**
* Опублікувати подкаст.
*/
public function publish(Publisher $publisher): void // [tl! remove]
public function publish(): void // [tl! add]
{
$this->update(['publishing' => now()]);
$publisher->publish($this); // [tl! remove]
Publisher::publish($this); // [tl! add]
}
}
Коли використовується фасад у реальному часі, реалізація publisher дістається з контейнера сервісів за тією частиною імені інтерфейсу або класу, що йде після префікса Facades. Під час тестування цей виклик методу можна підмінити вбудованими хелперами Laravel для тестування фасадів:
<?php
use App\Models\Podcast;
use Facades\App\Contracts\Publisher;
use Illuminate\Foundation\Testing\RefreshDatabase;
pest()->use(RefreshDatabase::class);
test('podcast can be published', function () {
$podcast = Podcast::factory()->create();
Publisher::shouldReceive('publish')->once()->with($podcast);
$podcast->publish();
});
<?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;
/**
* Приклад тесту.
*/
public function test_podcast_can_be_published(): void
{
$podcast = Podcast::factory()->create();
Publisher::shouldReceive('publish')->once()->with($podcast);
$podcast->publish();
}
}
Довідник класів фасадів
Нижче наведено кожен фасад і клас, що стоїть за ним. Це зручний спосіб швидко дістатися до документації API конкретного фасаду. Де це доречно, вказано також ключ привʼязки в контейнері сервісів.
Перекладаємо з офіційної документації, розділ за розділом, і не ховаємо недоперекладене. Помітили неточність у терміні чи реченні: напишіть, виправимо.