Увійти Реєстрація
Блог Серії
Кар'єра
Вакансії Компанії
Навчання
Документація Співбесіди Тестування Відео
Екосистема
Пакети Ресурси Проєкти
Інше
Події Про нас

Фасади

Вступ

У всій документації 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 пропонує різноманітні глобальні «функції-хелпери», які ще більше спрощують роботу з поширеними можливостями Laravel. Серед хелперів, з якими ви можете стикатися, - view, response, url, config тощо. Кожна функція-хелпер Laravel задокументована разом із відповідною можливістю, а повний список доступний в окремій документації з хелперів.

Наприклад, замість фасаду 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([
        // ...
    ]);
});

Коли використовувати фасади

Фасади мають багато переваг. Вони дають стислий, легкий для запам'ятовування синтаксис, що дозволяє користуватися можливостями Laravel, не пам'ятаючи довгих імен класів, які треба впроваджувати чи налаштовувати вручну. До того ж завдяки своєму особливому використанню динамічних методів PHP їх легко тестувати.

Однак із фасадами слід бути обачними. Головна їхня небезпека - «розповзання» відповідальності класу. Оскільки фасадами дуже просто користуватися і вони не потребують впровадження, легко дозволити своїм класам розростатися й використовувати багато фасадів в одному класі. При впровадженні залежностей цього ризику менше завдяки візуальному сигналу: великий конструктор одразу показує, що клас стає завеликим. Тож, використовуючи фасади, особливо уважно стежте за розміром класу, щоб його зона відповідальності лишалася вузькою. Якщо клас стає завеликим, подумайте про поділ його на кілька менших.

Фасади проти впровадження залежностей

Одна з головних переваг впровадження залежностей (dependency injection) - можливість підміняти реалізації впровадженого класу. Це корисно під час тестування, адже ви можете впровадити мок чи стаб і перевірити, що на ньому були викликані певні методи.

Зазвичай замокати чи застабити справді статичний метод класу неможливо. Однак оскільки фасади використовують динамічні методи, щоб проксіювати виклики до об'єктів, розв'язаних із сервіс-контейнера, ми фактично можемо тестувати фасади так само, як тестували б впроваджений екземпляр класу. Наприклад, маючи такий маршрут:

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;

/**
 * A basic functional test example.
 */
public function test_basic_example(): void
{
    Cache::shouldReceive('get')
        ->with('key')
        ->andReturn('value');

    $response = $this->get('/cache');

    $response->assertSee('value');
}

Фасади проти функцій-хелперів

Крім фасадів, Laravel містить різноманітні функції-хелпери, які виконують типові завдання: генерують представлення, запускають події, диспетчеризують завдання чи надсилають 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;

/**
 * A basic functional test example.
 */
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. Побіжно глянувши на цей код, можна припустити, що на класі Cache викликається статичний метод get:

<?php

namespace App\Http\Controllers;

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]);
    }
}

Зверніть увагу, що ближче до початку файлу ми «імпортуємо» фасад Cache. Цей фасад слугує проксі для доступу до реалізації інтерфейсу Illuminate\Contracts\Cache\Factory. Усі виклики, зроблені через фасад, буде передано до відповідного екземпляра сервісу кешу Laravel.

Якщо ми зазирнемо до класу Illuminate\Support\Facades\Cache, то побачимо, що статичного методу get там немає:

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

Натомість фасад Cache успадковує базовий клас Facade і визначає метод getFacadeAccessor(). Завдання цього методу - повернути ім'я прив'язки сервіс-контейнера. Коли користувач звертається до будь-якого статичного методу фасаду Cache, Laravel розв'язує прив'язку cache із сервіс-контейнера і виконує на цьому об'єкті запитаний метод (у цьому випадку - get).

Фасади в реальному часі

За допомогою фасадів у реальному часі ви можете поводитися з будь-яким класом свого застосунку так, ніби це фасад. Щоб проілюструвати, як це працює, спершу розгляньмо код, що їх не використовує. Припустімо, наша модель Podcast має метод publish. Однак, щоб опублікувати подкаст, нам потрібно впровадити екземпляр 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);
    }
}

