1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace ntentan\panie; |
4
|
|
|
|
5
|
|
|
use Psr\Container\ContainerInterface; |
6
|
|
|
|
7
|
|
|
/** |
8
|
|
|
* Container class through which dependencies are defined and resolved. |
9
|
|
|
* |
10
|
|
|
* @author ekow |
11
|
|
|
*/ |
12
|
|
|
class Container implements ContainerInterface |
13
|
|
|
{ |
14
|
|
|
|
15
|
|
|
/** |
16
|
|
|
* Holds all bindings defined. |
17
|
|
|
* |
18
|
|
|
* @var Bindings |
19
|
|
|
*/ |
20
|
|
|
private $bindings; |
21
|
|
|
|
22
|
|
|
/** |
23
|
|
|
* Holds instances of all singletons. |
24
|
|
|
* |
25
|
|
|
* @var array |
26
|
|
|
*/ |
27
|
|
|
private $singletons = []; |
28
|
|
|
|
29
|
19 |
|
public function __construct() |
30
|
|
|
{ |
31
|
19 |
|
$this->bindings = new Bindings(); |
32
|
19 |
|
} |
33
|
|
|
|
34
|
|
|
/** |
35
|
|
|
* Resolves names of items requested from the container to their correct binding definition. |
36
|
|
|
* |
37
|
|
|
* @param string $class |
38
|
|
|
* @return array|null The name of the class detected or null |
39
|
|
|
*/ |
40
|
17 |
|
private function getResolvedBinding(string $class) |
41
|
|
|
{ |
42
|
17 |
|
$bound = null; |
43
|
17 |
|
if ($this->bindings->has($class)) { |
44
|
12 |
|
$bound = $this->bindings->get($class); |
45
|
9 |
|
} else if (is_string($class) && class_exists($class)) { |
46
|
7 |
|
$bound = ['binding' => $class]; |
47
|
|
|
} |
48
|
17 |
|
return $bound; |
49
|
|
|
} |
50
|
|
|
|
51
|
|
|
/** |
52
|
|
|
* Starts the process of defining a binding. |
53
|
|
|
* This method selects an active binding for the internal bindings object. |
54
|
|
|
* |
55
|
|
|
* @param string $type |
56
|
|
|
* @return \ntentan\panie\Bindings |
57
|
|
|
*/ |
58
|
10 |
|
public function bind(string $type) : Bindings |
59
|
|
|
{ |
60
|
10 |
|
return $this->bindings->setActiveKey($type); |
61
|
|
|
} |
62
|
|
|
|
63
|
|
|
/** |
64
|
|
|
* Returns true if type is found in container otherwise it returns false. |
65
|
|
|
* |
66
|
|
|
* @param string $type |
67
|
|
|
* @return bool |
68
|
|
|
*/ |
69
|
2 |
|
public function has($type) : bool |
70
|
|
|
{ |
71
|
2 |
|
return $this->bindings->has($type); |
72
|
|
|
} |
73
|
|
|
|
74
|
|
|
/** |
75
|
|
|
* Pass an array of bindings to the container. |
76
|
|
|
* |
77
|
|
|
* @param array $bindings |
78
|
|
|
*/ |
79
|
5 |
|
public function setup(array $bindings) : void |
80
|
|
|
{ |
81
|
5 |
|
$this->bindings->merge($bindings); |
82
|
5 |
|
} |
83
|
|
|
|
84
|
|
|
/** |
85
|
|
|
* Resolves a type and returns an instance of an object of the requested type. |
86
|
|
|
* Optional constructor arguments could be provided to be used in initializing the object. This method throws a |
87
|
|
|
* ResolutionException in cases where the type could not be resolved. |
88
|
|
|
* |
89
|
|
|
* @param string $type |
90
|
|
|
* @param array $constructorArguments |
91
|
|
|
* @return mixed |
92
|
|
|
* @throws exceptions\ResolutionException |
93
|
|
|
*/ |
94
|
17 |
|
public function resolve(string $type, array $constructorArguments = []) |
95
|
|
|
{ |
96
|
17 |
|
$resolvedClass = $this->getResolvedBinding($type); |
97
|
17 |
|
if ($resolvedClass['binding'] === null) { |
98
|
2 |
|
throw new exceptions\ResolutionException("Could not resolve dependency of type [$type]"); |
99
|
|
|
} |
100
|
15 |
|
if ($resolvedClass['singleton'] ?? false) { |
101
|
2 |
|
$instance = $this->getSingletonInstance($type, $resolvedClass['binding'], $constructorArguments); |
102
|
|
|
} else { |
103
|
13 |
|
$instance = $this->getInstance($resolvedClass['binding'], $constructorArguments); |
104
|
|
|
} |
105
|
|
|
|
106
|
14 |
|
foreach($resolvedClass['calls'] ?? [] as $calls) { |
107
|
3 |
|
$method = new \ReflectionMethod($instance, $calls[0]); |
108
|
3 |
|
$method->invokeArgs($instance, $this->getMethodArguments($method, $calls[1])); |
109
|
|
|
} |
110
|
|
|
|
111
|
14 |
|
return $instance; |
112
|
|
|
} |
113
|
|
|
|
114
|
|
|
/** |
115
|
|
|
* Returns an instance of the type requested if this type (which was requested) is defined in the container. |
116
|
|
|
* |
117
|
|
|
* @param string $type |
118
|
|
|
* @return mixed |
119
|
|
|
* @throws exceptions\ResolutionException |
120
|
|
|
*/ |
121
|
1 |
|
public function get($type) |
122
|
|
|
{ |
123
|
1 |
|
return $this->resolve($type); |
124
|
|
|
} |
125
|
|
|
|
126
|
|
|
/** |
127
|
|
|
* Resolves an argument for a method or constructor. |
128
|
|
|
* If the argument passed is a string and the type hint of the argument points to an object, the string passed is |
129
|
|
|
* assumed to be a class binding and it is resolved. |
130
|
|
|
* |
131
|
|
|
* @param mixed $argument |
132
|
|
|
* @param string $class |
133
|
|
|
* @return mixed |
134
|
|
|
* @throws exceptions\ResolutionException |
135
|
|
|
*/ |
136
|
4 |
|
private function resolveArgument($argument, $class) |
137
|
|
|
{ |
138
|
4 |
|
if($class && is_string($argument)) { |
139
|
1 |
|
return $this->resolve($argument); |
140
|
|
|
} |
141
|
3 |
|
return $argument; |
142
|
|
|
} |
143
|
|
|
|
144
|
|
|
/** |
145
|
|
|
* Resolves all the arguments of a method or constructor. |
146
|
|
|
* |
147
|
|
|
* @param \ReflectionMethod $method |
148
|
|
|
* @param array $methodArguments |
149
|
|
|
* @return array |
150
|
|
|
* @throws exceptions\ResolutionException |
151
|
|
|
*/ |
152
|
6 |
|
private function getMethodArguments(\ReflectionMethod $method, array $methodArguments) : array |
153
|
|
|
{ |
154
|
6 |
|
$argumentValues = []; |
155
|
6 |
|
$parameters = $method->getParameters(); |
156
|
6 |
|
foreach ($parameters as $parameter) { |
157
|
6 |
|
$class = $parameter->getClass(); |
158
|
6 |
|
$className = $class ? $class->getName() : null; |
159
|
6 |
|
if (isset($methodArguments[$parameter->getName()])) { |
160
|
4 |
|
$argumentValues[] = $this->resolveArgument($methodArguments[$parameter->getName()], $className); |
161
|
|
|
} else { |
162
|
5 |
|
$argumentValues[] = $className ? $this->resolve($className) : |
163
|
6 |
|
($parameter->isDefaultValueAvailable() ? $parameter->getDefaultValue() : null); |
164
|
|
|
} |
165
|
|
|
} |
166
|
6 |
|
return $argumentValues; |
167
|
|
|
} |
168
|
|
|
|
169
|
|
|
/** |
170
|
|
|
* Returns a singleton of a given bound type. |
171
|
|
|
* |
172
|
|
|
* @param string $type |
173
|
|
|
* @param mixed $class |
174
|
|
|
* @param array $constructorArguments |
175
|
|
|
* @return mixed |
176
|
|
|
* @throws exceptions\ResolutionException |
177
|
|
|
*/ |
178
|
2 |
|
private function getSingletonInstance(string $type, $class, array $constructorArguments) |
179
|
|
|
{ |
180
|
2 |
|
if (!isset($this->singletons[$type])) { |
181
|
2 |
|
$this->singletons[$type] = $this->getInstance($class, $constructorArguments); |
182
|
|
|
} |
183
|
2 |
|
return $this->singletons[$type]; |
184
|
|
|
} |
185
|
|
|
|
186
|
|
|
/** |
187
|
|
|
* Returns an instance of a class. |
188
|
|
|
* |
189
|
|
|
* @param string|closure $className |
190
|
|
|
* @param array $constructorArguments |
191
|
|
|
* @return mixed |
192
|
|
|
* @throws exceptions\ResolutionException |
193
|
|
|
*/ |
194
|
15 |
|
private function getInstance($className, array $constructorArguments = []) |
195
|
|
|
{ |
196
|
15 |
|
if (is_callable($className)) { |
197
|
1 |
|
return $className($this); |
198
|
|
|
} |
199
|
14 |
|
$reflection = new \ReflectionClass($className); |
200
|
14 |
|
if ($reflection->isAbstract()) { |
201
|
1 |
|
throw new exceptions\ResolutionException( |
202
|
1 |
|
"Abstract class {$reflection->getName()} cannot be instantiated. " |
203
|
1 |
|
. "Please provide a binding to an implementation." |
204
|
|
|
); |
205
|
|
|
} |
206
|
13 |
|
$constructor = $reflection->getConstructor(); |
207
|
13 |
|
return $reflection->newInstanceArgs($constructor ? $this->getMethodArguments($constructor, $constructorArguments) : []); |
208
|
|
|
} |
209
|
|
|
} |
210
|
|
|
|