Invoker
Generic and extensible callable invoker., (*1)
, (*2)
Why?
Who doesn't need an over-engineered call_user_func()
?, (*3)
Named parameters
Does this Silex example look familiar:, (*4)
$app->get('/project/{project}/issue/{issue}', function ($project, $issue) {
// ...
});
Or this command defined with Silly:, (*5)
$app->command('greet [name] [--yell]', function ($name, $yell) {
// ...
});
Same pattern in Slim:, (*6)
$app->get('/hello/:name', function ($name) {
// ...
});
You get the point. These frameworks invoke the controller/command/handler using something akin to named parameters: whatever the order of the parameters, they are matched by their name., (*7)
This library allows to invoke callables with named parameters in a generic and extensible way., (*8)
Dependency injection
Anyone familiar with AngularJS is familiar with how dependency injection is performed:, (*9)
angular.controller('MyController', ['dep1', 'dep2', function(dep1, dep2) {
// ...
}]);
In PHP we find this pattern again in some frameworks and DI containers with partial to full support. For example in Silex you can type-hint the application to get it injected, but it only works with Silex\Application
:, (*10)
$app->get('/hello/{name}', function (Silex\Application $app, $name) {
// ...
});
In Silly, it only works with OutputInterface
to inject the application output:, (*11)
$app->command('greet [name]', function ($name, OutputInterface $output) {
// ...
});
PHP-DI provides a way to invoke a callable and resolve all dependencies from the container using type-hints:, (*12)
$container->call(function (Logger $logger, EntityManager $em) {
// ...
});
This library provides clear extension points to let frameworks implement any kind of dependency injection support they want., (*13)
TL/DR
In short, this library is meant to be a base building block for calling a function with named parameters and/or dependency injection., (*14)
Installation
$ composer require PHP-DI/invoker
Usage
Default behavior
By default the Invoker
can call using named parameters:, (*15)
$invoker = new Invoker\Invoker;
$invoker->call(function () {
echo 'Hello world!';
});
// Simple parameter array
$invoker->call(function ($name) {
echo 'Hello ' . $name;
}, ['John']);
// Named parameters
$invoker->call(function ($name) {
echo 'Hello ' . $name;
}, [
'name' => 'John'
]);
// Use the default value
$invoker->call(function ($name = 'world') {
echo 'Hello ' . $name;
});
// Invoke any PHP callable
$invoker->call(['MyClass', 'myStaticMethod']);
// Using Class::method syntax
$invoker->call('MyClass::myStaticMethod');
Dependency injection in parameters is supported but needs to be configured with your container. Read on or jump to Built-in support for dependency injection if you are impatient., (*16)
Additionally, callables can also be resolved from your container. Read on or jump to Resolving callables from a container if you are impatient., (*17)
Parameter resolvers
Extending the behavior of the Invoker
is easy and is done by implementing a ParameterResolver
., (*18)
This is explained in details the Parameter resolvers documentation., (*19)
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 2 optional resolvers:, (*20)
-
TypeHintContainerResolver
, (*21)
This resolver will inject container entries by searching for the class name using the type-hint:, (*22)
$invoker->call(function (Psr\Logger\LoggerInterface $logger) {
// ...
});
In this example it will ->get('Psr\Logger\LoggerInterface')
from the container and inject it., (*23)
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
, db
, etc.) instead of the class name: in that case use the resolver shown below., (*24)
-
ParameterNameContainerResolver
, (*25)
This resolver will inject container entries by searching for the name of the parameter:, (*26)
$invoker->call(function ($twig) {
// ...
});
In this example it will ->get('twig')
from the container and inject it., (*27)
These resolvers can work with any dependency injection container compliant with PSR-11., (*28)
Setting up those resolvers is simple:, (*29)
// $container must be an instance of Psr\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()->prependResolver($containerResolver);
You can also register both resolvers at the same time if you wish by prepending both. Implementing support for more tricky things is easy and up to you!, (*30)
Resolving callables from a container
The Invoker
can be wired to your DI container to resolve the callables., (*31)
For example with an invokable class:, (*32)
class MyHandler
{
public function __invoke()
{
// ...
}
}
// By default this doesn't work: an instance of the class should be provided
$invoker->call('MyHandler');
// If we set up the container to use
$invoker = new Invoker\Invoker(null, $container);
// Now 'MyHandler' is resolved using the container!
$invoker->call('MyHandler');
The same works for a class method:, (*33)
class WelcomeController
{
public function home()
{
// ...
}
}
// By default this doesn't work: home() is not a static method
$invoker->call(['WelcomeController', 'home']);
// If we set up the container to use
$invoker = new Invoker\Invoker(null, $container);
// Now 'WelcomeController' is resolved using the container!
$invoker->call(['WelcomeController', 'home']);
// Alternatively we can use the Class::method syntax
$invoker->call('WelcomeController::home');
That feature can be used as the base building block for a framework's dispatcher., (*34)
Again, any PSR-11 compliant container can be provided., (*35)