1
|
|
|
<?php |
2
|
|
|
namespace Intraxia\Jaxion\Core; |
3
|
|
|
|
4
|
|
|
use Intraxia\Jaxion\Contract\Core\Container as ContainerContract; |
5
|
|
|
use Intraxia\Jaxion\Contract\Core\ServiceProvider; |
6
|
|
|
|
7
|
|
|
/** |
8
|
|
|
* Class Container |
9
|
|
|
* |
10
|
|
|
* Contains, manages, and retrieves service objects. |
11
|
|
|
* |
12
|
|
|
* @package Intraxia\Jaxion |
13
|
|
|
* @subpackage Core |
14
|
|
|
*/ |
15
|
|
|
class Container implements ContainerContract { |
16
|
|
|
/** |
17
|
|
|
* ServiceProvider names to register with the container. |
18
|
|
|
* |
19
|
|
|
* Can be overwritten to include predefined providers. |
20
|
|
|
* |
21
|
|
|
* @var string[] |
22
|
|
|
*/ |
23
|
|
|
protected $providers = array(); |
24
|
|
|
|
25
|
|
|
/** |
26
|
|
|
* Registered definitions. |
27
|
|
|
* |
28
|
|
|
* @var mixed[] |
29
|
|
|
*/ |
30
|
|
|
private $definitions = array(); |
31
|
|
|
|
32
|
|
|
/** |
33
|
|
|
* Aliases to share between fetches. |
34
|
|
|
* |
35
|
|
|
* @var <string, true>[] |
36
|
|
|
*/ |
37
|
|
|
private $shared = array(); |
38
|
|
|
|
39
|
|
|
/** |
40
|
|
|
* Aliases of all the registered services. |
41
|
|
|
* |
42
|
|
|
* @var <string, true>[] |
43
|
|
|
*/ |
44
|
|
|
private $aliases = array(); |
45
|
|
|
|
46
|
|
|
/** |
47
|
|
|
* Array of classes registered on the container. |
48
|
|
|
* |
49
|
|
|
* @var <string, true>[] |
50
|
|
|
*/ |
51
|
|
|
private $classes = array(); |
52
|
|
|
|
53
|
|
|
/** |
54
|
|
|
* Current position in the loop. |
55
|
|
|
* |
56
|
|
|
* @var int |
57
|
|
|
*/ |
58
|
|
|
private $position; |
59
|
|
|
|
60
|
|
|
/** |
61
|
|
|
* 0-indexed array of aliases for looping. |
62
|
|
|
* |
63
|
|
|
* @var string[] |
64
|
|
|
*/ |
65
|
|
|
private $keys = array(); |
66
|
|
|
|
67
|
|
|
/** |
68
|
|
|
* Create a new container with the given providers. |
69
|
|
|
* |
70
|
|
|
* Providers can be instances or the class of the provider as a string. |
71
|
|
|
* |
72
|
|
|
* @param ServiceProvider[]|string[] $providers |
73
|
|
|
*/ |
74
|
69 |
|
public function __construct( array $providers = array() ) { |
75
|
|
|
// array_unique ensures we only register each provider once. |
76
|
69 |
|
$providers = array_unique( array_merge( $this->providers, $providers ) ); |
77
|
|
|
|
78
|
69 |
|
foreach ( $providers as $provider ) { |
79
|
|
|
if ( is_string( $provider ) && class_exists( $provider ) ) { |
80
|
|
|
$provider = new $provider; |
81
|
|
|
} |
82
|
|
|
|
83
|
|
|
if ( $provider instanceof ServiceProvider ) { |
84
|
|
|
$this->register( $provider ); |
85
|
|
|
} |
86
|
46 |
|
} |
87
|
69 |
|
} |
88
|
|
|
|
89
|
|
|
|
90
|
|
|
/** |
91
|
|
|
* {@inheritdoc} |
92
|
|
|
* |
93
|
|
|
* @param string|array $alias |
94
|
|
|
* @param mixed $definition |
95
|
|
|
* |
96
|
|
|
* @throws DefinedAliasException |
97
|
|
|
* |
98
|
|
|
* @return $this |
99
|
|
|
*/ |
100
|
63 |
|
public function define( $alias, $definition ) { |
101
|
63 |
|
if ( is_array( $alias ) ) { |
102
|
33 |
|
$class = current( $alias ); |
103
|
33 |
|
$alias = key( $alias ); |
104
|
22 |
|
} |
105
|
|
|
|
106
|
63 |
|
if ( isset( $this->aliases[ $alias ] ) ) { |
107
|
3 |
|
throw new DefinedAliasException( $alias ); |
108
|
|
|
} |
109
|
|
|
|
110
|
63 |
|
$this->aliases[ $alias ] = true; |
111
|
63 |
|
$this->definitions[ $alias ] = $definition; |
112
|
|
|
|
113
|
|
|
// Closures are treated as factories unless |
114
|
|
|
// defined via Container::share. |
115
|
63 |
|
if ( ! $definition instanceof \Closure ) { |
116
|
36 |
|
$this->shared[ $alias ] = true; |
117
|
24 |
|
} |
118
|
|
|
|
119
|
63 |
|
if ( isset( $class ) ) { |
120
|
33 |
|
$this->classes[ $class ] = $alias; |
121
|
22 |
|
} |
122
|
|
|
|
123
|
63 |
|
return $this; |
124
|
|
|
} |
125
|
|
|
|
126
|
|
|
/** |
127
|
|
|
* {@inheritdoc} |
128
|
|
|
* |
129
|
|
|
* @param string|array $alias |
130
|
|
|
* @param mixed $definition |
131
|
|
|
* |
132
|
|
|
* @throws DefinedAliasException |
133
|
|
|
* |
134
|
|
|
* @return $this |
135
|
|
|
*/ |
136
|
33 |
|
public function share( $alias, $definition ) { |
137
|
33 |
|
$this->define( $alias, $definition ); |
138
|
|
|
|
139
|
33 |
|
if ( is_array( $alias ) ) { |
140
|
30 |
|
$alias = key( $alias ); |
141
|
20 |
|
} |
142
|
|
|
|
143
|
33 |
|
$this->shared[ $alias ] = true; |
144
|
|
|
|
145
|
33 |
|
return $this; |
146
|
|
|
} |
147
|
|
|
|
148
|
|
|
/** |
149
|
|
|
* {@inheritdoc} |
150
|
|
|
* |
151
|
|
|
* @param string $alias |
152
|
|
|
* |
153
|
|
|
* @throws UndefinedAliasException |
154
|
|
|
* |
155
|
|
|
* @return mixed |
156
|
|
|
*/ |
157
|
39 |
|
public function fetch( $alias ) { |
158
|
39 |
|
if ( isset( $this->classes[ $alias ] ) ) { |
159
|
|
|
// If the alias is a class name, |
160
|
|
|
// then retrieve its linked alias. |
161
|
|
|
// This is only registered when |
162
|
|
|
// registering using an array. |
163
|
6 |
|
$alias = $this->classes[ $alias ]; |
164
|
6 |
|
} |
165
|
|
|
|
166
|
39 |
|
if ( ! isset( $this->aliases[ $alias ] ) ) { |
167
|
3 |
|
throw new UndefinedAliasException( $alias ); |
168
|
|
|
} |
169
|
|
|
|
170
|
36 |
|
$value = $this->definitions[ $alias ]; |
171
|
|
|
|
172
|
|
|
// If the shared value is a closure, |
173
|
|
|
// execute it and assign the result |
174
|
|
|
// in place of the closure. |
175
|
36 |
|
if ( $value instanceof \Closure ) { |
176
|
33 |
|
$factory = $value; |
177
|
33 |
|
$value = $factory( $this ); |
178
|
22 |
|
} |
179
|
|
|
|
180
|
|
|
// If the value is shared, save the shared value. |
181
|
36 |
|
if ( isset( $this->shared[ $alias ] ) ) { |
182
|
27 |
|
$this->definitions[ $alias ] = $value; |
183
|
18 |
|
} |
184
|
|
|
|
185
|
|
|
// Return the fetched value. |
186
|
36 |
|
return $value; |
187
|
|
|
} |
188
|
|
|
|
189
|
|
|
/** |
190
|
|
|
* {@inheritdoc} |
191
|
|
|
* |
192
|
|
|
* @param string $alias |
193
|
|
|
* |
194
|
|
|
* @return bool |
195
|
|
|
*/ |
196
|
18 |
|
public function has( $alias ) { |
197
|
18 |
|
return isset( $this->aliases[ $alias ] ); |
198
|
|
|
} |
199
|
|
|
|
200
|
|
|
/** |
201
|
|
|
* {@inheritDoc} |
202
|
|
|
* |
203
|
|
|
* @param string $alias |
204
|
|
|
* |
205
|
|
|
* @return $this |
206
|
|
|
*/ |
207
|
21 |
|
public function remove( $alias ) { |
208
|
21 |
|
if ( isset( $this->aliases[ $alias ] ) ) { |
209
|
|
|
/** |
210
|
|
|
* If there's no reference in the aliases array, |
211
|
|
|
* the service won't be found on fetching and |
212
|
|
|
* can be overwritten on setting. |
213
|
|
|
* |
214
|
|
|
* Pros: Quick setting/unsetting, faster |
215
|
|
|
* performance on those operations when doing |
216
|
|
|
* a lot of these. |
217
|
|
|
* |
218
|
|
|
* Cons: Objects and values set in the container |
219
|
|
|
* can't get garbage collected. |
220
|
|
|
* |
221
|
|
|
* If this is a problem, this may need to be revisited. |
222
|
|
|
*/ |
223
|
21 |
|
unset( $this->aliases[ $alias ] ); |
224
|
14 |
|
} |
225
|
|
|
|
226
|
21 |
|
return $this; |
|
|
|
|
227
|
|
|
} |
228
|
|
|
|
229
|
|
|
/** |
230
|
|
|
* {@inheritDoc} |
231
|
|
|
* |
232
|
|
|
* @param ServiceProvider $provider |
233
|
|
|
* |
234
|
|
|
* @return $this |
235
|
|
|
*/ |
236
|
3 |
|
public function register( ServiceProvider $provider ) { |
237
|
|
|
// @todo make sure provider is only registered once |
238
|
3 |
|
$provider->register( $this ); |
239
|
|
|
|
240
|
3 |
|
return $this; |
241
|
|
|
} |
242
|
|
|
|
243
|
|
|
/** |
244
|
|
|
* Set a value into the container. |
245
|
|
|
* |
246
|
|
|
* @param string $id |
247
|
|
|
* @param mixed $value |
248
|
|
|
* |
249
|
|
|
* @see define |
250
|
|
|
*/ |
251
|
6 |
|
public function offsetSet( $id, $value ) { |
252
|
6 |
|
$this->define( $id, $value ); |
253
|
6 |
|
} |
254
|
|
|
|
255
|
|
|
/** |
256
|
|
|
* Get an value from the container. |
257
|
|
|
* |
258
|
|
|
* @param string $id |
259
|
|
|
* |
260
|
|
|
* @return object |
261
|
|
|
* @throws UndefinedAliasException |
262
|
|
|
* |
263
|
|
|
* @see fetch |
264
|
|
|
*/ |
265
|
6 |
|
public function offsetGet( $id ) { |
266
|
6 |
|
return $this->fetch( $id ); |
267
|
|
|
} |
268
|
|
|
|
269
|
|
|
/** |
270
|
|
|
* Checks if a key is set on the container. |
271
|
|
|
* |
272
|
|
|
* @param string $id |
273
|
|
|
* |
274
|
|
|
* @return bool |
275
|
|
|
* |
276
|
|
|
* @see has |
277
|
|
|
*/ |
278
|
6 |
|
public function offsetExists( $id ) { |
279
|
6 |
|
return $this->has( $id ); |
280
|
|
|
} |
281
|
|
|
|
282
|
|
|
/** |
283
|
|
|
* Remove a key from the container. |
284
|
|
|
* |
285
|
|
|
* @param string $id |
286
|
|
|
* |
287
|
|
|
* @see remove |
288
|
|
|
*/ |
289
|
3 |
|
public function offsetUnset( $id ) { |
290
|
3 |
|
$this->remove( $id ); |
291
|
3 |
|
} |
292
|
|
|
|
293
|
|
|
/** |
294
|
|
|
* Sets the object properties to prepare for the loop. |
295
|
|
|
*/ |
296
|
12 |
|
public function rewind() { |
297
|
12 |
|
$this->position = 0; |
298
|
12 |
|
$this->keys = array_keys( $this->aliases ); |
|
|
|
|
299
|
12 |
|
} |
300
|
|
|
|
301
|
|
|
/** |
302
|
|
|
* Retrieves the service object for the current step in the loop. |
303
|
|
|
* |
304
|
|
|
* @return object |
305
|
|
|
*/ |
306
|
12 |
|
public function current() { |
307
|
12 |
|
return $this->fetch( $this->keys[ $this->position ] ); |
308
|
|
|
} |
309
|
|
|
|
310
|
|
|
/** |
311
|
|
|
* Retrieves the key for the current step in the loop. |
312
|
|
|
* |
313
|
|
|
* @return string |
314
|
|
|
*/ |
315
|
12 |
|
public function key() { |
316
|
12 |
|
return $this->keys[ $this->position ]; |
317
|
|
|
} |
318
|
|
|
|
319
|
|
|
/** |
320
|
|
|
* Increments to the next step in the loop. |
321
|
|
|
*/ |
322
|
12 |
|
public function next() { |
323
|
12 |
|
$this->position ++; |
324
|
12 |
|
} |
325
|
|
|
|
326
|
|
|
/** |
327
|
|
|
* Checks if the next step in the loop in valid. |
328
|
|
|
* |
329
|
|
|
* @return bool |
330
|
|
|
*/ |
331
|
12 |
|
public function valid() { |
332
|
12 |
|
return isset( $this->keys[ $this->position ] ); |
333
|
|
|
} |
334
|
|
|
} |
335
|
|
|
|
If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.
Let’s take a look at an example:
Our function
my_function
expects aPost
object, and outputs the author of the post. The base classPost
returns a simple string and outputting a simple string will work just fine. However, the child classBlogPost
which is a sub-type ofPost
instead decided to return anobject
, and is therefore violating the SOLID principles. If aBlogPost
were passed tomy_function
, PHP would not complain, but ultimately fail when executing thestrtoupper
call in its body.