diff --git a/src/Illuminate/Config/Attributes/InjectFromConfig.php b/src/Illuminate/Config/Attributes/InjectFromConfig.php new file mode 100644 index 000000000000..53ea53ed39e0 --- /dev/null +++ b/src/Illuminate/Config/Attributes/InjectFromConfig.php @@ -0,0 +1,55 @@ +key = $key; + $this->default = $default; + } + + /** + * Resolve the dependency from the container. + * + * @param \Illuminate\Contracts\Container\Container $container + * @return mixed + * + * @throws \Illuminate\Contracts\Container\BindingResolutionException + */ + public function resolve(Container $container): mixed + { + if (! $container->bound('config')) { + return value($this->default); + } + + return $container->make('config')->get($this->key, $this->default); + } +} diff --git a/src/Illuminate/Container/Container.php b/src/Illuminate/Container/Container.php index adb73bb51f86..ccf796487941 100755 --- a/src/Illuminate/Container/Container.php +++ b/src/Illuminate/Container/Container.php @@ -8,7 +8,9 @@ use Illuminate\Contracts\Container\BindingResolutionException; use Illuminate\Contracts\Container\CircularDependencyException; use Illuminate\Contracts\Container\Container as ContainerContract; +use Illuminate\Contracts\Container\DependencyResolver; use LogicException; +use ReflectionAttribute; use ReflectionClass; use ReflectionException; use ReflectionFunction; @@ -974,12 +976,25 @@ protected function resolveDependencies(array $dependencies) continue; } - // If the class is null, it means the dependency is a string or some other - // primitive type which we can not resolve since it is not a class and - // we will just bomb out with an error since we have no-where to go. - $result = is_null(Util::getParameterClassName($dependency)) - ? $this->resolvePrimitive($dependency) - : $this->resolveClass($dependency); + $resolvers = $dependency->getAttributes( + DependencyResolver::class, + ReflectionAttribute::IS_INSTANCEOF, + ); + + if (count($resolvers) > 0) { + if (count($resolvers) !== 1) { + throw new LogicException('Parameter cannot have multiple dependency resolvers'); + } + + $result = array_values($resolvers)[0]->newInstance()->resolve($this); + } else { + // If the class is null, it means the dependency is a string or some other + // primitive type which we can not resolve since it is not a class and + // we will just bomb out with an error since we have no-where to go. + $result = is_null(Util::getParameterClassName($dependency)) + ? $this->resolvePrimitive($dependency) + : $this->resolveClass($dependency); + } if ($dependency->isVariadic()) { $results = array_merge($results, $result); diff --git a/src/Illuminate/Contracts/Container/DependencyResolver.php b/src/Illuminate/Contracts/Container/DependencyResolver.php new file mode 100644 index 000000000000..fbfb7a7070e0 --- /dev/null +++ b/src/Illuminate/Contracts/Container/DependencyResolver.php @@ -0,0 +1,14 @@ +shouldReceive('bound')->with('config')->once()->andReturnFalse(); + + $this->assertSame('default', $attribute->resolve($container)); + } + + public function testItGetsFromConfig() + { + $attribute = new InjectFromConfig('app.name', 'default'); + + $config = m::mock(Repository::class); + $config->shouldReceive('get')->with('app.name', 'default')->once()->andReturn('Laravel'); + + $container = m::mock(Container::class); + $container->shouldReceive('bound')->with('config')->once()->andReturnTrue(); + $container->shouldReceive('make')->with('config')->once()->andReturn($config); + + $this->assertSame('Laravel', $attribute->resolve($container)); + } +} diff --git a/tests/Container/ContainerTest.php b/tests/Container/ContainerTest.php index 53cf9b6713ac..842e3ca6acdd 100755 --- a/tests/Container/ContainerTest.php +++ b/tests/Container/ContainerTest.php @@ -2,9 +2,12 @@ namespace Illuminate\Tests\Container; +use Attribute; use Illuminate\Container\Container; use Illuminate\Container\EntryNotFoundException; use Illuminate\Contracts\Container\BindingResolutionException; +use Illuminate\Contracts\Container\Container as ContainerContract; +use Illuminate\Contracts\Container\DependencyResolver; use PHPUnit\Framework\TestCase; use Psr\Container\ContainerExceptionInterface; use stdClass; @@ -150,6 +153,24 @@ public function testAutoConcreteResolution() $this->assertInstanceOf(ContainerConcreteStub::class, $container->make(ContainerConcreteStub::class)); } + public function testDependencyResolverAttributes() + { + $container = new Container(); + $result = $container->make(ContainerDependencyResolver::class); + $this->assertSame('bar', $result->foo); + } + + public function testParametersTakePrecedentOverDependencyResolverAttributes() + { + $container = new Container(); + + $result = $container->make(ContainerDependencyResolver::class, [ + 'foo' => 'baz', + ]); + + $this->assertSame('baz', $result->foo); + } + public function testSharedConcreteResolution() { $container = new Container; @@ -718,6 +739,24 @@ class ContainerConcreteStub // } +class ContainerDependencyResolver +{ + public function __construct( + #[AlwaysResolveAsBar] + public string $foo, + ) { + } +} + +#[Attribute(Attribute::TARGET_PARAMETER)] +class AlwaysResolveAsBar implements DependencyResolver +{ + public function resolve(ContainerContract $container): mixed + { + return 'bar'; + } +} + interface IContainerContractStub { //