Skip to content

2.4. Фасады

1. Введение

В документации Laravel вы встретите примеры кода, взаимодействующего с функциями 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 многие примеры будут использовать фасады для демонстрации различных возможностей фреймворка.

1.1. Вспомогательные функции

В дополнение к фасадам 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([
// ...
]);
});

2. Когда использовать фасады

Фасады имеют множество преимуществ. Они предоставляют лаконичный и запоминающийся синтаксис, позволяя использовать функции Laravel без необходимости запоминать длинные имена классов, которые нужно вручную внедрять или настраивать. Кроме того, благодаря уникальному использованию динамических методов PHP, фасады легко тестировать.

Однако при использовании фасадов нужно соблюдать осторожность. Основная опасность фасадов заключается в "размывании области ответственности" класса. Поскольку фасады настолько просты в использовании и не требуют внедрения, легко допустить, чтобы ваш класс продолжал расти и использовал множество фасадов в одном классе. При использовании внедрения зависимостей эту проблему можно смягчить благодаря визуальной подсказке — большой конструктор явно показывает, что ваш класс становится слишком большим. Поэтому при использовании фасадов уделяйте особое внимание размеру вашего класса, чтобы его область ответственности оставалась узкой. Если ваш класс становится слишком большим, рассмотрите возможность разделения его на несколько более мелких классов.

2.1. Фасады против Внедрения зависимостей

Одним из основных преимуществ внедрения зависимостей является возможность замены реализаций внедряемого класса. Это особенно полезно при тестировании, так как вы можете внедрить mock или stub и проверить, что различные методы были вызваны на этом 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;
 
/**
* 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');
}

2.2. Фасады против вспомогательных функций

В дополнение к фасадам 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');
}

3. Как работают фасады

В приложении Laravel фасад — это класс, который предоставляет доступ к объекту из сервис-контейнера. Механизм, обеспечивающий эту функциональность, находится в классе Facade. Фасады Laravel, а также любые созданные вами пользовательские фасады, расширяют базовый класс Illuminate\Support\Facades\Facade.

Базовый класс Facade использует магический метод __callStatic(), чтобы перенаправлять вызовы с вашего фасада к объекту, извлеченному из сервис-контейнера. В приведенном ниже примере производится вызов системы кэширования Laravel. На первый взгляд может показаться, что статический метод get вызывается у класса Cache:

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

Обратите внимание, что в верхней части файла мы "импортируем" фасад 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) для этого объекта.

4. Фасады в реальном времени

Используя фасады в реальном времени, вы можете обращаться с любым классом в вашем приложении так, как если бы это был фасад. Чтобы показать, как это может быть использовано, сначала рассмотрим код, который не использует фасады в реальном времени. Например, предположим, что наша модель 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 при каждом вызове метода publish. Используя фасады в реальном времени, мы можем сохранить ту же тестируемость, не требуя явной передачи экземпляра Publisher. Чтобы создать фасад в реальном времени, добавьте префикс Facades к пространству имен импортируемого класса:

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

Когда используется фасад в реальном времени, реализация издателя будет извлечена из сервис-контейнера, используя часть имени интерфейса или класса, которая указана после префикса Facades. При тестировании мы можем использовать встроенные в Laravel инструменты для тестирования фасадов, чтобы замокировать этот вызов метода:

<?php
 
use App\Models\Podcast;
use Facades\App\Contracts\Publisher;
use Illuminate\Foundation\Testing\RefreshDatabase;
 
uses(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();
}
}

5.Справочник по классам фасадов

Ниже приведен список всех фасадов и их базовых классов. Это полезный инструмент для быстрого изучения документации по 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