Passed
Push — main ( 23c9b6...c645a3 )
by Sammy
01:36 queued 11s
created

LeMarchand::getInstance()   B

Complexity

Conditions 8
Paths 62

Size

Total Lines 33
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Importance

Changes 4
Bugs 1 Features 0
Metric Value
eloc 22
c 4
b 1
f 0
dl 0
loc 33
rs 8.4444
cc 8
nc 62
nop 2
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
    // public const RX_CONTROLLER_NAME = '/([a-zA-Z]+)Controller$/';
20
    // public const RX_MODEL_CLASS = '/([a-zA-Z]+)(Class|Model)$/';
21
    const RX_SETTINGS = '/^settings\./';
22
    // public const RX_INTERFACE_NAME = '/([a-zA-Z]+)Interface$/';
23
    const RX_CLASS_NAME = '/([a-zA-Z]+)(Class|Model|Controller|Interface)$/';
24
25
26
    public static function box($settings = null): ContainerInterface
27
    {
28
        if (is_null(self::$instance)) {
29
            if (is_array($settings)) {
30
                return (self::$instance = new LeMarchand($settings));
31
            }
32
            throw new LamentException('UNABLE_TO_OPEN_BOX');
33
        }
34
35
        return self::$instance;
36
    }
37
38
39
    private function __construct($settings)
40
    {
41
        $this->configurations['settings'] = $settings;
42
        if(isset($settings['LeMarchand']))
43
        {
44
          $this->namespace_cascade = $settings['LeMarchand']['cascade'] ?? [];
45
          $this->interface_wiring = $settings['LeMarchand']['wiring'] ?? [];
46
        }
47
    }
48
49
    public function __debugInfo(): array
50
    {
51
        $dbg = get_object_vars($this);
52
        if (isset($dbg['configurations']['template_engine'])) {
53
          // way too long of an object
54
            $dbg['configurations']['template_engine'] = get_class($dbg['configurations']['template_engine']);
55
        }
56
        return $dbg;
57
    }
58
59
    public function put($configuration, $instance)
60
    {
61
        if (!is_string($configuration)) {
62
            throw new LamentException($configuration);
63
        }
64
        $this->configurations[$configuration] = $instance;
65
    }
66
67
    public function has($configuration)
68
    {
69
        try {
70
            $this->get($configuration);
71
            return true;
72
        } catch (NotFoundExceptionInterface $e) {
73
            return false;
74
        } catch (ContainerExceptionInterface $e) {
75
            return false;
76
        }
77
    }
78
79
80
    public function get($configuration)
81
    {
82
83
        if (!is_string($configuration)) {
84
            throw new LamentException($configuration);
85
        }
86
        // 1. is it a first level key ?
87
        if (isset($this->configurations[$configuration])) {
88
            return $this->configurations[$configuration];
89
        }
90
91
        // 2. is it configuration data ?
92
        if (preg_match(self::RX_SETTINGS, $configuration, $m) === 1) {
93
            return $this->getSettings($configuration);
94
        }
95
        // 3. is it a class
96
        if (preg_match(self::RX_CLASS_NAME, $configuration, $m) === 1) {
97
            return $this->classification($m[1], $m[2]);
98
        }
99
100
        throw new ConfigurationException($configuration);
101
    }
102
103
104
    private function getSettings($setting)
105
    {
106
      // vdt(__FUNCTION__);
107
        $ret = $this->configurations;
108
109
      //dot based hierarchy, parse and climb
110
        foreach (explode('.', $setting) as $k) {
111
            if (!isset($ret[$k])) {
112
                throw new ConfigurationException($setting);
113
            }
114
            $ret = $ret[$k];
115
        }
116
117
        return $ret;
118
    }
119
120
    private function classification($name, $type)
121
    {
122
        if ($type === 'Interface') {
123
          return $this->wireInstance($name.$type);
124
        }
125
126
        $class_name = $this->cascadeNamespace($name, $type);
127
128
        if ($type === 'Class') {
129
            return $class_name;
130
        }
131
132
        return $this->getInstance($class_name);
133
    }
134
135
    private function cascadeNamespace($class_name, $mvc_type = null)
136
    {
137
        // does the name already exists ?
138
        if (class_exists($class_name)) {
139
            return $class_name;
140
        }
141
142
        if ($mvc_type === 'Class') {
143
            $mvc_type = 'Model';
144
        }
145
146
        if ($mvc_type === 'Controller') {
147
            $class_name = $class_name . 'Controller';
148
        }
149
150
        // not fully namespaced, lets cascade
151
        foreach ($this->namespace_cascade as $ns) {
152
            if (class_exists($full_name = $ns . $mvc_type . 's\\' . $class_name)) {
153
                return $full_name;
154
            }
155
        }
156
157
        throw new ConfigurationException($class_name);
158
    }
159
160
    private function wireInstance($interface)
161
    {
162
        if (!isset($this->interface_wiring[$interface])) {
163
            throw new ConfigurationException($interface);
164
        }
165
        $wire = $this->interface_wiring[$interface];
166
        if(is_array($wire)){
167
          $class = array_shift($wire);
168
          return $this->getInstance($class, $wire);
169
        }
170
        return $this->getInstance($this->interface_wiring[$interface]);
171
    }
172
173
    private function getInstance($class, $construction_args = [])
174
    {
175
        try {
176
            $rc = new \ReflectionClass($class);
177
            $instance = null;
178
179
            if (!is_null($rc->getConstructor())) {
180
              if(empty($construction_args)){
181
                foreach ($rc->getConstructor()->getParameters() as $param) {
182
                  try{
183
                    if($param->getType())
184
                      $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

184
                      $construction_args [] = $this->get($param->getType()->/** @scrutinizer ignore-call */ getName());
Loading history...
185
                    else{
186
                      $setting = 'settings.Constructor.'.$class.'.'.$param->getName();
187
                      $construction_args []= $this->getSettings($setting);
188
                    }
189
                  }catch(NotFoundExceptionInterface $e){
190
                    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

190
                    /** @scrutinizer ignore-call */ 
191
                    dd($e);
Loading history...
191
                  }
192
                }
193
              }
194
              $instance = $rc->newInstanceArgs($construction_args);
195
            } else {
196
                $instance = $rc->newInstanceArgs();
197
            }
198
199
            if ($rc->hasMethod('set_container')) {
200
                $instance->set_container($this);
201
            }
202
203
            return $instance;
204
        } catch (\ReflectionException $e) {
205
            return null;
206
        }
207
    }
208
}
209