File: README.md

package info (click to toggle)
php-di-invoker 2.3.6-1
  • links: PTS, VCS
  • area: main
  • in suites: sid, trixie
  • size: 256 kB
  • sloc: php: 1,161; makefile: 13; sh: 6
file content (234 lines) | stat: -rw-r--r-- 7,306 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
# Invoker

Generic and extensible callable invoker.

[![CI](https://github.com/PHP-DI/Invoker/actions/workflows/ci.yml/badge.svg)](https://github.com/PHP-DI/Invoker/actions/workflows/ci.yml)
[![Latest Version](https://img.shields.io/github/release/PHP-DI/invoker.svg?style=flat-square)](https://packagist.org/packages/PHP-DI/invoker)
[![Total Downloads](https://img.shields.io/packagist/dt/php-di/invoker.svg?style=flat-square)](https://packagist.org/packages/php-di/invoker)

## Why?

Who doesn't need an over-engineered `call_user_func()`?

### Named parameters

Does this [Silex](https://github.com/silexphp/Silex#readme) example look familiar:

```php
$app->get('/project/{project}/issue/{issue}', function ($project, $issue) {
    // ...
});
```

Or this command defined with [Silly](https://github.com/mnapoli/silly#usage):

```php
$app->command('greet [name] [--yell]', function ($name, $yell) {
    // ...
});
```

Same pattern in [Slim](https://www.slimframework.com):

```php
$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.

**This library allows to invoke callables with named parameters in a generic and extensible way.**

### Dependency injection

Anyone familiar with AngularJS is familiar with how dependency injection is performed:

```js
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`:

```php
$app->get('/hello/{name}', function (Silex\Application $app, $name) {
    // ...
});
```

In Silly, it only works with `OutputInterface` to inject the application output:

```php
$app->command('greet [name]', function ($name, OutputInterface $output) {
    // ...
});
```

[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:

```php
$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.**

### 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.

## Installation

```sh
$ composer require PHP-DI/invoker
```

## Usage

### Default behavior

By default the `Invoker` can call using named parameters:

```php
$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*](#built-in-support-for-dependency-injection) if you are impatient.

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.

### Parameter resolvers

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).

This is explained in details the [Parameter resolvers documentation](doc/parameter-resolvers.md).

#### 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:

- [`TypeHintContainerResolver`](https://github.com/PHP-DI/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 and inject it.

    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.

- [`ParameterNameContainerResolver`](https://github.com/PHP-DI/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 and inject it.

These resolvers can work with any dependency injection container compliant with [PSR-11](http://www.php-fig.org/psr/psr-11/).

Setting up those resolvers is simple:

```php
// $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!

### Resolving callables from a container

The `Invoker` can be wired to your DI container to resolve the callables.

For example with an invokable class:

```php
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:

```php
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.

Again, any [PSR-11](https://www.php-fig.org/psr/psr-11/) compliant container can be provided.