Passed
Push — main ( 52c3fb...56f34e )
by Sammy
01:27
created

LeMarchand::processComplexConfigurationString()   A

Complexity

Conditions 5
Paths 5

Size

Total Lines 17
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 11
c 0
b 0
f 0
dl 0
loc 17
rs 9.6111
cc 5
nc 5
nop 1
1
<?php
2
3
namespace HexMakina\LeMarchand;
4
5
use Psr\Container\ContainerInterface;
6
use Psr\Container\ContainerExceptionInterface;
7
use Psr\Container\NotFoundExceptionInterface;
8
9
class LeMarchand implements ContainerInterface
10
{
11
    private static $instance = null;
12
    // stores all the settings
13
    private $configurations = [];
14
15
    // stores the namespace cascade
16
    private $namespace_cascade = [];
17
18
    // stores the interface to class wiring
19
    private $interface_wiring = [];
20
21
    // store the resolved names for performance
22
    private $resolved_cache = [];
23
24
    // stores the automatically created instances, by class name
25
    private $instance_cache = [];
26
27
28
    public static function box($settings = null): ContainerInterface
29
    {
30
        if (is_null(self::$instance)) {
31
            if (is_array($settings)) {
32
                return (self::$instance = new LeMarchand($settings));
33
            }
34
            throw new ContainerException('UNABLE_TO_OPEN_BOX');
35
        }
36
37
        return self::$instance;
38
    }
39
40
41
    private function __construct($settings)
42
    {
43
        if (isset($settings[__CLASS__])) {
44
            $this->namespace_cascade = $settings[__CLASS__]['cascade'] ?? [];
45
            $this->interface_wiring = $settings[__CLASS__]['wiring'] ?? [];
46
            unset($settings[__CLASS__]);
47
        }
48
        $this->configurations['settings'] = $settings;
49
    }
50
51
    public function __debugInfo(): array
52
    {
53
        $dbg = get_object_vars($this);
54
55
        foreach ($dbg['instance_cache'] as $class => $instance) {
56
            $dbg['instance_cache'][$class] = true;
57
        }
58
59
        foreach ($dbg['interface_wiring'] as $interface => $wire) {
60
            if (is_array($wire)) {
61
                $wire = array_shift($wire) . ' --array #' . count($wire);
62
            }
63
            $dbg['interface_wiring'][$interface] = $wire;
64
        }
65
66
        return $dbg;
67
    }
68
69
    public function has($configuration)
70
    {
71
        try {
72
            $this->get($configuration);
73
            return true;
74
        } catch (NotFoundExceptionInterface $e) {
75
            return false;
76
        } catch (ContainerExceptionInterface $e) {
77
            return false;
78
        }
79
        return false;
80
    }
81
82
83
    public function get($configuration_string)
84
    {
85
        if (!is_string($configuration_string)) {
86
            throw new ContainerException($configuration_string);
87
        }
88
89
        if ($this->isFirstLevelKey($configuration_string)) {
90
            return $this->configurations[$configuration_string];
91
        }
92
93
        // not a simple configuration string, it has meaning
94
        $res = $this->processComplexConfigurationString($configuration_string);
95
96
        if (!is_null($res)) {
97
            throw new NotFoundException($configuration_string);
98
        }
99
100
        return $res;
101
    }
102
103
    private function processComplexConfigurationString($configuration_string)
104
    {
105
        $configuration = new Configuration($configuration_string);
106
107
        $ret = null;
108
109
        if ($configuration->isSettings()) {
110
            $ret = $this->getSettings($configuration);
111
        } elseif (class_exists($lament)) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $lament seems to be never defined.
Loading history...
112
            $ret = $this->getInstance($configuration);
113
        } elseif ($configuration->isInterface()) {
114
            $ret = $this->wireInstance($configuration);
115
        } elseif ($configuration->isModelOrController()) {
116
            $ret = $this->cascadeInstance($configuration);
117
        }
118
119
        return $ret;
120
    }
121
122
    private function isFirstLevelKey($configuration_string)
123
    {
124
        return isset($this->configurations[$configuration_string]);
125
    }
126
127
    private function getSettings($setting)
128
    {
129
        // vd(__FUNCTION__);
130
        $ret = $this->configurations;
131
132
      //dot based hierarchy, parse and climb
133
        foreach (explode('.', $setting) as $k) {
134
            if (!isset($ret[$k])) {
135
                throw new NotFoundException($setting);
136
            }
137
            $ret = $ret[$k];
138
        }
139
140
        return $ret;
141
    }
142
143
    private function cascadeInstance(Configuration $configuration){
144
        $class_name = $configuration->getModelOrControllerName();
145
        $class_name = $this->cascadeNamespace($class_name);
146
147
        if ($configuration->hasClassNameModifier()) {
148
            $ret = $class_name;
0 ignored issues
show
Unused Code introduced by
The assignment to $ret is dead and can be removed.
Loading history...
149
        }
150
        elseif ($configuration->hasNewInstanceModifier()) {
151
            $ret = $this->makeInstance($class_name);
152
        }
153
154
        $ret = $this->getInstance($class_name);
155
156
        return $ret;
157
    }
158
159
    public function resolved($clue, $solution = null)
160
    {
161
        if (!is_null($solution)) {
162
            $this->resolved_cache[$clue] = $solution;
163
        }
164
        // vd($clue, __FUNCTION__);
165
        return $this->resolved_cache[$clue] ?? null;
166
    }
167
168
    private function isResolved($clue): bool
169
    {
170
        return isset($this->resolved_cache[$clue]);
171
    }
172
173
    private function cascadeNamespace($class_name)
174
    {
175
        if ($this->isResolved($class_name)) {
176
            return $this->resolved($class_name);
177
        }
178
179
        // not fully namespaced, lets cascade
180
        foreach ($this->namespace_cascade as $ns) {
181
            if (class_exists($fully_namespaced = $ns . $class_name)) {
182
                $this->resolved($class_name, $fully_namespaced);
183
                return $fully_namespaced;
184
            }
185
        }
186
        throw new NotFoundException($class_name);
187
    }
188
189
    private function wireInstance($interface)
190
    {
191
        if (!isset($this->interface_wiring[$interface])) {
192
            throw new NotFoundException($interface);
193
        }
194
195
        $wire = $this->interface_wiring[$interface];
196
197
        // interface + constructor params
198
        if ($this->hasEmbeddedConstructorParameters($wire)) {
199
            $class = array_shift($wire);
200
            $args = $wire;
201
        } else {
202
            $class = $wire;
203
            $args = null;
204
        }
205
206
        if ($this->isResolved($class) && $this->hasPrivateContructor($class)) {
207
            return $this->resolved($class);
208
        }
209
210
        return $this->getInstance($class, $args);
211
    }
212
213
    private function hasPrivateContructor($class_name): bool
214
    {
215
        $rc = new \ReflectionClass($class_name);
216
        return !is_null($constructor = $rc->getConstructor()) && $constructor->isPrivate();
217
    }
218
219
    private function hasEmbeddedConstructorParameters($wire)
220
    {
221
        return is_array($wire);
222
    }
223
224
    private function getInstance($class, $construction_args = [])
225
    {
226
        if (isset($this->instance_cache[$class])) {
227
            return $this->instance_cache[$class];
228
        }
229
230
        return $this->makeInstance($class, $construction_args);
231
    }
232
233
    private function makeInstance($class, $construction_args = [])
234
    {
235
        $instance = ReflectionFactory::make($class, $construction_args, $this);
236
        $this->instance_cache[$class] = $instance;
237
        return $instance;
238
    }
239
}
240