Reflection is a dependency injection container for PHP powered by auto-wiring and reflection.
It supports auto-wiring, contextual bindings, singletons, attribute-driven resolution, runtime parameter overrides, circular dependency detection, and PSR-11 compliance.
- Auto-Wiring: Resolves class constructor dependencies automatically using reflection.
- PSR-11 Compliance: Implements
Psr\Container\ContainerInterface(getandhas). - Contextual Binding: Configure different implementations for specific classes using
when()->needs()->give(). - Singletons: Share instances across multiple resolutions.
- Resolution Attributes: Declare injection targets, scalar values, and singleton lifecycles directly on classes.
- Parameter Overrides: Pass runtime parameters as associative arrays or named arguments.
- Circular Dependency Detection: Detects recursive dependency chains and throws descriptive exceptions.
- Helper Function: Global
app()helper for quick access and resolution.
- PHP 8.3 or higher
psr/container ^2.0
composer require mykemeynell/reflectionuse mykemeynell\Reflection\Application\Container;
$container = new Container();
class Database {}
class UserRepository {
public function __construct(public Database $db) {}
}
$userRepository = $container->make(UserRepository::class);Classes with type-hinted constructor dependencies resolve without manual configuration:
class Logger {}
class OrderService {
public function __construct(public Logger $logger) {}
}
$service = $container->make(OrderService::class);Default parameter values are used when no argument is supplied:
class HttpClient {
public function __construct(public int $timeout = 30) {}
}
$client = $container->make(HttpClient::class); // $client->timeout === 30$container->bind(MailerInterface::class, SmtpMailer::class);
$mailer = $container->make(MailerInterface::class);$container->bind(MailerInterface::class, function (Container $app, array $params = []) {
return new SmtpMailer($params['key'] ?? 'default');
});
$mailer = $container->make(MailerInterface::class, ['key' => 'custom-key']);Register a singleton to return the same instance on later resolutions:
$container->singleton(Database::class);
$db1 = $container->make(Database::class);
$db2 = $container->make(Database::class);
// $db1 === $db2Register an existing instance:
$config = new AppConfig();
$container->instance(AppConfig::class, $config);Inject different implementations based on the consumer class:
$container->when(DirectDispatcher::class)
->needs(TransportInterface::class)
->give(HttpTransport::class);
$container->when(CustomerDispatcher::class, OutletDispatcher::class)
->needs(TransportInterface::class)
->give(QueueTransport::class);give() also accepts a closure or a concrete instance:
$container->when(ReportGenerator::class)
->needs(StorageInterface::class)
->give(fn (Container $app) => new S3Storage('bucket-name'));Override constructor parameters using named arguments:
class ApiService {
public function __construct(
public HttpClient $client,
public string $apiKey,
public int $timeout = 30
) {}
}
$service = $container->make(
ApiService::class,
apiKey: 'my-token',
timeout: 60,
);Associative parameter arrays remain supported:
$service = $container->make(ApiService::class, [
'apiKey' => 'my-token',
'timeout' => 60,
]);The previous named parameter-array form remains supported for compatibility:
$service = $container->make(
ApiService::class,
parameters: ['apiKey' => 'my-token', 'timeout' => 60],
);Named arguments can also be used through the container returned by app():
$service = app()->make(ApiService::class, timeout: 60);Use Inject when a constructor parameter needs a specific resolution target:
use mykemeynell\Reflection\Attributes\Inject;
final readonly class ReportService
{
public function __construct(
#[Inject(S3Storage::class)]
public StorageInterface $storage,
) {}
}An ordinary Inject is a fallback after contextual and global bindings. Add Override when the injection point must take precedence over those registrations:
use mykemeynell\Reflection\Attributes\Inject;
use mykemeynell\Reflection\Attributes\Override;
final readonly class ReportService
{
public function __construct(
#[Inject(S3Storage::class), Override]
public StorageInterface $storage,
) {}
}Runtime arguments always take precedence, including when Override is present.
Use Value for scalar constructor configuration:
use mykemeynell\Reflection\Attributes\Value;
final readonly class HttpClient
{
public function __construct(
#[Value(3)]
public int $retries,
#[Value(30)]
public int $timeout,
) {}
}Value attributes are checked against the declared parameter type. A runtime argument overrides the attribute value.
Use Singleton to share an automatically resolved concrete class:
use mykemeynell\Reflection\Attributes\Singleton;
#[Singleton]
final class DatabaseConnection {}An explicit instance(), singleton(), or transient bind() registration overrides the class attribute.
Object dependencies use this order:
- Runtime argument
- Contextual binding
- Registered instance or global binding
Inject- Automatic concrete-class resolution
- Constructor default
For parameters marked with both Inject and Override, Inject moves directly below the runtime argument. Scalar parameters use runtime argument, Value, then constructor default. Lifecycle selection uses registered instance, explicit singleton, explicit transient binding, Singleton, then transient automatic resolution.
Circular dependencies are detected automatically during resolution:
class ServiceA {
public function __construct(public ServiceB $b) {}
}
class ServiceB {
public function __construct(public ServiceA $a) {}
}
$container->make(ServiceA::class);
// Throws ContainerException: Circular dependency detected while resolving [ServiceA]: ServiceA -> ServiceB -> ServiceA.Reflection implements Psr\Container\ContainerInterface:
use Psr\Container\ContainerInterface;
function bootstrap(ContainerInterface $container): void {
if ($container->has(Router::class)) {
$router = $container->get(Router::class);
}
}PSR-11 exception classes:
mykemeynell\Reflection\Exceptions\NotFoundException(implementsPsr\Container\NotFoundExceptionInterface)mykemeynell\Reflection\Exceptions\ContainerException(implementsPsr\Container\ContainerExceptionInterface)mykemeynell\Reflection\Exceptions\DependencyNotSpecifiedException(implementsPsr\Container\ContainerExceptionInterface)
Import the app helper function:
use function mykemeynell\Reflection\Helpers\app;
// Retrieve container instance
$container = app();
// Resolve service
$mailer = app(MailerInterface::class);
// Resolve with parameters
$service = app(ApiService::class, apiKey: 'token', timeout: 45);
// Resolve closure
$result = app(fn (Container $app) => $app->make(Logger::class));Run tests:
composer testCheck code style:
composer lint:checkFormat code style:
composer lintInstall the PHP 8.4 static-analysis toolchain and run PHPStan:
composer analyze:install
composer analyzeMIT License. See LICENSE for details.