Passed
Branch dev (cf6f3e)
by Raffael
02:22
created

Container::storeService()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 12
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 3

Importance

Changes 0
Metric Value
cc 3
eloc 6
nc 3
nop 3
dl 0
loc 12
ccs 7
cts 7
cp 1
crap 3
rs 9.4285
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
18
class Container extends AbstractContainer
19
{
20
    /**
21
     * Get service.
22
     *
23
     * @param string $name
24
     *
25
     * @return mixed
26
     */
27 39
    public function get($name)
28
    {
29
        try {
30 39
            return $this->resolve($name);
31 14
        } catch (Exception\ServiceNotFound $e) {
32 11
            return $this->autoWireClass($name);
33
        }
34
    }
35
36
    /**
37
     * Resolve service.
38
     *
39
     * @param string $name
40
     *
41
     * @return mixed
42
     */
43 39
    public function resolve(string $name)
44
    {
45 39
        if ($this->has($name)) {
46 4
            return $this->service[$name];
47
        }
48
49 39
        if (isset($this->registry[$name])) {
50 3
            return $this->addStaticService($name);
51
        }
52
53 36
        if ($this->config->has($name)) {
54 33
            return $this->autoWireClass($name);
55
        }
56
57 12
        if (null !== $this->parent_service) {
58 1
            $parents = array_merge([$name], class_implements($this->parent_service), class_parents($this->parent_service));
59
60 1
            if (in_array($name, $parents, true) && $this->parent_service instanceof $name) {
61
                return $this->parent_service;
62
            }
63
        }
64
65 12
        if (null !== $this->parent) {
66 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

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