gmarche/vendor/php-di/invoker/doc/parameter-resolvers.md

3.4 KiB

Parameter resolvers

Extending the behavior of the Invoker is easy and is done by implementing a ParameterResolver:

interface ParameterResolver
{
    public function getParameters(
        ReflectionFunctionAbstract $reflection,
        array $providedParameters,
        array $resolvedParameters
    );
}
  • $providedParameters contains the parameters provided by the user when calling $invoker->call($callable, $parameters)
  • $resolvedParameters contains parameters that have already been resolved by other parameter resolvers

An Invoker can chain multiple parameter resolvers to mix behaviors, e.g. you can mix "named parameters" support with "dependency injection" support. This is why a ParameterResolver should skip parameters that are already resolved in $resolvedParameters.

Here is an implementation example for dumb dependency injection that creates a new instance of the classes type-hinted:

class MyParameterResolver implements ParameterResolver
{
    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;
            }

            $class = $parameter->getClass();

            if ($class) {
                $resolvedParameters[$index] = $class->newInstance();
            }
        }

        return $resolvedParameters;
    }
}

To use it:

$invoker = new Invoker\Invoker(new MyParameterResolver);

$invoker->call(function (ArticleManager $articleManager) {
    $articleManager->publishArticle('Hello world', 'This is the article content.');
});

A new instance of ArticleManager will be created by our parameter resolver.

Chaining parameter resolvers

The fun starts to happen when we want to add support for many things:

  • named parameters
  • dependency injection for type-hinted parameters
  • ...

This is where we should use the ResolverChain. This resolver implements the Chain of responsibility design pattern.

For example the default chain is:

$parameterResolver = new ResolverChain([
    new NumericArrayResolver,
    new AssociativeArrayResolver,
    new DefaultValueResolver,
]);

It allows to support even the weirdest use cases like:

$parameters = [];

// First parameter will receive "Welcome"
$parameters[] = 'Welcome';

// Parameter named "content" will receive "Hello world!"
$parameters['content'] = 'Hello world!';

// $published is not defined so it will use its default value
$invoker->call(function ($title, $content, $published = true) {
    // ...
}, $parameters);

We can put our custom parameter resolver in the list and created a super-duper invoker that also supports basic dependency injection:

$parameterResolver = new ResolverChain([
    new MyParameterResolver, // Our resolver is at the top for highest priority
    new NumericArrayResolver,
    new AssociativeArrayResolver,
    new DefaultValueResolver,
]);

$invoker = new Invoker\Invoker($parameterResolver);