1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
/* |
4
|
|
|
* This file is part of Pimple. |
5
|
|
|
* |
6
|
|
|
* Copyright (c) 2009 Fabien Potencier |
7
|
|
|
* |
8
|
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy |
9
|
|
|
* of this software and associated documentation files (the "Software"), to deal |
10
|
|
|
* in the Software without restriction, including without limitation the rights |
11
|
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
12
|
|
|
* copies of the Software, and to permit persons to whom the Software is furnished |
13
|
|
|
* to do so, subject to the following conditions: |
14
|
|
|
* |
15
|
|
|
* The above copyright notice and this permission notice shall be included in all |
16
|
|
|
* copies or substantial portions of the Software. |
17
|
|
|
* |
18
|
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
19
|
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
20
|
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
21
|
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
22
|
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
23
|
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
24
|
|
|
* THE SOFTWARE. |
25
|
|
|
*/ |
26
|
|
|
|
27
|
|
|
namespace Pimple; |
28
|
|
|
|
29
|
|
|
/** |
30
|
|
|
* Container main class. |
31
|
|
|
* |
32
|
|
|
* @author Fabien Potencier |
33
|
|
|
*/ |
34
|
|
|
class Container implements \ArrayAccess |
35
|
|
|
{ |
36
|
|
|
private $values = array(); |
37
|
|
|
private $factories; |
38
|
|
|
private $protected; |
39
|
|
|
private $frozen = array(); |
40
|
|
|
private $raw = array(); |
41
|
|
|
private $keys = array(); |
42
|
|
|
|
43
|
|
|
/** |
44
|
|
|
* Instantiate the container. |
45
|
|
|
* |
46
|
|
|
* Objects and parameters can be passed as argument to the constructor. |
47
|
|
|
* |
48
|
|
|
* @param array $values The parameters or objects. |
49
|
|
|
*/ |
50
|
40 |
|
public function __construct(array $values = array()) |
51
|
|
|
{ |
52
|
40 |
|
$this->factories = new \SplObjectStorage(); |
53
|
40 |
|
$this->protected = new \SplObjectStorage(); |
54
|
|
|
|
55
|
40 |
|
foreach ($values as $key => $value) { |
56
|
1 |
|
$this->offsetSet($key, $value); |
57
|
40 |
|
} |
58
|
40 |
|
} |
59
|
|
|
|
60
|
|
|
/** |
61
|
|
|
* Sets a parameter or an object. |
62
|
|
|
* |
63
|
|
|
* Objects must be defined as Closures. |
64
|
|
|
* |
65
|
|
|
* Allowing any PHP callable leads to difficult to debug problems |
66
|
|
|
* as function names (strings) are callable (creating a function with |
67
|
|
|
* the same name as an existing parameter would break your container). |
68
|
|
|
* |
69
|
|
|
* @param string $id The unique identifier for the parameter or object |
70
|
|
|
* @param mixed $value The value of the parameter or a closure to define an object |
71
|
|
|
* |
72
|
|
|
* @throws \RuntimeException Prevent override of a frozen service |
73
|
|
|
*/ |
74
|
32 |
|
public function offsetSet($id, $value) |
75
|
|
|
{ |
76
|
32 |
|
if (isset($this->frozen[$id])) { |
77
|
1 |
|
throw new \RuntimeException(sprintf('Cannot override frozen service "%s".', $id)); |
78
|
|
|
} |
79
|
|
|
|
80
|
32 |
|
$this->values[$id] = $value; |
81
|
32 |
|
$this->keys[$id] = true; |
82
|
32 |
|
} |
83
|
|
|
|
84
|
|
|
/** |
85
|
|
|
* Gets a parameter or an object. |
86
|
|
|
* |
87
|
|
|
* @param string $id The unique identifier for the parameter or object |
88
|
|
|
* |
89
|
|
|
* @return mixed The value of the parameter or an object |
90
|
|
|
* |
91
|
|
|
* @throws \InvalidArgumentException if the identifier is not defined |
92
|
|
|
*/ |
93
|
23 |
|
public function offsetGet($id) |
94
|
|
|
{ |
95
|
23 |
|
if (!isset($this->keys[$id])) { |
96
|
1 |
|
throw new \InvalidArgumentException(sprintf('Identifier "%s" is not defined.', $id)); |
97
|
|
|
} |
98
|
|
|
|
99
|
|
|
if ( |
100
|
22 |
|
isset($this->raw[$id]) |
101
|
22 |
|
|| !is_object($this->values[$id]) |
102
|
22 |
|
|| isset($this->protected[$this->values[$id]]) |
103
|
18 |
|
|| !method_exists($this->values[$id], '__invoke') |
104
|
22 |
|
) { |
105
|
13 |
|
return $this->values[$id]; |
106
|
|
|
} |
107
|
|
|
|
108
|
15 |
|
if (isset($this->factories[$this->values[$id]])) { |
109
|
5 |
|
return $this->values[$id]($this); |
110
|
|
|
} |
111
|
|
|
|
112
|
14 |
|
$raw = $this->values[$id]; |
113
|
14 |
|
$val = $this->values[$id] = $raw($this); |
114
|
14 |
|
$this->raw[$id] = $raw; |
115
|
|
|
|
116
|
14 |
|
$this->frozen[$id] = true; |
117
|
|
|
|
118
|
14 |
|
return $val; |
119
|
|
|
} |
120
|
|
|
|
121
|
|
|
/** |
122
|
|
|
* Checks if a parameter or an object is set. |
123
|
|
|
* |
124
|
|
|
* @param string $id The unique identifier for the parameter or object |
125
|
|
|
* |
126
|
|
|
* @return bool |
127
|
|
|
*/ |
128
|
2 |
|
public function offsetExists($id) |
129
|
|
|
{ |
130
|
2 |
|
return isset($this->keys[$id]); |
131
|
|
|
} |
132
|
|
|
|
133
|
|
|
/** |
134
|
|
|
* Unsets a parameter or an object. |
135
|
|
|
* |
136
|
|
|
* @param string $id The unique identifier for the parameter or object |
137
|
|
|
*/ |
138
|
3 |
|
public function offsetUnset($id) |
139
|
|
|
{ |
140
|
3 |
|
if (isset($this->keys[$id])) { |
141
|
3 |
|
if (is_object($this->values[$id])) { |
142
|
2 |
|
unset($this->factories[$this->values[$id]], $this->protected[$this->values[$id]]); |
143
|
2 |
|
} |
144
|
|
|
|
145
|
3 |
|
unset($this->values[$id], $this->frozen[$id], $this->raw[$id], $this->keys[$id]); |
146
|
3 |
|
} |
147
|
3 |
|
} |
148
|
|
|
|
149
|
|
|
/** |
150
|
|
|
* Marks a callable as being a factory service. |
151
|
|
|
* |
152
|
|
|
* @param callable $callable A service definition to be used as a factory |
153
|
|
|
* |
154
|
|
|
* @return callable The passed callable |
155
|
|
|
* |
156
|
|
|
* @throws \InvalidArgumentException Service definition has to be a closure of an invokable object |
157
|
|
|
*/ |
158
|
9 |
View Code Duplication |
public function factory($callable) |
|
|
|
|
159
|
|
|
{ |
160
|
9 |
|
if (!method_exists($callable, '__invoke')) { |
161
|
2 |
|
throw new \InvalidArgumentException('Service definition is not a Closure or invokable object.'); |
162
|
|
|
} |
163
|
|
|
|
164
|
7 |
|
$this->factories->attach($callable); |
165
|
|
|
|
166
|
7 |
|
return $callable; |
|
|
|
|
167
|
|
|
} |
168
|
|
|
|
169
|
|
|
/** |
170
|
|
|
* Protects a callable from being interpreted as a service. |
171
|
|
|
* |
172
|
|
|
* This is useful when you want to store a callable as a parameter. |
173
|
|
|
* |
174
|
|
|
* @param callable $callable A callable to protect from being evaluated |
175
|
|
|
* |
176
|
|
|
* @return callable The passed callable |
177
|
|
|
* |
178
|
|
|
* @throws \InvalidArgumentException Service definition has to be a closure of an invokable object |
179
|
|
|
*/ |
180
|
4 |
View Code Duplication |
public function protect($callable) |
|
|
|
|
181
|
|
|
{ |
182
|
4 |
|
if (!method_exists($callable, '__invoke')) { |
183
|
2 |
|
throw new \InvalidArgumentException('Callable is not a Closure or invokable object.'); |
184
|
|
|
} |
185
|
|
|
|
186
|
2 |
|
$this->protected->attach($callable); |
187
|
|
|
|
188
|
2 |
|
return $callable; |
|
|
|
|
189
|
|
|
} |
190
|
|
|
|
191
|
|
|
/** |
192
|
|
|
* Gets a parameter or the closure defining an object. |
193
|
|
|
* |
194
|
|
|
* @param string $id The unique identifier for the parameter or object |
195
|
|
|
* |
196
|
|
|
* @return mixed The value of the parameter or the closure defining an object |
197
|
|
|
* |
198
|
|
|
* @throws \InvalidArgumentException if the identifier is not defined |
199
|
|
|
*/ |
200
|
3 |
|
public function raw($id) |
201
|
|
|
{ |
202
|
3 |
|
if (!isset($this->keys[$id])) { |
203
|
1 |
|
throw new \InvalidArgumentException(sprintf('Identifier "%s" is not defined.', $id)); |
204
|
|
|
} |
205
|
|
|
|
206
|
2 |
|
if (isset($this->raw[$id])) { |
207
|
|
|
return $this->raw[$id]; |
208
|
|
|
} |
209
|
|
|
|
210
|
2 |
|
return $this->values[$id]; |
211
|
|
|
} |
212
|
|
|
|
213
|
|
|
/** |
214
|
|
|
* Extends an object definition. |
215
|
|
|
* |
216
|
|
|
* Useful when you want to extend an existing object definition, |
217
|
|
|
* without necessarily loading that object. |
218
|
|
|
* |
219
|
|
|
* @param string $id The unique identifier for the object |
220
|
|
|
* @param callable $callable A service definition to extend the original |
221
|
|
|
* |
222
|
|
|
* @return callable The wrapped callable |
223
|
|
|
* |
224
|
|
|
* @throws \InvalidArgumentException if the identifier is not defined or not a service definition |
225
|
|
|
*/ |
226
|
10 |
|
public function extend($id, $callable) |
227
|
|
|
{ |
228
|
10 |
|
if (!isset($this->keys[$id])) { |
229
|
1 |
|
throw new \InvalidArgumentException(sprintf('Identifier "%s" is not defined.', $id)); |
230
|
|
|
} |
231
|
|
|
|
232
|
9 |
|
if (!is_object($this->values[$id]) || !method_exists($this->values[$id], '__invoke')) { |
233
|
2 |
|
throw new \InvalidArgumentException(sprintf('Identifier "%s" does not contain an object definition.', $id)); |
234
|
|
|
} |
235
|
|
|
|
236
|
7 |
|
if (!is_object($callable) || !method_exists($callable, '__invoke')) { |
237
|
2 |
|
throw new \InvalidArgumentException('Extension service definition is not a Closure or invokable object.'); |
238
|
|
|
} |
239
|
|
|
|
240
|
5 |
|
$factory = $this->values[$id]; |
241
|
|
|
|
242
|
5 |
|
$extended = function ($c) use ($callable, $factory) { |
243
|
4 |
|
return $callable($factory($c), $c); |
244
|
5 |
|
}; |
245
|
|
|
|
246
|
5 |
|
if (isset($this->factories[$factory])) { |
247
|
3 |
|
$this->factories->detach($factory); |
248
|
3 |
|
$this->factories->attach($extended); |
|
|
|
|
249
|
3 |
|
} |
250
|
|
|
|
251
|
5 |
|
return $this[$id] = $extended; |
252
|
|
|
} |
253
|
|
|
|
254
|
|
|
/** |
255
|
|
|
* Returns all defined value names. |
256
|
|
|
* |
257
|
|
|
* @return array An array of value names |
258
|
|
|
*/ |
259
|
1 |
|
public function keys() |
260
|
|
|
{ |
261
|
1 |
|
return array_keys($this->values); |
262
|
|
|
} |
263
|
|
|
|
264
|
|
|
/** |
265
|
|
|
* Registers a service provider. |
266
|
|
|
* |
267
|
|
|
* @param ServiceProviderInterface $provider A ServiceProviderInterface instance |
268
|
|
|
* @param array $values An array of values that customizes the provider |
269
|
|
|
* |
270
|
|
|
* @return static |
271
|
|
|
*/ |
272
|
2 |
|
public function register(ServiceProviderInterface $provider, array $values = array()) |
273
|
|
|
{ |
274
|
2 |
|
$provider->register($this); |
275
|
|
|
|
276
|
2 |
|
foreach ($values as $key => $value) { |
277
|
1 |
|
$this[$key] = $value; |
278
|
2 |
|
} |
279
|
|
|
|
280
|
2 |
|
return $this; |
281
|
|
|
} |
282
|
|
|
} |
283
|
|
|
|
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.