Быстрый старт
От установки до полностью автовайренного сервиса за пять минут: вы опишете небольшой граф
объектов, дадите контейнеру собрать его по типам и вызовете метод контроллера с внедрёнными
зависимостями — не написав ни одного new.
Что вы соберёте
UserService, который зависит от UserRepository, а тот — от DatabaseConnection. Вы
объявите эти зависимости параметрами конструктора, и контейнер соберёт всю цепочку одним
вызовом make(). Затем вы вызовете метод контроллера через call(), позволив контейнеру
подставить аргументы. Цикл такой: описать → bootstrap → make → call.
Перед началом
- PHP ≥ 8.4 (проверьте командой
php -v)
Установите пакет:
composer require flytachi/winter-diШаг 1 — Опишите сервисы
Дайте каждому классу scope-атрибут и объявите зависимости типизированными параметрами
конструктора. #[Singleton] означает один общий экземпляр на процесс.
<?php
use Flytachi\Winter\DI\Attribute\Singleton;
#[Singleton]
class DatabaseConnection
{
public function query(string $sql): array
{
// притворимся, что это обращение к настоящей базе
return [['id' => 1, 'name' => 'Ada'], ['id' => 2, 'name' => 'Linus']];
}
}
#[Singleton]
class UserRepository
{
// DatabaseConnection автовайрится по своему типу
public function __construct(private DatabaseConnection $db) {}
public function all(): array
{
return $this->db->query('SELECT * FROM users');
}
}
#[Singleton]
class UserService
{
public function __construct(private UserRepository $repo) {}
public function names(): array
{
return array_column($this->repo->all(), 'name');
}
}Нет атрибута? Тоже работает
Класс без scope-атрибута по умолчанию transient (новый экземпляр каждый раз) и всё равно
автовайрится. Атрибут лишь делает время жизни явным. См. Scope’ы.
Шаг 2 — Инициализируйте контейнер
Создайте контейнер один раз, затем дайте Scanner найти аннотированные классы и
зарегистрировать их автоматически.
<?php
require 'vendor/autoload.php';
use Flytachi\Winter\DI\Container;
use Flytachi\Winter\DI\Scanner;
use Flytachi\Winter\DI\Collector\DICollector;
$container = Container::init();
Scanner::run(__DIR__ . '/src')
->collect(new DICollector($container))
->execute();Шаг 3 — Разрешите сервис через make()
Запросите у контейнера UserService. Он читает конструктор, видит, что нужен
UserRepository, которому нужен DatabaseConnection, и строит для вас всю цепочку.
$service = $container->make(UserService::class);
print_r($service->names());
// Array ( [0] => Ada [1] => Linus )Поскольку все классы #[Singleton], второй make(UserService::class) вернёт тот же самый
экземпляр — граф строится один раз и кэшируется.
Шаг 4 — Вызовите метод с внедрением
Контроллеры и команды редко создают свои зависимости сами. call() вызывает метод и
разрешает его параметры из контейнера — даже те, которых у самого класса нет.
<?php
class UserController
{
public function index(UserService $service): array
{
return $service->names();
}
}$names = $container->call([UserController::class, 'index']);
print_r($names);
// Array ( [0] => Ada [1] => Linus )Контейнер создал UserController, увидел, что index() нужен UserService, разрешил его
(переиспользовав синглтон) и вызвал метод. Если вы видите два имени — весь граф собрался
за вас. 🎉
Всё целиком
Полный вариант для копирования:
<?php
require 'vendor/autoload.php';
use Flytachi\Winter\DI\Container;
use Flytachi\Winter\DI\Attribute\Singleton;
#[Singleton]
class DatabaseConnection
{
public function query(string $sql): array
{
return [['id' => 1, 'name' => 'Ada'], ['id' => 2, 'name' => 'Linus']];
}
}
#[Singleton]
class UserRepository
{
public function __construct(private DatabaseConnection $db) {}
public function all(): array { return $this->db->query('SELECT * FROM users'); }
}
#[Singleton]
class UserService
{
public function __construct(private UserRepository $repo) {}
public function names(): array { return array_column($this->repo->all(), 'name'); }
}
class UserController
{
public function index(UserService $service): array { return $service->names(); }
}
$container = Container::init();
print_r($container->call([UserController::class, 'index']));
// Array ( [0] => Ada [1] => Linus )Здесь Scanner не нужен
Этот однофайловый пример пропускает шаг со Scanner: make() и call() автовайрят
конкретные классы по требованию даже без регистрации. Scanner важен, когда код разбит на
множество файлов и нужно автоматически применять scope-атрибуты — см.
Сканирование и автообнаружение.
Что только что произошло
- Вы ни разу не вызвали
newдля сервиса — контейнер прочитал типы и построил граф рекурсивно. #[Singleton]задал время жизни — каждый сервис создаётся один раз и переиспользуется.make()разрешил класс;call()разрешил параметры метода и вызвал его.- Зависимости текут по типу — объявленный класс параметра и есть проводка.
Следующие шаги
- Ментальная модель — две идеи, из которых следует всё остальное
- Сервис-провайдеры — привяжите интерфейс к реализации
- Внедрение в свойства —
#[Autowired]иcall() - Сканирование и автообнаружение — авторегистрация аннотированных классов
- Справочник API — каждый метод контейнера