Completed
Push — master ( ebf04a...4626f9 )
by Pierre
06:07 queued 04:38
created

Container::injectService()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 3

Importance

Changes 0
Metric Value
dl 0
loc 10
ccs 6
cts 6
cp 1
rs 9.9332
c 0
b 0
f 0
cc 3
nc 3
nop 2
crap 3
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
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use stdClass.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
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
0 ignored issues
show
Bug introduced by
There is no parameter named $serviceName. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
227
     * @return boolean
228
     */
229 1
    protected function constructable($value): bool
0 ignored issues
show
Coding Style introduced by
function constructable() does not seem to conform to the naming convention (^(?:is|has|should|may|supports)).

This check examines a number of code elements and verifies that they conform to the given naming conventions.

You can set conventions for local variables, abstract classes, utility classes, constant, properties, methods, parameters, interfaces, classes, exceptions and special methods.

Loading history...
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