Completed
Push — master ( d12d8b...9dc324 )
by Taosikai
48:49 queued 33:54
created

DefinitionResolver::resolveParameter()   B

Complexity

Conditions 7
Paths 4

Size

Total Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 21
rs 8.6506
c 0
b 0
f 0
cc 7
nc 4
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 DependencyInjectionException('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 DependencyInjectionException(sprintf('Class "%s" is invalid', $definition->getClass()));
62
        }
63
        if (!$reflection->isInstantiable()) {
64
            throw new DependencyInjectionException(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($definition, $constructor, $definition->getArguments());
71
            $instance = $reflection->newInstanceArgs($arguments);
72
        }
73
        return [$reflection, $instance];
74
    }
75
76
    /**
77
     * @param Definition $definition
78
     * @return object
79
     */
80
    protected function createFromFactory(Definition $definition)
81
    {
82
        $factory = $definition->getFactory();
83
        try {
84
            $reflection = is_array($factory)
85
                ? new \ReflectionMethod($factory[0], $factory[1])
86
                : new \ReflectionFunction($factory);
87
88
            if ($reflection->getNumberOfParameters() > 0) {
89
                $arguments = $this->resolveFunctionArguments($definition, $reflection, $definition->getArguments());
90
            } else {
91
                $arguments = [];
92
            }
93
            return call_user_func_array($factory, $arguments);
94
95
        } catch (\ReflectionException $exception) {
96
            throw new ConfigException('The factory is invalid.');
97
        }
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
                $definition,
119
                $reflectionMethod,
120
                $method[1]
121
            ));
122
        }
123
    }
124
125
    /**
126
     * @param Definition $definition
127
     * @param object $instance
128
     */
129
    protected function invokeProperties(Definition $definition, $instance)
130
    {
131
        foreach ($definition->getProperties() as $propertyName => $propertyValue) {
132
            if (property_exists($instance, $propertyName)) {
133
                $instance->$propertyName = $propertyValue;
134
            } else {
135
                throw new DependencyInjectionException(sprintf(
136
                    "Class '%s' has no property '%s'",
137
                    $definition->getClass(),
138
                    $propertyName
139
                ));
140
            }
141
        }
142
    }
143
144
    /**
145
     * Resolves all arguments for the function or method.
146
     *
147
     * @param Definition $definition
148
     * @param \ReflectionFunctionAbstract $method
149
     * @param array $arguments
150
     * @throws DependencyInjectionException
151
     * @return array
152
     */
153
    public function resolveFunctionArguments(
154
        Definition $definition,
155
        \ReflectionFunctionAbstract $method,
156
        array $arguments
157
    ) {
158
        $solvedArguments = [];
159
        $arguments = $this->resolveParameters($arguments);
160
        $autowired = $definition->isAutowired(); //autowired
161
        foreach ($method->getParameters() as $parameter) {
162
            //If the dependency is provided directly
163
            if (isset($arguments[$parameter->getPosition()])) {
164
                $solvedArguments[] = $arguments[$parameter->getPosition()];
165
            } elseif (isset($arguments[$parameter->name])) {
166
                $solvedArguments[] = $arguments[$parameter->name];
167
            } elseif ($autowired && ($dependency = $parameter->getClass()) != null) {
168
                $dependencyName = $dependency->name;
169
                try {
170
                    $solvedArguments[] = $this->container->get($dependencyName);
171
                } catch (NotFoundException $exception) {
172
                    if ($parameter->isOptional()) {
173
                        $solvedArguments[] = $parameter->getDefaultValue();
174
                    } else {
175
                        throw $exception;
176
                    }
177
                }
178
            } elseif ($parameter->isOptional()) {
179
                $solvedArguments[] = $parameter->getDefaultValue();
180
            } else {
181
                throw new DependencyInjectionException(sprintf(
182
                    'Missing required parameter "%s" when calling "%s"',
183
                    $parameter->name,
184
                    $method->getName()
185
                ));
186
            }
187
        }
188
        return $solvedArguments;
189
    }
190
191
    /**
192
     * Resolves array of parameters
193
     * @param array $parameters
194
     * @return array
195
     */
196
    protected function resolveParameters($parameters)
197
    {
198
        return array_map(function($parameter) {
199
            if (is_array($parameter)) {
200
                return $this->resolveParameters($parameter);
201
            } else {
202
                return $this->resolveParameter($parameter);
203
            }
204
        }, $parameters);
205
    }
206
207
    /**
208
     * Formats parameter value
209
     *
210
     * @param string|Reference $value
211
     * @return string
212
     * @throws DependencyInjectionException
213
     */
214
    protected function resolveParameter($value)
215
    {
216
        //Reference
217
        if ($value instanceof Reference) {
218
            return $this->container->get($value->getId());
219
        }
220
        if (is_string($value) && ($len = strlen($value)) > 0) {
221
            if ($len >= 2 && '@' === $value[0]) {
222
                return $this->container->get(substr($value, 1));
223
            }
224
            //"fool%bar%baz"
225
            return preg_replace_callback("#%([^%\s]+)%#", function ($matches) {
226
                $key = $matches[1];
227
                if ($parameter = $this->container->getParameter($key)) {
228
                    return $parameter;
229
                }
230
                throw new DependencyInjectionException(sprintf("Parameter [%s] is not defined", $key));
231
            }, $value);
232
        }
233
        return $value;
234
    }
235
}