Passed
Push — main ( 0acc1f...dcc917 )
by Sammy
01:48
created

LeMarchand.php (1 issue)

Labels
Severity
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
    private $configurations = [];
11
12
    // public const RX_CONTROLLER_NAME = '/([a-zA-Z]+)Controller$/';
13
    // public const RX_MODEL_CLASS = '/([a-zA-Z]+)(Class|Model)$/';
14
    const RX_SETTINGS = '/^settings\./';
15
    // public const RX_INTERFACE_NAME = '/([a-zA-Z]+)Interface$/';
16
    const RX_CLASS_NAME = '/([a-zA-Z]+)(Class|Model|Controller|Interface)$/';
17
18
19
    public static function box($settings = null): ContainerInterface
20
    {
21
        if (is_null(self::$instance)) {
22
            if (is_array($settings)) {
23
                return (self::$instance = new LeMarchand($settings));
24
            }
25
            throw new LamentException('UNABLE_TO_OPEN_BOX');
26
        }
27
28
        return self::$instance;
29
    }
30
31
32
    private function __construct($settings)
33
    {
34
        $this->configurations['settings'] = $settings;
35
    }
36
37
    public function __debugInfo(): array
38
    {
39
        $dbg = get_object_vars($this);
40
        if (isset($dbg['configurations']['template_engine'])) {
41
          // way too long of an object
42
            $dbg['configurations']['template_engine'] = get_class($dbg['configurations']['template_engine']);
43
        }
44
        return $dbg;
45
    }
46
47
    public function register($configuration, $instance)
48
    {
49
        $this->configurations[$configuration] = $instance;
50
    }
51
52
    public function has($configuration)
53
    {
54
        try {
55
            $this->get($configuration);
56
            return true;
57
        } catch (NotFoundExceptionInterface $e) {
58
            return false;
59
        } catch (ContainerExceptionInterface $e) {
60
            return false;
61
        }
62
    }
63
64
65
    public function get($configuration)
66
    {
67
        if (!is_string($configuration)) {
68
            throw new LamentException($configuration);
69
        }
70
        // 1. is it a first level key ?
71
        if (isset($this->configurations[$configuration])) {
72
            return $this->configurations[$configuration];
73
        }
74
75
        // 2. is it configuration data ?
76
        if (preg_match(self::RX_SETTINGS, $configuration, $m) === 1) {
77
            return $this->getSettings($configuration);
78
        }
79
        // 3. is it a class
80
        if (preg_match(self::RX_CLASS_NAME, $configuration, $m) === 1) {
81
            return $this->classification($m[1], $m[2]);
82
        }
83
84
        throw new ConfigurationException($configuration);
85
    }
86
87
88
    private function getSettings($setting)
89
    {
90
        $ret = $this->configurations;
91
92
      //dot based hierarchy, parse and climb
93
        foreach (explode('.', $setting) as $k) {
94
            if (!isset($ret[$k])) {
95
                throw new ConfigurationException($setting);
96
            }
97
            $ret = $ret[$k];
98
        }
99
100
        return $ret;
101
    }
102
103
    private function classification($name, $type)
104
    {
105
        $class_name = $this->cascadeNamespace($name, $type);
106
107
        if ($type === 'Class') {
108
            return $class_name;
109
        }
110
111
        if ($type === 'Interface') {
112
            return $this->wireInstance();
0 ignored issues
show
The call to HexMakina\LeMarchand\LeMarchand::wireInstance() has too few arguments starting with interface. ( Ignorable by Annotation )

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

112
            return $this->/** @scrutinizer ignore-call */ wireInstance();

This check compares calls to functions or methods with their respective definitions. If the call has less arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
113
        }
114
115
        return $this->getInstance($class_name);
116
    }
117
118
    private function cascadeNamespace($class_name, $mvc_type = null)
119
    {
120
        // does the name already exists ?
121
        if (class_exists($class_name)) {
122
            return $class_name;
123
        }
124
125
        if ($mvc_type === 'Class') {
126
            $mvc_type = 'Model';
127
        }
128
129
        if ($mvc_type === 'Controller') {
130
            $class_name = $class_name . 'Controller';
131
        }
132
133
        // not fully namespaced, lets cascade
134
        foreach ($this->getSettings('settings.namespaces') as $ns) {
135
            if (class_exists($full_name = $ns . $mvc_type . 's\\' . $class_name)) {
136
                return $full_name;
137
            }
138
        }
139
140
        throw new ConfigurationException($class_name);
141
    }
142
143
    private function wireInstance($interface)
144
    {
145
        $wire = $this->getSettings('settings.interface_implementations');
146
147
        if (!isset($wire[$interface])) {
148
            throw new ConfigurationException($interface);
149
        }
150
151
        return $this->getInstance($wire[$interface]);
152
    }
153
154
    private function getInstance($class)
155
    {
156
        try {
157
            $rc = new \ReflectionClass($class);
158
            $instance = null;
159
            $construction_args = [];
160
            if (!is_null($rc->getConstructor())) {
161
                foreach ($rc->getConstructor()->getParameters() as $param) {
162
                    $construction_args [] = $this->get($param->getType() . '');
163
                }
164
                $instance = $rc->newInstanceArgs($construction_args);
165
            } else {
166
                $instance = $rc->newInstanceArgs();
167
            }
168
169
            if ($rc->hasMethod('set_container')) {
170
                $instance->set_container($this);
171
            }
172
173
            return $instance;
174
        } catch (\ReflectionException $e) {
175
            return null;
176
        }
177
    }
178
}
179