Completed
Pull Request — master (#1)
by Raffael
02:24
created

Container::resolveServiceArgument()   B

Complexity

Conditions 5
Paths 4

Size

Total Lines 16
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 5.0342

Importance

Changes 0
Metric Value
cc 5
eloc 9
nc 4
nop 3
dl 0
loc 16
ccs 8
cts 9
cp 0.8889
crap 5.0342
rs 8.8571
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * Micro\Container
7
 *
8
 * @copyright   Copryright (c) 2018 gyselroth GmbH (https://gyselroth.com)
9
 * @license     MIT https://opensource.org/licenses/MIT
10
 */
11
12
namespace Micro\Container;
13
14
use ProxyManager\Factory\LazyLoadingValueHolderFactory;
15
use ReflectionClass;
16
use ReflectionMethod;
17
use ReflectionParameter;
18
19
class Container extends AbstractContainer
20
{
21
    /**
22
     * Get service.
23
     *
24
     * @param string $name
25
     *
26
     * @return mixed
27
     */
28 39
    public function get($name)
29
    {
30
        try {
31 39
            return $this->resolve($name);
32 14
        } catch (Exception\ServiceNotFound $e) {
33 11
            return $this->autoWireClass($name);
34
        }
35
    }
36
37
    /**
38
     * Resolve service.
39
     *
40
     * @param string $name
41
     *
42
     * @return mixed
43
     */
44 39
    public function resolve(string $name)
45
    {
46 39
        if ($this->has($name)) {
47 4
            return $this->service[$name];
48
        }
49
50 39
        if (isset($this->registry[$name])) {
51 3
            return $this->addStaticService($name);
52
        }
53
54 36
        if ($this->config->has($name)) {
55 33
            return $this->autoWireClass($name);
56
        }
57
58 12
        if (null !== $this->parent_service) {
59 1
            $parents = array_merge([$name], class_implements($this->parent_service), class_parents($this->parent_service));
60
61 1
            if (in_array($name, $parents, true) && $this->parent_service instanceof $name) {
62
                return $this->parent_service;
63
            }
64
        }
65
66 12
        if (null !== $this->parent) {
67 1
            return $this->parent->resolve($name);
0 ignored issues
show
Bug introduced by
The method resolve() does not exist on Psr\Container\ContainerInterface. It seems like you code against a sub-type of Psr\Container\ContainerInterface such as Micro\Container\Container. ( Ignorable by Annotation )

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

67
            return $this->parent->/** @scrutinizer ignore-call */ resolve($name);
Loading history...
68
        }
69
70 11
        throw new Exception\ServiceNotFound("service $name was not found in service tree");
71
    }
72
73
    /**
74
     * Auto wire.
75
     *
76
     * @param string $name
77
     * @param array  $config
78
     * @param array  $parents
79
     *
80
     * @return mixed
81
     */
82 36
    protected function autoWireClass(string $name)
83
    {
84 36
        $config = $this->config->get($name);
85 34
        $class = $config['use'];
86
87 34
        if (preg_match('#^\{([^{}]+)\}$#', $class, $match)) {
88 1
            return $this->wireReference($name, $match[1], $config);
89
        }
90
91
        try {
92 34
            $reflection = new ReflectionClass($class);
93
        } catch (\Exception $e) {
94
            throw new Exception\ServiceNotFound($class.' can not be resolved to an existing class for service '.$name);
95
        }
96
97 34
        $constructor = $reflection->getConstructor();
98
99 34
        if (null === $constructor) {
100 3
            return $this->storeService($name, $config, new $class());
101
        }
102
103 31
        $args = $this->autoWireMethod($name, $constructor, $config);
104
105 29
        return $this->createInstance($name, $reflection, $args, $config);
106
    }
107
108
    /**
109
     * Wire named referenced service.
110
     *
111
     * @param string $name
112
     * @param string $refrence
113
     * @param array  $config
114
     *
115
     * @return mixed
116
     */
117 1
    protected function wireReference(string $name, string $reference, array $config)
118
    {
119 1
        $service = $this->get($reference);
120 1
        $reflection = new ReflectionClass(get_class($service));
121 1
        $config = $this->config->get($name);
122 1
        $service = $this->prepareService($name, $service, $reflection, $config);
123
124 1
        return $service;
125
    }
126
127
    /**
128
     * Get instance (virtual or real instance).
129
     *
130
     * @param string          $name
131
     * @param ReflectionClass $class
132
     * @param array           $arguments
133
     * @param array           $config
134
     *
135
     * @return mixed
136
     */
137 29
    protected function createInstance(string $name, ReflectionClass $class, array $arguments, array $config)
138
    {
139 29
        if (true === $config['lazy']) {
140 2
            return $this->getProxyInstance($name, $class, $arguments, $config);
141
        }
142
143 27
        return $this->getRealInstance($name, $class, $arguments, $config);
144
    }
145
146
    /**
147
     * Create proxy instance.
148
     *
149
     * @param string          $name
150
     * @param ReflectionClass $class
151
     * @param array           $arguments
152
     * @param array           $config
153
     *
154
     * @return mixed
155
     */
156 2
    protected function getProxyInstance(string $name, ReflectionClass $class, array $arguments, array $config)
157
    {
158 2
        $factory = new LazyLoadingValueHolderFactory();
159 2
        $that = $this;
160
161 2
        return $factory->createProxy(
162 2
            $class->getName(),
163 2
            function (&$wrappedObject, $proxy, $method, $parameters, &$initializer) use ($that, $name,$class,$arguments,$config) {
164 1
                $wrappedObject = $that->getRealInstance($name, $class, $arguments, $config);
165 1
                $initializer = null;
166 2
            }
167
        );
168
    }
169
170
    /**
171
     * Create real instance.
172
     *
173
     * @param string          $name
174
     * @param ReflectionClass $class
175
     * @param array           $arguments
176
     * @param array           $config
177
     *
178
     * @return mixed
179
     */
180 28
    protected function getRealInstance(string $name, ReflectionClass $class, array $arguments, array $config)
181
    {
182 28
        $instance = $class->newInstanceArgs($arguments);
183 28
        $instance = $this->prepareService($name, $instance, $class, $config);
184
185 27
        return $instance;
186
    }
187
188
    /**
189
     * Prepare service (execute sub selects and excute setter injections).
190
     *
191
     * @param string          $name
192
     * @param mixed           $service
193
     * @param ReflectionClass $class
194
     * @param array           $config
195
     *
196
     * @return mixed
197
     */
198 28
    protected function prepareService(string $name, $service, ReflectionClass $class, array $config)
199
    {
200 28
        foreach ($config['selects'] as $select) {
201 2
            $args = $this->autoWireMethod($name, $class->getMethod($select['method']), $select);
202 2
            $service = call_user_func_array([&$service, $select['method']], $args);
203
        }
204
205 28
        $this->storeService($name, $config, $service);
206
207 28
        foreach ($config['calls'] as $call) {
208 6
            if (null === $call) {
209
                continue;
210
            }
211
212 6
            if (!isset($call['method'])) {
213 1
                throw new Exception\InvalidConfiguration('method is required for setter injection in service '.$name);
214
            }
215
216 5
            $arguments = [];
0 ignored issues
show
Unused Code introduced by
The assignment to $arguments is dead and can be removed.
Loading history...
217
218
            try {
219 5
                $method = $class->getMethod($call['method']);
220
            } catch (\ReflectionException $e) {
221
                throw new Exception\InvalidConfiguration('method '.$call['method'].' is not callable in class '.$class->getName().' for service '.$name);
222
            }
223
224 5
            $arguments = $this->autoWireMethod($name, $method, $call);
225 5
            call_user_func_array([&$service, $call['method']], $arguments);
226
        }
227
228 27
        return $service;
229
    }
230
231
    /**
232
     * Autowire method.
233
     *
234
     * @param string           $name
235
     * @param ReflectionMethod $method
236
     * @param array            $config
237
     *
238
     * @return array
239
     */
240 31
    protected function autoWireMethod(string $name, ReflectionMethod $method, array $config): array
241
    {
242 31
        $params = $method->getParameters();
243 31
        $args = [];
244
245 31
        foreach ($params as $param) {
246 31
            $type = $param->getClass();
247 31
            $param_name = $param->getName();
248
249 31
            if (isset($config['arguments'][$param_name])) {
250 26
                $args[$param_name] = $this->parseParam($config['arguments'][$param_name], $name);
251 16
            } elseif (null !== $type) {
252 9
                $args[$param_name] = $this->resolveServiceArgument($name, $type, $param);
253 9
            } elseif ($param->isDefaultValueAvailable()) {
254 9
                $args[$param_name] = $param->getDefaultValue();
255 1
            } elseif ($param->allowsNull()) {
256 1
                $args[$param_name] = null;
257
            } else {
258 29
                throw new Exception\InvalidConfiguration('no value found for argument '.$param_name.' in method '.$method->getName().' for service '.$name);
259
            }
260
        }
261
262 29
        return $args;
263
    }
264
265
    /**
266
     * Resolve service argument.
267
     *
268
     * @param string              $name
269
     * @param ReflectionClass     $type
270
     * @param ReflectionParameter $param
271
     *
272
     * @return mixed
273
     */
274 9
    protected function resolveServiceArgument(string $name, ReflectionClass $type, ReflectionParameter $param)
275
    {
276 9
        $type_class = $type->getName();
277
278 9
        if ($type_class === $name) {
279
            throw new Exception\InvalidConfiguration('class '.$type_class.' can not depend on itself');
280
        }
281
282
        try {
283 9
            return $this->findService($name, $type_class);
284 2
        } catch (\Exception $e) {
285 2
            if ($param->isDefaultValueAvailable() && null === $param->getDefaultValue()) {
286 1
                return null;
287
            }
288
289 1
            throw $e;
290
        }
291
    }
292
293
    /**
294
     * Parse param value.
295
     *
296
     * @param mixed  $param
297
     * @param string $name
298
     *
299
     * @return mixed
300
     */
301 26
    protected function parseParam($param, string $name)
302
    {
303 26
        if (is_iterable($param)) {
304 1
            foreach ($param as $key => $value) {
305 1
                $param[$key] = $this->parseParam($value, $name);
306
            }
307
308 1
            return $param;
309
        }
310
311 26
        if (is_string($param)) {
312 26
            $param = $this->config->getEnv($param);
313
314 25
            if (preg_match('#^\{\{([^{}]+)\}\}$#', $param, $matches)) {
315 1
                return '{'.$matches[1].'}';
316
            }
317 24
            if (preg_match('#^\{([^{}]+)\}$#', $param, $matches)) {
318 1
                return $this->findService($name, $matches[1]);
319
            }
320
321 24
            return $param;
322
        }
323
324
        return $param;
325
    }
326
327
    /**
328
     * Locate service.
329
     *
330
     * @param string $current_service
331
     * @param string $service
332
     */
333 10
    protected function findService(string $current_service, string $service)
334
    {
335 10
        if (isset($this->children[$current_service])) {
336 1
            return $this->children[$current_service]->get($service);
337
        }
338
339 10
        $config = $this->config->get($current_service);
340 10
        if (isset($config['services'])) {
341 2
            $this->children[$current_service] = new self($config['services'], $this);
342
343 2
            return $this->children[$current_service]->get($service);
344
        }
345
346 9
        return $this->get($service);
347
    }
348
}
349