mrclay/props-dic
A lightweight PHP DI container that exposes services as typed properties and factory methods ($c->foo, $c->new_foo()), letting you add @property/@method PHPDoc for IDE autocomplete and static analysis. Supports lazy resolution, caching, and factories.
Strengths:
@property-read and @method annotations to provide static type hints, enabling autocompletion, refactoring, and static analysis (Psalm/PHPStan). This directly addresses Laravel’s container’s lack of IDE-friendly property access, improving developer experience (DX) without sacrificing PSR-11 compliance.Psr\Container\ContainerInterface, ensuring compatibility with Laravel’s service container where basic DI is sufficient. Ideal for modular applications or microservices where IDE tooling is prioritized.new_* method pattern (e.g., $container->new_user()) provides fine-grained control over dependency lifecycle, a feature missing in Laravel’s container.Props\Pimple allows gradual migration for teams using Pimple, reducing disruption.Weaknesses:
app()->makeWith(), when()). Would require custom wrappers or decorators to replicate functionality..env overrides) or package-based service providers.bindIf() or App::singleton().make:command/make:controller support, requiring manual setup for CLI tools.App\Services\AuthContainer) while retaining Laravel’s container for framework-level dependencies.LaravelPropsContainer) to bridge Props with Laravel’s Container methods (e.g., bind(), singleton()), translating them to Props’ setFactory() or property assignments.app('foo') or app()->get('foo') with typed property access (e.g., $container->userRepository), reducing magic strings and improving maintainability.bind(), singleton()) to Props’ setFactory() or direct assignments. Risk: Breaking changes to existing codebase.app()->get() or resolve() syntax may resist property/method access ($container->user). Requires training or documentation to highlight IDE benefits.Scope of Adoption:
App\Services\AuthContainer)?app()->makeWith())?Feature Parity Requirements:
app()->tag())? If so, how will these be implemented (e.g., custom wrappers)?Migration Strategy:
bind(), singleton(), and app()->get() calls be translated to Props’ setFactory() or property assignments?IDE/Static Analysis Priority:
Testing and CI/CD Impact:
php artisan test) that would break?Long-Term Maintenance:
Ideal Use Cases:
App\Services\PaymentContainer, App\Services\AuthContainer) where IDE support and type safety are critical.app('user_repository') with typed property access ($container->userRepository) to eliminate magic strings and improve maintainability.Poor Fit:
new_*) may introduce minimal but measurable overhead.@property-read and @method annotations, which may not align with teams preferring dynamic property access.Phase 1: Pilot with a Domain-Specific Container
App\Services\AuthContainer) for a non-critical module (e.g., authentication).// Before: Laravel container
$userRepository = app()->get('App\Repositories\UserRepository');
// After: Props container
$authContainer = new AuthContainer();
$userRepository = $authContainer->userRepository;
Phase 2: Hybrid Integration
LaravelPropsContainer) to bridge Props with Laravel’s container:
class LaravelPropsContainer extends Props\Container {
public function __construct(private \Illuminate\Container\Container $laravelContainer) {}
public function get($id) {
return $this->laravelContainer->get($id);
}
// Translate Laravel's bind() to Props' setFactory()
public function bind($abstract, $concrete = null, $shared = true) {
if (is_callable($concrete)) {
$this->setFactory($abstract, $concrete);
} else {
$this->{$abstract} = $this->laravelContainer->make($concrete);
}
}
}
Phase 3: Full Migration (Optional)
app()->get() calls with Props property access.setFactory() instead of bind().How can I help you explore Laravel packages today?