A fast dependency injection container for PHP featuring build-time resolution and PSR-11 compliance.
- ✅ PSR-11 Compliant - Fully compatible with PSR-11 Container Interface
- ⚡ Build-time Resolution - Pre-compile dependency graphs for maximum performance
- 🔧 Autowiring - Automatic dependency resolution using reflection
- 🌍 Environment Injection - Direct injection of environment variables
- 🏭 Factory Pattern Support - Flexible object creation with custom factories
- 💤 Lazy Initialization - Defer object creation until needed
- 🔄 Circular Dependency Detection - Prevents infinite dependency loops
- 📋 Validation Tools - Command-line tools for configuration validation
- 🔗 Expandable -a fallback container to the container handles cases not covered by it
- PHP 8.4 or higher
- PSR Container 2.0.2+
composer require idrinth/quickly
use Idrinth\Quickly\EnvironmentFactory;
// Create container factory
$factory = new EnvironmentFactory();
$container = $factory->createContainer();
// Get services
$myService = $container->get(MyService::class);
Set the DI_USE_REFLECTION=true
environment variable to enable automatic dependency resolution:
// Your environment
$_ENV['DI_USE_REFLECTION'] = 'true';
// Container will automatically resolve dependencies
$container = $factory->createContainer();
$service = $container->get(SomeService::class);
use Idrinth\Quickly\DependencyInjection\EnvironmentInject;
class DatabaseService
{
public function __construct(
#[EnvironmentInject('DATABASE_URL')]
private string $databaseUrl,
#[EnvironmentInject('DB_TIMEOUT')]
private int $timeout = 30
) {}
}
use Idrinth\Quickly\DependencyInjection\ResolveWithFactory;
use Idrinth\Quickly\DependencyInjection\Factory;
class MyFactory implements Factory
{
public function pickImplementation(string $parameter, string $key, string $forClass): string
{
return ConcreteImplementation::class;
}
}
class ServiceConsumer
{
public function __construct(
#[ResolveWithFactory(MyFactory::class, 'implementation-key')]
private SomeInterface $service
) {}
}
use Idrinth\Quickly\DependencyInjection\LazyInitialization;
#[LazyInitialization]
class ExpensiveService
{
}
Looking at your overwrites implementation, here's what I would add to the README:
Overwrites allow you to specify exact dependency resolutions for specific constructor parameters, bypassing automatic resolution logic. This is particularly useful for configuration-specific dependencies, testing scenarios, or when you need precise control over what gets injected.
Create an overwrites configuration file at .quickly/overwrites.php
:
<?php
use Idrinth\Quickly\DependencyInjection\Definitions\StaticValue;
use Idrinth\Quickly\DependencyInjection\Definitions\Environment;
use Idrinth\Quickly\DependencyInjection\Definitions\ClassObject;
return [
// Override specific constructor parameters
'App\\Services\\DatabaseService' => [
'timeout' => new StaticValue(30),
'host' => new Environment('databaseHost'),
],
'App\\Services\\ApiClient' => [
'httpClient' => new ClassObject('App\\Http\\CustomHttpClient'),
'apiKey' => new Environment('apiKey'),
],
];
You can also specify overwrites directly when creating the container:
use Idrinth\Quickly\DependencyInjection\Container;
use Idrinth\Quickly\DependencyInjection\Definitions\Environment;
use Idrinth\Quickly\DependencyInjection\Definitions\StaticValue;
$container = new Container(
environments: $_ENV,
overwrites: [
'MyService' => [
'timeout' => new StaticValue(60),
'apiUrl' => new Environment('apiUrl'),
],
],
fallbackContainer: $fallback
);
- StaticValue - Inject a specific value (string, int, array, object, etc.)
- Environment - Inject an environment variable
- ClassObject - Inject a specific class instance
- Factory - Use a factory to resolve the dependency
// Static values
'timeout' => new StaticValue(30),
'isProduction' => new StaticValue(true),
'config' => new StaticValue(['key' => 'value']),
// Environment variables
'databaseUrl' => new Environment('databaseUrl'),
'apiKey' => new Environment('apiKey'),
// Specific class implementations
'logger' => new ClassObject('App\\Logging\\FileLogger'),
'cache' => new ClassObject('App\\Cache\\RedisCache'),
// Factory-based resolution
'paymentProcessor' => new Factory(
'App\\Factories\\PaymentFactory',
'stripe',
'processor',
'App\\Services\\OrderService'
),
- Configuration Management: Override default values with environment-specific settings
- Testing: Inject mock objects or test-specific values
- Feature Flags: Conditionally inject different implementations
- Legacy Integration: Specify exact dependencies for legacy code
- Performance Tuning: Inject optimized implementations for specific parameters
Overwrites take precedence over all other resolution methods, including reflection-based autowiring and factory resolution, giving you complete control over dependency injection for critical parameters.
Quickly provides several CLI commands accessible via vendor/bin/quickly
:
vendor/bin/quickly build [filepath]
Creates an optimized production configuration file for maximum performance.
vendor/bin/quickly validate [filepath]
Validates your dependency injection configuration for errors.
vendor/bin/quickly help
Shows available commands and usage information.
use Idrinth\Quickly\DependencyInjection\Container;
use Idrinth\Quickly\DependencyInjection\Definitions\ClassObject;
use Idrinth\Quickly\DependencyInjection\Definitions\Environment;
$container = new Container(
environments: $_ENV,
overwrites: [
'SomeFullyQualifiedClassName' => [
'someArgumentName' => new Environment('configValue')
],
],
fallbackContainer: $somePSR11Container,
constructors: [
MyService::class => [
new ClassObject(Dependency::class),
new Environment('configValue'),
],
],
classAliases: [
'MyInterface' => 'ConcreteImplementation',
]
);
For production use, generate optimized configuration:
// .quickly/generated.php (example)
return [
'constructors' => [
// Pre-resolved constructor dependencies
],
'factories' => [
// Factory mappings
],
'classAliases' => [
// Interface to implementation mappings
]
];
About twice as fast as even the generated configuration, this is your best option in most cases.
Have a look at .quickly/entrypoints.php for configuring entry points without having to add any Attributes.
<?php
namespace Idrinth\Quickly\Built\DependendyInjection;
use Exception;
use Idrinth\Quickly\DependencyInjection\FallbackFailed;
use Psr\Container\ContainerInterface;
final class Container implements ContainerInterface
{
private readonly array $defined;
private readonly array $environments;
private array $built = [];
public function __construct(array $environments, private readonly ContainerInterface $fallbackContainer)
{
foreach (array (
) as $variableName => $environment) {
if (isset($environments[$environment])) {
$this->environments["Environment:$variableName"] = $environments[$environment];
}
}
$this->defined = [
'Idrinth\Quickly\CommandLineOutput'=>true,
'Idrinth\Quickly\CommandLineOutputs\Colorless'=>true,
'Idrinth\Quickly\Commands\Build'=>true,
'Idrinth\Quickly\Commands\Help'=>true,
'Idrinth\Quickly\Commands\Validate'=>true,
];
}
public function get(string $id): string|object
{
if (isset($this->built[$id])) {
return $this->built[$id];
}
return $this->built[$id] = match ($id) {
'Idrinth\Quickly\CommandLineOutput'=>$this->get('Idrinth\Quickly\CommandLineOutputs\Colorless'),
'Idrinth\Quickly\CommandLineOutputs\Colorless'=>new \Idrinth\Quickly\CommandLineOutputs\Colorless(),
'Idrinth\Quickly\Commands\Build'=>new \Idrinth\Quickly\Commands\Build($this->get('Idrinth\Quickly\CommandLineOutputs\Colorless')),
'Idrinth\Quickly\Commands\Help'=>new \Idrinth\Quickly\Commands\Help($this->get('Idrinth\Quickly\CommandLineOutputs\Colorless')),
'Idrinth\Quickly\Commands\Validate'=>new \Idrinth\Quickly\Commands\Validate($this->get('Idrinth\Quickly\CommandLineOutputs\Colorless')),
default => $this->fallBackOn($id),
};
}
private function fallBackOn(string $id): object
{
try {
return $this->fallbackContainer->get($id);
} catch (Exception $e) {
throw new FallbackFailed("Couldn't fall back on {$id}", previous: $e);
}
}
public function has(string $id): bool
{
return isset($this->defined[$id]) || isset($this->environments[$id]) || $this->fallbackContainer->has($id);
}
}
Environment variables are automatically converted to camelCase for injection:
DATABASE_URL
→databaseUrl
API_KEY
→apiKey
REDIS_HOST
→redisHost
Quickly provides comprehensive exception handling with PSR-11 compliant exceptions for different error scenarios. All exceptions implement either Psr\Container\ContainerExceptionInterface
or Psr\Container\NotFoundExceptionInterface
.
-
InvalidClassName
- Invalid or empty class name provided in configuration- Extends:
InvalidArgumentException
- Implements:
Psr\Container\ContainerExceptionInterface
- Extends:
-
InvalidParameterName
- Invalid or empty parameter name provided in configuration- Extends:
InvalidArgumentException
- Implements:
Psr\Container\ContainerExceptionInterface
- Extends:
-
InvalidDependency
- Invalid dependency definition in constructor arguments- Extends:
InvalidArgumentException
- Implements:
Psr\Container\ContainerExceptionInterface
- Extends:
-
DependencyTypeUnknown
- Unknown dependency definition type encountered- Extends:
InvalidArgumentException
- Implements:
Psr\Container\ContainerExceptionInterface
- Extends:
-
DependencyNotFound
- Service not registered in container- Extends:
OutOfBoundsException
- Implements:
Psr\Container\NotFoundExceptionInterface
- Extends:
-
DependencyUnbuildable
- Cannot construct service due to runtime issues- Extends:
UnexpectedValueException
- Implements:
Psr\Container\ContainerExceptionInterface
- Extends:
-
CircularDependency
- Circular dependency detected during resolution- Extends:
DependencyUnbuildable
- Implements:
Psr\Container\ContainerExceptionInterface
(inherited)
- Extends:
-
FallbackFailed
- Fallback container failed to provide dependency- Extends:
DependencyUnbuildable
- Implements:
Psr\Container\ContainerExceptionInterface
(inherited)
- Extends:
DependencyUnresolvable
- Dependency cannot be resolved at build time- Extends:
DependencyUnbuildable
- Implements:
Psr\Container\ContainerExceptionInterface
(inherited)
- Extends:
NoImplementationFound
- Factory cannot resolve implementation for given parameters- Extends:
UnexpectedValueException
- Implements:
Psr\Container\NotFoundExceptionInterface
- Extends:
InvalidArgumentException
├── InvalidClassName
├── InvalidDependency
└── DependencyTypeUnknown
OutOfBoundsException
└── DependencyNotFound
UnexpectedValueException
├── DependencyUnbuildable
│ ├── CircularDependency
│ ├── DependencyUnresolvable
│ └── FallbackFailed
└── NoImplementationFound
All exceptions are PSR-11 compliant and provide detailed error messages to help with debugging dependency injection issues.
// This will throw CircularDependency exception
class ServiceA
{
public function __construct(ServiceB $serviceB) {}
}
class ServiceB
{
public function __construct(ServiceA $serviceA) {}
}
All services are automatically singletons - the same instance is returned for subsequent requests:
$service1 = $container->get(MyService::class);
$service2 = $container->get(MyService::class);
// $service1 === $service2
Run the test suite:
composer test
Or with PHPUnit directly:
vendor/bin/phpunit
Idrinth/php-dependency-injection-benchmark contains up to date benchmarks with comparisons to competitors.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature
) - Commit your changes (
git commit -m 'Add some amazing feature'
) - Push to the branch (
git push origin feature/amazing-feature
) - Open a Pull Request
This project is licensed under the MIT License - see the LICENSE file for details.
Björn 'Idrinth' Büttner
Built with ❤️ for high-performance PHP applications.