diff --git a/README.md b/README.md index 8094287..d50e929 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/src/ParameterResolver/Container/ParameterNameContainerResolver.php b/src/ParameterResolver/Container/ParameterNameContainerResolver.php new file mode 100644 index 0000000..3c14926 --- /dev/null +++ b/src/ParameterResolver/Container/ParameterNameContainerResolver.php @@ -0,0 +1,49 @@ + + */ +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; + } +} diff --git a/tests/InvokerTest.php b/tests/InvokerTest.php index 00553fd..5ce1003 100644 --- a/tests/InvokerTest.php +++ b/tests/InvokerTest.php @@ -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; @@ -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); @@ -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 */ diff --git a/tests/ParameterResolver/Container/ParameterNameContainerResolverTest.php b/tests/ParameterResolver/Container/ParameterNameContainerResolverTest.php new file mode 100644 index 0000000..1bb5d00 --- /dev/null +++ b/tests/ParameterResolver/Container/ParameterNameContainerResolverTest.php @@ -0,0 +1,70 @@ +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); + } +}