Passed
Push — main ( b29636...65d534 )
by Sammy
07:11 queued 05:49
created

LeMarchand   C

Complexity

Total Complexity 53

Size/Duplication

Total Lines 272
Duplicated Lines 0 %

Importance

Changes 40
Bugs 8 Features 4
Metric Value
eloc 115
c 40
b 8
f 4
dl 0
loc 272
rs 6.96
wmc 53

17 Methods

Rating   Name   Duplication   Size   Complexity  
A makeInstance() 0 29 5
A isResolved() 0 3 1
A cascadeNamespace() 0 14 4
A resolved() 0 7 2
A hasPrivateContructor() 0 4 2
A isSetting() 0 2 1
A isInterface() 0 2 1
A getConstructorParameters() 0 19 4
A __debugInfo() 0 13 4
A __construct() 0 8 2
A has() 0 9 3
A getSettings() 0 14 3
B get() 0 44 10
A box() 0 10 3
A getInstance() 0 7 2
A hasEmbeddedConstructorParameters() 0 3 1
A wireInstance() 0 22 5

How to fix   Complexity   

Complex Class

Complex classes like LeMarchand often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use LeMarchand, and based on these observations, apply Extract Interface, too.

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 const RX_SETTINGS = '/^settings\./';
29
30
    public const RX_MVC = '/(Models|Controllers)\\\([a-zA-Z]+)(::class|::new)?/';
31
32
    public const RX_INTERFACE = '/([a-zA-Z]+)Interface$/';
33
34
35
    public static function box($settings = null): ContainerInterface
36
    {
37
        if (is_null(self::$instance)) {
38
            if (is_array($settings)) {
39
                return (self::$instance = new LeMarchand($settings));
40
            }
41
            throw new ContainerException('UNABLE_TO_OPEN_BOX');
42
        }
43
44
        return self::$instance;
45
    }
46
47
48
    private function __construct($settings)
49
    {
50
        if (isset($settings[__CLASS__])) {
51
            $this->namespace_cascade = $settings[__CLASS__]['cascade'] ?? [];
52
            $this->interface_wiring = $settings[__CLASS__]['wiring'] ?? [];
53
            unset($settings[__CLASS__]);
54
        }
55
        $this->configurations['settings'] = $settings;
56
    }
57
58
    public function __debugInfo(): array
59
    {
60
        $dbg = get_object_vars($this);
61
62
        foreach ($dbg['instance_cache'] as $class => $instance) {
63
            $dbg['instance_cache'][$class] = true;
64
        }
65
66
        foreach ($dbg['interface_wiring'] as $interface => $wire) {
67
            $dbg['interface_wiring'][$interface] = is_array($wire) ? array_shift($wire) . ' --array #' . count($wire) : $wire;
68
        }
69
70
        return $dbg;
71
    }
72
73
    public function has($configuration)
74
    {
75
        try {
76
            $this->get($configuration);
77
            return true;
78
        } catch (NotFoundExceptionInterface $e) {
79
            return false;
80
        } catch (ContainerExceptionInterface $e) {
81
            return false;
82
        }
83
    }
84
85
    public function isSetting($configuration): bool{
86
      return preg_match(self::RX_SETTINGS, $configuration) === 1;
87
    }
88
89
    public function isInterface($configuration): bool{
90
      return preg_match(self::RX_INTERFACE, $configuration) === 1;
91
    }
92
93
    public function get($configuration)
94
    {
95
        if (!is_string($configuration)) {
96
            throw new ContainerException($configuration);
97
        }
98
        // 1. is it a first level key ?
99
        if (isset($this->configurations[$configuration])) {
100
            return $this->configurations[$configuration];
101
        }
102
103
        // 2. is it configuration data ?
104
        if ($this->isSetting($configuration)) {
105
            return $this->getSettings($configuration);
106
        }
107
108
        // 3. is it an existing class ?
109
        if (class_exists($configuration)) {
110
            return $this->getInstance($configuration);
111
        }
112
113
        // 4. is it an interface ?
114
        if ($this->isInterface($configuration)) {
115
          return $this->wireInstance($configuration);
116
        }
117
118
        // 5. is it cascadable ?
119
        if (preg_match(self::RX_MVC, $configuration, $m) === 1) {
120
121
            $class_name = $this->cascadeNamespace($m[1] . '\\' . $m[2]);
122
123
            if (!isset($m[3])) {
124
                return $this->getInstance($class_name);
125
            }
126
127
            if ($m[3] === '::class') {
128
                return $class_name;
129
            }
130
131
            if ($m[3] === '::new') {
132
                return $this->makeInstance($class_name);
133
            }
134
        }
135
136
        throw new NotFoundException($configuration);
137
    }
