From 4d6c2adcd5ad45bd4d4498636c5c9df0b27b7d92 Mon Sep 17 00:00:00 2001 From: Yitz Willroth Date: Sun, 1 Jun 2025 14:11:00 -0400 Subject: [PATCH 1/3] feat: add contextual implementation/interface binding via attribute --- .../Container/Attributes/Provide.php | 46 ++++++++++++++++ .../ContextualAttributeBindingTest.php | 53 ++++++++++++++++++- 2 files changed, 98 insertions(+), 1 deletion(-) create mode 100644 src/Illuminate/Container/Attributes/Provide.php diff --git a/src/Illuminate/Container/Attributes/Provide.php b/src/Illuminate/Container/Attributes/Provide.php new file mode 100644 index 000000000000..94f0a072d668 --- /dev/null +++ b/src/Illuminate/Container/Attributes/Provide.php @@ -0,0 +1,46 @@ + $class The concrete class to instantiate + * @param array|null $params Constructor parameters (optional) + * + * @example Basic usage: + * #[Provide(EloquentUserRepository::class)] + * private UserRepository $EloquentUserRepository + * + * @example With constructor parameters: + * #[Provide(SendGridEmailService::class, ['template' => 'welcome', 'from' => 'noreply@app.com'])] + * private EmailService $emailService + */ + public function __construct( + public string $class, + public array $params = [] + ) {} + + /** + * Resolve the dependency. + * + * @param self $attribute + * @param \Illuminate\Contracts\Container\Container $container + * @return mixed + */ + public static function resolve(self $attribute, Container $container): mixed + { + return $container->make($attribute->class, $attribute->params); + } +} diff --git a/tests/Container/ContextualAttributeBindingTest.php b/tests/Container/ContextualAttributeBindingTest.php index 4eb73ad12d7d..5c37af1aa08e 100644 --- a/tests/Container/ContextualAttributeBindingTest.php +++ b/tests/Container/ContextualAttributeBindingTest.php @@ -15,6 +15,7 @@ use Illuminate\Container\Attributes\CurrentUser; use Illuminate\Container\Attributes\Database; use Illuminate\Container\Attributes\Log; +use Illuminate\Container\Attributes\Provide; use Illuminate\Container\Attributes\RouteParameter; use Illuminate\Container\Attributes\Storage; use Illuminate\Container\Attributes\Tag; @@ -46,7 +47,7 @@ public function testDependencyCanBeResolvedFromAttributeBinding() { $container = new Container; - $container->bind(ContainerTestContract::class, fn () => new ContainerTestImplB); + $container->bind(ContainerTestContract::class, fn (): ContainerTestImplB => new ContainerTestImplB); $container->whenHasAttribute(ContainerTestAttributeThatResolvesContractImpl::class, function (ContainerTestAttributeThatResolvesContractImpl $attribute) { return match ($attribute->name) { 'A' => new ContainerTestImplA, @@ -65,6 +66,30 @@ public function testDependencyCanBeResolvedFromAttributeBinding() $this->assertInstanceOf(ContainerTestImplB::class, $classB->property); } + public function testSimpleDependencyCanBeResolvedCorrectlyFromProvideAttributeBinding() + { + $container = new Container; + + $container->bind(ContainerTestContract::class, concrete: ContainerTestImplA::class); + + $resolution = $container->make(ProvideTestSimple::class); + + $this->assertInstanceOf(SimpleDependency::class, $resolution->dependency); + } + + + public function testComplexDependencyCanBeResolvedCorrectlyFromProvideAttributeBinding() + { + $container = new Container; + + $container->bind(ContainerTestContract::class, concrete: ContainerTestImplA::class); + + $resolution = $container->make(ProvideTestComplex::class); + + $this->assertInstanceOf(ComplexDependency::class, $resolution->dependency); + $this->assertTrue($resolution->dependency->param); + } + public function testScalarDependencyCanBeResolvedFromAttributeBinding() { $container = new Container; @@ -161,6 +186,7 @@ public function testConfigAttribute() $container->make(ConfigTest::class); } + public function testDatabaseAttribute() { $container = new Container; @@ -435,6 +461,13 @@ public function __construct( } } +final class SimpleDependency implements ContainerTestContract {} + +final class ComplexDependency implements ContainerTestContract +{ + public function __construct(public bool $param) {} +} + final class AuthedTest { public function __construct(#[Authenticated('foo')] AuthenticatableContract $foo, #[CurrentUser('bar')] AuthenticatableContract $bar) @@ -505,6 +538,24 @@ public function __construct(#[Storage('foo')] Filesystem $foo, #[Storage('bar')] } } +final class ProvideTestSimple +{ + public function __construct( + #[Provide(SimpleDependency::class)] + public readonly ContainerTestContract $dependency + ) { + } +} + +final class ProvideTestComplex +{ + public function __construct( + #[Provide(ComplexDependency::class, ['param' => true])] + public readonly ContainerTestContract $dependency + ) { + } +} + final class TimezoneObject { public function __construct( From 7a276090fc024ab2baf4514525c8112cbb527927 Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Mon, 2 Jun 2025 09:30:21 -0500 Subject: [PATCH 2/3] Update Provide.php --- src/Illuminate/Container/Attributes/Provide.php | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/src/Illuminate/Container/Attributes/Provide.php b/src/Illuminate/Container/Attributes/Provide.php index 94f0a072d668..38d90354b83b 100644 --- a/src/Illuminate/Container/Attributes/Provide.php +++ b/src/Illuminate/Container/Attributes/Provide.php @@ -11,21 +11,10 @@ class Provide implements ContextualAttribute { /** * Provide a concrete class implementation for dependency injection. - * - * Note: This attribute requires a concrete class name. Abstract classes - * and interfaces are not supported and will result in binding resolution errors. * * @template T - * @param class-string $class The concrete class to instantiate - * @param array|null $params Constructor parameters (optional) - * - * @example Basic usage: - * #[Provide(EloquentUserRepository::class)] - * private UserRepository $EloquentUserRepository - * - * @example With constructor parameters: - * #[Provide(SendGridEmailService::class, ['template' => 'welcome', 'from' => 'noreply@app.com'])] - * private EmailService $emailService + * @param class-string $class + * @param array|null $params */ public function __construct( public string $class, From b3c5c46cc47e20c3eff0037037a73c67cc53dc51 Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Mon, 2 Jun 2025 09:33:00 -0500 Subject: [PATCH 3/3] formatting --- .../Attributes/{Provide.php => Give.php} | 2 +- .../ContextualAttributeBindingTest.php | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) rename src/Illuminate/Container/Attributes/{Provide.php => Give.php} (94%) diff --git a/src/Illuminate/Container/Attributes/Provide.php b/src/Illuminate/Container/Attributes/Give.php similarity index 94% rename from src/Illuminate/Container/Attributes/Provide.php rename to src/Illuminate/Container/Attributes/Give.php index 38d90354b83b..88048a9f7c25 100644 --- a/src/Illuminate/Container/Attributes/Provide.php +++ b/src/Illuminate/Container/Attributes/Give.php @@ -7,7 +7,7 @@ use Illuminate\Contracts\Container\ContextualAttribute; #[Attribute(Attribute::TARGET_PARAMETER)] -class Provide implements ContextualAttribute +class Give implements ContextualAttribute { /** * Provide a concrete class implementation for dependency injection. diff --git a/tests/Container/ContextualAttributeBindingTest.php b/tests/Container/ContextualAttributeBindingTest.php index 5c37af1aa08e..64aec56d6394 100644 --- a/tests/Container/ContextualAttributeBindingTest.php +++ b/tests/Container/ContextualAttributeBindingTest.php @@ -15,7 +15,7 @@ use Illuminate\Container\Attributes\CurrentUser; use Illuminate\Container\Attributes\Database; use Illuminate\Container\Attributes\Log; -use Illuminate\Container\Attributes\Provide; +use Illuminate\Container\Attributes\Give; use Illuminate\Container\Attributes\RouteParameter; use Illuminate\Container\Attributes\Storage; use Illuminate\Container\Attributes\Tag; @@ -66,25 +66,25 @@ public function testDependencyCanBeResolvedFromAttributeBinding() $this->assertInstanceOf(ContainerTestImplB::class, $classB->property); } - public function testSimpleDependencyCanBeResolvedCorrectlyFromProvideAttributeBinding() + public function testSimpleDependencyCanBeResolvedCorrectlyFromGiveAttributeBinding() { $container = new Container; $container->bind(ContainerTestContract::class, concrete: ContainerTestImplA::class); - $resolution = $container->make(ProvideTestSimple::class); + $resolution = $container->make(GiveTestSimple::class); $this->assertInstanceOf(SimpleDependency::class, $resolution->dependency); } - public function testComplexDependencyCanBeResolvedCorrectlyFromProvideAttributeBinding() + public function testComplexDependencyCanBeResolvedCorrectlyFromGiveAttributeBinding() { $container = new Container; $container->bind(ContainerTestContract::class, concrete: ContainerTestImplA::class); - $resolution = $container->make(ProvideTestComplex::class); + $resolution = $container->make(GiveTestComplex::class); $this->assertInstanceOf(ComplexDependency::class, $resolution->dependency); $this->assertTrue($resolution->dependency->param); @@ -538,19 +538,19 @@ public function __construct(#[Storage('foo')] Filesystem $foo, #[Storage('bar')] } } -final class ProvideTestSimple +final class GiveTestSimple { public function __construct( - #[Provide(SimpleDependency::class)] + #[Give(SimpleDependency::class)] public readonly ContainerTestContract $dependency ) { } } -final class ProvideTestComplex +final class GiveTestComplex { public function __construct( - #[Provide(ComplexDependency::class, ['param' => true])] + #[Give(ComplexDependency::class, ['param' => true])] public readonly ContainerTestContract $dependency ) { }