Test Failed
Push — main ( 64a808...fd2bfe )
by ANDREY
02:26
created

Container   A

Complexity

Total Complexity 32

Size/Duplication

Total Lines 141
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 32
eloc 67
c 1
b 0
f 0
dl 0
loc 141
rs 9.84
ccs 68
cts 68
cp 1

13 Methods

Rating   Name   Duplication   Size   Complexity  
A isInjectable() 0 14 5
A has() 0 4 2
A prepareObject() 0 6 3
A get() 0 5 1
A setBubblePropagation() 0 4 1
A checkInjectableTree() 0 8 3
A entityIsInjectable() 0 9 3
A reloadContainers() 0 3 1
A getObject() 0 27 6
A __construct() 0 2 1
A interfaceIsInjectable() 0 4 1
A parentClassIsInjectable() 0 4 1
A registerContainers() 0 16 4
1
<?php
2
declare(strict_types=1);
3
4
5
namespace VPA\DI;
6
7
use PHPUnit\Exception;
8
use Psr\Container\ContainerInterface;
9
use ReflectionClass;
10
use ReflectionNamedType;
11
12
class Container implements ContainerInterface
13
{
14
    private static array $classes = [];
15
    private static bool $bubblePropagation = true;
16
    private static array $manualConfig;
17
18
    function __construct()
19
    {
20
    }
21
22 3
    public function setBubblePropagation(bool $bubblePropagation): void
23
    {
24 3
        self::$bubblePropagation = $bubblePropagation;
25 3
        $this->reloadContainers();
26
    }
27
28
    /**
29
     * @param array $manualConfig
30
     * @throws NotFoundException
31
     */
32 18
    public function registerContainers(array $manualConfig = []): void
33
    {
34 18
        self::$manualConfig = $manualConfig;
35 18
        $injectedClasses = [];
36 18
        $classes = get_declared_classes();
37 18
        $loadedClasses = array_combine($classes, $classes);
38 18
        $classesNeedCheck = array_merge($loadedClasses, $manualConfig);
39 18
        foreach ($classesNeedCheck as $alias => $class) {
40
            assert(is_string($class));
41 18
            if (class_exists($class)) {
42 18
                if ($this->isInjectable($class)) {
43 18
                    $injectedClasses[$alias] = $class;
44
                }
45
            }
46
        }
47 18
        self::$classes = $injectedClasses;
48
    }
49
50 3
    private function reloadContainers(): void
51
    {
52 3
        $this->registerContainers(self::$manualConfig);
53
    }
54
55 18
    private function entityIsInjectable(string $entity): bool
56
    {
57
        assert(class_exists($entity) || interface_exists($entity));
58
        try {
59 18
        $reflectionClass = new ReflectionClass($entity);
60 1
        } catch (\ReflectionException $e) {
61 1
            throw new NotFoundException("VPA\DI\Container::registerClasses: Class $entity not found");
62
        }
63 18
        return !empty($reflectionClass->getAttributes(Injectable::class));
64
    }
65
66 16
    private function parentClassIsInjectable(string $class): bool
67
    {
68 16
        $parents = class_parents($class);
69 16
        return $this->checkInjectableTree($parents);
70
    }
71
72 16
    private function interfaceIsInjectable(string $class): bool
73
    {
74 16
        $interfaces = class_implements($class);
75 16
        return $this->checkInjectableTree($interfaces);
76
    }
77
78 16
    private function checkInjectableTree(array $tree): bool
79
    {
80 16
        foreach ($tree as $branch) {
81 16
            if ($this->entityIsInjectable($branch)) {
82 16
                return true;
83
            }
84
        }
85 16
        return false;
86
    }
87
88 18
    private function isInjectable(string $class): bool
89
    {
90 18
        if ($this->entityIsInjectable($class)) {
91 18
            return true;
92
        }
93 18
        if (self::$bubblePropagation) {
94 16
            if ($this->parentClassIsInjectable($class)) {
95 16
                return true;
96
            }
97 16
            if ($this->interfaceIsInjectable($class)) {
98 16
                return true;
99
            }
100
        }
101 18
        return false;
102
    }
103
104 14
    private function prepareObject(string $aliasName, string $className, array $params = []): object
105
    {
106 14
        if ($this->has($className) || $this->isInjectable($className)) {
107 10
            return $this->getObject($className, $params);
108
        }
109 3
        throw new NotFoundException("VPA\DI\Container::get('$aliasName->$className'): Class with attribute Injectable not found. Check what class exists and attribute Injectable is set");
110
    }
111
112 10
    private function getObject(string $className, array $params): object
113
    {
114
        assert(class_exists($className));
115 10
        $reflectionClass = new ReflectionClass($className);
116 10
        $constructReflector = $reflectionClass->getConstructor();
117 10
        if (empty($constructReflector)) {
118 9
            return new $className;
119
        }
120
121 3
        $constructArguments = $constructReflector->getParameters();
122 3
        if (empty($constructArguments)) {
123 1
            return new $className;
124
        }
125 2
        $args = [];
126 2
        foreach ($constructArguments as $argument) {
127 2
            $argumentType = $argument->getType();
128 2
            $argumentName = $argument->getName();
129
            assert($argumentType instanceof ReflectionNamedType);
130 2
            $argumentTypeName = $argumentType->getName();
131 2
            if (class_exists($argumentTypeName) || interface_exists($argumentTypeName)) {
132 2
                $args[$argumentName] = $this->get($argumentTypeName);
133
            } else {
134 1
                $args[$argumentName] = $params[$argumentName] ?? null;
135
            }
136
        }
137
138 2
        return new $className(...$args);
139
    }
140
141
142 14
    public function get(string $id, array $params = []): object
143
    {
144 14
        $class = self::$classes[$id] ?? $id;
145
        assert(is_string($class));
146 14
        return $this->prepareObject($id, $class, $params);
147
    }
148
149 18
    public function has(string $id): bool
150
    {
151 18
        $class = self::$classes[$id] ?? $id;
152 18
        return (isset(self::$classes[$id]) || $this->isInjectable($class));
153
    }
154
}