Skip to content

Commit

Permalink
Add dependency resolver attributes
Browse files Browse the repository at this point in the history
  • Loading branch information
Neol3108 committed Jan 13, 2024
1 parent 299aae5 commit 3ada9ac
Show file tree
Hide file tree
Showing 5 changed files with 165 additions and 6 deletions.
55 changes: 55 additions & 0 deletions src/Illuminate/Config/Attributes/InjectFromConfig.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<?php

namespace Illuminate\Config\Attributes;

use Attribute;
use Illuminate\Contracts\Container\Container;
use Illuminate\Contracts\Container\DependencyResolver;

#[Attribute(Attribute::TARGET_PARAMETER)]
class InjectFromConfig implements DependencyResolver
{
/**
* Key to get from config.
*
* @var string|array
*/
private $key;

/**
* Default in case config is not present or bound.
*
* @var mixed
*/
private $default;

/**
* Create a new InjectFromConfig instance.
*
* @param array|string $key
* @param mixed $default
* @return void
*/
public function __construct($key, $default = null)
{
$this->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);
}
}
27 changes: 21 additions & 6 deletions src/Illuminate/Container/Container.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down
14 changes: 14 additions & 0 deletions src/Illuminate/Contracts/Container/DependencyResolver.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

namespace Illuminate\Contracts\Container;

interface DependencyResolver
{
/**
* Resolve the dependency from the container.
*
* @param \Illuminate\Contracts\Container\Container $container
* @return mixed
*/
public function resolve(Container $container): mixed;
}
36 changes: 36 additions & 0 deletions tests/Config/Attributes/InjectFromConfigTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

namespace Illuminate\Tests\Config\Attributes;

use Illuminate\Config\Attributes\InjectFromConfig;
use Illuminate\Contracts\Config\Repository;
use Illuminate\Contracts\Container\Container;
use Mockery as m;
use PHPUnit\Framework\TestCase;

class InjectFromConfigTest extends TestCase
{
public function testItReturnsDefaultWhenConfigNotBound()
{
$attribute = new InjectFromConfig('app.name', 'default');

$container = m::mock(Container::class);
$container->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));
}
}
39 changes: 39 additions & 0 deletions tests/Container/ContainerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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
{
//
Expand Down

0 comments on commit 3ada9ac

Please sign in to comment.