Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
100.00% |
48 / 48 |
|
100.00% |
11 / 11 |
CRAP | |
100.00% |
1 / 1 |
Micro | |
100.00% |
48 / 48 |
|
100.00% |
11 / 11 |
29 | |
100.00% |
1 / 1 |
run | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
2 | |||
instance | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
add | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
3 | |||
hasInterface | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getClass | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
3 | |||
get | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
2 | |||
interfaces | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
create | |
100.00% |
11 / 11 |
|
100.00% |
1 / 1 |
4 | |||
createDependencies | |
100.00% |
13 / 13 |
|
100.00% |
1 / 1 |
6 | |||
getCallable | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
2 | |||
isMicroCallable | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
4 |
1 | <?php |
2 | |
3 | namespace Dynart\Micro; |
4 | |
5 | /** |
6 | * Micro PHP Dependency Injection |
7 | * |
8 | * @package Dynart\Micro |
9 | */ |
10 | class Micro { |
11 | |
12 | /** |
13 | * Holds the instance of the application |
14 | * @var App |
15 | */ |
16 | protected static $instance; |
17 | |
18 | /** |
19 | * Stores the classes in [interface => class] format, the class can be null |
20 | * @var array |
21 | */ |
22 | protected static $classes = []; |
23 | |
24 | /** |
25 | * Stores the instances in [interface => instance] format |
26 | * @var array |
27 | */ |
28 | protected static $instances = []; |
29 | |
30 | /** |
31 | * Sets the application instance and runs it |
32 | * |
33 | * First it sets the instance, then calls the `fullInit()` and `fullProcess()` methods of the `$app`. |
34 | * |
35 | * @throws MicroException if the instance was set before |
36 | * @param App $app The application for init and process |
37 | */ |
38 | public static function run(App $app): void { |
39 | if (self::$instance) { |
40 | throw new MicroException("App was instantiated before!"); |
41 | } |
42 | self::$instance = $app; |
43 | $app->fullInit(); |
44 | $app->fullProcess(); |
45 | } |
46 | |
47 | /** |
48 | * Returns the instance of the application |
49 | * @return mixed The instance of the application |
50 | */ |
51 | public static function instance() { |
52 | return self::$instance; |
53 | } |
54 | |
55 | /** |
56 | * Adds a class for an interface |
57 | * |
58 | * For example: |
59 | * |
60 | * <pre> |
61 | * Micro::add(ConfigInterface::class, Config::class); |
62 | * </pre> |
63 | * |
64 | * or |
65 | * |
66 | * <pre> |
67 | * Micro::add(Config::class); |
68 | * </pre> |
69 | * |
70 | * @param string $interface The interface |
71 | * @param null $class The class, it can be null, then the interface itself a class |
72 | */ |
73 | public static function add(string $interface, $class = null) { |
74 | if ($class != null && !(is_subclass_of($class, $interface))) { |
75 | throw new MicroException("$class does not implement $interface"); |
76 | } |
77 | self::$classes[$interface] = $class; |
78 | } |
79 | |
80 | /** |
81 | * @param string $interface |
82 | * @return bool Is the interface was added? |
83 | */ |
84 | public static function hasInterface(string $interface) { |
85 | return array_key_exists($interface, self::$classes); |
86 | } |
87 | |
88 | /** |
89 | * Returns with the class for the given interface |
90 | * @throws MicroException If the interface wasn't added |
91 | * @param string $interface The interface |
92 | * @return string The class for the interface |
93 | */ |
94 | public static function getClass(string $interface) { |
95 | if (!self::hasInterface($interface)) { |
96 | throw new MicroException("$interface was not added"); |
97 | } |
98 | return isset(self::$classes[$interface]) ? self::$classes[$interface] : $interface; |
99 | } |
100 | |
101 | /** |
102 | * Creates the singleton instance for the given interface, stores it in `$instances`, then returns with it |
103 | * |
104 | * It returns instantly if the instance was stored before. |
105 | * |
106 | * @param string $interface The interface |
107 | * @param array $parameters The parameters for the constructor. Important: only the parameters that are not injected! |
108 | * @param array $dependencyStack |
109 | * @return mixed |
110 | */ |
111 | public static function get(string $interface, array $parameters = [], array $dependencyStack = []) { |
112 | if (array_key_exists($interface, self::$instances)) { |
113 | return self::$instances[$interface]; |
114 | } |
115 | $result = self::create(self::getClass($interface), $parameters, $dependencyStack); |
116 | self::$instances[$interface] = $result; |
117 | return $result; |
118 | } |
119 | |
120 | /** |
121 | * Returns with all of the interfaces in an array |
122 | * @return array All of the added interfaces |
123 | */ |
124 | public static function interfaces() { |
125 | return array_keys(self::$classes); |
126 | } |
127 | |
128 | /** |
129 | * Creates an instance for the given interface |
130 | * |
131 | * In the following example, the `Something` class constructor will get the `Config` instance |
132 | * and the 'someParameterValue' in the `$someParameter`. |
133 | * |
134 | * <pre> |
135 | * use Dynart\Micro\Micro; |
136 | * use Dynart\Micro\App; |
137 | * use Dynart\Micro\Config; |
138 | * |
139 | * class Something { |
140 | * private $someParameter; |
141 | * public function __construct(Config $config, $someParameter) { |
142 | * $this->someParameter = $someParameter; |
143 | * } |
144 | * |
145 | * public function someParameter() { |
146 | * return $this->someParameter; |
147 | * } |
148 | * } |
149 | * |
150 | * class MyApp extends App { |
151 | * private $something; |
152 | * public function __construct() { |
153 | * Micro::add(Config::class); |
154 | * Micro::add(Something::class); |
155 | * } |
156 | * |
157 | * public function init() { |
158 | * $this->something = Micro::create(Something::class, ['someParameterValue']); |
159 | * } |
160 | * |
161 | * public function process() { |
162 | * echo $this->something->someParameter(); |
163 | * } |
164 | * } |
165 | * </pre> |
166 | * |
167 | * If the class has a `postConstruct()` method it will be called after creation. It can be used for lazy injection. |
168 | * |
169 | * @param string $class The name of the class |
170 | * @param array $parameters Parameters for the constructor. Important: only the parameters that are not injected! |
171 | * @param array $dependencyStack |
172 | * @return mixed |
173 | */ |
174 | public static function create(string $class, array $parameters = [], array $dependencyStack = []) { |
175 | if (in_array($class, $dependencyStack)) { |
176 | throw new MicroException("Circular dependency: ".join(" <- ", $dependencyStack)); |
177 | } |
178 | $dependencyStack[] = $class; |
179 | try { |
180 | $reflectionClass = new \ReflectionClass($class); |
181 | } catch (\ReflectionException $e) { |
182 | throw new MicroException("Couldn't create reflection class for $class"); |
183 | } |
184 | $dependencies = self::createDependencies($class, $reflectionClass, $dependencyStack); |
185 | $result = $reflectionClass->newInstanceArgs(array_merge($dependencies, $parameters)); |
186 | if (method_exists($result, 'postConstruct')) { |
187 | $result->postConstruct(); |
188 | } |
189 | return $result; |
190 | } |
191 | |
192 | /** |
193 | * Creates the singleton dependencies for a given class and returns with it as an array |
194 | * @param string $class The class name |
195 | * @param \ReflectionClass $reflectionClass |
196 | * @param array $dependencyStack |
197 | * @return array The created singleton instances |
198 | */ |
199 | private static function createDependencies(string $class, \ReflectionClass $reflectionClass, array $dependencyStack = []) { |
200 | $result = []; |
201 | $constructor = $reflectionClass->getConstructor(); |
202 | if (!$constructor) { |
203 | return $result; |
204 | } |
205 | foreach ($constructor->getParameters() as $parameter) { |
206 | $type = $parameter->getType(); |
207 | if (!$type || $type->isBuiltin()) { |
208 | continue; |
209 | } |
210 | $interface = $type->getName(); |
211 | if (self::hasInterface($interface)) { |
212 | $result[] = self::get($interface, [], $dependencyStack); |
213 | } else { |
214 | throw new MicroException("Non existing dependency `$interface` for `$class`"); |
215 | } |
216 | } |
217 | return $result; |
218 | } |
219 | |
220 | |
221 | /** |
222 | * Creates and instance of the callable if needed, then returns with it |
223 | * @param $callable |
224 | * @return mixed |
225 | */ |
226 | public static function getCallable($callable) { |
227 | return self::isMicroCallable($callable) ? [Micro::get($callable[0]), $callable[1]] : $callable; |
228 | } |
229 | |
230 | /** |
231 | * Returns true if the `$callable` is a Micro Framework callable |
232 | * |
233 | * Micro Framework callable means: an array with two strings. |
234 | * The first one is the class name, the second is the method name. |
235 | * |
236 | * Example: |
237 | * <pre> |
238 | * [Something::class, 'theMethodName'] |
239 | * </pre> |
240 | * |
241 | * @param $callable mixed The callable for the check |
242 | * @return bool |
243 | */ |
244 | public static function isMicroCallable($callable): bool { |
245 | return is_array($callable) |
246 | && count($callable) == 2 |
247 | && is_string($callable[0]) |
248 | && is_string($callable[1]); |
249 | } |
250 | } |