Completed
Push — master ( 351c32...271583 )
by Taosikai
19:55 queued 04:54
created

Resolver::formatArgument()   B

Complexity

Conditions 7
Paths 4

Size

Total Lines 22

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 22
rs 8.6346
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 Resolver
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
     *
36
     * @return mixed
37
     */
38
    public function resolve(Definition $definition)
39
    {
40
        $this->parseConcrete($definition);
41
42
        if (null !== $definition->getFactory()) {
43
            $instance = $this->createFromFactory($definition);
44
        } elseif (null !== $definition->getClass()) {
45
            $instance = $this->createFromClass($definition);
46
        } elseif (null !== $definition->getResolved()) {
47
            $instance = $definition->getResolved();
48
        } else {
49
            throw new ConfigException('The definition is not invalid.');
50
        }
51
        $this->invokeMethods($definition, $instance);
52
        $this->invokeProperties($definition, $instance);
53
        $definition->setResolved($instance);
54
55
        return $instance;
56
    }
57
58
    protected function parseConcrete(Definition $definition)
59
    {
60
        $concrete = $definition->getConcrete();
61
        if (is_string($concrete)) {
62
            $definition->setClass($concrete);
63
        } elseif (is_array($concrete) || $concrete instanceof \Closure) {
64
            $definition->setFactory($concrete);
65
        } elseif (is_object($concrete)) {
66
            $definition->setResolved($concrete)
67
                ->setShared(true);
68
        } else {
69
            throw new ConfigException('The concrete of the definition is invalid');
70
        }
71
    }
72
73
    protected function createFromClass(Definition $definition)
74
    {
75
        $class = $definition->getClass();
76
        if (null === $class) {
77
            throw new ConfigException('You must set a class or factory for definition.');
78
        }
79
        try {
80
            $reflection = new \ReflectionClass($definition->getClass());
81
        } catch (\ReflectionException $e) {
82
            throw new DependencyInjectionException(sprintf('Class "%s" is invalid', $definition->getClass()));
83
        }
84
        if (!$reflection->isInstantiable()) {
85
            throw new DependencyInjectionException(sprintf('Can not instantiate "%s"', $definition->getClass()));
86
        }
87
        $constructor = $reflection->getConstructor();
88
        if (is_null($constructor)) {
89
            $instance = $reflection->newInstanceWithoutConstructor();
90
        } else {
91
            $arguments = $this->resolveArguments($definition->getArguments());
92
            if ($definition->isAutowired()) {
93
                $arguments = $this->resolveReflectionArguments($constructor, $arguments);
94
            }
95
            if (count($arguments) < $constructor->getNumberOfRequiredParameters()) {
96
                throw new ConfigException(sprintf('Too few arguments for class "%s"', $class));
97
            }
98
            $instance = $reflection->newInstanceArgs($arguments);
99
        }
100
101
        return $instance;
102
    }
103
104
    /**
105
     * @param Definition $definition
106
     *
107
     * @return object
108
     */
109
    protected function createFromFactory(Definition $definition)
110
    {
111
        $factory = $definition->getFactory();
112
        if (is_array($factory)) {
113
            $factory = $this->resolveArguments($factory);
114
        }
115
116
        return call_user_func_array($factory,
117
            $this->resolveArguments($definition->getArguments()) ?: [$this->container]
118
        );
119
    }
120
121
    /**
122
     * @param Definition $definition
123
     * @param object     $instance
124
     */
125
    protected function invokeMethods(Definition $definition, $instance)
126
    {
127
        foreach ($definition->getMethodCalls() as $method) {
128
            call_user_func_array([$instance, $method[0]], $this->resolveArguments($method[1]));
129
        }
130
    }
131
132
    /**
133
     * @param Definition $definition
134
     * @param object     $instance
135
     */
136
    protected function invokeProperties(Definition $definition, $instance)
137
    {
138
        $properties = $this->resolveArguments($definition->getProperties());
139
        foreach ($properties as $name => $value) {
140
            $instance->$name = $value;
141
        }
142
    }
143
144
    /**
145
     * Resolves all arguments for the function or method.
146
     *
147
     * @param \ReflectionFunctionAbstract $method
148
     * @param array                       $arguments
149
     *
150
     * @throws
151
     *
152
     * @return array
153
     */
154
    public function resolveReflectionArguments(
155
        \ReflectionFunctionAbstract $method,
156
        array $arguments
157
    ) {
158
        $solvedArguments = [];
159
        foreach ($method->getParameters() as $parameter) {
160
            //If the dependency is provided directly
161
            if (isset($arguments[$parameter->getPosition()])) {
162
                $solvedArguments[] = $arguments[$parameter->getPosition()];
163
            } elseif (isset($arguments[$parameter->name])) {
164
                $solvedArguments[] = $arguments[$parameter->name];
165
            } elseif (null != ($dependency = $parameter->getClass())) {
166
                $dependencyName = $dependency->name;
167
                try {
168
                    $solvedArguments[] = $this->container->get($dependencyName);
169
                } catch (NotFoundException $exception) {
170
                    if ($parameter->isOptional()) {
171
                        $solvedArguments[] = $parameter->getDefaultValue();
172
                    } else {
173
                        throw $exception;
174
                    }
175
                }
176
            } elseif ($parameter->isOptional()) {
177
                $solvedArguments[] = $parameter->getDefaultValue();
178
            } else {
179
                throw new DependencyInjectionException(sprintf(
180
                    'Missing required parameter "%s" when calling "%s"',
181
                    $parameter->name,
182
                    $method->getName()
183
                ));
184
            }
185
        }
186
187
        return $solvedArguments;
188
    }
189
190
    /**
191
     * Resolves array of parameters.
192
     *
193
     * @param array $arguments
194
     *
195
     * @return array
196
     */
197
    protected function resolveArguments($arguments)
198
    {
199
        return array_map(function ($argument) {
200
            if (is_array($argument)) {
201
                return $this->resolveArguments($argument);
202
            } else {
203
                return $this->formatArgument($argument);
204
            }
205
        }, $arguments);
206
    }
207
208
    /**
209
     * Formats argument value.
210
     *
211
     * @param string $value
212
     *
213
     * @return string
214
     *
215
     * @throws DependencyInjectionException
216
     */
217
    protected function formatArgument($value)
218
    {
219
        if ($value instanceof Reference) {
220
            return $this->container->get($value->getId());
221
        }
222
223
        if (is_string($value) && ($len = strlen($value)) > 0) {
224
            if ($len >= 2 && '@' === $value[0]) {
225
                return $this->container->get(substr($value, 1));
226
            }
227
            //"fool%bar%baz"
228
            return preg_replace_callback("#%([^%\s]+)%#", function ($matches) {
229
                $key = $matches[1];
230
                if ($parameter = $this->container->getParameter($key)) {
231
                    return $parameter;
232
                }
233
                throw new DependencyInjectionException(sprintf('Parameter [%s] is not defined', $key));
234
            }, $value);
235
        }
236
237
        return $value;
238
    }
239
}
240