Install
The runtime has one dependency: the PSR-11 container contract.
composer require dvictorjhg/php-injector
PHP 8.3+ / PSR-11
A compact dependency injector for classes and callables. Reflection does the inspection; you decide the explicit inputs, providers, and lifecycle.
<?php
declare(strict_types=1);
use PHPInjector\DI\Injector;
final class MyService
{
}
$service = Injector::inject(MyService::class);
echo get_class($service);
MyService
Register only the values that are genuinely external to the object graph. Let type declarations describe the rest.
The runtime has one dependency: the PSR-11 container contract.
composer require dvictorjhg/php-injector
A provider is a value or factory stored under an identifier. Types are the natural default; named identifiers are useful for interfaces and configuration.
<?php
declare(strict_types=1);
use PHPInjector\DI\Injector;
final class Logger
{
}
$logger = new Logger();
$dsn = 'mysql:host=db;dbname=app';
$injector = new Injector();
$injector->addProvider(Logger::class, $logger);
$injector->addProvider('db.dsn', $dsn);
echo Injector::inject('db.dsn');
mysql:host=db;dbname=app
| Identifier | How it is used |
|---|---|
| Logger::class | Matches a parameter type. |
| db.dsn | A parameter marked #[Inject('db.dsn')] receives this value. |
| Logger::class => $logger | A supplied value is returned as registered. |
Child injectors consult their own providers first, then walk toward the parent. A provider is never silently pulled from an unrelated global container.
The injector caches concrete classes by default. Use the Transient attribute when each resolution needs a fresh object, or implement the Singleton contract when the class should own its instance.
Add #[Transient] to skip injector caching. Each resolution that constructs the class gets a fresh instance.
Implement Singleton when the class should own a shared instance. The injector calls getInstance() instead of its constructor.
Pass an explicit instance when one resolution needs a fresh dependency. #[Transient] applies to the whole class and does not replace Singleton.
<?php
declare(strict_types=1);
use PHPInjector\DI\Attributes\Transient;
use PHPInjector\DI\Injector;
#[Transient]
final class RequestStamp
{
}
$first = Injector::inject(RequestStamp::class);
$second = Injector::inject(RequestStamp::class);
var_dump($first === $second);
bool(false)
The resolver follows one ordered path. The first matching source wins, which keeps a complex graph inspectable in a debugger or a code review.
The optional second argument to Injector::inject() is checked first. Keys can be a type, parameter name, or numeric position.
$args[Logger::class]
type
$args['logger']
name
$args[0]
position
$args belongs to this call only; it is separate from the injector's persistent provider map.
<?php
declare(strict_types=1);
use PHPInjector\DI\Injector;
$args = ['string' => 'cache'];
$length = Injector::inject('strlen', $args);
echo $length;
5
A parameter-level identifier selects a named provider after no explicit argument matches.
The declared class or interface name is looked up in the active injector and then its parent chain.
A declared default value is used when no explicit value, attribute, or typed provider is available.
A required parameter with no source fails loudly. There is no hidden fallback or service locator detour.
One rule of thumb: make the ordinary path obvious, then use explicit arguments or attributes only where the graph needs a deliberate exception.
Supported targets include class names, object/class method arrays, callable strings, closures, and invokable objects.
The public entry point accepts the common PHP callable shapes, then routes each one through the same parameter resolver.
strlen.
__invoke method.
<?php
declare(strict_types=1);
use PHPInjector\DI\Injector;
final class Logger
{
public function __construct(public string $channel = 'app')
{
}
}
final class ReportJobs
{
public static function warm(Logger $logger): string
{
return "warming {$logger->channel}";
}
}
$injector = new Injector([Logger::class]);
echo Injector::inject('ReportJobs::warm') . PHP_EOL;
echo Injector::inject(static fn (Logger $logger): string => $logger->channel);
warming app
app
For function sum(int ...$values), numeric values from the variadic position onward are collected and passed as separate arguments.
<?php
declare(strict_types=1);
use PHPInjector\DI\Injector;
echo Injector::inject(
fn (int ...$values): int => array_sum($values),
[2, 3, 5],
);
10
The API is intentionally short. The useful detail is in the contracts between the injector, its providers, and reflection.
Injector::inject Resolve a class, callable, closure, method array, or invokable object. The second argument supplies per-call values.
Injector::addProvider()Store a value, class string, or callable factory under a string identifier.
SourceInjector::getProvider()Read a provider from the current injector or its parent chain. Missing identifiers throw a container exception.
SourceInjector::hasProvider()Check whether an identifier exists in the current injector or its parent chain.
SourceContainerUse the PSR-11 container directly when a provider map is the right abstraction. It supports get, has, and set.
SourceThis project keeps its feedback loop deliberately plain: Composer scripts, strict static analysis, and a focused unit suite.
composer analyse
composer test
composer test:coverage