Passed
Push — main ( 9f095f...31373f )
by Sammy
01:22
created

LeMarchand::cascadeInstance()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 14
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 8
c 1
b 0
f 0
dl 0
loc 14
rs 10
cc 3
nc 3
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;
1 ignored issue
show
Unused Code introduced by
return false is not reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
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
        $configuration = new Configuration($configuration_string);
95
96
        $ret = null;
97
98
        if ($configuration->isSettings()) {
99
            $ret = $this->getSettings($configuration);
100
        } elseif (class_exists($lament)) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $lament seems to be never defined.
Loading history...
101
            $ret = $this->getInstance($configuration);
102
        } elseif ($configuration->isInterface()) {
103
            $ret = $this->wireInstance($configuration);
104
        } elseif ($configuration->hasModelOrController()) {
0 ignored issues
show
Bug introduced by
The method hasModelOrController() does not exist on HexMakina\LeMarchand\Configuration. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

104
        } elseif ($configuration->/** @scrutinizer ignore-call */ hasModelOrController()) {

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
105
            $ret = $this->cascadeInstance($configuration);
106
        }
107
108
        if (is_null($ret)) {
109
            throw new NotFoundException($configuration);
110
        }
111
112
        return $ret;
113
    }
114
115
    private function isFirstLevelKey($configuration_string)
116
    {
117
        return isset($this->configurations[$configuration_string]);
118
    }
119
120
    private function getSettings($setting)
121
    {
122
        // vd(__FUNCTION__);
123
        $ret = $this->configurations;
124
125
      //dot based hierarchy, parse and climb
126
        foreach (explode('.', $setting) as $k) {
127
            if (!isset($ret[$k])) {
128
                throw new NotFoundException($setting);
129
            }
130
            $ret = $ret[$k];
131
        }
132
133
        return $ret;
134
    }
135
136
    private function cascadeInstance($configuration){
137
        $class_name = $configuration->getModelOrControllerName();
138
        $class_name = $this->cascadeNamespace($class_name);
139
140
        if ($configuration->hasClassNameModifier()) {
141
            $ret = $class_name;
0 ignored issues
show
Unused Code introduced by
The assignment to $ret is dead and can be removed.
Loading history...
142
        }
143
        elseif ($configuration->hasNewInstanceModifier()) {
144
            $ret = $this->makeInstance($class_name);
145
        }
146
147
        $ret = $this->getInstance($class_name);
148
149
        return $ret;
150
    }
151
    
152
    public function resolved($clue, $solution = null)
153
    {
154
        if (!is_null($solution)) {
155
            $this->resolved_cache[$clue] = $solution;
156
        }
157
        // vd($clue, __FUNCTION__);
158
        return $this->resolved_cache[$clue] ?? null;
159
    }
160
161
    private function isResolved($clue): bool
162
    {
163
        return isset($this->resolved_cache[$clue]);
164
    }
165
166
    private function cascadeNamespace($class_name)
167
    {
168
        if ($this->isResolved($class_name)) {
169
            return $this->resolved($class_name);
170
        }
171
172
        // not fully namespaced, lets cascade
173
        foreach ($this->namespace_cascade as $ns) {
174
            if (class_exists($fully_namespaced = $ns . $class_name)) {
175
                $this->resolved($class_name, $fully_namespaced);
176
                return $fully_namespaced;
177
            }
178
        }
179
        throw new NotFoundException($class_name);
180
    }
181
182
    private function wireInstance($interface)
183
    {
184
        if (!isset($this->interface_wiring[$interface])) {
185
            throw new NotFoundException($interface);
186
        }
187
188
        $wire = $this->interface_wiring[$interface];
189
190
        // interface + constructor params
191
        if ($this->hasEmbeddedConstructorParameters($wire)) {
192
            $class = array_shift($wire);
193
            $args = $wire;
194
        } else {
195
            $class = $wire;
196
            $args = null;
197
        }
198
199
        if ($this->isResolved($class) && $this->hasPrivateContructor($class)) {
200
            return $this->resolved($class);
201
        }
202
203
        return $this->getInstance($class, $args);
204
    }
205
206
    private function hasPrivateContructor($class_name): bool
207
    {
208
        $rc = new \ReflectionClass($class_name);
209
        return !is_null($constructor = $rc->getConstructor()) && $constructor->isPrivate();
210
    }
211
212
    private function hasEmbeddedConstructorParameters($wire)
213
    {
214
        return is_array($wire);
215
    }
216
217
    private function getInstance($class, $construction_args = [])
218
    {
219
        if (isset($this->instance_cache[$class])) {
220
            return $this->instance_cache[$class];
221
        }
222
223
        return $this->makeInstance($class, $construction_args);
224
    }
225
226
    private function makeInstance($class, $construction_args = [])
227
    {
228
        $instance = ReflectionFactory::make($class, $construction_args, $this);
229
        $this->instance_cache[$class] = $instance;
230
        return $instance;
231
    }
232
}
233