Skip to content

Commit

Permalink
Added a new resolver ParameterNameContainerResolver
Browse files Browse the repository at this point in the history
Resolves parameters from a container by name
  • Loading branch information
mnapoli committed Mar 30, 2015
1 parent fde4881 commit 705d753
Show file tree
Hide file tree
Showing 4 changed files with 174 additions and 18 deletions.
50 changes: 34 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,34 +133,52 @@ This is explained in details the [Parameter resolvers documentation](doc/paramet

#### Built-in support for dependency injection

Rather than have you re-implement support for dependency injection with different containers every time, this package ships with a [`TypeHintContainerResolver`](https://github.com/mnapoli/Invoker/blob/master/src/ParameterResolver/Container/TypeHintContainerResolver.php) that can work with any dependency injection container thanks to [container-interop](https://github.com/container-interop/container-interop).
Rather than have you re-implement support for dependency injection with different containers every time, this package ships with 2 optional resolvers:

Using it is simple:
- [`TypeHintContainerResolver`](https://github.com/mnapoli/Invoker/blob/master/src/ParameterResolver/Container/TypeHintContainerResolver.php)

This resolver will inject container entries by searching for the class name using the type-hint:

```php
$invoker->call(function (Psr\Logger\LoggerInterface $logger) {
// ...
});
```

In this example it will `->get('Psr\Logger\LoggerInterface')` from the container.

This resolver is only useful if you store objects in your container using the class (or interface) name. Silex or Symfony for example store services under a custom name (e.g. `twig`, `doctrine.orm.entity_manager`, etc.) instead of the class name: in that case use the resolver shown below.

- [`ParameterNameContainerResolver`](https://github.com/mnapoli/Invoker/blob/master/src/ParameterResolver/Container/ParameterNameContainerResolver.php)

This resolver will inject container entries by searching for the name of the parameter:

```php
$invoker->call(function ($twig) {
// ...
});
```

In this example it will `->get('twig')` from the container.

These resolvers can work with any dependency injection container compliant with [container-interop](https://github.com/container-interop/container-interop). If you container is not compliant you can use the [Acclimate](https://github.com/jeremeamia/acclimate-container) package.

Setting up those resolvers is simple:

```php
// $container must be an instance of Interop\Container\ContainerInterface
$container = ...

$containerResolver = new TypeHintContainerResolver($container);
// or
$containerResolver = new ParameterNameContainerResolver($container);

$invoker = new Invoker\Invoker;
// Register it before all the other parameter resolvers
$invoker->getParameterResolver()->unshiftResolver($containerResolver);
```

This parameter resolver will use the type-hints to look into the container:

```php
$invoker->call(function (Psr\Logger\LoggerInterface $logger) {
// ...
});
$invoker->getParameterResolver()->prependResolver($containerResolver);
```

In this example it will `->get('Psr\Logger\LoggerInterface')` from the container.

*Note:* if you container is not compliant with [container-interop](https://github.com/container-interop/container-interop), you can use the [Acclimate](https://github.com/jeremeamia/acclimate-container) package.

This implementation will only do dependency injection based on type-hints. Implementing support for doing dependency injection based on parameter names, or whatever, is easy and up to you!
You can also register both resolvers at the same time if you wish by prepending both (in the order you wish). Implementing support for more tricky things is easy and up to you!

### Resolving callables from a container

Expand Down
49 changes: 49 additions & 0 deletions src/ParameterResolver/Container/ParameterNameContainerResolver.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php

namespace Invoker\ParameterResolver\Container;

use Interop\Container\ContainerInterface;
use Invoker\ParameterResolver\ParameterResolver;
use ReflectionFunctionAbstract;

/**
* Inject entries from a DI container using the parameter names.
*
* @author Matthieu Napoli <[email protected]>
*/
class ParameterNameContainerResolver implements ParameterResolver
{
/**
* @var ContainerInterface
*/
private $container;

/**
* @param ContainerInterface $container The container to get entries from.
*/
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}

public function getParameters(
ReflectionFunctionAbstract $reflection,
array $providedParameters,
array $resolvedParameters
) {
foreach ($reflection->getParameters() as $index => $parameter) {
if (array_key_exists($index, $resolvedParameters)) {
// Skip already resolved parameters
continue;
}

$name = $parameter->getName();

if ($name && $this->container->has($name)) {
$resolvedParameters[$index] = $this->container->get($name);
}
}

return $resolvedParameters;
}
}
23 changes: 21 additions & 2 deletions tests/InvokerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace Invoker\Test;

use Invoker\Invoker;
use Invoker\ParameterResolver\Container\ParameterNameContainerResolver;
use Invoker\ParameterResolver\Container\TypeHintContainerResolver;
use Invoker\Test\Mock\ArrayContainer;
use Invoker\Test\Mock\CallableSpy;
Expand Down Expand Up @@ -130,10 +131,10 @@ public function should_invoke_callable_with_default_value_for_undefined_paramete
/**
* @test
*/
public function should_do_dependency_injection_with_container_parameter_resolver()
public function should_do_dependency_injection_with_typehint_container_resolver()
{
$resolver = new TypeHintContainerResolver($this->container);
$this->invoker->getParameterResolver()->unshiftResolver($resolver);
$this->invoker->getParameterResolver()->prependResolver($resolver);

$expected = new \stdClass();
$this->container->set('stdClass', $expected);
Expand All @@ -145,6 +146,24 @@ public function should_do_dependency_injection_with_container_parameter_resolver
$this->assertSame($expected, $result);
}

/**
* @test
*/
public function should_do_dependency_injection_with_parameter_name_container_resolver()
{
$resolver = new ParameterNameContainerResolver($this->container);
$this->invoker->getParameterResolver()->prependResolver($resolver);

$expected = new \stdClass();
$this->container->set('foo', $expected);

$result = $this->invoker->call(function ($foo) {
return $foo;
});

$this->assertSame($expected, $result);
}

/**
* @test
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
<?php

namespace Invoker\Test\ParameterResolver\Container;

use Invoker\ParameterResolver\Container\ParameterNameContainerResolver;
use Invoker\Test\Mock\ArrayContainer;

class ParameterNameContainerResolverTest extends \PHPUnit_Framework_TestCase
{
/**
* @var ParameterNameContainerResolver
*/
private $resolver;

/**
* @var ArrayContainer
*/
private $container;

public function setUp()
{
$this->container = new ArrayContainer;
$this->resolver = new ParameterNameContainerResolver($this->container);
}

/**
* @test
*/
public function should_resolve_parameter_with_parameter_name_from_container()
{
$callable = function ($foo) {};
$reflection = new \ReflectionFunction($callable);

$this->container->set('foo', 'bar');

$parameters = $this->resolver->getParameters($reflection, array(), array());

$this->assertCount(1, $parameters);
$this->assertSame('bar', $parameters[0]);
}

/**
* @test
*/
public function should_skip_parameter_if_container_cannot_provide_parameter()
{
$callable = function ($foo) {};
$reflection = new \ReflectionFunction($callable);

$parameters = $this->resolver->getParameters($reflection, array(), array());

$this->assertCount(0, $parameters);
}

/**
* @test
*/
public function should_skip_parameter_if_already_resolved()
{
$callable = function ($foo) {};
$reflection = new \ReflectionFunction($callable);

$this->container->set('foo', 'bar');

$resolvedParameters = array('first param value');
$parameters = $this->resolver->getParameters($reflection, array(), $resolvedParameters);

$this->assertSame($resolvedParameters, $parameters);
}
}

0 comments on commit 705d753

Please sign in to comment.