Container   A
last analyzed

Complexity

Total Complexity 36

Size/Duplication

Total Lines 153
Duplicated Lines 0 %

Test Coverage

Coverage 98.67%

Importance

Changes 3
Bugs 0 Features 0
Metric Value
wmc 36
eloc 71
c 3
b 0
f 0
dl 0
loc 153
rs 9.52
ccs 74
cts 75
cp 0.9867

13 Methods

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