Completed
Push — master ( 923e9d...6c8214 )
by Pierre
02:55
created

Container::createDependencies()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 12
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 4

Importance

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