Completed
Branch master (b52e58)
by Pierre
03:02 queued 37s
created

Container::createCoreService()   B

Complexity

Conditions 7
Paths 3

Size

Total Lines 23
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 7
eloc 17
c 1
b 0
f 0
nc 3
nop 2
dl 0
loc 23
rs 8.8333
1
<?php
2
3
namespace App;
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
    public function __construct(array $servicesConfig)
39
    {
40
        $this->setServiceConfig($servicesConfig);
41
        $this->services = [];
42
        $this->initReporter();
43
        $this->load();
44
    }
45
46
    /**
47
     * init reporter
48
     *
49
     * @return Container
50
     */
51
    protected function initReporter(): Container
52
    {
53
        $this->reporter = new \stdClass();
54
        $this->reporter->injected = 0;
55
        $this->reporter->constructable = 0;
56
        $this->reporter->unconstructable = 0;
57
        $this->reporter->exists = 0;
58
        $this->reporter->notexists = 0;
59
        return $this;
60
    }
61
62
    /**
63
     * returns reporter
64
     *
65
     * @return object
66
     */
67
    public function getReporter(): stdClass
68
    {
69
        return $this->reporter;
70
    }
71
72
    /**
73
     * return all services's container
74
     *
75
     * @return array
76
     */
77
    public function getServices(): array
78
    {
79
        return $this->services;
80
    }
81
82
    /**
83
     * return service from classname
84
     *
85
     * @param string $serviceName
86
     * @return object
87
     * @throws Exception
88
     */
89
    public function getService(string $serviceName)
90
    {
91
        if (!$this->hasService($serviceName)) {
92
            throw new \Exception(
93
                sprintf('Container service %s undefined', $serviceName)
94
            );
95
        }
96
        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
    public function setService(string $serviceName, $inst): Container
110
    {
111
        if (empty($serviceName) || !is_object($inst)) {
112
            throw new \Exception(
113
                sprintf('Container invalid argument')
114
            );
115
        }
116
        $this->services[$serviceName] = $inst;
117
        return $this;
118
    }
119
120
    /**
121
     * load service from config service
122
     *
123
     * @return Container
124
     * @throws Exception
125
     */
126
    protected function load(): Container
127
    {
128
        if (count($this->servicesConfig) === 0) {
129
            throw new \Exception('Container config missing');
130
        }
131
        foreach ($this->servicesConfig as $serviceName => $serviceParams) {
132
            $this->create($serviceName, $serviceParams);
133
        }
134
        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
    protected function create(string $serviceName, array $serviceParams): Container
145
    {
146
        $this->createDependencies($serviceParams);
147
        $this->createCoreService($serviceName, $serviceParams);
148
        return $this;
149
    }
150
151
    /**
152
     * create core service
153
     *
154
     * @param string $serviceName
155
     * @param array $serviceParams
156
     * @return Container
157
     */
158
    protected function createCoreService(string $serviceName, array $serviceParams): Container
159
    {
160
        if ($this->constructable($serviceName)) {
161
            if (!$this->hasService($serviceName)) {
162
                $args = array_map(function ($value) {
163
                    if (is_array($value)) {
164
                        $values = [];
165
                        foreach ($value as $i) {
166
                            $values[] = ($this->constructable($i))
167
                                ? $this->services[$i]
168
                                : $i;
169
                        }
170
                        return $values;
171
                    } else {
172
                        return ($this->constructable($value))
173
                            ? $this->services[$value]
174
                            : $value;
175
                    }
176
                }, $serviceParams);
177
                $this->injectService($serviceName, $args);
178
            }
179
        }
180
        return $this;
181
    }
182
183
    /**
184
     * create dependent services
185
     *
186
     * @param array $serviceParams
187
     * @return Container
188
     */
189
    protected function createDependencies(array $serviceParams): Container
190
    {
191
        foreach ($serviceParams as $serviceParam) {
192
            if (is_array($serviceParam)) {
193
                foreach ($serviceParam as $serviceParamsItem) {
194
                    $this->injectService($serviceParamsItem, []);
195
                }
196
            } else {
197
                $this->injectService($serviceParam, []);
198
            }
199
        }
200
        return $this;
201
    }
202
203
    /**
204
     * inject service in container
205
     *
206
     * @param mixed $serviceName
207
     * @param array $serviceParams
208
     * @return Container
209
     */
210
    protected function injectService($serviceName, array $serviceParams): Container
211
    {
212
        if ($this->constructable($serviceName)) {
213
            if (!$this->hasService($serviceName)) {
214
                $this->reporter->injected++;
215
                $this->services[$serviceName] = new $serviceName(...$serviceParams);
216
            }
217
        }
218
        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
    protected function constructable($value): bool
228
    {
229
        if ($this->isBasicType($value)) {
230
            return false;
231
        }
232
        $constructable = (class_exists($value)
233
            && is_callable([$value, self::_CONSTRUCT], true));
234
        if ($constructable) {
235
            ++$this->reporter->constructable;
236
        } else {
237
            ++$this->reporter->unconstructable;
238
        }
239
        return $constructable;
240
    }
241
242
    /**
243
     * return true if service instanciated in container
244
     *
245
     * @param string $serviceName
246
     * @return boolean
247
     */
248
    protected function hasService(string $serviceName): bool
249
    {
250
        $exists = isset($this->services[$serviceName]);
251
        if ($exists) {
252
            ++$this->reporter->exists;
253
        } else {
254
            ++$this->reporter->notexists;
255
        }
256
        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
    protected function isBasicType($value): bool
266
    {
267
        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
    protected function setServiceConfig(array $servicesConfig): Container
278
    {
279
        $this->servicesConfig = $servicesConfig;
280
        return $this;
281
    }
282
}
283