Прошло полтора года с тех пор, как я всерьёз начал пилить DI контейнер для Thesis. Набор требований у меня был большой:
Модульность
Всё есть модуль, включая само приложение.
PHP DSL
Выразительный и лаконичный, не на массивах.
Типобезопасность
Не строковые идентификаторы, а объекты с дженериком. Легко написать плагин статанализатора для тайпчека аргументов.
Импорт/экспорт
У модуля прозрачный типизированный контракт на вход (конфигурация) и на выход (экспорт сервисов).
Scoped сервисы
В асинхронном приложении можно писать код как в умирающем: инжектить репозиторий по интерфейсу в конструктор обработчика и не замечать, что у него под капотом транзакция, открытая специально для текущего сообщения.
Disposer-коллбэки
К сервису, не меняя его код, можно привязать функцию, которая будет вызвана по завершении скоупа. Удобно для закрытия ресурсов и остановки фоновых корутин.
Предсказуемый автовайринг
Модуль из vendor не может внезапно затереть сервис в модуле проекта. Все биндинги локализованы, нет глобального пространства имён.
Автоконфигурация
Возможность порефлексировать сервисы перед сборкой и по подтипу или атрибуту накинуть теги.
Теги
Имплементируют
Tag<T> и содержат типизированные данные. Через хук можно собрать протегированные сервисы, преобразовать их и заинжектить куда угодно.First-class callable сервисы
myFunc(...) и $service->method(...) легко объявить сервисами, частично применить аргументы, прочитать атрибуты и подключить как обработчики сообщений, не написав CompilerPass на 1000 строк.И вот мы здесь. 30 июня, я только что поставил тег 0.5.0 с осознанием, что до 1.0 рукой подать и я готов с вами поделиться!
В README есть всё необходимое для первого знакоства, в
docs/ поподробнее про часть аспектов. Буду рад скоро постримить и показать больше!https://github.com/thesis-php/dic
https://github.com/thesis-php/symfony-console-module
/**
* @implements Module<Ref<Application>>
*/
final readonly class App implements Module
{
public function configure(Dic $dic): mixed
{
$dic->apply(new AutoconfigureCommands());
$dic->function(self::greet(...));
return $dic->import(new SymfonyConsoleModule(name: 'Пых'));
}
#[AsCommand('greet', aliases: ['g'])]
private static function greet(#[Argument] string $name, SymfonyStyle $io): int
{
$io->title("Hello, {$name}!");
return Command::SUCCESS;
}
}
exit(Dic::run(
module: new App(),
main: static fn(Application $app) => $app->run(),
));