Контейнер и провайдеры
В контроллер можно написать параметр Request $request — и Laravel сам передаст нужный объект. За этим стоит сервис-контейнер: он создаёт объекты и подставляет их туда, где они нужны. Понимание контейнера объясняет, как устроен весь фреймворк.
Внедрение зависимостей
Класс не создаёт свои зависимости сам, а получает их в конструкторе:
namespace App\Services;
class PriceCalculator
{
public function withTax(int $price): float
{
return $price * 1.2;
}
}use App\Services\PriceCalculator;
class OrderController extends Controller
{
public function __construct(private PriceCalculator $calculator) {}
public function show(Order $order)
{
return $this->calculator->withTax($order->total);
}
}Нигде не написано new PriceCalculator(): контейнер увидел тип параметра и создал объект сам. Если у PriceCalculator есть свои зависимости, он создаст и их.
Зачем это нужно
Класс зависит не от конкретной реализации, а от того, что ему передали. В тесте вместо настоящего сервиса оплаты можно подставить поддельный — менять код класса не придётся.
Интерфейс и реализация
Интерфейс контейнер создать не может. Ему сообщают, какой класс подставлять:
namespace App\Contracts;
interface PaymentGateway
{
public function charge(int $amount): bool;
}namespace App\Providers;
use App\Contracts\PaymentGateway;
use App\Services\StripeGateway;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
public function register(): void
{
$this->app->bind(PaymentGateway::class, StripeGateway::class);
}
}Теперь любой класс, который просит PaymentGateway, получит StripeGateway. Чтобы сменить платёжную систему, меняют одну строку.
Один объект на всё приложение
bind() создаёт новый объект при каждом обращении. singleton() создаёт его один раз и дальше отдаёт тот же:
$this->app->singleton(PaymentGateway::class, function ($app) {
return new StripeGateway(config('services.stripe.secret'));
});Функция нужна, когда конструктору требуются значения из настроек.
Провайдеры
Провайдер — класс, который настраивает часть приложения. У него два метода:
| Метод | Что в нём делают |
|---|---|
register() | Только привязки в контейнере |
boot() | Всё остальное: правила доступа, слушатели, настройки |
boot() вызывается, когда все провайдеры уже зарегистрированы, поэтому в нём можно пользоваться любыми сервисами.
php artisan make:provider PaymentServiceProviderПолучение объекта вручную
Там, где параметров конструктора нет, объект берут функцией app():
$gateway = app(PaymentGateway::class);Фасады
Запись Cache::get('key') выглядит как статический вызов, но это фасад: он достаёт объект кеша из контейнера и вызывает его метод. Поэтому фасады тоже легко подменить в тестах:
use Illuminate\Support\Facades\Cache;
Cache::shouldReceive('get')->with('key')->andReturn('value');Частая ошибка
В методе register() нельзя обращаться к другим сервисам: провайдер, который их регистрирует, мог ещё не выполниться. Маршруты, слушатели, правила Gate — всё, кроме привязок, — помещайте в boot().