Passed
Push — modular-container ( c3741a )
by Viktor
03:13
created

ModuleContainer::getSubmoduleContainer()   A

Complexity

Conditions 5
Paths 9

Size

Total Lines 30
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 30

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 5
eloc 18
c 1
b 0
f 0
nc 9
nop 1
dl 0
loc 30
ccs 0
cts 16
cp 0
crap 30
rs 9.3554
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Di;
6
7
use InvalidArgumentException;
8
use Psr\Container\ContainerInterface;
9
use Yiisoft\Factory\Definition\ArrayDefinition;
10
use Yiisoft\Factory\Definition\Normalizer;
11
use Yiisoft\Factory\Exception\NotFoundException;
12
13
final class ModuleContainer implements ContainerInterface
14
{
15
    private array $building = [];
16
    private array $resolved = [];
17
    private string $namespace;
18
    private array $definitions;
19
    private array $submoduleContainers = [];
20
    private array $submoduleDefinitions;
21
22
    public function __construct(
23
        string $namespace,
24
        array $definitions,
25
        array $submoduleDefinitions = []
26
    ) {
27
        $this->namespace = $namespace;
28
        $this->definitions = $definitions;
29
        $this->submoduleDefinitions = $submoduleDefinitions;
30
    }
31
32
    /**
33
     * @inheritDoc
34
     */
35
    public function get($id)
36
    {
37
        return $this->resolved[$id] ?? $this->resolve($id);
38
    }
39
40
    public function has($id): bool
41
    {
42
        return $id === ContainerInterface::class
43
            || isset($this->resolved[$id])
44
            || isset($this->definitions[$id])
45
            || strpos($id, $this->namespace) === 0;
46
    }
47
48
    /**
49
     * @param string $id
50
     *
51
     * @return mixed
52
     */
53
    private function resolve(string $id)
54
    {
55
        if ($id === ContainerInterface::class) {
56
            return $this;
57
        }
58
59
        if (isset($this->building[$id])) {
60
            throw new InvalidArgumentException(
61
                sprintf(
62
                    'Circular reference to "%s" detected while building: %s.',
63
                    $id,
64
                    implode(',', array_keys($this->building))
65
                )
66
            );
67
        }
68
        $this->building[$id] = true;
69
70
        try {
71
            if (isset($this->definitions[$id])) {
72
                $this->resolved[$id] = $this->build($this->definitions[$id]);
73
            } elseif (class_exists($id)) {
74
                $container = $this->getSubmoduleContainer($id);
75
                if ($container === null) {
76
                    if ($this->getNamespaceMatch($id, $this->namespace) < count(explode('\\', $this->namespace))) {
77
                        throw new NotFoundException($id);
78
                    }
79
80
                    $this->resolved[$id] = $this->build($id);
81
                } else {
82
                    $this->resolved[$id] = $container->get($id);
83
                }
84
            }
85
        } finally {
86
            unset($this->building[$id]);
87
        }
88
89
        if (isset($this->resolved[$id])) {
90
            return $this->resolved[$id];
91
        }
92
93
        throw new NotFoundException($id);
94
    }
95
96
    private function build($definition)
97
    {
98
        if (is_string($definition)) {
99
            $definition = new ArrayDefinition([ArrayDefinition::CLASS_NAME => $definition], false);
100
        } else {
101
            $definition = Normalizer::normalize($definition);
102
        }
103
104
        return $definition->resolve($this);
105
    }
106
107
    private function getSubmoduleContainer(string $id): ?ModuleContainer
108
    {
109
        $namespaceChosen = null;
110
        $length = $this->getNamespaceMatch($id, $this->namespace);
111
112
        foreach ($this->submoduleDefinitions as $namespace => $definitions) {
113
            $match = $this->getNamespaceMatch($id, $namespace);
114
            if ($match > $length) {
115
                $length = $match;
116
                $namespaceChosen = $namespace;
117
            }
118
        }
119
120
        if ($namespaceChosen !== null) {
121
            if (!isset($this->submoduleContainers[$namespaceChosen])) {
122
                $definitions = $this->submoduleDefinitions[$namespaceChosen];
123
                $submodules = $definitions[ModuleRootContainer::KEY_SUBMODULES] ?? [];
124
                unset($definitions[ModuleRootContainer::KEY_SUBMODULES]);
125
126
                $this->submoduleContainers[$namespaceChosen] = new ModuleContainer(
127
                    $namespaceChosen,
128
                    $definitions,
129
                    $submodules
130
                );
131
            }
132
133
            return $this->submoduleContainers[$namespaceChosen];
134
        }
135
136
        return null;
137
    }
138
139
    private function getNamespaceMatch(string $className, string $namespace): int
140
    {
141
        $idNamespace = explode('\\', trim($className, '\\'));
142
        array_pop($idNamespace); // remove class name
143
144
        $namespaceDivided = explode('\\', $namespace);
145
146
        $result = 0;
147
        foreach ($namespaceDivided as $i => $part) {
148
            if ($idNamespace[$i] === $part) {
149
                $result++;
150
            } else {
151
                return $result;
152
            }
153
        }
154
155
        return $result;
156
    }
157
}
158