Passed
Pull Request — main (#1)
by ANDREY
11:21
created

Container::isInjectable()   A

Complexity

Conditions 5
Paths 5

Size

Total Lines 14
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 5

Importance

Changes 0
Metric Value
eloc 8
c 0
b 0
f 0
dl 0
loc 14
rs 9.6111
ccs 9
cts 9
cp 1
cc 5
nc 5
nop 1
crap 5
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 16
    public function registerContainers(array $manualConfig = []): void
28
    {
29 16
        self::$manualConfig = $manualConfig;
30 16
        $injectedClasses = [];
31 16
        $classes = get_declared_classes();
32 16
        $loadedClasses = array_combine($classes, $classes);
33 16
        $classesNeedCheck = array_merge($loadedClasses, $manualConfig);
34 16
        foreach ($classesNeedCheck as $alias => $class) {
35
            assert(is_string($class));
36 16
            if (class_exists($class)) {
37 16
                if ($this->isInjectable($class)) {
38 16
                    $injectedClasses[$alias] = $class;
39
                }
40
            } else {
41
                throw new NotFoundException("VPA\DI\Container::registerClasses: Class $class not found");
42
            }
43
        }
44 16
        self::$classes = $injectedClasses;
45
    }
46
47 3
    private function reloadContainers(): void
48
    {
49 3
        $this->registerContainers(self::$manualConfig);
50
    }
51
52 16
    private function entityIsInjectable(string $entity): bool
53
    {
54
        assert(class_exists($entity) || interface_exists($entity));
55 16
        $reflectionClass = new ReflectionClass($entity);
56 16
        $attributes = $reflectionClass->getAttributes();
57 16
        foreach ($attributes as $attribute) {
58 16
            $typeOfEntity = $attribute->getName();
59 16
            if ($typeOfEntity === 'VPA\DI\Injectable') {
60 16
                return true;
61
            }
62
        }
63 16
        return false;
64
    }
65
66 14
    private function parentClassIsInjectable(string $class): bool
67
    {
68 14
        $parents = class_parents($class);
69 14
        foreach ($parents as $parent) {
70 14
            if ($this->entityIsInjectable($parent)) {
71 14
                return true;
72
            }
73
        }
74 14
        return false;
75
    }
76
77 14
    private function interfaceIsInjectable(string $class): bool
78
    {
79 14
        $interfaces = class_implements($class);
80 14
        foreach ($interfaces as $interface) {
81 14
            if ($this->entityIsInjectable($interface)) {
82 14
                return true;
83
            }
84
        }
85 14
        return false;
86
    }
87
88 16
    private function isInjectable(string $class): bool
89
    {
90 16
        if ($this->entityIsInjectable($class)) {
91 16
            return true;
92
        }
93 16
        if (self::$bubblePropagation) {
94 14
            if ($this->parentClassIsInjectable($class)) {
95 14
                return true;
96
            }
97 14
            if ($this->interfaceIsInjectable($class)) {
98 14
                return true;
99
            }
100
        }
101 16
        return false;
102
    }
103
104 12
    private function prepareObject(string $aliasName, string $className, array $params = []): object
105
    {
106 12
        if ($this->has($className) || $this->isInjectable($className)) {
107 9
            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 9
    private function getObject(string $className, array $params): object
113
    {
114
        assert(class_exists($className));
115 9
        $reflectionClass = new ReflectionClass($className);
116 9
        $constructReflector = $reflectionClass->getConstructor();
117 9
        if (empty($constructReflector)) {
118 9
            return new $className;
119
        }
120
121 2
        $constructArguments = $constructReflector->getParameters();
122 2
        if (empty($constructArguments)) {
123
            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 12
    public function get(string $id, array $params = []): object
143
    {
144 12
        $class = self::$classes[$id] ?? $id;
145
        assert(is_string($class));
146 12
        return $this->prepareObject($id, $class, $params);
147
    }
148
149 16
    public function has(string $id): bool
150
    {
151 16
        $class = self::$classes[$id] ?? $id;
152 16
        return (isset(self::$classes[$id]) || $this->isInjectable($class));
153
    }
154
}