Completed
Push — master ( e299e4...94cf65 )
by Taosikai
15:49 queued 02:33
created

DefinitionResolver::resolve()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 14
rs 9.7998
c 0
b 0
f 0
cc 2
nc 2
nop 1
1
<?php
2
3
/*
4
 * This file is part of the slince/di package.
5
 *
6
 * (c) Slince <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Slince\Di;
13
14
use Slince\Di\Exception\ConfigException;
15
use Slince\Di\Exception\DependencyInjectionException;
16
use Slince\Di\Exception\NotFoundException;
17
18
class DefinitionResolver
19
{
20
    /**
21
     * @var Container
22
     */
23
    protected $container;
24
25
    /**
26
     * @param Container $container
27
     */
28
    public function __construct(Container $container)
29
    {
30
        $this->container = $container;
31
    }
32
33
    /**
34
     * @param Definition $definition
35
     * @return mixed
36
     */
37
    public function resolve(Definition $definition)
38
    {
39
        if (null !== $definition->getFactory()) {
40
            $instance = $this->createFromFactory($definition);
41
            $reflection = new \ReflectionObject($instance);
42
        } else {
43
            list($reflection, $instance) = $this->createFromClass($definition);
44
45
        }
46
        $this->invokeMethods($definition, $instance, $reflection);
47
        $this->invokeProperties($definition, $instance);
48
49
        return $instance;
50
    }
51
52
    protected function createFromClass(Definition $definition)
53
    {
54
        $class = $definition->getClass();
55
        if ($class === null) {
56
            throw new ConfigException('You must set a class or factory for definition.');
57
        }
58
        try {
59
            $reflection = new \ReflectionClass($definition->getClass());
60
        } catch (\ReflectionException $e) {
61
            throw new ConfigException(sprintf('Class "%s" is invalid', $definition->getClass()));
62
        }
63
        if (!$reflection->isInstantiable()) {
64
            throw new ConfigException(sprintf('Can not instantiate "%s"', $definition->getClass()));
65
        }
66
        $constructor = $reflection->getConstructor();
67
        if (is_null($constructor)) {
68
            $instance = $reflection->newInstanceWithoutConstructor();
69
        } else {
70
            $arguments = $this->resolveFunctionArguments($constructor, $definition->getArguments());
71
            $instance = $reflection->newInstanceArgs($arguments);
72
        }
73
        return [$reflection, $instance];
74
    }
75
76
    protected function createFromFactory(Definition $definition)
77
    {
78
        $factory = $definition->getFactory();
79
80
        try {
81
82
            $reflection = is_array($factory)
83
                ? new \ReflectionMethod($factory[0], $factory[1])
84
                : new \ReflectionFunction($factory);
85
86
            if ($reflection->getNumberOfParameters() > 0) {
87
                $arguments = $this->resolveFunctionArguments($reflection, $definition->getArguments());
88
            } else {
89
                $arguments = [];
90
            }
91
            return call_user_func_array($factory, $arguments);
92
93
        } catch (\ReflectionException $exception) {
94
            throw new ConfigException('The factory is invalid.');
95
        }
96
97
        return $instance;
0 ignored issues
show
Unused Code introduced by
return $instance; does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
98
    }
99
100
    /**
101
     * @param Definition $definition
102
     * @param object $instance
103
     * @param \ReflectionClass $reflection
104
     */
105
    protected function invokeMethods(Definition $definition, $instance, \ReflectionClass $reflection)
106
    {
107
        foreach ($definition->getMethodCalls() as $method) {
108
            try {
109
                $reflectionMethod = $reflection->getMethod($method[0]);
110
            } catch (\ReflectionException $e) {
111
                throw new DependencyInjectionException(sprintf(
112
                    'Class "%s" has no method "%s"',
113
                    $definition->getClass(),
114
                    $method[0]
115
                ));
116
            }
117
            $reflectionMethod->invokeArgs($instance, $this->resolveFunctionArguments(
118
                $reflectionMethod,
119
                $method[1]
120
            ));
121
        }
122
    }
123
124
    /**
125
     * @param Definition $definition
126
     * @param object $instance
127
     */
128
    protected function invokeProperties(Definition $definition, $instance)
129
    {
130
        foreach ($definition->getProperties() as $propertyName => $propertyValue) {
131
            if (property_exists($instance, $propertyName)) {
132
                $instance->$propertyName = $propertyValue;
133
            } else {
134
                throw new DependencyInjectionException(sprintf(
135
                    "Class '%s' has no property '%s'",
136
                    $definition->getClass(),
137
                    $propertyName
138
                ));
139
            }
140
        }
141
    }
142
143
    /**
144
     * Resolves all arguments for the function or method.
145
     *
146
     * @param \ReflectionFunctionAbstract $method
147
     * @param array $arguments
148
     * @throws DependencyInjectionException
149
     * @return array
150
     */
151
    public function resolveFunctionArguments(
152
        \ReflectionFunctionAbstract $method,
153
        array $arguments
154
    ) {
155
        $functionArguments = [];
156
        $arguments = $this->resolveParameters($arguments);
157
        foreach ($method->getParameters() as $parameter) {
158
            //If the dependency is provided directly
159
            if (isset($arguments[$parameter->getPosition()])) {
160
                $functionArguments[] = $arguments[$parameter->getPosition()];
161
            } elseif (isset($arguments[$parameter->name])) {
162
                $functionArguments[] = $arguments[$parameter->name];
163
            } elseif (($dependency = $parameter->getClass()) != null) {
164
                $dependencyName = $dependency->name;
165
                try {
166
                    $functionArguments[] = $this->container->get($dependencyName);
167
                } catch (NotFoundException $exception) {
168
                    if ($parameter->isOptional()) {
169
                        $functionArguments[] = $parameter->getDefaultValue();
170
                    } else {
171
                        throw $exception;
172
                    }
173
                }
174
            } elseif ($parameter->isOptional()) {
175
                $functionArguments[] = $parameter->getDefaultValue();
176
            } else {
177
                throw new DependencyInjectionException(sprintf(
178
                    'Missing required parameter "%s" when calling "%s"',
179
                    $parameter->name,
180
                    $method->getName()
181
                ));
182
            }
183
        }
184
        return $functionArguments;
185
    }
186
187
    /**
188
     * Resolves array of parameters
189
     * @param array $parameters
190
     * @return array
191
     */
192
    protected function resolveParameters($parameters)
193
    {
194
        return array_map(function($parameter) {
195
            if (is_array($parameter)) {
196
                return $this->resolveParameters($parameter);
197
            } else {
198
                return $this->resolveParameter($parameter);
199
            }
200
        }, $parameters);
201
    }
202
203
    /**
204
     * Formats parameter value
205
     *
206
     * @param string|Reference $value
207
     * @return string
208
     * @throws DependencyInjectionException
209
     */
210
    protected function resolveParameter($value)
211
    {
212
        //Reference
213
        if ($value instanceof Reference) {
214
            return $this->container->get($value->getId());
215
        }
216
        if ('@' === $value[0]) {
217
            return $this->container->get(substr($value, 1));
218
        }
219
        //"fool%bar%baz"
220
        return preg_replace_callback("#%([^%\s]+)%#", function ($matches) {
221
            $key = $matches[1];
222
            if ($parameter = $this->container->getParameter($key)) {
223
                return $parameter;
224
            }
225
            throw new DependencyInjectionException(sprintf("Parameter [%s] is not defined", $key));
226
        }, $value);
227
    }
228
}