Впровадження реалізації publisher у метод дозволяє легко тестувати його ізольовано, адже ми можемо замокати впроваджений publisher. Однак це вимагає щоразу передавати екземпляр 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
{
    /**
     * Publish the podcast.
     */
    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;

    /**
     * A test example.
     */
    public function test_podcast_can_be_published(): void
    {
        $podcast = Podcast::factory()->create();

        Publisher::shouldReceive('publish')->once()->with($podcast);

        $podcast->publish();
    }
}

Довідник класів фасадів

Нижче наведено кожен фасад і клас, що лежить у його основі. Це зручний інструмент, щоб швидко зазирнути в документацію API для конкретного фасаду. Де це доречно, також вказано ключ прив'язки сервіс-контейнера.

Фасад Клас Прив'язка сервіс-контейнера
App Illuminate\Foundation\Application app
Artisan Illuminate\Contracts\Console\Kernel artisan
Auth (Instance) Illuminate\Contracts\Auth\Guard auth.driver
Auth Illuminate\Auth\AuthManager auth
Blade Illuminate\View\Compilers\BladeCompiler blade.compiler
Broadcast (Instance) Illuminate\Contracts\Broadcasting\Broadcaster  
Broadcast Illuminate\Contracts\Broadcasting\Factory  
Bus Illuminate\Contracts\Bus\Dispatcher  
Cache (Instance) Illuminate\Cache\Repository cache.store
Cache Illuminate\Cache\CacheManager cache
Config Illuminate\Config\Repository config
Context Illuminate\Log\Context\Repository  
Cookie Illuminate\Cookie\CookieJar cookie
Crypt Illuminate\Encryption\Encrypter encrypter
Date Illuminate\Support\DateFactory date
DB (Instance) Illuminate\Database\Connection db.connection
DB Illuminate\Database\DatabaseManager db
Event Illuminate\Events\Dispatcher events
Exceptions (Instance) Illuminate\Contracts\Debug\ExceptionHandler  
Exceptions Illuminate\Foundation\Exceptions\Handler  
File Illuminate\Filesystem\Filesystem files
Gate Illuminate\Contracts\Auth\Access\Gate  
Hash Illuminate\Contracts\Hashing\Hasher hash
Http Illuminate\Http\Client\Factory  
Lang Illuminate\Translation\Translator translator
Log Illuminate\Log\LogManager log
Mail Illuminate\Mail\Mailer mailer
Notification Illuminate\Notifications\ChannelManager  
Password (Instance) Illuminate\Auth\Passwords\PasswordBroker auth.password.broker
Password Illuminate\Auth\Passwords\PasswordBrokerManager auth.password
Pipeline (Instance) Illuminate\Pipeline\Pipeline  
Process Illuminate\Process\Factory  
Queue (Base Class) Illuminate\Queue\Queue  
Queue (Instance) Illuminate\Contracts\Queue\Queue queue.connection
Queue Illuminate\Queue\QueueManager queue
RateLimiter Illuminate\Cache\RateLimiter  
Redirect Illuminate\Routing\Redirector redirect
Redis (Instance) Illuminate\Redis\Connections\Connection redis.connection
Redis Illuminate\Redis\RedisManager redis
Request Illuminate\Http\Request request
Response (Instance) Illuminate\Http\Response  
Response Illuminate\Contracts\Routing\ResponseFactory  
Route Illuminate\Routing\Router router
Schedule Illuminate\Console\Scheduling\Schedule  
Schema Illuminate\Database\Schema\Builder  
Session (Instance) Illuminate\Session\Store session.store
Session Illuminate\Session\SessionManager session
Storage (Instance) Illuminate\Contracts\Filesystem\Filesystem filesystem.disk
Storage Illuminate\Filesystem\FilesystemManager filesystem
URL Illuminate\Routing\UrlGenerator url
Validator (Instance) Illuminate\Validation\Validator  
Validator Illuminate\Validation\Factory validator
View (Instance) Illuminate\View\View  
View Illuminate\View\Factory view
Vite Illuminate\Foundation\Vite