Passed
Branch main (64a808)
by ANDREY
11:04 queued 08:30
created

Container::checkInjectableTree()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 3

Importance

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