Completed
Push — master ( 086554...ec0577 )
by Raffael
02:12
created

Container   B

Complexity

Total Complexity 53

Size/Duplication

Total Lines 467
Duplicated Lines 0 %

Test Coverage

Coverage 89.36%

Importance

Changes 0
Metric Value
dl 0
loc 467
ccs 126
cts 141
cp 0.8936
rs 7.4757
c 0
b 0
f 0
wmc 53

19 Methods

Rating   Name   Duplication   Size   Complexity  
A addStaticService() 0 11 2
A __construct() 0 5 1
A setParentService() 0 5 1
A getParent() 0 3 1
A get() 0 6 2
A has() 0 3 1
A add() 0 9 2
C resolve() 0 27 8
A getConfig() 0 3 1
B parseParam() 0 24 6
C autoWireMethod() 0 29 8
A wireReference() 0 8 1
B prepareService() 0 28 5
A getRealInstance() 0 6 1
A storeService() 0 12 3
B autoWireClass() 0 24 4
A findService() 0 14 3
A getProxyInstance() 0 10 1
A createInstance() 0 7 2

How to fix   Complexity   

Complex Class

Complex classes like Container often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Container, and based on these observations, apply Extract Interface, too.

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 Psr\Container\ContainerInterface;
17
use ReflectionClass;
18
use ReflectionMethod;
19
20
class Container implements ContainerInterface
21
{
22
    /**
23
     * Config.
24
     *
25
     * @var Config
26
     */
27
    protected $config;
28
29
    /**
30
     * Service registry.
31
     *
32
     * @var array
33
     */
34
    protected $service = [];
35
36
    /**
37
     * Registered but not initialized service registry.
38
     *
39
     * @var array
40
     */
41
    protected $registry = [];
42
43
    /**
44
     * Parent container.
45
     *
46
     * @var ContainerInterface
47
     */
48
    protected $parent;
49
50
    /**
51
     * Children container.
52
     *
53
     * @var ContainerInterface[]
54
     */
55
    protected $children = [];
56
57
    /**
58
     * Parent service.
59
     *
60
     * @var mixed
61
     */
62
    protected $parent_service;
63
64
    /**
65
     * Create container.
66
     *
67
     * @param iterable           $config
68
     * @param ContainerInterface $parent
69
     */
70 41
    public function __construct(Iterable $config = [], ?ContainerInterface $parent = null)
71
    {
72 41
        $this->config = new Config($config, $this);
73 41
        $this->parent = $parent;
74 41
        $this->service[ContainerInterface::class] = $this;
75 41
    }
76
77
    /**
78
     * Get service.
79
     *
80
     * @param string $name
81
     *
82
     * @return mixed
83
     */
84 32
    public function get($name)
85
    {
86
        try {
87 32
            return $this->resolve($name);
88 12
        } catch (Exception\ServiceNotFound $e) {
89 9
            return $this->autoWireClass($name);
90
        }
91
    }
92
93
    /**
94
     * Get parent container.
95
     *
96
     * @return ContainerInterface
97
     */
98 32
    public function getParent(): ?ContainerInterface
99
    {
100 32
        return $this->parent;
101
    }
102
103
    /**
104
     * Add service.
105
     *
106
     * @param string $name
107
     * @param mixed  $service
108
     *
109
     * @return Container
110
     */
111 3
    public function add(string $name, $service): self
112
    {
113 3
        if ($this->has($name)) {
114 1
            throw new Exception\ServiceAlreadyExists('service '.$name.' is already registered');
115
        }
116
117 3
        $this->registry[$name] = $service;
118
119 3
        return $this;
120
    }
121
122
    /**
123
     * Check if service is registered.
124
     *
125
     * @param mixed $name
126
     *
127
     * @return bool
128
     */
129 33
    public function has($name): bool
130
    {
131 33
        return isset($this->service[$name]);
132
    }
133
134
    /**
135
     * Set parent service on container
136
     * (Used internally, there is no point to call this method directly).
137
     *
138
     * @param mixed $service
139
     *
140
     * @return ContainerInterface
141
     */
142 1
    public function setParentService($service): ContainerInterface
143
    {
144 1
        $this->parent_service = $service;
145
146 1
        return $this;
147
    }
148
149
    /**
150
     * Get config.
151
     *
152
     * @return Config
153
     */
154 1
    public function getConfig(): Config
155
    {
156 1
        return $this->config;
157
    }
158
159
    /**
160
     * Resolve service.
161
     *
162
     * @param string $name
163
     *
164
     * @return mixed
165
     */
166 32
    public function resolve(string $name)
167
    {
168 32
        if ($this->has($name)) {
169 3
            return $this->service[$name];
170
        }
171
172 32
        if (isset($this->registry[$name])) {
173 3
            return $this->addStaticService($name);
174
        }
175
176 29
        if ($this->config->has($name)) {
177 26
            return $this->autoWireClass($name);
178
        }
179
180 9
        if (null !== $this->parent_service) {
181
            $parents = array_merge([$name], class_implements($this->parent_service), class_parents($this->parent_service));
182
183
            if (in_array($name, $parents, true) && $this->parent_service instanceof $name) {
184
                return $this->parent_service;
185
            }
186
        }
187
188 9
        if (null !== $this->parent) {
189
            return $this->parent->resolve($name);
190
        }
191
192 9
        throw new Exception\ServiceNotFound("service $name was not found in service tree");
193
    }
194
195
    /**
196
     * Check for static injections.
197
     *
198
     * @param string $name
199
     *
200
     * @return mixed
201
     */
202 3
    protected function addStaticService(string $name)
203
    {
204 3
        if ($this->registry[$name] instanceof Closure) {
205 1
            $this->service[$name] = $this->registry[$name]->call($this);
206
        } else {
207 2
            $this->service[$name] = $this->registry[$name];
208
        }
209
210 3
        unset($this->registry[$name]);
211
212 3
        return $this->service[$name];
213
    }
214
215
    /**
216
     * Auto wire.
217
     *
218
     * @param string $name
219
     * @param array  $config
220
     * @param array  $parents
221
     *
222
     * @return mixed
223
     */
224 29
    protected function autoWireClass(string $name)
225
    {
226 29
        $config = $this->config->get($name);
227 28
        $class = $config['use'];
228
229 28
        if (preg_match('#^\{([^{}]+)\}$#', $class, $match)) {
230 1
            return $this->wireReference($name, $match[1], $config);
231
        }
232
233
        try {
234 28
            $reflection = new ReflectionClass($class);
235 1
        } catch (\Exception $e) {
236 1
            throw new Exception\ServiceNotFound($class.' can not be resolved to an existing class for service '.$name);
237
        }
238
239 27
        $constructor = $reflection->getConstructor();
240
241 27
        if (null === $constructor) {
242 3
            return $this->storeService($name, $config, new $class());
243
        }
244
245 24
        $args = $this->autoWireMethod($name, $constructor, $config);
246
247 23
        return $this->createInstance($name, $reflection, $args, $config);
248
    }
249
250
    /**
251
     * Wire named referenced service.
252
     *
253
     * @param string $name
254
     * @param string $refrence
255
     * @param array  $config
256
     *
257
     * @return mixed
258
     */
259 1
    protected function wireReference(string $name, string $reference, array $config)
260
    {
261 1
        $service = $this->get($reference);
262 1
        $reflection = new ReflectionClass(get_class($service));
263 1
        $config = $this->config->get($name);
264 1
        $service = $this->prepareService($name, $service, $reflection, $config);
265
266 1
        return $service;
267
    }
268
269
    /**
270
     * Store service.
271
     *
272
     * @param param string $name
273
     * @param array        $config
274
     * @param mixed        $service
275
     *
276
     * @return mixed
277
     */
278 25
    protected function storeService(string $name, array $config, $service)
279
    {
280 25
        if (true === $config['singleton']) {
281 2
            return $service;
282
        }
283 24
        $this->service[$name] = $service;
284
285 24
        if (isset($this->children[$name])) {
286 1
            $this->children[$name]->setParentService($service);
287
        }
288
289 24
        return $service;
290
    }
291
292
    /**
293
     * Get instance (virtual or real instance).
294
     *
295
     * @param string          $name
296
     * @param ReflectionClass $class
297
     * @param array           $arguments
298
     * @param array           $config
299
     *
300
     * @return mixed
301
     */
302 23
    protected function createInstance(string $name, ReflectionClass $class, array $arguments, array $config)
303
    {
304 23
        if (true === $config['lazy']) {
305 2
            return $this->getProxyInstance($name, $class, $arguments, $config);
306
        }
307
308 21
        return $this->getRealInstance($name, $class, $arguments, $config);
309
    }
310
311
    /**
312
     * Create proxy instance.
313
     *
314
     * @param string          $name
315
     * @param ReflectionClass $class
316
     * @param array           $arguments
317
     * @param array           $config
318
     *
319
     * @return mixed
320
     */
321 2
    protected function getProxyInstance(string $name, ReflectionClass $class, array $arguments, array $config)
322
    {
323 2
        $factory = new LazyLoadingValueHolderFactory();
324 2
        $that = $this;
325
326 2
        return $factory->createProxy(
327 2
            $class->getName(),
328 2
            function (&$wrappedObject, $proxy, $method, $parameters, &$initializer) use ($that, $name,$class,$arguments,$config) {
329 1
                $wrappedObject = $that->getRealInstance($name, $class, $arguments, $config);
330 1
                $initializer = null;
331 2
            }
332
        );
333
    }
334
335
    /**
336
     * Create real instance.
337
     *
338
     * @param string          $name
339
     * @param ReflectionClass $class
340
     * @param array           $arguments
341
     * @param array           $config
342
     *
343
     * @return mixed
344
     */
345 22
    protected function getRealInstance(string $name, ReflectionClass $class, array $arguments, array $config)
346
    {
347 22
        $instance = $class->newInstanceArgs($arguments);
348 22
        $instance = $this->prepareService($name, $instance, $class, $config);
349
350 21
        return $instance;
351
    }
352
353
    /**
354
     * Prepare service (execute sub selects and excute setter injections).
355
     *
356
     * @param string          $name
357
     * @param mixed           $service
358
     * @param ReflectionClass $class
359
     * @param array           $config
360
     *
361
     * @return mixed
362
     */
363 22
    protected function prepareService(string $name, $service, ReflectionClass $class, array $config)
364
    {
365 22
        foreach ($config['selects'] as $select) {
366 2
            $args = $this->autoWireMethod($name, $class->getMethod($select['method']), $select);
367 2
            $service = call_user_func_array([&$service, $select['method']], $args);
368
        }
369
370 22
        $this->storeService($name, $config, $service);
371
        //$config = $this->config->get($name);
372
373 22
        foreach ($config['calls'] as $call) {
374 3
            if (!isset($call['method'])) {
375 1
                throw new Exception\InvalidConfiguration('method is required for setter injection in service '.$name);
376
            }
377
378 2
            $arguments = [];
0 ignored issues
show
Unused Code introduced by
The assignment to $arguments is dead and can be removed.
Loading history...
379
380
            try {
381 2
                $method = $class->getMethod($call['method']);
382
            } catch (\ReflectionException $e) {
383
                throw new Exception\InvalidConfiguration('method '.$call['method'].' is not callable in class '.$class->getName().' for service '.$name);
384
            }
385
386 2
            $arguments = $this->autoWireMethod($name, $method, $call);
387 2
            call_user_func_array([&$service, $call['method']], $arguments);
388
        }
389
390 21
        return $service;
391
    }
392
393
    /**
394
     * Autowire method.
395
     *
396
     * @param string           $name
397
     * @param ReflectionMethod $method
398
     * @param array            $config
399
     *
400
     * @return array
401
     */
402 24
    protected function autoWireMethod(string $name, ReflectionMethod $method, array $config): array
403
    {
404 24
        $params = $method->getParameters();
405 24
        $args = [];
406
407 24
        foreach ($params as $param) {
408 24
            $type = $param->getClass();
409 24
            $param_name = $param->getName();
410
411 24
            if (isset($config['arguments'][$param_name])) {
412 22
                $args[$param_name] = $this->parseParam($config['arguments'][$param_name], $name);
413 10
            } elseif (null !== $type) {
414 6
                $type_class = $type->getName();
415
416 6
                if ($type_class === $name) {
417
                    throw new Exception\InvalidConfiguration('class '.$type_class.' can not depend on itself');
418
                }
419
420 6
                $args[$param_name] = $this->findService($name, $type_class);
421 6
            } elseif ($param->isDefaultValueAvailable()) {
422 6
                $args[$param_name] = $param->getDefaultValue();
423
            } elseif ($param->allowsNull() && $param->hasType()) {
424
                $args[$param_name] = null;
425
            } else {
426 23
                throw new Exception\InvalidConfiguration('no value found for argument '.$param_name.' in method '.$method->getName().' for service '.$name);
427
            }
428
        }
429
430 23
        return $args;
431
    }
432
433
    /**
434
     * Parse param value.
435
     *
436
     * @param mixed  $param
437
     * @param string $name
438
     *
439
     * @return mixed
440
     */
441 22
    protected function parseParam($param, string $name)
442
    {
443 22
        if (is_iterable($param)) {
444
            foreach ($param as $key => $value) {
445
                $param[$key] = $this->parseParam($value, $name);
446
            }
447
448
            return $param;
449
        }
450
451 22
        if (is_string($param)) {
452 22
            $param = $this->config->getEnv($param);
453
454 21
            if (preg_match('#^\{\{([^{}]+)\}\}$#', $param, $matches)) {
455 1
                return '{'.$matches[1].'}';
456
            }
457 20
            if (preg_match('#^\{([^{}]+)\}$#', $param, $matches)) {
458
                return $this->findService($name, $matches[1]);
459
            }
460
461 20
            return $param;
462
        }
463
464
        return $param;
465
    }
466
467
    /**
468
     * Locate service.
469
     *
470
     * @param string $current_service
471
     * @param string $service
472
     */
473 6
    protected function findService(string $current_service, string $service)
474
    {
475 6
        if (isset($this->children[$current_service])) {
476
            return $this->children[$current_service]->get($service);
477
        }
478
479 6
        $config = $this->config->get($current_service);
480 6
        if (isset($config['services'])) {
481 1
            $this->children[$current_service] = new self($config['services'], $this);
482
483 1
            return $this->children[$current_service]->get($service);
484
        }
485
486 5
        return $this->get($service);
487
    }
488
}
489