Container::__construct()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 4
c 1
b 0
f 0
nc 1
nop 1
dl 0
loc 6
ccs 5
cts 5
cp 1
crap 1
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Nymfonya\Component;
6
7
use stdClass;
8
9
class Container
10
{
11
12
    const _CONSTRUCT = '__construct';
13
14
    /**
15
     * is the config array
16
     *
17
     * @var array
18
     */
19
    protected $servicesConfig;
20
21
    /**
22
     * service storage
23
     *
24
     * @var array
25
     */
26
    protected $services;
27
28
    /**
29
     * reporter
30
     *
31
     * @var stdClass
32
     */
33
    protected $reporter;
34
35
    /**
36
     * instanciate
37
     *
38
     * @param array $servicesConfig
39
     */
40 18
    public function __construct(array $servicesConfig)
41
    {
42 18
        $this->setServiceConfig($servicesConfig);
43 18
        $this->services = [];
44 18
        $this->initReporter();
45 18
        $this->load();
46
    }
47
48
    /**
49
     * init reporter
50
     *
51
     * @return Container
52
     */
53 1
    protected function initReporter(): Container
54
    {
55 1
        $this->reporter = new \stdClass();
56 1
        $this->reporter->injected = 0;
57 1
        $this->reporter->constructable = 0;
58 1
        $this->reporter->unconstructable = 0;
59 1
        $this->reporter->exists = 0;
60 1
        $this->reporter->notexists = 0;
61 1
        return $this;
62
    }
63
64
    /**
65
     * returns reporter
66
     *
67
     * @return object
68
     */
69 1
    public function getReporter(): stdClass
70
    {
71 1
        return $this->reporter;
72
    }
73
74
    /**
75
     * return all services's container
76
     *
77
     * @return array
78
     */
79 3
    public function getServices(): array
80
    {
81 3
        return $this->services;
82
    }
83
84
    /**
85
     * return service from classname
86
     *
87
     * @param string $serviceName
88
     * @return object
89
     * @throws Exception
90
     */
91 3
    public function getService(string $serviceName)
92
    {
93 3
        if (!$this->hasService($serviceName)) {
94 1
            throw new \Exception(
95 1
                sprintf('Container service %s undefined', $serviceName)
96
            );
97
        }
98 2
        return $this->services[$serviceName];
99
    }
100
101
    /**
102
     * set an object instance for a service name
103
     * this should be used in test to mock a service
104
     * or update an existing service yet instanciated
105
     *
106
     * @param string $serviceName
107
     * @param mixed $inst
108
     * @return Container
109
     * @throws Exception
110
     */
111 3
    public function setService(string $serviceName, $inst): Container
112
    {
113 3
        if (empty($serviceName) || !is_object($inst)) {
114 2
            throw new \Exception(
115 2
                sprintf('Container invalid argument')
116
            );
117
        }
118 1
        $this->services[$serviceName] = $inst;
119 1
        return $this;
120
    }
121
122
    /**
123
     * load service from config service
124
     *
125
     * @return Container
126
     * @throws Exception
127
     */
128 2
    protected function load(): Container
129
    {
130 2
        if (count($this->servicesConfig) === 0) {
131 1
            throw new \Exception('Container config missing');
132
        }
133 2
        foreach ($this->servicesConfig as $serviceName => $serviceParams) {
134 2
            $this->create($serviceName, $serviceParams);
135
        }
136 2
        return $this;
137
    }
138
139
    /**
140
     * create a service and append in service containter
141
     *
142
     * @param string $serviceName
143
     * @param array $serviceParams
144
     * @return Container
145
     */
146 1
    protected function create(string $serviceName, array $serviceParams): Container
147
    {
148 1
        $this->createDependencies($serviceParams);
149 1
        $this->createCoreService($serviceName, $serviceParams);
150 1
        return $this;
151
    }
152
153
    /**
154
     * create core service
155
     *
156
     * @param string $serviceName
157
     * @param array $serviceParams
158
     * @return Container
159
     */
160 1
    protected function createCoreService(string $serviceName, array $serviceParams): Container
161
    {
162 1
        if ($this->constructable($serviceName)) {
163 1
            if (!$this->hasService($serviceName)) {
164
                $args = array_map(function ($value) {
165 1
                    if (is_array($value)) {
166
                        $values = [];
167
                        foreach ($value as $i) {
168
                            $values[] = ($this->constructable($i))
169
                                ? $this->services[$i]
170
                                : $i;
171
                        }
172
                        return $values;
173
                    } else {
174 1
                        return ($this->constructable($value))
175
                            ? $this->services[$value]
176 1
                            : $value;
177
                    }
178 1
                }, $serviceParams);
179 1
                $this->injectService($serviceName, $args);
180
            }
181
        }
182 1
        return $this;
183
    }
184
185
    /**
186
     * create dependent services
187
     *
188
     * @param array $serviceParams
189
     * @return Container
190
     */
191 1
    protected function createDependencies(array $serviceParams): Container
192
    {
193 1
        foreach ($serviceParams as $serviceParam) {
194 1
            if (is_array($serviceParam)) {
195 1
                foreach ($serviceParam as $serviceParamsItem) {
196 1
                    $this->injectService($serviceParamsItem, []);
197
                }
198
            } else {
199 1
                $this->injectService($serviceParam, []);
200
            }
201
        }
202 1
        return $this;
203
    }
204
205
    /**
206
     * inject service in container
207
     *
208
     * @param mixed $serviceName
209
     * @param array $serviceParams
210
     * @return Container
211
     */
212 1
    protected function injectService($serviceName, array $serviceParams): Container
213
    {
214 1
        if ($this->constructable($serviceName)) {
215 1
            if (!$this->hasService($serviceName)) {
216 1
                $this->reporter->injected++;
217 1
                $this->services[$serviceName] = new $serviceName(...$serviceParams);
218
            }
219
        }
220 1
        return $this;
221
    }
222
223
    /**
224
     * return true is service class exists with a constructor
225
     *
226
     * @param mixed $serviceName
227
     * @return boolean
228
     */
229 1
    protected function constructable($value): bool
230
    {
231 1
        if ($this->isBasicType($value)) {
232 1
            return false;
233
        }
234 1
        $constructable = (class_exists($value)
235 1
            && is_callable([$value, self::_CONSTRUCT], true));
236 1
        if ($constructable) {
237 1
            ++$this->reporter->constructable;
238
        } else {
239 1
            ++$this->reporter->unconstructable;
240
        }
241 1
        return $constructable;
242
    }
243
244
    /**
245
     * return true if service instanciated in container
246
     *
247
     * @param string $serviceName
248
     * @return boolean
249
     */
250 1
    protected function hasService(string $serviceName): bool
251
    {
252 1
        $exists = isset($this->services[$serviceName]);
253 1
        if ($exists) {
254 1
            ++$this->reporter->exists;
255
        } else {
256 1
            ++$this->reporter->notexists;
257
        }
258 1
        return isset($this->services[$serviceName]);
259
    }
260
261
    /**
262
     * return true if is boolean or int types
263
     *
264
     * @param mixed $value
265
     * @return boolean
266
     */
267 1
    protected function isBasicType($value): bool
268
    {
269 1
        return (is_int($value) || is_bool($value) || is_object($value));
270
    }
271
272
    /**
273
     * set config for service
274
     * testing purpose
275
     *
276
     * @param array $servicesConfig
277
     * @return Container
278
     */
279 1
    protected function setServiceConfig(array $servicesConfig): Container
280
    {
281 1
        $this->servicesConfig = $servicesConfig;
282 1
        return $this;
283
    }
284
}
285