1 |
efrain |
1 |
# Invoker
|
|
|
2 |
|
|
|
3 |
Generic and extensible callable invoker.
|
|
|
4 |
|
|
|
5 |
[](https://github.com/PHP-DI/Invoker/actions/workflows/ci.yml)
|
|
|
6 |
[](https://packagist.org/packages/PHP-DI/invoker)
|
|
|
7 |
[](https://packagist.org/packages/php-di/invoker)
|
|
|
8 |
|
|
|
9 |
## Why?
|
|
|
10 |
|
|
|
11 |
Who doesn't need an over-engineered `call_user_func()`?
|
|
|
12 |
|
|
|
13 |
### Named parameters
|
|
|
14 |
|
|
|
15 |
Does this [Silex](https://github.com/silexphp/Silex#readme) example look familiar:
|
|
|
16 |
|
|
|
17 |
```php
|
|
|
18 |
$app->get('/project/{project}/issue/{issue}', function ($project, $issue) {
|
|
|
19 |
// ...
|
|
|
20 |
});
|
|
|
21 |
```
|
|
|
22 |
|
|
|
23 |
Or this command defined with [Silly](https://github.com/mnapoli/silly#usage):
|
|
|
24 |
|
|
|
25 |
```php
|
|
|
26 |
$app->command('greet [name] [--yell]', function ($name, $yell) {
|
|
|
27 |
// ...
|
|
|
28 |
});
|
|
|
29 |
```
|
|
|
30 |
|
|
|
31 |
Same pattern in [Slim](https://www.slimframework.com):
|
|
|
32 |
|
|
|
33 |
```php
|
|
|
34 |
$app->get('/hello/:name', function ($name) {
|
|
|
35 |
// ...
|
|
|
36 |
});
|
|
|
37 |
```
|
|
|
38 |
|
|
|
39 |
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.
|
|
|
40 |
|
|
|
41 |
**This library allows to invoke callables with named parameters in a generic and extensible way.**
|
|
|
42 |
|
|
|
43 |
### Dependency injection
|
|
|
44 |
|
|
|
45 |
Anyone familiar with AngularJS is familiar with how dependency injection is performed:
|
|
|
46 |
|
|
|
47 |
```js
|
|
|
48 |
angular.controller('MyController', ['dep1', 'dep2', function(dep1, dep2) {
|
|
|
49 |
// ...
|
|
|
50 |
}]);
|
|
|
51 |
```
|
|
|
52 |
|
|
|
53 |
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`:
|
|
|
54 |
|
|
|
55 |
```php
|
|
|
56 |
$app->get('/hello/{name}', function (Silex\Application $app, $name) {
|
|
|
57 |
// ...
|
|
|
58 |
});
|
|
|
59 |
```
|
|
|
60 |
|
|
|
61 |
In Silly, it only works with `OutputInterface` to inject the application output:
|
|
|
62 |
|
|
|
63 |
```php
|
|
|
64 |
$app->command('greet [name]', function ($name, OutputInterface $output) {
|
|
|
65 |
// ...
|
|
|
66 |
});
|
|
|
67 |
```
|
|
|
68 |
|
|
|
69 |
[PHP-DI](https://php-di.org/doc/container.html) provides a way to invoke a callable and resolve all dependencies from the container using type-hints:
|
|
|
70 |
|
|
|
71 |
```php
|
|
|
72 |
$container->call(function (Logger $logger, EntityManager $em) {
|
|
|
73 |
// ...
|
|
|
74 |
});
|
|
|
75 |
```
|
|
|
76 |
|
|
|
77 |
**This library provides clear extension points to let frameworks implement any kind of dependency injection support they want.**
|
|
|
78 |
|
|
|
79 |
### TL/DR
|
|
|
80 |
|
|
|
81 |
In short, this library is meant to be a base building block for calling a function with named parameters and/or dependency injection.
|
|
|
82 |
|
|
|
83 |
## Installation
|
|
|
84 |
|
|
|
85 |
```sh
|
|
|
86 |
$ composer require PHP-DI/invoker
|
|
|
87 |
```
|
|
|
88 |
|
|
|
89 |
## Usage
|
|
|
90 |
|
|
|
91 |
### Default behavior
|
|
|
92 |
|
|
|
93 |
By default the `Invoker` can call using named parameters:
|
|
|
94 |
|
|
|
95 |
```php
|
|
|
96 |
$invoker = new Invoker\Invoker;
|
|
|
97 |
|
|
|
98 |
$invoker->call(function () {
|
|
|
99 |
echo 'Hello world!';
|
|
|
100 |
});
|
|
|
101 |
|
|
|
102 |
// Simple parameter array
|
|
|
103 |
$invoker->call(function ($name) {
|
|
|
104 |
echo 'Hello ' . $name;
|
|
|
105 |
}, ['John']);
|
|
|
106 |
|
|
|
107 |
// Named parameters
|
|
|
108 |
$invoker->call(function ($name) {
|
|
|
109 |
echo 'Hello ' . $name;
|
|
|
110 |
}, [
|
|
|
111 |
'name' => 'John'
|
|
|
112 |
]);
|
|
|
113 |
|
|
|
114 |
// Use the default value
|
|
|
115 |
$invoker->call(function ($name = 'world') {
|
|
|
116 |
echo 'Hello ' . $name;
|
|
|
117 |
});
|
|
|
118 |
|
|
|
119 |
// Invoke any PHP callable
|
|
|
120 |
$invoker->call(['MyClass', 'myStaticMethod']);
|
|
|
121 |
|
|
|
122 |
// Using Class::method syntax
|
|
|
123 |
$invoker->call('MyClass::myStaticMethod');
|
|
|
124 |
```
|
|
|
125 |
|
|
|
126 |
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*](#built-in-support-for-dependency-injection) if you are impatient.
|
|
|
127 |
|
|
|
128 |
Additionally, callables can also be resolved from your container. Read on or jump to [*Resolving callables from a container*](#resolving-callables-from-a-container) if you are impatient.
|
|
|
129 |
|
|
|
130 |
### Parameter resolvers
|
|
|
131 |
|
|
|
132 |
Extending the behavior of the `Invoker` is easy and is done by implementing a [`ParameterResolver`](https://github.com/PHP-DI/Invoker/blob/master/src/ParameterResolver/ParameterResolver.php).
|
|
|
133 |
|
|
|
134 |
This is explained in details the [Parameter resolvers documentation](doc/parameter-resolvers.md).
|
|
|
135 |
|
|
|
136 |
#### Built-in support for dependency injection
|
|
|
137 |
|
|
|
138 |
Rather than have you re-implement support for dependency injection with different containers every time, this package ships with 2 optional resolvers:
|
|
|
139 |
|
|
|
140 |
- [`TypeHintContainerResolver`](https://github.com/PHP-DI/Invoker/blob/master/src/ParameterResolver/Container/TypeHintContainerResolver.php)
|
|
|
141 |
|
|
|
142 |
This resolver will inject container entries by searching for the class name using the type-hint:
|
|
|
143 |
|
|
|
144 |
```php
|
|
|
145 |
$invoker->call(function (Psr\Logger\LoggerInterface $logger) {
|
|
|
146 |
// ...
|
|
|
147 |
});
|
|
|
148 |
```
|
|
|
149 |
|
|
|
150 |
In this example it will `->get('Psr\Logger\LoggerInterface')` from the container and inject it.
|
|
|
151 |
|
|
|
152 |
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.
|
|
|
153 |
|
|
|
154 |
- [`ParameterNameContainerResolver`](https://github.com/PHP-DI/Invoker/blob/master/src/ParameterResolver/Container/ParameterNameContainerResolver.php)
|
|
|
155 |
|
|
|
156 |
This resolver will inject container entries by searching for the name of the parameter:
|
|
|
157 |
|
|
|
158 |
```php
|
|
|
159 |
$invoker->call(function ($twig) {
|
|
|
160 |
// ...
|
|
|
161 |
});
|
|
|
162 |
```
|
|
|
163 |
|
|
|
164 |
In this example it will `->get('twig')` from the container and inject it.
|
|
|
165 |
|
|
|
166 |
These resolvers can work with any dependency injection container compliant with [PSR-11](http://www.php-fig.org/psr/psr-11/).
|
|
|
167 |
|
|
|
168 |
Setting up those resolvers is simple:
|
|
|
169 |
|
|
|
170 |
```php
|
|
|
171 |
// $container must be an instance of Psr\Container\ContainerInterface
|
|
|
172 |
$container = ...
|
|
|
173 |
|
|
|
174 |
$containerResolver = new TypeHintContainerResolver($container);
|
|
|
175 |
// or
|
|
|
176 |
$containerResolver = new ParameterNameContainerResolver($container);
|
|
|
177 |
|
|
|
178 |
$invoker = new Invoker\Invoker;
|
|
|
179 |
// Register it before all the other parameter resolvers
|
|
|
180 |
$invoker->getParameterResolver()->prependResolver($containerResolver);
|
|
|
181 |
```
|
|
|
182 |
|
|
|
183 |
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!
|
|
|
184 |
|
|
|
185 |
### Resolving callables from a container
|
|
|
186 |
|
|
|
187 |
The `Invoker` can be wired to your DI container to resolve the callables.
|
|
|
188 |
|
|
|
189 |
For example with an invokable class:
|
|
|
190 |
|
|
|
191 |
```php
|
|
|
192 |
class MyHandler
|
|
|
193 |
{
|
|
|
194 |
public function __invoke()
|
|
|
195 |
{
|
|
|
196 |
// ...
|
|
|
197 |
}
|
|
|
198 |
}
|
|
|
199 |
|
|
|
200 |
// By default this doesn't work: an instance of the class should be provided
|
|
|
201 |
$invoker->call('MyHandler');
|
|
|
202 |
|
|
|
203 |
// If we set up the container to use
|
|
|
204 |
$invoker = new Invoker\Invoker(null, $container);
|
|
|
205 |
// Now 'MyHandler' is resolved using the container!
|
|
|
206 |
$invoker->call('MyHandler');
|
|
|
207 |
```
|
|
|
208 |
|
|
|
209 |
The same works for a class method:
|
|
|
210 |
|
|
|
211 |
```php
|
|
|
212 |
class WelcomeController
|
|
|
213 |
{
|
|
|
214 |
public function home()
|
|
|
215 |
{
|
|
|
216 |
// ...
|
|
|
217 |
}
|
|
|
218 |
}
|
|
|
219 |
|
|
|
220 |
// By default this doesn't work: home() is not a static method
|
|
|
221 |
$invoker->call(['WelcomeController', 'home']);
|
|
|
222 |
|
|
|
223 |
// If we set up the container to use
|
|
|
224 |
$invoker = new Invoker\Invoker(null, $container);
|
|
|
225 |
// Now 'WelcomeController' is resolved using the container!
|
|
|
226 |
$invoker->call(['WelcomeController', 'home']);
|
|
|
227 |
// Alternatively we can use the Class::method syntax
|
|
|
228 |
$invoker->call('WelcomeController::home');
|
|
|
229 |
```
|
|
|
230 |
|
|
|
231 |
That feature can be used as the base building block for a framework's dispatcher.
|
|
|
232 |
|
|
|
233 |
Again, any [PSR-11](https://www.php-fig.org/psr/psr-11/) compliant container can be provided.
|