flytachi / winter-di
Lightweight PSR-11 DI container for Winter framework (autowiring, scopes, attributes)
Requires
- php: >=8.3
- psr/container: ^2.0
Requires (Dev)
- phpunit/phpunit: @stable
- squizlabs/php_codesniffer: @stable
Suggests
- ext-swoole: Required for request scope coroutine isolation in Swoole runtime
README
Lightweight PSR-11 dependency injection container for the Winter framework. Autowiring, three lifecycle scopes, attribute-based configuration, and service providers.
Requirements
- PHP 8.3+
psr/container ^2.0ext-swoole(optional) — required forrequestscope coroutine isolation
Installation
composer require flytachi/winter-di
Quick start
use Flytachi\Winter\DI\Container; use Flytachi\Winter\DI\Scanner; use Flytachi\Winter\DI\Collector\DICollector; // bootstrap.php — once at application start $container = Container::init(); Scanner::run(__DIR__ . '/src', cache: __DIR__ . '/var/cache/di.php') ->collect(new DICollector($container)) // auto-register #[Singleton], #[Request], #[Transient] ->execute(); $container->register(AppServiceProvider::class); // bind interfaces and factories // Resolve anywhere $service = Container::getInstance()->make(UserService::class); // Call a method with full injection $result = Container::getInstance()->call([UserController::class, 'index']);
Scopes
| Scope | Lifetime | Safe in Swoole |
|---|---|---|
singleton |
One instance per process | ✓ if stateless |
transient |
New instance on every make() |
✓ always |
request |
One instance per request / coroutine | ✓ isolated via Coroutine::getContext() |
Default scope when no attribute and no manual registration: transient.
Attributes
use Flytachi\Winter\DI\Attribute\Singleton; use Flytachi\Winter\DI\Attribute\Transient; use Flytachi\Winter\DI\Attribute\Request; use Flytachi\Winter\DI\Attribute\Autowired; use Flytachi\Winter\DI\Attribute\Inject; // Scope on class #[Singleton] class UserRepository { } #[Request] class AuthContext { } #[Transient] class QueryBuilder { } // Injection overrides on constructor parameters class UserService { public function __construct( private UserRepository $repo, // autowired by type (no attribute needed) #[Inject(FileCache::class)] private CacheInterface $fallback, // specific implementation #[Inject('config.timeout')] private int $timeout, // named value ) {} } // Property injection (when constructor is unavailable) class SomeCommand { #[Autowired] // by declared type — idiomatic choice private UserService $service; #[Inject(FileCache::class)] // specific implementation override private CacheInterface $cache; }
Container API
$c = Container::init(); // initialise (bootstrap) $c = Container::getInstance();// get anywhere // Binding $c->bind(CacheInterface::class, RedisCache::class); // transient $c->singleton(CacheInterface::class, RedisCache::class); // singleton $c->transient(QueryBuilder::class); // transient (explicit) $c->request(AuthContext::class); // request-scoped $c->set('config.timeout', 30); // named scalar / instance // Factory closure — receives the container $c->bind(MailerInterface::class, fn(Container $c) => new SmtpMailer(env('MAIL_HOST'), $c->make(LoggerInterface::class)) ); // Resolution $service = $c->make(UserService::class); $service = $c->make(UserService::class, ['timeout' => 60]); // with overrides // Method / closure injection $result = $c->call([UserController::class, 'index']); $result = $c->call([$controller, 'store']); $result = $c->call(fn(UserService $s) => $s->all()); $result = $c->call([ImportJob::class, 'run'], ['chunkSize' => 500]); // PSR-11 $c->has(UserService::class); // bool $c->get(UserService::class); // mixed — alias for make()
Service providers
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->request(AuthContext::class); $c->set('config.timeout', (int) env('APP_TIMEOUT', 30)); $c->bind(MailerInterface::class, fn($c) => new SmtpMailer(env('MAIL_HOST'), $c->make(LoggerInterface::class)) ); } } // bootstrap.php Container::init() ->register(AppServiceProvider::class) ->register(DatabaseServiceProvider::class);
Scanner
Scanner walks the project tree once and dispatches every discovered class to all
registered CollectorInterface implementations — a single filesystem pass, multiple consumers.
use Flytachi\Winter\DI\Scanner; use Flytachi\Winter\DI\Collector\DICollector; // Without cache — always scans (dev mode, PPA, Cmd, Db collectors) Scanner::run($rootDir) ->collect(new PpaCollector()) ->collect(new CmdCollector()) ->execute(); // With cache — skips FS walk on cache hit (production) Scanner::run($rootDir, cache: '/var/cache/scanner.php') ->collect(new DICollector($container)) ->collect(new MappingCollector($router)) ->execute(); // Exclude additional directories (vendor/ is always excluded) Scanner::run($rootDir) ->exclude(['/path/to/legacy', '/path/to/generated']) ->collect(new DICollector($container)) ->execute();
The cache stores only the list of discovered FQCNs as a plain PHP file — fast require,
no serialization overhead. Delete the file to force a rescan.
ReflectionCache
Per-process cache for reflection objects. Creates each ReflectionClass,
ReflectionMethod, and ReflectionParameter[] once and reuses it for the
process lifetime — critical in Swoole where workers handle many requests.
use Flytachi\Winter\DI\ReflectionCache; $ref = ReflectionCache::classOf(UserService::class); // ReflectionClass $enum = ReflectionCache::enumOf(Status::class); // ReflectionEnum $method = ReflectionCache::method(UserService::class, 'handle'); // ReflectionMethod $params = ReflectionCache::parameters(UserService::class, 'handle'); // ReflectionParameter[]
Used internally by ReflectionResolver — available as a public utility for
frameworks and libraries that perform their own reflection-based parameter resolution.
Exceptions
| Exception | When |
|---|---|
ContainerException |
Circular dependency, unresolvable parameter, provider error |
NotFoundException |
No binding and class does not exist |
Both implement the PSR-11 interfaces (ContainerExceptionInterface, NotFoundExceptionInterface).
Documentation
Full documentation is available in docs/:
| File | Contents |
|---|---|
| 01-overview.md | Features, installation, quick start |
| 02-container.md | Complete Container API reference |
| 03-scopes.md | Scopes — singleton, transient, request; Swoole behaviour |
| 04-attributes.md | #[Singleton], #[Transient], #[Request], #[Autowired], #[Inject] |
| 05-providers.md | ServiceProvider — grouping bindings |
| 06-scan.md | Directory scan — auto-discovery |
| 07-reflection-cache.md | ReflectionCache — per-process reflection object cache |
License
MIT License. See LICENSE.