138
139
140
    private function getSettings($setting)
141
    {
142
        // vd(__FUNCTION__);
143
        $ret = $this->configurations;
144
145
      //dot based hierarchy, parse and climb
146
        foreach (explode('.', $setting) as $k) {
147
            if (!isset($ret[$k])) {
148
                throw new NotFoundException($setting);
149
            }
150
            $ret = $ret[$k];
151
        }
152
153
        return $ret;
154
    }
155
156
    private function resolved($clue, $solution = null)
157
    {
158
        if (!is_null($solution)) {
159
            $this->resolved_cache[$clue] = $solution;
160
        }
161
        // vd($clue, __FUNCTION__);
162
        return $this->resolved_cache[$clue] ?? null;
163
    }
164
165
    private function isResolved($clue): bool
166
    {
167
        return isset($this->resolved_cache[$clue]);
168
    }
169
170
    private function cascadeNamespace($class_name)
171
    {
172
        if ($this->isResolved($class_name)) {
173
            return $this->resolved($class_name);
174
        }
175
176
        // not fully namespaced, lets cascade
177
        foreach ($this->namespace_cascade as $ns) {
178
            if (class_exists($fully_namespaced = $ns . $class_name)) {
179
                $this->resolved($class_name, $fully_namespaced);
180
                return $fully_namespaced;
181
            }
182
        }
183
        throw new NotFoundException($class_name);
184
    }
185
186
    private function wireInstance($interface)
187
    {
188
        if (!isset($this->interface_wiring[$interface])) {
189
            throw new NotFoundException($interface);
190
        }
191
192
        $wire = $this->interface_wiring[$interface];
193
194
        // interface + constructor params
195
        if ($this->hasEmbeddedConstructorParameters($wire)) {
196
            $class = array_shift($wire);
197
            $args = $wire;
198
        } else {
199
            $class = $wire;
200
            $args = null;
201
        }
202
203
        if ($this->isResolved($class) && $this->hasPrivateContructor($class)) {
204
            return $this->resolved($class);
205
        }
206
207
        return $this->getInstance($class, $args);
208
    }
209
210
    private function hasPrivateContructor($class_name): bool
211
    {
212
        $rc = new \ReflectionClass($class_name);
213
        return !is_null($constructor = $rc->getConstructor()) && $constructor->isPrivate();
214
    }
215
216
    private function hasEmbeddedConstructorParameters($wire)
217
    {
218
        return is_array($wire);
219
    }
220
221
    private function getInstance($class, $construction_args = [])
222
    {
223
        if (isset($this->instance_cache[$class])) {
224
            return $this->instance_cache[$class];
225
        }
226
227
        return $this->makeInstance($class, $construction_args);
228
    }
229
230
    private function makeInstance($class, $construction_args = [])
231
    {
232
        try {
233
            $rc = new \ReflectionClass($class);
234
            $instance = null;
235
236
            if (!is_null($constructor = $rc->getConstructor())) {
237
                $construction_args = $this->getConstructorParameters($constructor, $construction_args);
238
239
                if ($constructor->isPrivate()) { // singleton ?
240
                  // first argument is the static instance-making method
241
                    $singleton_method = $rc->getMethod(array_shift($construction_args));
242
                  // invoke the method with remaining constructor args
243
                    $instance = $this->resolved($class, $singleton_method->invoke(null, $construction_args));
244
                } else {
245
                    $instance = $rc->newInstanceArgs($construction_args);
246
                }
247
            } else {
248
                $instance = $rc->newInstanceArgs();
249
            }
250
251
            if ($rc->hasMethod('set_container')) {
252
                $instance->set_container($this);
253
            }
254
            $this->instance_cache[$class] = $instance;
255
256
            return $instance;
257
        } catch (\ReflectionException $e) {
258
            throw new ContainerException($e->getMessage());
259
        }
260
    }
261
262
    private function getConstructorParameters(\ReflectionMethod $constructor, $construction_args = [])
263
    {
264
      // vd(__FUNCTION__);
265
266
        if (empty($construction_args)) {
267
            foreach ($constructor->getParameters() as $param) {
268
                // try {
269
                if ($param->getType()) {
270
                    $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

270
                    $construction_args [] = $this->get($param->getType()->/** @scrutinizer ignore-call */ getName());
Loading history...
271
                } else {
272
                    $setting = 'settings.Constructor.' . $constructor->class . '.' . $param->getName();
273
                    $construction_args [] = $this->getSettings($setting);
274
                }
275
                // } catch (NotFoundExceptionInterface $e) {
276
                //     dd($e);
277
                // }
278
            }
279
        }
280
        return $construction_args;
281
    }
282
}
283