Passed
Push — main ( cd1997...8c175e )
by Sammy
01:28
created

LeMarchand   A

Complexity

Total Complexity 39

Size/Duplication

Total Lines 193
Duplicated Lines 0 %

Importance

Changes 45
Bugs 9 Features 4
Metric Value
wmc 39
eloc 88
c 45
b 9
f 4
dl 0
loc 193
rs 9.28

14 Methods

Rating   Name   Duplication   Size   Complexity  
A isFirstLevelKey() 0 3 1
A getSettings() 0 14 3
A makeInstance() 0 5 1
A hasPrivateContructor() 0 4 2
A cascadeInstance() 0 15 3
A __debugInfo() 0 16 4
A __construct() 0 8 2
A getComplexConfigurationString() 0 17 5
A has() 0 11 3
A get() 0 18 4
A box() 0 10 3
A getInstance() 0 7 2
A hasEmbeddedConstructorParameters() 0 3 1
A wireInstance() 0 22 5
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
13
    private $configurations = [];
14
15
    private $interface_wiring = [];
16
17
    private $instance_cache = [];
18
19
    private $resolver = null;
20
21
    public static function box($settings = null): ContainerInterface
22
    {
23
        if (is_null(self::$instance)) {
24
            if (is_array($settings)) {
25
                return (self::$instance = new LeMarchand($settings));
26
            }
27
            throw new ContainerException('UNABLE_TO_OPEN_BOX');
28
        }
29
30
        return self::$instance;
31
    }
32
33
34
    private function __construct($settings)
35
    {
36
        if (isset($settings[__CLASS__])) {
37
            $this->resolver = new Resolver($settings[__CLASS__]['cascade'] ?? []);
38
            $this->interface_wiring = $settings[__CLASS__]['wiring'] ?? [];
39
            unset($settings[__CLASS__]);
40
        }
41
        $this->configurations['settings'] = $settings;
42
    }
43
44
    public function __debugInfo(): array
45
    {
46
        $dbg = get_object_vars($this);
47
48
        foreach ($dbg['instance_cache'] as $class => $instance) {
49
            $dbg['instance_cache'][$class] = true;
50
        }
51
52
        foreach ($dbg['interface_wiring'] as $interface => $wire) {
53
            if (is_array($wire)) {
54
                $wire = array_shift($wire) . ' --array #' . count($wire);
55
            }
56
            $dbg['interface_wiring'][$interface] = $wire;
57
        }
58
59
        return $dbg;
60
    }
61
62
    public function has($configuration)
63
    {
64
        try {
65
            $this->get($configuration);
66
            return true;
67
        } catch (NotFoundExceptionInterface $e) {
68
            return false;
69
        } catch (ContainerExceptionInterface $e) {
70
            return false;
71
        }
72
        return false;
73
    }
74
75
76
    public function get($configuration_string)
77
    {
78
        if (!is_string($configuration_string)) {
79
            throw new ContainerException($configuration_string);
80
        }
81
82
        if ($this->isFirstLevelKey($configuration_string)) {
83
            return $this->configurations[$configuration_string];
84
        }
85
86
        // not a simple configuration string, it has meaning
87
        $res = $this->getComplexConfigurationString($configuration_string);
88
89
        if (!is_null($res)) {
90
            throw new NotFoundException($configuration_string);
91
        }
92
93
        return $res;
94
    }
95
96
    private function getComplexConfigurationString($configuration_string)
97
    {
98
        $configuration = new Configuration($configuration_string);
99
100
        $ret = null;
101
102
        if ($configuration->isSettings()) {
103
            $ret = $this->getSettings($configuration);
104
        } elseif (class_exists($configuration_string)) {
105
            $ret = $this->getInstance($configuration);
106
        } elseif ($configuration->isInterface()) {
107
            $ret = $this->wireInstance($configuration);
108
        } elseif ($configuration->isModelOrController()) {
109
            $ret = $this->cascadeInstance($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 $configuration)
137
    {
138
        $class_name = $configuration->getModelOrControllerName();
139
        $class_name = $this->resolver->cascadeNamespace($class_name);
140
141
        if ($configuration->hasClassNameModifier()) {
142
            $ret = $class_name;
143
        } elseif ($configuration->hasNewInstanceModifier()) {
144
            $ret = $this->makeInstance($class_name);
145
        }
146
        else{
147
          $ret = $this->getInstance($class_name);
148
        }
149
150
        return $ret;
151
    }
152
153
    private function wireInstance($interface)
154
    {
155
        if (!isset($this->interface_wiring[$interface])) {
156
            throw new NotFoundException($interface);
157
        }
158
159
        $wire = $this->interface_wiring[$interface];
160
161
        // interface + constructor params
162
        if ($this->hasEmbeddedConstructorParameters($wire)) {
163
            $class = array_shift($wire);
164
            $args = $wire;
165
        } else {
166
            $class = $wire;
167
            $args = null;
168
        }
169
170
        if ($this->resolver->isResolved($class) && $this->hasPrivateContructor($class)) {
171
            return $this->resolver->resolved($class);
172
        }
173
174
        return $this->getInstance($class, $args);
175
    }
176
177
    private function hasPrivateContructor($class_name): bool
178
    {
179
        $rc = new \ReflectionClass($class_name);
180
        return !is_null($constructor = $rc->getConstructor()) && $constructor->isPrivate();
181
    }
182
183
    private function hasEmbeddedConstructorParameters($wire)
184
    {
185
        return is_array($wire);
186
    }
187
188
    private function getInstance($class, $construction_args = [])
189
    {
190
        if (isset($this->instance_cache[$class])) {
191
            return $this->instance_cache[$class];
192
        }
193
194
        return $this->makeInstance($class, $construction_args);
195
    }
196
197
    private function makeInstance($class, $construction_args = [])
198
    {
199
        $instance = ReflectionFactory::make($class, $construction_args, $this);
200
        $this->instance_cache[$class] = $instance;
201
        return $instance;
202
    }
203
}
204