Passed
Branch dev (45cd88)
by Raffael
02:16
created

Container::autoWireMethod()   D

Complexity

Conditions 10
Paths 9

Size

Total Lines 39
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 23
CRAP Score 10.0071

Importance

Changes 0
Metric Value
cc 10
eloc 25
nc 9
nop 3
dl 0
loc 39
ccs 23
cts 24
cp 0.9583
crap 10.0071
rs 4.8196
c 0
b 0
f 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * Micro\Container
7
 *
8
 * @copyright   Copryright (c) 2018 gyselroth GmbH (https://gyselroth.com)
9
 * @license     MIT https://opensource.org/licenses/MIT
10
 */
11
12
namespace Micro\Container;
13
14
use Closure;
15
use ProxyManager\Factory\LazyLoadingValueHolderFactory;
16
use ReflectionClass;
17
use ReflectionMethod;
18
19
class Container extends AbstractContainer
20
{
21
    /**
22
     * Get service.
23
     *
24
     * @param string $name
25
     *
26
     * @return mixed
27
     */
28 39
    public function get($name)
29
    {
30
        try {
31 39
            return $this->resolve($name);
32 14
        } catch (Exception\ServiceNotFound $e) {
33 11
            return $this->autoWireClass($name);
34
        }
35
    }
36
37
    /**
38
     * Resolve service.
39
     *
40
     * @param string $name
41
     *
42
     * @return mixed
43
     */
44 39
    public function resolve(string $name)
45
    {
46 39
        if ($this->has($name)) {
47 4
            return $this->service[$name];
48
        }
49
50 39
        if (isset($this->registry[$name])) {
51 3
            return $this->addStaticService($name);
52
        }
53
54 36
        if ($this->config->has($name)) {
55 33
            return $this->autoWireClass($name);
56
        }
57
58 12
        if (null !== $this->parent_service) {
59 1
            $parents = array_merge([$name], class_implements($this->parent_service), class_parents($this->parent_service));
60
61 1
            if (in_array($name, $parents, true) && $this->parent_service instanceof $name) {
62
                return $this->parent_service;
63
            }
64
        }
65
66 12
        if (null !== $this->parent) {
67 1
            return $this->parent->resolve($name);
0 ignored issues
show
Bug introduced by
The method resolve() does not exist on Psr\Container\ContainerInterface. It seems like you code against a sub-type of Psr\Container\ContainerInterface such as Micro\Container\Container. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

67
            return $this->parent->/** @scrutinizer ignore-call */ resolve($name);
Loading history...
68
        }
69
70 11
        throw new Exception\ServiceNotFound("service $name was not found in service tree");
71
    }
72
73
    /**
74
     * Check for static injections.
75
     *
76
     * @param string $name
77
     *
78
     * @return mixed
79
     */
80 3
    protected function addStaticService(string $name)
81
    {
82 3
        if ($this->registry[$name] instanceof Closure) {
83 1
            $this->service[$name] = $this->registry[$name]->call($this);
84
        } else {
85 2
            $this->service[$name] = $this->registry[$name];
86
        }
87
88 3
        unset($this->registry[$name]);
89
90 3
        return $this->service[$name];
91
    }
92
93
    /**
94
     * Auto wire.
95
     *
96
     * @param string $name
97
     * @param array  $config
98
     * @param array  $parents
99
     *
100
     * @return mixed
101
     */
102 36
    protected function autoWireClass(string $name)
103
    {
104 36
        $config = $this->config->get($name);
105 34
        $class = $config['use'];
106
107 34
        if (preg_match('#^\{([^{}]+)\}$#', $class, $match)) {
108 1
            return $this->wireReference($name, $match[1], $config);
109
        }
110
111
        try {
112 34
            $reflection = new ReflectionClass($class);
113
        } catch (\Exception $e) {
114
            throw new Exception\ServiceNotFound($class.' can not be resolved to an existing class for service '.$name);
115
        }
116
117 34
        $constructor = $reflection->getConstructor();
118
119 34
        if (null === $constructor) {
120 3
            return $this->storeService($name, $config, new $class());
121
        }
122
123 31
        $args = $this->autoWireMethod($name, $constructor, $config);
124
125 29
        return $this->createInstance($name, $reflection, $args, $config);
126
    }
127
128
    /**
129
     * Wire named referenced service.
130
     *
131
     * @param string $name
132
     * @param string $refrence
133
     * @param array  $config
134
     *
135
     * @return mixed
136
     */
137 1
    protected function wireReference(string $name, string $reference, array $config)
138
    {
139 1
        $service = $this->get($reference);
140 1
        $reflection = new ReflectionClass(get_class($service));
141 1
        $config = $this->config->get($name);
142 1
        $service = $this->prepareService($name, $service, $reflection, $config);
143
144 1
        return $service;
145
    }
146
147
    /**
148
     * Store service.
149
     *
150
     * @param param string $name
151
     * @param array        $config
152
     * @param mixed        $service
153
     *
154
     * @return mixed
155
     */
156 31
    protected function storeService(string $name, array $config, $service)
157
    {
158 31
        if (true === $config['singleton']) {
159 2
            return $service;
160
        }
161 30
        $this->service[$name] = $service;
162
163 30
        if (isset($this->children[$name])) {
164 2
            $this->children[$name]->setParentService($service);
165
        }
166
167 30
        return $service;
168
    }
169
170
    /**
171
     * Get instance (virtual or real instance).
172
     *
173
     * @param string          $name
174
     * @param ReflectionClass $class
175
     * @param array           $arguments
176
     * @param array           $config
177
     *
178
     * @return mixed
179
     */
180 29
    protected function createInstance(string $name, ReflectionClass $class, array $arguments, array $config)
181
    {
182 29
        if (true === $config['lazy']) {
183 2
            return $this->getProxyInstance($name, $class, $arguments, $config);
184
        }
185
186 27
        return $this->getRealInstance($name, $class, $arguments, $config);
187
    }
188
189
    /**
190
     * Create proxy instance.
191
     *
192
     * @param string          $name
193
     * @param ReflectionClass $class
194
     * @param array           $arguments
195
     * @param array           $config
196
     *
197
     * @return mixed
198
     */
199 2
    protected function getProxyInstance(string $name, ReflectionClass $class, array $arguments, array $config)
200
    {
201 2
        $factory = new LazyLoadingValueHolderFactory();
202 2
        $that = $this;
203
204 2
        return $factory->createProxy(
205 2
            $class->getName(),
206 2
            function (&$wrappedObject, $proxy, $method, $parameters, &$initializer) use ($that, $name,$class,$arguments,$config) {
207 1
                $wrappedObject = $that->getRealInstance($name, $class, $arguments, $config);
208 1
                $initializer = null;
209 2
            }
210
        );
211
    }
212
213
    /**
214
     * Create real instance.
215
     *
216
     * @param string          $name
217
     * @param ReflectionClass $class
218
     * @param array           $arguments
219
     * @param array           $config
220
     *
221
     * @return mixed
222
     */
223 28
    protected function getRealInstance(string $name, ReflectionClass $class, array $arguments, array $config)
224
    {
225 28
        $instance = $class->newInstanceArgs($arguments);
226 28
        $instance = $this->prepareService($name, $instance, $class, $config);
227
228 27
        return $instance;
229
    }
230
231
    /**
232
     * Prepare service (execute sub selects and excute setter injections).
233
     *
234
     * @param string          $name
235
     * @param mixed           $service
236
     * @param ReflectionClass $class
237
     * @param array           $config
238
     *
239
     * @return mixed
240
     */
241 28
    protected function prepareService(string $name, $service, ReflectionClass $class, array $config)
242
    {
243 28
        foreach ($config['selects'] as $select) {
244 2
            $args = $this->autoWireMethod($name, $class->getMethod($select['method']), $select);
245 2
            $service = call_user_func_array([&$service, $select['method']], $args);
246
        }
247
248 28
        $this->storeService($name, $config, $service);
249
250 28
        foreach ($config['calls'] as $call) {
251 6
            if (null === $call) {
252
                continue;
253
            }
254
255 6
            if (!isset($call['method'])) {
256 1
                throw new Exception\InvalidConfiguration('method is required for setter injection in service '.$name);
257
            }
258
259 5
            $arguments = [];
0 ignored issues
show
Unused Code introduced by
The assignment to $arguments is dead and can be removed.
Loading history...
260
261
            try {
262 5
                $method = $class->getMethod($call['method']);
263
            } catch (\ReflectionException $e) {
264
                throw new Exception\InvalidConfiguration('method '.$call['method'].' is not callable in class '.$class->getName().' for service '.$name);
265
            }
266
267 5
            $arguments = $this->autoWireMethod($name, $method, $call);
268 5
            call_user_func_array([&$service, $call['method']], $arguments);
269
        }
270
271 27
        return $service;
272
    }
273
274
    /**
275
     * Autowire method.
276
     *
277
     * @param string           $name
278
     * @param ReflectionMethod $method
279
     * @param array            $config
280
     *
281
     * @return array
282
     */
283 31
    protected function autoWireMethod(string $name, ReflectionMethod $method, array $config): array
284
    {
285 31
        $params = $method->getParameters();
286 31
        $args = [];
287
288 31
        foreach ($params as $param) {
289 31
            $type = $param->getClass();
290 31
            $param_name = $param->getName();
291
292 31
            if (isset($config['arguments'][$param_name])) {
293 26
                $args[$param_name] = $this->parseParam($config['arguments'][$param_name], $name);
294 16
            } elseif (null !== $type) {
295 9
                $type_class = $type->getName();
296
297 9
                if ($type_class === $name) {
298
                    throw new Exception\InvalidConfiguration('class '.$type_class.' can not depend on itself');
299
                }
300
301
                try {
302 9
                    $args[$param_name] = $this->findService($name, $type_class);
303 2
                } catch (\Exception $e) {
304 2
                    if ($param->isDefaultValueAvailable() && null === $param->getDefaultValue()) {
305 1
                        $args[$param_name] = null;
306
307 1
                        continue;
308
                    }
309
310 8
                    throw $e;
311
                }
312 9
            } elseif ($param->isDefaultValueAvailable()) {
313 9
                $args[$param_name] = $param->getDefaultValue();
314 1
            } elseif ($param->allowsNull()) {
315 1
                $args[$param_name] = null;
316
            } else {
317 28
                throw new Exception\InvalidConfiguration('no value found for argument '.$param_name.' in method '.$method->getName().' for service '.$name);
318
            }
319
        }
320
321 29
        return $args;
322
    }
323
324
    /**
325
     * Parse param value.
326
     *
327
     * @param mixed  $param
328
     * @param string $name
329
     *
330
     * @return mixed
331
     */
332 26
    protected function parseParam($param, string $name)
333
    {
334 26
        if (is_iterable($param)) {
335 1
            foreach ($param as $key => $value) {
336 1
                $param[$key] = $this->parseParam($value, $name);
337
            }
338
339 1
            return $param;
340
        }
341
342 26
        if (is_string($param)) {
343 26
            $param = $this->config->getEnv($param);
344
345 25
            if (preg_match('#^\{\{([^{}]+)\}\}$#', $param, $matches)) {
346 1
                return '{'.$matches[1].'}';
347
            }
348 24
            if (preg_match('#^\{([^{}]+)\}$#', $param, $matches)) {
349 1
                return $this->findService($name, $matches[1]);
350
            }
351
352 24
            return $param;
353
        }
354
355
        return $param;
356
    }
357
358
    /**
359
     * Locate service.
360
     *
361
     * @param string $current_service
362
     * @param string $service
363
     */
364 10
    protected function findService(string $current_service, string $service)
365
    {
366 10
        if (isset($this->children[$current_service])) {
367 1
            return $this->children[$current_service]->get($service);
368
        }
369
370 10
        $config = $this->config->get($current_service);
371 10
        if (isset($config['services'])) {
372 2
            $this->children[$current_service] = new self($config['services'], $this);
373
374 2
            return $this->children[$current_service]->get($service);
375
        }
376
377 9
        return $this->get($service);
378
    }
379
}
380