Привязка интерфейсов и фабрик
Автовайринг сам разрешает конкретные классы, но не может угадать, какую реализацию должен
получить интерфейс, или как построить объект, которому нужна runtime-конфигурация.
ServiceProvider — место для таких привязок, собранных вместе, а не разбросанных по bootstrap.
Создайте провайдер
Унаследуйтесь от ServiceProvider и поместите привязки в register(). Контейнер передаётся
внутрь.
<?php
use Flytachi\Winter\DI\Contract\ServiceProvider;
use Flytachi\Winter\DI\Container;
class AppServiceProvider extends ServiceProvider
{
public function register(Container $c): void
{
// интерфейс → реализация
$c->singleton(CacheInterface::class, RedisCache::class);
// фабрика-замыкание — получает контейнер
$c->bind(MailerInterface::class, fn(Container $c) =>
new SmtpMailer(
host: env('MAIL_HOST', 'localhost'),
logger: $c->make(LoggerInterface::class),
)
);
// именованное скалярное значение
$c->set('config.timeout', (int) env('APP_TIMEOUT', 30));
}
}Зарегистрируйте его один раз при bootstrap. Провайдеры выполняются сразу, в порядке регистрации:
Container::init()
->register(AppServiceProvider::class)
->register(DatabaseServiceProvider::class);Привяжите интерфейс к реализации
Самая частая привязка: когда класс просит CacheInterface, дайте ему RedisCache.
$c->singleton(CacheInterface::class, RedisCache::class);
// Теперь любой автовайренный CacheInterface разрешается в общий RedisCache
class PageRenderer
{
public function __construct(private CacheInterface $cache) {}
}Выбирайте метод привязки по нужному времени жизни — scope зашит в вызов:
| Метод | Scope | Для чего |
|---|---|---|
singleton() |
один общий экземпляр | stateless-сервисы, соединения, репозитории |
bind() / transient() |
новый каждый раз | объекты с состоянием, билдеры |
request() |
один на запрос / корутину | пер-запросное состояние |
Полные сигнатуры — в Справочнике API; руководство по временам жизни — в Scope’ах.
Зарегистрируйте фабрику-замыкание
Когда для конструирования нужна runtime-конфигурация или своя логика, привяжите замыкание. Оно получает контейнер, чтобы разрешить другие зависимости.
$c->singleton(Connection::class, fn(Container $c) =>
new PdoConnection(env('DB_DSN'), env('DB_USER'), env('DB_PASS'))
);
$c->bind(ReportBuilder::class, fn(Container $c) =>
new ReportBuilder($c->make(Connection::class), locale: 'en_US')
);Сохраните именованное значение
Скаляры и заранее построенные экземпляры кладутся под строковый ключ через set(), затем
внедряются по имени через #[Inject('key')].
$c->set('config.timeout', 30);
$c->set('db.connection', $existingPdoInstance);
class ApiClient
{
public function __construct(
#[Inject('config.timeout')] private int $timeout,
) {}
}Разбивайте провайдеры по доменам
Группируйте привязки по назначению — один провайдер на подсистему держит bootstrap читаемым.
class DatabaseServiceProvider extends ServiceProvider
{
public function register(Container $c): void
{
$c->singleton(Connection::class, fn() =>
new PdoConnection(env('DB_DSN'), env('DB_USER'), env('DB_PASS'))
);
$c->singleton(UserRepository::class);
$c->singleton(OrderRepository::class);
}
}
class AuthServiceProvider extends ServiceProvider
{
public function register(Container $c): void
{
$c->request(AuthContext::class);
$c->singleton(TokenValidator::class, JwtTokenValidator::class);
$c->set('auth.secret', env('JWT_SECRET'));
}
}Провайдеры или атрибуты
Используйте scope-атрибут (#[Singleton] …), когда конкретный класс владеет своим временем
жизни. Используйте провайдер для сопоставлений интерфейс → реализация, фабрик с
runtime-конфигурацией и именованных значений — того, что конкретный класс не может
объявить о себе сам.
Связанное
- Внедрение в свойства —
#[Autowired]и#[Inject] - Логгер на потребителя — фабрики
contextual() - Scope’ы — выбор времени жизни
- Справочник API —
bind/singleton/transient/request/set