Авторегистрация аннотированных классов
Пометка класса #[Singleton] объявляет его scope, но кто-то должен прочитать этот атрибут и
зарегистрировать привязку. Scanner проходит по дереву проекта один раз и передаёт каждый
класс в DICollector, который регистрирует те, у кого есть scope-атрибут — так что вам не
нужно перечислять их вручную.
Базовое сканирование
Наведите Scanner на корень исходников, подключите DICollector, привязанный к контейнеру, и
запустите.
<?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();DICollector сопоставляет каждый scope-атрибут соответствующей привязке:
| Атрибут | Регистрация |
|---|---|
#[Singleton] |
$container->singleton($class) |
#[Request] |
$container->request($class) |
#[Transient] |
$container->transient($class) |
Класс без scope-атрибута коллектор пропускает — он остаётся автовайримым по требованию как
transient.
Что Scanner пропускает
Абстрактные классы, интерфейсы и трейты отфильтровываются до запуска коллекторов, поэтому
collect() всегда видит только инстанцируемые классы. vendor/ исключается всегда.
Добавьте продакшн-кэш
Сканировать файловую систему при каждом старте расточительно в продакшене. Передайте путь
cache: первый старт обходит дерево и записывает список найденных имён классов; последующие
старты загружают этот список и полностью пропускают обход.
Scanner::run(__DIR__ . '/src', cache: __DIR__ . '/var/cache/di.php')
->collect(new DICollector($container))
->execute();Файл кэша — обычный PHP-массив полностью квалифицированных имён классов:
<?php
return [
'App\\Service\\UserService',
'App\\Repository\\UserRepository',
// ...
];Инвалидация кэша
Кэш хранит список классов, а не привязки. Добавление, переименование или перемещение класса делает список устаревшим — удалите файл кэша, чтобы принудительно пересканировать при следующем старте. Встройте это в шаг деплоя.
Исключение каталогов
vendor/ исключается всегда. Добавьте другие пути через exclude() (абсолютные пути):
Scanner::run(__DIR__ . '/src')
->exclude([
__DIR__ . '/src/legacy',
__DIR__ . '/src/generated',
])
->collect(new DICollector($container))
->execute();Запуск нескольких коллекторов за один проход
DICollector — один коллектор; у фреймворка их обычно больше (маршруты, обработчики
исключений, консольные команды). Зарегистрируйте их все на одном Scanner, и они разделят
один обход файловой системы — ни один класс не читается дважды.
Scanner::run(__DIR__ . '/src', cache: $cachePath)
->collect(new DICollector($container)) // scope-атрибуты
->collect(new MappingCollector($router)) // атрибуты маршрутов
->collect(new ExceptionCollector()) // обработчики исключений
->execute();Коллекторы вызываются в порядке регистрации для каждого класса.
Напишите свой коллектор
Любой класс, реализующий CollectorInterface, может присоединиться к сканированию. Он получает
имя каждого класса и ReflectionClass — делайте своё чтение атрибутов и регистрацию.
<?php
use Flytachi\Winter\DI\Contract\CollectorInterface;
use ReflectionClass;
final class RouteCollector implements CollectorInterface
{
public function __construct(private readonly Router $router) {}
public function collect(string $class, ReflectionClass $ref): void
{
foreach ($ref->getAttributes(Route::class) as $attr) {
$route = $attr->newInstance();
$this->router->add($route->method, $route->path, $class);
}
}
}Держите коллекторы лёгкими
collect() выполняется в плотном цикле по потенциально сотням классов. Делайте там только
дешёвую работу (чтение атрибутов, регистрация привязки); откладывайте инстанцирование и
тяжёлую настройку на время разрешения.
Связанное
- Scope’ы — что означает каждый атрибут
- Атрибуты — справочник по scope-атрибутам
- Сервис-провайдеры — привязки, которые атрибуты не выразят
- Жизненный цикл разрешения — что происходит при
make()