1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Northwoods\Container\Config; |
4
|
|
|
|
5
|
|
|
use Auryn\Injector; |
6
|
|
|
use Northwoods\Container\InjectorConfig; |
7
|
|
|
use Psr\Container\ContainerInterface; |
8
|
|
|
|
9
|
|
|
class ServiceConfig implements InjectorConfig |
10
|
|
|
{ |
11
|
|
|
/** |
12
|
|
|
* @var array |
13
|
|
|
*/ |
14
|
|
|
private $config; |
15
|
|
|
|
16
|
|
|
/** |
17
|
|
|
* @var bool |
18
|
|
|
*/ |
19
|
|
|
private $sharedByDefault = true; |
20
|
|
|
|
21
|
|
|
/** |
22
|
|
|
* @var array |
23
|
|
|
*/ |
24
|
|
|
private $shared = []; |
25
|
|
|
|
26
|
4 |
|
public function __construct(array $config) |
27
|
|
|
{ |
28
|
4 |
|
$this->config = $config; |
29
|
4 |
|
} |
30
|
|
|
|
31
|
|
|
/** |
32
|
|
|
* @param Injector $injector |
33
|
|
|
* @return void |
34
|
|
|
*/ |
35
|
4 |
|
public function apply(Injector $injector) |
36
|
|
|
{ |
37
|
|
|
// Enable or disable sharing all services by default. |
38
|
4 |
|
if (isset($this->config['shared_by_default'])) { |
39
|
2 |
|
$this->sharedByDefault = (bool) $this->config['shared_by_default']; |
40
|
2 |
|
} |
41
|
|
|
|
42
|
|
|
// Overload specific services to be shared. |
43
|
4 |
|
if (isset($this->config['shared'])) { |
44
|
2 |
|
$this->shared = $this->config['shared']; |
45
|
2 |
|
} |
46
|
|
|
|
47
|
|
|
// Aliases are exactly the same as aliases. Natch. |
48
|
4 |
|
if (isset($this->config['aliases'])) { |
49
|
4 |
|
$this->applyAliases($injector, $this->config['aliases']); |
50
|
4 |
|
} |
51
|
|
|
|
52
|
|
|
// Delegators are effectively a chain of prepare() statements. |
53
|
4 |
|
if (isset($this->config['delegators'])) { |
54
|
4 |
|
$this->applyDelegators($injector, $this->config['delegators']); |
55
|
4 |
|
} |
56
|
|
|
|
57
|
|
|
// Factories are exactly the same thing as a delegate. |
58
|
4 |
|
if (isset($this->config['factories'])) { |
59
|
4 |
|
$this->applyDelegates($injector, $this->config['factories']); |
60
|
4 |
|
} |
61
|
|
|
|
62
|
|
|
// Invokables are references to classes that have no constructor parameters. |
63
|
|
|
// This means nothing in Auryn so we just alias the reference. |
64
|
4 |
|
if (isset($this->config['invokables'])) { |
65
|
4 |
|
$this->applyAliases($injector, $this->config['invokables']); |
66
|
4 |
|
} |
67
|
|
|
|
68
|
|
|
// Services are already constructed instances of something. To handle this, |
69
|
|
|
// we simply wrap the instance in a callable that returns the instance. |
70
|
4 |
|
if (isset($this->config['services'])) { |
71
|
4 |
|
$this->applyDelegates($injector, $this->kAll($this->config['services'])); |
72
|
4 |
|
} |
73
|
4 |
|
} |
74
|
|
|
|
75
|
|
|
/** |
76
|
|
|
* @param string $name |
77
|
|
|
* @return bool |
78
|
|
|
*/ |
79
|
4 |
|
private function isShared($name) |
80
|
|
|
{ |
81
|
4 |
|
if (isset($this->shared[$name])) { |
82
|
2 |
|
return (bool) $this->shared[$name]; |
83
|
|
|
} |
84
|
|
|
|
85
|
4 |
|
return $this->sharedByDefault; |
86
|
|
|
} |
87
|
|
|
|
88
|
|
|
/** |
89
|
|
|
* @param array $services |
90
|
|
|
* @return void |
91
|
|
|
*/ |
92
|
4 |
View Code Duplication |
private function applyAliases(Injector $injector, array $services) |
|
|
|
|
93
|
|
|
{ |
94
|
4 |
|
foreach ($services as $name => $object) { |
95
|
4 |
|
$injector->alias($name, $object); |
96
|
4 |
|
if ($this->isShared($name)) { |
97
|
2 |
|
$injector->share($name); |
98
|
2 |
|
} |
99
|
4 |
|
} |
100
|
4 |
|
} |
101
|
|
|
|
102
|
|
|
/** |
103
|
|
|
* @param array $services |
104
|
|
|
* @return void |
105
|
|
|
*/ |
106
|
4 |
View Code Duplication |
private function applyDelegates(Injector $injector, array $services) |
|
|
|
|
107
|
|
|
{ |
108
|
4 |
|
foreach ($services as $name => $object) { |
109
|
4 |
|
$injector->delegate($name, $object); |
110
|
4 |
|
if ($this->isShared($name)) { |
111
|
3 |
|
$injector->share($name); |
112
|
3 |
|
} |
113
|
4 |
|
} |
114
|
4 |
|
} |
115
|
|
|
|
116
|
|
|
/** |
117
|
|
|
* @param array $delegators |
118
|
|
|
* @return void |
119
|
|
|
*/ |
120
|
4 |
|
private function applyDelegators(Injector $injector, array $delegators) |
121
|
|
|
{ |
122
|
|
|
// https://github.com/rdlowrey/auryn#prepares-and-setter-injection |
123
|
4 |
|
foreach ($delegators as $service => $prepares) { |
124
|
4 |
|
$injector->prepare($service, $this->createDelegator($service, $prepares)); |
125
|
4 |
|
if ($this->isShared($service)) { |
126
|
2 |
|
$injector->share($service); |
127
|
2 |
|
} |
128
|
4 |
|
} |
129
|
4 |
|
} |
130
|
|
|
|
131
|
|
|
/** |
132
|
|
|
* Create a chained prepare() |
133
|
|
|
* |
134
|
|
|
* @param string $service |
135
|
|
|
* @param string[] $delegators |
136
|
|
|
* @return callable |
137
|
|
|
*/ |
138
|
4 |
|
private function createDelegator($service, array $delegators) |
139
|
|
|
{ |
140
|
|
|
// Prepare the service by calling each delegator with the result of the previous. |
141
|
|
|
return function ($instance, $injector) use ($service, $delegators) { |
142
|
4 |
|
return array_reduce($delegators, $this->delegatorReducer($injector, $service), $instance); |
143
|
4 |
|
}; |
144
|
|
|
} |
145
|
|
|
|
146
|
|
|
/** |
147
|
|
|
* Create a reducer for a chained prepare() |
148
|
|
|
* |
149
|
|
|
* @param string $service |
150
|
|
|
* @return callable |
151
|
|
|
*/ |
152
|
4 |
|
private function delegatorReducer(Injector $injector, $service) |
153
|
|
|
{ |
154
|
|
|
// https://docs.zendframework.com/zend-expressive/features/container/delegator-factories/ |
155
|
|
|
return function ($instance, $delegator) use ($injector, $service) { |
156
|
4 |
|
if (!is_callable($delegator)) { |
157
|
4 |
|
$delegator = $injector->make($delegator); |
158
|
4 |
|
} |
159
|
4 |
|
$callable = $this->k($instance); |
160
|
4 |
|
return $injector->execute($this->curryDelegator($delegator, $service, $callable)); |
161
|
4 |
|
}; |
162
|
|
|
} |
163
|
|
|
|
164
|
|
|
/** |
165
|
|
|
* Curry the delegator to only require a container |
166
|
|
|
* |
167
|
|
|
* @param callable $delegator that will be ultimately called |
168
|
|
|
* @param string $service name of service being prepared |
169
|
|
|
* @param callable $callable that returns the instance |
170
|
|
|
* @return callable |
171
|
|
|
*/ |
172
|
4 |
|
private function curryDelegator(callable $delegator, $service, callable $callable) |
173
|
|
|
{ |
174
|
|
|
return static function (ContainerInterface $container) use ($delegator, $service, $callable) { |
175
|
4 |
|
return $delegator($container, $service, $callable); |
176
|
4 |
|
}; |
177
|
|
|
} |
178
|
|
|
|
179
|
|
|
/** |
180
|
|
|
* Returns a function that always returns the same value |
181
|
|
|
* |
182
|
|
|
* Also known as a "kestrel" or "k combinator". |
183
|
|
|
* |
184
|
|
|
* @param mixed $x |
185
|
|
|
* @return callable |
186
|
|
|
*/ |
187
|
4 |
|
private function k($x) |
188
|
|
|
{ |
189
|
|
|
return static function () use ($x) { |
190
|
4 |
|
return $x; |
191
|
4 |
|
}; |
192
|
|
|
} |
193
|
|
|
|
194
|
|
|
/** |
195
|
|
|
* @param array $values |
196
|
|
|
* @return callable[] |
197
|
|
|
*/ |
198
|
4 |
|
private function kAll(array $values) |
199
|
|
|
{ |
200
|
4 |
|
return array_map( |
201
|
4 |
|
function ($x) { |
202
|
4 |
|
return $this->k($x); |
203
|
4 |
|
}, |
204
|
|
|
$values |
205
|
4 |
|
); |
206
|
|
|
} |
207
|
|
|
} |
208
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.