gacela-project / container
A minimalistic container dependency resolver
Fund package maintenance!
Requires
- php: >=8.3
- psr/container: ^1.1 || ^2.0
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.75
- infection/infection: ^0.31 || ^0.34
- phpbench/phpbench: ^1.4
- phpstan/phpstan: ^2.2
- phpunit/phpunit: ^12.5
- symfony/var-dumper: ^6.4 || ^7.0
- symfony/yaml: ^6.4 || ^7.0
- vimeo/psalm: ^6.16
Suggests
- symfony/yaml: To load container definitions from .yaml/.yml files with loadFile(); not required โ parse it yourself and use load()
README
A minimalistic, PSR-11 compliant dependency injection container with automatic constructor injection and zero configuration.
Features
- ๐ Zero Configuration: Automatic constructor injection without verbose setup
- ๐ Circular Dependency Detection: Clear error messages when dependencies form a loop
- ๐ฆ PSR-11 Compliant: Standard container interface for interoperability
- โก Performance Optimized: Warm resolution builds straight to
new, plus warmup, a compiled cache that skips reflection, and one plan cache shared across sibling containers - ๐งฉ Fluent Registration: Register bindings after construction with
bind(),singleton()andlazy() - ๐ฑ Scopes: Child containers that inherit registration without copying it, for per-request lifetimes
- ๐ Typed Resolution:
make()returns a typed instance;getOrFail()never returnsnull - ๐งต Tags: Group services under a tag and resolve them lazily, as a list or as a keyed map
- ๐งญ Contextual Bindings:
when()scopes a dependency โ or a scalar โ to the classes that ask for it - ๐ Definitions as Data: Ship and override wiring as arrays, PHP, JSON or YAML files with
load()/loadFile() - ๐ช Resolution Hooks:
afterResolving()callbacks run once an id is built - ๐ Introspection: Debug and inspect container state easily
- ๐ฏ Type Safe: Requires type hints for reliable dependency resolution
- ๐ท๏ธ PHP 8 Attributes: Declarative configuration with
#[Inject],#[Singleton],#[Factory]and#[Lazy]โ on parameters, properties and setters, and subclassable under your own namespace - โ
Build-time Validation:
validate()proves a set of classes resolves without resolving them, so broken wiring fails a deploy instead of a request - ๐ ๏ธ A CLI:
gacela-container compile|report|validateโ no console framework,psr/containerstays the only runtime dependency - ๐ Built to be Wrapped:
withSelfReference()hands a decorator's facade to service closures, so composing over thefinalcontainer costs one call
Installation
composer require gacela-project/container
Requires PHP >= 8.3.
Hello World
use Gacela\Container\Container; class Greeter { public function __construct(private Clock $clock) {} public function greet(): string { return 'Hello World at ' . $this->clock->now(); } } class Clock { public function now(): string { return date('H:i:s'); } } // Zero configuration โ dependencies are auto-wired from type hints $container = new Container(); $greeter = $container->make(Greeter::class); echo $greeter->greet();
Need interfaces, singletons, attributes, or a compiled cache? See the docs below.
How it compares
| Gacela | Pimple | Laravel | PHP-DI | Symfony | |
|---|---|---|---|---|---|
| PSR-11 | โ | wrapper | โ | โ | โ |
| Autowiring with zero config | โ | โ | โ | โ | needs config |
| Framework-independent | โ | โ | pulls illuminate/contracts |
โ | โ |
| Lifetimes as attributes | #[Singleton] #[Factory] #[Lazy] |
โ | โ | #[Inject] only |
#[Autoconfigure] |
| Property injection | โ
#[Inject] |
โ | โ | โ | via config |
| Lazy services | โ
native lazy objects (PHP 8.4+), attribute or lazy(), no proxy class |
โ | โ | โ via proxy library | โ |
| Compiled resolution | โ plans + generated factories | โ | โ | โ | โ |
| Reflection shared across sibling containers | โ
PlanCache, in-process |
โ | โ | via APCu cache | n/a โ dumped once |
| Child/scope containers | โ inherits without copying | โ | โ | โ | โ |
| Contextual bindings | โ | โ | โ | definitions | โ |
| Tags | โ list or keyed map | โ | โ | โ | โ |
| Resolution hooks | โ
afterResolving() |
โ | โ | โ | โ |
| Invoke any callable | โ
resolve() |
โ | โ
call() |
โ
call() |
โ |
| Circular dependencies | โ named exception + path | โ | โ | โ | โ at compile time |
| Introspection | โ
stats(), dependency tree, compileReport(), typo hints |
โ | โ | limited | โ via console |
| Array access | โ | โ | โ | โ | โ |
| Definitions as data | โ arrays, PHP + JSON + YAML files | โ | โ | โ | โ |
| YAML definition files | โ
optional, via symfony/yaml โ never a hard dependency |
โ | โ | โ | โ |
| XML definition files | โ by design โ parse it and load() the array |
โ | โ | โ | โ |
| Build-time validation | โ
validate(), gacela-container validate |
โ | โ | โ | โ via compiler passes |
| Compiler passes / extensions | โ by design โ packages expose definitions instead | โ | โ | โ | โ |
Use this if you want Pimple's footprint with real autowiring, or Laravel's container API without Laravel โ plus lazy services, per-request scopes, and a compiled cache that skips reflection entirely.
Look elsewhere if you need XML definitions or compiler-pass style extension
points. Both are deliberate boundaries rather than a backlog โ XML has no
canonical mapping to a definition array short of inventing a schema
(#139), and passes
operate on a definition set an autowiring container mostly does not have
(#140) โ the two
things passes are actually reached for are covered instead: validate() gives
the build-time feedback, and a package registers services by exposing
definitions for load(). Each issue records the reasoning and what would change
it. YAML is supported, as a
suggest โ install symfony/yaml and loadFile() reads it; the runtime
requirement is still psr/container alone.
Documentation
| Guide | What's inside |
|---|---|
| Getting Started | Installation, basic usage, how resolution works |
| Bindings & Registration | Constructor bindings, bind()/singleton(), contextual bindings, aliasing |
| Definitions as Data | load()/loadFile(): wiring from arrays, PHP, JSON and YAML files |
| Resolving Services | get(), make(), getOrFail(), resolve(), transient vs. shared |
| PHP 8 Attributes | #[Inject], #[Singleton], #[Factory], #[Lazy] |
| Managing Services | Factories, extending, protecting closures, introspection |
| Scopes | Child containers: inherited registration, per-request lifetimes |
| Performance & Compilation | warmUp(), compiled cache, generated factories, validate(), the CLI |
| Cookbook | Recipes: testing, config, plugins, decorating, debugging |
| Error Handling | Every exception, what causes it, how to fix it |
| Best Practices | Recommended patterns |
| API Reference | Full method, static, and attribute reference |
| Backward Compatibility | What semver covers here, and what it does not |
| Upgrade Guide | Migrating to 2.0, and 0.10.0 to 1.0.0 |
Real-World Example
See how it's used in the Gacela Framework.
Testing
composer test # Run tests composer quality # Run static analysis composer test-coverage # Generate coverage report
License
MIT License. See LICENSE file for details.