Passed
Push — main ( f9bc30...9f095f )
by Sammy
01:20
created

LeMarchand::get()   B

Complexity

Conditions 10
Paths 16

Size

Total Lines 40
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

Changes 25
Bugs 3 Features 4
Metric Value
eloc 23
c 25
b 3
f 4
dl 0
loc 40
rs 7.6666
cc 10
nc 16
nop 1

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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
        $ret = null;
90
91
92
        if ($this->isFirstLevelKey($configuration_string)) {
93
            return $this->configurations[$configuration_string];
94
        }
95
96
        // not a simple configuration string, it has meaning
97
        $configuration = new Configuration($configuration_string);
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
            // 5. is it cascadable ?
106
107
            $class_name = $configuration->getModelOrControllerName();
108
            $class_name = $this->cascadeNamespace($class_name);
109
110
            if ($configuration->hasClassNameModifier()) {
111
                $ret = $class_name;
0 ignored issues
show
Unused Code introduced by
The assignment to $ret is dead and can be removed.
Loading history...
112
            } elseif ($configuration->hasNewInstanceModifier()) {
113
                $ret = $this->makeInstance($class_name);
114
            }
115
            $ret = $this->getInstance($class_name);
116
        }
117
118
        if (is_null($ret)) {
119
            throw new NotFoundException($configuration);
120
        }
121
122
        return $ret;
123
    }
124
125
    public function isFirstLevelKey($configuration_string)
126
    {
127
        return isset($this->configurations[$configuration_string]);
128
    }
129
130
    private function getSettings($setting)
131
    {
132
        // vd(__FUNCTION__);
133
        $ret = $this->configurations;
134
135
      //dot based hierarchy, parse and climb
136
        foreach (explode('.', $setting) as $k) {
137
            if (!isset($ret[$k])) {
138
                throw new NotFoundException($setting);
139
            }
140
            $ret = $ret[$k];
141
        }
142
143
        return $ret;
144
    }
145
146
    private function resolved($clue, $solution = null)
147
    {
148
        if (!is_null($solution)) {
149
            $this->resolved_cache[$clue] = $solution;
150
        }
151
        // vd($clue, __FUNCTION__);
152
        return $this->resolved_cache[$clue] ?? null;
153
    }
154
155
    private function isResolved($clue): bool
156
    {
157
        return isset($this->resolved_cache[$clue]);
158
    }
159
160
    private function cascadeNamespace($class_name)
161
    {
162
        if ($this->isResolved($class_name)) {
163
            return $this->resolved($class_name);
164
        }
165
166
        // not fully namespaced, lets cascade
167
        foreach ($this->namespace_cascade as $ns) {
168
            if (class_exists($fully_namespaced = $ns . $class_name)) {
169
                $this->resolved($class_name, $fully_namespaced);
170
                return $fully_namespaced;
171
            }
172
        }
173
        throw new NotFoundException($class_name);
174
    }
175
176
    private function wireInstance($interface)
177
    {
178
        if (!isset($this->interface_wiring[$interface])) {
179
            throw new NotFoundException($interface);
180
        }
181
182
        $wire = $this->interface_wiring[$interface];
183
184
        // interface + constructor params
185
        if ($this->hasEmbeddedConstructorParameters($wire)) {
186
            $class = array_shift($wire);
187
            $args = $wire;
188
        } else {
189
            $class = $wire;
190
            $args = null;
191
        }
192
193
        if ($this->isResolved($class) && $this->hasPrivateContructor($class)) {
194
            return $this->resolved($class);
195
        }
196
197
        return $this->getInstance($class, $args);
198
    }
199
200
    private function hasPrivateContructor($class_name): bool
201
    {
202
        $rc = new \ReflectionClass($class_name);
203
        return !is_null($constructor = $rc->getConstructor()) && $constructor->isPrivate();
204
    }
205
206
    private function hasEmbeddedConstructorParameters($wire)
207
    {
208
        return is_array($wire);
209
    }
210
211
    private function getInstance($class, $construction_args = [])
212
    {
213
        if (isset($this->instance_cache[$class])) {
214
            return $this->instance_cache[$class];
215
        }
216
217
        return $this->makeInstance($class, $construction_args);
218
    }
219
220
    private function makeInstance($class, $construction_args = [])
221
    {
222
        try {
223
            $rc = new \ReflectionClass($class);
224
            $instance = null;
225
226
            if (!is_null($constructor = $rc->getConstructor())) {
227
                $construction_args = $this->getConstructorParameters($constructor, $construction_args);
228
229
                if ($constructor->isPrivate()) { // singleton ?
230
                  // first argument is the static instance-making method
231
                    $singleton_method = $rc->getMethod(array_shift($construction_args));
232
                  // invoke the method with remaining constructor args
233
                    $instance = $this->resolved($class, $singleton_method->invoke(null, $construction_args));
234
                } else {
235
                    $instance = $rc->newInstanceArgs($construction_args);
236
                }
237
            } else {
238
                $instance = $rc->newInstanceArgs();
239
            }
240
241
            if ($rc->hasMethod('set_container')) {
242
                $instance->set_container($this);
243
            }
244
            $this->instance_cache[$class] = $instance;
245
246
            return $instance;
247
        } catch (\ReflectionException $e) {
248
            throw new ContainerException($e->getMessage());
249
        }
250
    }
251
252
    private function getConstructorParameters(\ReflectionMethod $constructor, $construction_args = [])
253
    {
254
      // vd(__FUNCTION__);
255
256
        if (empty($construction_args)) {
257
            foreach ($constructor->getParameters() as $param) {
258
                // try {
259
                if ($param->getType()) {
260
                    $construction_args [] = $this->get($param->getType()->getName());
0 ignored issues
show
Bug introduced by
The method getName() does not exist on ReflectionType. It seems like you code against a sub-type of ReflectionType such as ReflectionNamedType. ( Ignorable by Annotation )

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

260
                    $construction_args [] = $this->get($param->getType()->/** @scrutinizer ignore-call */ getName());
Loading history...
261
                } else {
262
                    $setting = 'settings.Constructor.' . $constructor->class . '.' . $param->getName();
263
                    $construction_args [] = $this->getSettings($setting);
264
                }
265
                // } catch (NotFoundExceptionInterface $e) {
266
                //     dd($e);
267
                // }
268
            }
269
        }
270
        return $construction_args;
271
    }
272
}
273