idrinth / quickly
A fast dependency injection container featuring build time resolution.
Requires
- php: ^8.4
- psr/container: ^2.0.2
Requires (Dev)
- phpunit/phpunit: ^12.3.7
This package is auto-updated.
Last update: 2025-09-10 12:39:10 UTC
README
A fast dependency injection container for PHP featuring build-time resolution and PSR-11 compliance.
Features
- β 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
Requirements
- PHP 8.4 or higher
- PSR Container 2.0.2+
Installation
composer require idrinth/quickly
Quick Start
Basic Usage
use Idrinth\Quickly\EnvironmentFactory; // Create container factory $factory = new EnvironmentFactory(); $container = $factory->createContainer(); // Get services $myService = $container->get(MyService::class);
With Reflection (Development Mode)
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);
Dependency Injection Attributes
Environment Variable Injection
use Idrinth\Quickly\DependencyInjection\EnvironmentInject; class DatabaseService { public function __construct( #[EnvironmentInject('DATABASE_URL')] private string $databaseUrl, #[EnvironmentInject('DB_TIMEOUT')] private int $timeout = 30 ) {} }
Factory-based Resolution
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 ) {} }
Lazy Initialization
use Idrinth\Quickly\DependencyInjection\LazyInitialization; #[LazyInitialization] class ExpensiveService { }
Looking at your overwrites implementation, here's what I would add to the README:
Overwrites
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.
Configuration File
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'), ], ];
Manual Configuration
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 );
Available Override Types
- 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
Examples
// 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' ),
Use Cases
- 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.
Command Line Tools
Quickly provides several CLI commands accessible via vendor/bin/quickly
:
Build Configuration (Functional)
vendor/bin/quickly build [filepath]
Creates an optimized production configuration file for maximum performance.
Validate Configuration (WIP)
vendor/bin/quickly validate [filepath]
Validates your dependency injection configuration for errors.
Help
vendor/bin/quickly help
Shows available commands and usage information.
Configuration
Manual Configuration
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', ] );
Generated Configuration
For production use, generate optimized configuration:
// .quickly/generated.php (example) return [ 'constructors' => [ // Pre-resolved constructor dependencies ], 'factories' => [ // Factory mappings ], 'classAliases' => [ // Interface to implementation mappings ] ];
Compiled Configuration
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 Variable Mapping
Environment variables are automatically converted to camelCase for injection:
DATABASE_URL
βdatabaseUrl
API_KEY
βapiKey
REDIS_HOST
βredisHost
Error Handling
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
.
Configuration & Setup Errors
-
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:
Resolution & Runtime Errors
-
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:
Build-time Errors
DependencyUnresolvable
- Dependency cannot be resolved at build time- Extends:
DependencyUnbuildable
- Implements:
Psr\Container\ContainerExceptionInterface
(inherited)
- Extends:
Factory-specific Errors
NoImplementationFound
- Factory cannot resolve implementation for given parameters- Extends:
UnexpectedValueException
- Implements:
Psr\Container\NotFoundExceptionInterface
- Extends:
Exception Hierarchy
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.
Advanced Features
Circular Dependency Detection
// This will throw CircularDependency exception class ServiceA { public function __construct(ServiceB $serviceB) {} } class ServiceB { public function __construct(ServiceA $serviceA) {} }
Singleton Behavior
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
Testing
Run the test suite:
composer test
Or with PHPUnit directly:
vendor/bin/phpunit
Benchmarks
Idrinth/php-dependency-injection-benchmark contains up to date benchmarks with comparisons to competitors.
Contributing
- 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
License
This project is licensed under the MIT License - see the LICENSE file for details.
Author
BjΓΆrn 'Idrinth' BΓΌttner
Built with β€οΈ for high-performance PHP applications.