Фасады
- Введение
- Когда использовать фасады
- Как работают фасады
- Создание фасадов на лету
- Справочное описание классов фасадов
Введение
Фасады предоставляют "статический" интерфейс к классам, доступным в сервис-контейнере. Laravel поставляется со множеством фасадов, которые предоставляют доступ практически ко всем функциям Laravel. Фасады Laravel служат "статическими прокси" для основополагающих классов в сервис-контейнере, предоставляя преимущество лаконичного, выразительного синтаксиса, сохраняя при этом большую тестируемость и гибкость по сравнению с обычными статическими методами.
Все фасады Laravel определены в пространстве имен Illuminate\Support\Facades
. Таким образом, мы можем легко получить доступ к фасаду:
use Illuminate\Support\Facades\Cache;
Route::get('/cache', function () {
return Cache::get('key');
});
В документации Laravel фасады будут использоваться во многих примерах, чтобы продемонстрировать различный функционал фреймворка.
Когда использовать фасады
У фасадов множество преимуществ. Они обеспечивают лаконичный, запоминающийся синтаксис, который позволяет использовать функции Laravel без необходимости запоминать длинные названия классов, которые нужно вставить или настроить вручную. Кроме того, из-за их уникального использования динамических методов PHP, их легко тестировать.
Однако при использовании фасадов необходимо проявлять определенную осторожность. Основная опасность фасадов - расширение области ответственности класса. Поскольку фасады настолько просты в использовании и не требуют внедрения, легко будет позволить вашим классам продолжать расти и использовать много фасадов в одном классе. При использовании внедрения зависимостей этот потенциал подавляется визуальной обратной связью, которую дает большой конструктор относительно того, что ваш класс становится слишком большим - т.е. однажды вы посмотрите на конструктор с множеством подключённых сторонних классов и подумаете "что-то я тут увлёкся. разобью-ка этот класс на два", а при использовании фасадов накопление сторонних зависимостей в классе проходит гораздо незаметнее. Поэтому при использовании фасадов обращайте особое внимание на размер вашего класса, чтобы его область ответственности оставалась малой, чтобы он делал одну узкоспециализированную задачу.
При создании стороннего пакета, который взаимодействует с Laravel, лучше внедрять контракты Laravel вместо использования фасадов. Так как пакеты создаются вне самого Laravel, у вас не будет доступа к хелперам тестирования Laravel.
Фасады в сравнении с Внедрением зависимостей
Одним из основных преимуществ внедрения зависимостей является возможность замены реализаций внедряемого класса. Это полезно во время тестирования, так как вы можете внедрить заглушку и утверждать, что на заглушке есть различные методы.
Для настоящих статических методов обычно невозможно сделать заглушку. Однако, поскольку фасады используют динамические методы для вызова метода прокси для объектов, отделенных от сервис-контейнеров, мы фактически можем тестировать фасады так же, как мы бы тестировали экземпляр внедренного класса. Например, учитывая следующий роут:
use Illuminate\Support\Facades\Cache;
Route::get('/cache', function () {
return Cache::get('key');
});
Мы можем написать следующий тест, чтобы проверить, что метод Cache::get
был вызван с ожидаемым аргументом:
use Illuminate\Support\Facades\Cache;
/**
* Пример базового функционального теста.
*
* @return void
*/
public function testBasicExample()
{
Cache::shouldReceive('get')
->with('key')
->andReturn('value');
$this->visit('/cache')
->see('value');
}
Фасады в сравнении с Хелперами
Помимо фасадов, Laravel включает множество вспомогательных функций, хелперов, которые могут выполнять общие задачи, такие как формирование шаблонов, запуск событий, постановка задач в очередь или отправка HTTP-ответов. Многие из этих хелперов выполняют ту же функцию, как и соответствующий фасад. Например, этот вызов фасада и этот вызов хелпера эквивалентны:
return View::make('profile');
return view('profile');
Нет никакой практической разницы между фасадами и хелперами. При использовании хелперов вы можете тестировать их точно так же, как и соответствующие фасады. Например, учитывая следующий роут:
Route::get('/cache', function () {
return cache('key');
});
С точки зрения внутренней структуры, помощник cache
собирается вызвать метод get
в классе, основополагающем для фасада Cache
. Поэтому, хотя мы используем вспомогательную функцию, мы можем написать следующий тест, чтобы убедиться, что метод был вызван с ожидаемым аргументом:
use Illuminate\Support\Facades\Cache;
/**
* Пример базового функционального теста.
*
* @return void
*/
public function testBasicExample()
{
Cache::shouldReceive('get')
->with('key')
->andReturn('value');
$this->visit('/cache')
->see('value');
}
Как работают фасады
В контексте приложения на Laravel, фасад - это класс, который предоставляет доступ к объекту в контейнере. Весь этот механизм реализован в классе Facade
. Фасады как Laravel, так и ваши собственные, наследуют этот базовый класс Illuminate\Support\Facades\Facade
.
Класс Facade
использует магический метод PHP __callStatic()
для перенаправления вызовов методов с вашего фасада на полученный объект из контейнера. В примере ниже делается обращение к механизму кэширования Laravel. На первый взгляд может показаться, что статический метод get
принадлежит классу Cache
:
<?php
namespace App\Http\Controllers;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Cache;
class UserController extends Controller
{
/**
* Показать профиль для данного пользователя.
*
* @param int $id
* @return Response
*/
public function showProfile($id)
{
$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
{
/**
* Получить зарегистрированное имя компонента.
*
* @return string
*/
protected static function getFacadeAccessor() { return 'cache'; }
}
Вместо этого фасад Cache
расширяется на базе класса Facade
и определяет метод getFacadeAccessor()
. Задача этого метода - вернуть строковое имя (ключ) привязки объекта в сервис-контейнере. Когда вы обращаетесь к любому статическому методу фасада Cache
, Laravel получает объект cache
из сервис-контейнера и вызывает у него требуемый метод (в этом случае - get
).
Создание фасадов на лету
Вы можете относиться к любому классу в вашем приложении, как к фасаду. Чтобы проиллюстрировать, как это можно использовать, давайте рассмотрим альтернативу. Например, предположим, что наша модель Podcast
имеет метод publish
. Однако, для того, чтобы опубликовать подкаст, нам нужно заинжектить в подкаст экземпляр Publisher
:
<?php
namespace App;
use App\Contracts\Publisher;
use Illuminate\Database\Eloquent\Model;
class Podcast extends Model
{
/**
* Publish the podcast.
*
* @param Publisher $publisher
* @return void
*/
public function publish(Publisher $publisher)
{
$this->update(['publishing' => now()]);
$publisher->publish($this);
}
}
Подобная инъекция зависимости позволяет нам легко тестировать метод, так как мы можем "замокать" издателя. Однако, это требует от нас всегда передавать экземпляр издателя каждый раз, когда мы называем метод publish
. Используя фасады на лету, мы можем сохранить ту же самую возможность тестирования, не требуя при этом явной передачи экземпляра Publisher
. Для создания фасада на лету, добавим префикс Facades
в пространство имён импортируемого класса - в итоге получится Facades\App\Contracts\Publisher
:
<?php
namespace App;
use Facades\App\Contracts\Publisher;
use Illuminate\Database\Eloquent\Model;
class Podcast extends Model
{
/**
* Publish the podcast.
*
* @return void
*/
public function publish()
{
$this->update(['publishing' => now()]);
Publisher::publish($this);
}
}
При таком подходе Publisher
будет автоматически взят из сервис-контейнера по неймспейсу, который идёт сразу за словом Facades
. В тестах мы можем использовать встроенный хелпер Laravel для тестирования фасадов, который "мокает" заданный метод:
<?php
namespace Tests\Feature;
use App\Podcast;
use Facades\App\Contracts\Publisher;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class PodcastTest extends TestCase
{
use RefreshDatabase;
/**
* A test example.
*
* @return void
*/
public function test_podcast_can_be_published()
{
$podcast = factory(Podcast::class)->create();
Publisher::shouldReceive('publish')->once()->with($podcast);
$podcast->publish();
}
}
Справочное описание классов фасадов
Ниже вы найдете каждый фасад и его основной класс. Это полезный инструмент для быстрой обработки документации API для данной корневой директории фасада. Ключ привязки сервис-контейнера также включен там, где это применимо.