Passed
Push — main ( c645a3...262e33 )
by Sammy
07:03 queued 05:33
created

LeMarchand::hasPrivateContructor()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 2
c 0
b 0
f 0
dl 0
loc 4
rs 10
cc 2
nc 2
nop 1
1
<?php
2
3
namespace HexMakina\LeMarchand;
4
5
use Psr\Container\{ContainerInterface, ContainerExceptionInterface, NotFoundExceptionInterface};
6
7
class LeMarchand implements ContainerInterface
8
{
9
    private static $instance = null;
10
    // stores all the settings
11
    private $configurations = [];
12
13
    // stores the namespace cascade
14
    private $namespace_cascade = [];
15
16
    // stores the interface to class wiring
17
    private $interface_wiring = [];
18
19
    // store the resolved names for performance
20
    private $resolved_cache = [];
21
    // public const RX_CONTROLLER_NAME = '/([a-zA-Z]+)Controller$/';
22
    // public const RX_MODEL_CLASS = '/([a-zA-Z]+)(Class|Model)$/';
23
    const RX_SETTINGS = '/^settings\./';
24
    // public const RX_INTERFACE_NAME = '/([a-zA-Z]+)Interface$/';
25
    const RX_CLASS_NAME = '/([a-zA-Z]+)(Class|Model|Controller|Interface)$/';
26
    const RX_INTERFACE = '/([a-zA-Z]+)Interface$/';
27
28
    const RX_MVC = '/(Models|Controllers)\\\([a-zA-Z]+)(::class)?/';
29
30
    public static function box($settings = null): ContainerInterface
31
    {
32
        if (is_null(self::$instance)) {
33
            if (is_array($settings)) {
34
                return (self::$instance = new LeMarchand($settings));
35
            }
36
            throw new LamentException('UNABLE_TO_OPEN_BOX');
37
        }
38
39
        return self::$instance;
40
    }
41
42
43
    private function __construct($settings)
44
    {
45
        $this->configurations['settings'] = $settings;
46
        if (isset($settings['LeMarchand'])) {
47
            $this->namespace_cascade = $settings['LeMarchand']['cascade'] ?? [];
48
            $this->interface_wiring = $settings['LeMarchand']['wiring'] ?? [];
49
        }
50
    }
51
52
    public function __debugInfo(): array
53
    {
54
        $dbg = get_object_vars($this);
55
        if (isset($dbg['configurations']['template_engine'])) {
56
          // way too long of an object
57
            $dbg['configurations']['template_engine'] = get_class($dbg['configurations']['template_engine']);
58
        }
59
        return $dbg;
60
    }
61
62
    public function put($configuration, $instance)
63
    {
64
        if (!is_string($configuration)) {
65
            throw new LamentException($configuration);
66
        }
67
        $this->configurations[$configuration] = $instance;
68
    }
69
70
    public function has($configuration)
71
    {
72
        try {
73
            $this->get($configuration);
74
            return true;
75
        } catch (NotFoundExceptionInterface $e) {
76
            return false;
77
        } catch (ContainerExceptionInterface $e) {
78
            return false;
79
        }
80
    }
81
82
83
    public function get($configuration)
84
    {
85
        if (!is_string($configuration)) {
86
            throw new LamentException($configuration);
87
        }
88
        // 1. is it a first level key ?
89
        if (isset($this->configurations[$configuration])) {
90
            return $this->configurations[$configuration];
91
        }
92
93
        // 2. is it configuration data ?
94
        if (preg_match(self::RX_SETTINGS, $configuration, $m) === 1) {
95
            return $this->getSettings($configuration);
96
        }
97
98
        // 3. is it an existing class
99
        if (class_exists($configuration)) {
100
            return $this->getInstance($configuration);
101
        }
102
103
104
        // vdt($configuration, __FUNCTION__);
105
        if (preg_match(self::RX_MVC, $configuration, $m) === 1) {
106
            return $this->classification($m[2], $m[1], isset($m[3]));
107
        }
108
109
        // if it is an interface, we respond with an instance
110
        if (preg_match(self::RX_INTERFACE, $configuration, $m) === 1) {
111
          // vdt($configuration,__FUNCTION__);
112
            return $this->wireInstance($configuration);
113
            // return $this->classification($m[2], $m[1], isset($m[3]));
114
        }
115
116
        // 3. is it a class
117
        // vdt($configuration, __FUNCTION__);
118
        // if (preg_match(self::RX_CLASS_NAME, $configuration, $m) === 1) {
119
        //     return $this->classification($m[1], $m[2]);
120
        // }
121
122
        throw new ConfigurationException($configuration);
123
    }
124
125
126
    private function getSettings($setting)
127
    {
128
        // vd(__FUNCTION__);
129
        $ret = $this->configurations;
130
131
      //dot based hierarchy, parse and climb
132
        foreach (explode('.', $setting) as $k) {
133
            if (!isset($ret[$k])) {
134
                throw new ConfigurationException($setting);
135
            }
136
            $ret = $ret[$k];
137
        }
138
139
        return $ret;
140
    }
141
142
    private function classification($name, $type, $only_class_name = false)
143
    {
144
        $class_name = $this->cascadeNamespace("$type\\$name");
145
        // vd($class_name, __FUNCTION__);
146
        if ($only_class_name === true) {
147
            return $class_name;
148
        }
149
150
        return $this->getInstance($class_name);
151
    }
152
153
    private function resolved($clue, $solution = null)
154
    {
155
        if (!is_null($solution)) {
156
            $this->resolved_cache[$clue] = $solution;
157
        }
158
        // vd($clue, __FUNCTION__);
159
        return $this->resolved_cache[$clue] ?? null;
160
    }
161
162
    private function isResolved($clue): bool
163
    {
164
        return isset($this->resolved_cache[$clue]);
165
    }
166
167
    private function cascadeNamespace($class_name, $mvc_type = null)
0 ignored issues
show
Unused Code introduced by
The parameter $mvc_type is not used and could be removed. ( Ignorable by Annotation )

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

167
    private function cascadeNamespace($class_name, /** @scrutinizer ignore-unused */ $mvc_type = null)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
168
    {
169
        if ($this->isResolved($class_name)) {
170
            return $this->resolved($class_name);
171
        }
172
173
        // not fully namespaced, lets cascade
174
        foreach ($this->namespace_cascade as $ns) {
175
            if (class_exists($fully_namespaced = $ns . $class_name)) {
176
                $this->resolved($class_name, $fully_namespaced);
177
                return $fully_namespaced;
178
            }
179
        }
180
        throw new ConfigurationException($class_name);
181
    }
182
183
    private function wireInstance($interface)
184
    {
185
        // vd($interface, __FUNCTION__);
186
187
        if (!isset($this->interface_wiring[$interface])) {
188
            throw new ConfigurationException($interface);
189
        }
190
191
        $wire = $this->interface_wiring[$interface];
192
193
        // interface + constructor params
194
        if ($this->hasEmbeddedConstructorParameters($wire)) {
195
            $class = array_shift($wire);
196
            $args = $wire;
197
        } else {
198
            $class = $wire;
199
            $args = null;
200
        }
201
202
        if ($this->isResolved($class) && $this->hasPrivateContructor($class)) {
203
            return $this->resolved($class);
204
        }
205
206
        return $this->getInstance($class, $args);
207
    }
208
209
    private function hasPrivateContructor($class_name): bool
210
    {
211
        $rc = new \ReflectionClass($class_name);
212
        return !is_null($constructor = $rc->getConstructor()) && $constructor->isPrivate();
213
    }
214
215
    private function hasEmbeddedConstructorParameters($wire)
216
    {
217
        return is_array($wire);
218
    }
219
220
    private function getInstance($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
245
            return $instance;
246
        } catch (\ReflectionException $e) {
247
            throw new LamentException($e->getMessage());
248
        }
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);
0 ignored issues
show
Bug introduced by
The function dd was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

266
                    /** @scrutinizer ignore-call */ 
267
                    dd($e);
Loading history...
267
                }
268
            }
269
        }
270
        return $construction_args;
271
    }
272
}
273