-
Notifications
You must be signed in to change notification settings - Fork 10
/
README.md
240 lines (181 loc) · 7.85 KB
/
README.md
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
235
236
237
238
239
240
# Standard Service Providers
This project attempts to define a common standard for service providers, aka "bundles" or "modules" in various frameworks.
Service providers are classes that provide service definitions to a [PSR-11](https://www.php-fig.org/psr/psr-11/) container.
The PSR depends on the PSR-11 [Container Interface](https://www.php-fig.org/psr/psr-11/#31-psrcontainercontainerinterface). Service providers operate on PSR-11 compatible containers.
#### ⚠️ Work in progress
> the project is currently experimental and is being tried in frameworks, containers and modules until considered viable. Until a 1.0.0 release, the code in this repository is not stable. Expect breaking changes between versions such as `0.1.x` and `0.2.0`.
#### 👉 Refer to the [current PSR draft](./PSR-XX-provider.md) for the PSR proposal itself.
#### 🧐 Refer to the [PSR meta document](./PSR-XX-provider-meta.md) for the history and reasoning behind this proposal.
#### 💬 Join us to review [open issues](/container-interop/service-provider/issues) or [participate in ongoing discussions](/container-interop/service-provider/discussions).
<!-- TODO resolve #67
## Usage
To declare a service provider, simply implement the `ServiceProviderInterface` interface.
```php
use Psr\Container\ServiceProviderInterface;
class MyServiceProvider implements ServiceProviderInterface
{
public function getFactories()
{
return [
'my_service' => function(ContainerInterface $container) {
$dependency = $container->get('my_other_service');
return new MyService($dependency);
}
];
}
public function getExtensions()
{
return [
'my_extended_service' => function(ContainerInterface $container, $extendedService) {
$extendedService->registerExtension($container->get('my_service'));
return $extendedService;
}
];
}
}
```
### Aliases
To alias a container entry to another, you can get the aliased entry from the container and return it:
```php
class MyServiceProvider implements ServiceProviderInterface
{
public function getFactories()
{
return [
'my_service' => fn() => new MyService(),
'my_alias' => fn(ContainerInterface $container) => $container->get('my_service'),
];
}
// ...
}
```
### Entry overriding
Overriding an entry defined in another service provider is as easy as defining it again.
Module A:
```php
class A implements ServiceProviderInterface
{
public function getFactories()
{
return [
'foo' => fn() => 'abc',
];
}
// ...
}
```
Module B:
```php
class B implements ServiceProviderInterface
{
public function getFactories()
{
return [
'foo' => fn() => 'def',
];
}
// ...
}
```
If you register the service providers in the correct order in your container (A first, then B), then the entry `foo` will be `'def'` because B's definition will override A's.
### Entry extension
Extending an entry before it is returned by the container is done via the `getExtensions` method.
Module A:
```php
class A implements ServiceProviderInterface
{
public function getFactories()
{
return [
'logger' => fn() => new MyLogger(),
];
}
// ...
}
```
Module B:
```php
class B implements ServiceProviderInterface
{
public function getExtensions()
{
return [
'logger' => function (ContainerInterface $container, MyLogger $logger) {
$logger->addHandler(new MyHandler());
return $logger;
},
];
}
// ...
}
```
The second parameter of extensions SHOULD use type-hinting when applicable.
```php
function (ContainerInterface $container, MyLogger $logger)
```
If a container passes a service that does not match the type hint, a `TypeError` will be thrown while bootstrapping the Container (in PHP 7+), or a catchable fatal error in PHP 5.
The second parameter of extensions CAN be nullable.
```php
function (ContainerInterface $container, ?MyLogger $logger)
```
This allows an extension to handle a service that has been explicitly registered as `null` - for example:
```php
class B implements ServiceProviderInterface
{
public function getExtensions()
{
return [
'logger' => function (ContainerInterface $container, ?MyLogger $logger) {
if ($logger) {
$logger->addHandler(new MyHandler());
}
return $logger; // if `logger` is `null`, the extension will simply return `null`
},
];
}
// ...
}
```
-->
## Compatible projects
### Projects consuming `v0.4` *service providers*
- [Laravel service provider bridge](https://github.com/thecodingmachine/laravel-universal-service-provider/): Use container-interop's service-providers into any [Laravel](http://laravel.com/) application.
- [Simplex](https://github.com/mnapoli/simplex): A [Pimple 3](https://github.com/silexphp/Pimple) fork with full [container-interop](https://github.com/container-interop/container-interop) compliance and cross-framework service-provider support.
- [Service provider bridge bundle](https://github.com/thecodingmachine/service-provider-bridge-bundle): Use container-interop's service-providers into a Symfony container.
- [Yaco](https://github.com/thecodingmachine/yaco): A compiler that generates container-interop compliant containers. Yaco can consume service-providers.
### Packages providing `v0.4` *service providers*
- [DBAL Module](https://github.com/thecodingmachine/dbal-universal-module): A module integrating [Doctrine DBAL](http://www.doctrine-project.org/projects/dbal.html) in an application using a service provider.
- [Doctrine Annotations Module](https://github.com/thecodingmachine/doctrine-annotations-universal-module): A service provider for Doctrine's annotation reader.
- [Glide Module](https://github.com/mnapoli/glide-module): A module integrating Glide in an application using a service provider.
- [PSR-6 to Doctrine cache bridge module](https://github.com/thecodingmachine/psr-6-doctrine-bridge-universal-module): A service provider providing a Doctrine cache provider wrapping a PSR-6 cache pool.
- [Slim-framework Module](https://github.com/thecodingmachine/slim-service-provider): A module integrating Slim framework v3 using a service provider.
- [Stash Module](https://github.com/thecodingmachine/stash-universal-module): A service provider for the Stash PSR-6 caching library.
- [Stratigility Module](https://github.com/thecodingmachine/stratigility-harmony): A service provider for the Stratigility PSR-7 middleware.
- [Twig Module](https://github.com/thecodingmachine/twig-universal-module): A service provider for the Twig templating library.
- [Whoops PSR-7 Middleware Module](https://github.com/thecodingmachine/whoops-middleware-universal-module): a service provider for the [Whoops](https://filp.github.io/whoops/) [PSR-7 middleware](https://github.com/franzliedke/whoops-middleware).
<!-- TODO resolve #65
## Best practices
### Managing configuration
The service created by a factory should only depend on the input parameters of the factory (`$container` and `$getPrevious`).
If the factory needs to fetch parameters, those should be fetched from the container directly.
```php
class MyServiceProvider implements ServiceProviderInterface
{
public function getFactories()
{
return [
'logger' => [ MyServiceProvider::class, 'createLogger' ],
];
}
public function getExtensions()
{
return [];
}
public static function createLogger(ContainerInterface $container)
{
// The path to the log file is fetched from the container, not from the service provider state.
return new FileLogger($this->container->get('logFilePath'));
}
}
```
-->