a05725b44010c1872f7924f4ae4d5a014ee57abc
[GitHub/WoltLab/WCF.git] /
1 <?php
2 /**
3 * PHP-DI
4 *
5 * @link http://php-di.org/
6 * @copyright Matthieu Napoli (http://mnapoli.fr/)
7 * @license http://www.opensource.org/licenses/mit-license.php MIT (see the LICENSE file)
8 */
9
10 namespace DI\Definition\Helper;
11
12 use DI\Definition\ObjectDefinition;
13 use DI\Definition\ObjectDefinition\MethodInjection;
14 use DI\Definition\ObjectDefinition\PropertyInjection;
15 use DI\Definition\Exception\DefinitionException;
16
17 /**
18 * Helps defining how to create an instance of a class.
19 *
20 * @author Matthieu Napoli <matthieu@mnapoli.fr>
21 */
22 class ObjectDefinitionHelper implements DefinitionHelper
23 {
24 /**
25 * @var string|null
26 */
27 private $className;
28
29 /**
30 * @var boolean|null
31 */
32 private $lazy;
33
34 /**
35 * @var string|null
36 */
37 private $scope;
38
39 /**
40 * Array of constructor parameters.
41 * @var array
42 */
43 private $constructor = [];
44
45 /**
46 * Array of properties and their value.
47 * @var array
48 */
49 private $properties = [];
50
51 /**
52 * Array of methods and their parameters.
53 * @var array
54 */
55 private $methods = [];
56
57 /**
58 * Helper for defining an object.
59 *
60 * @param string|null $className Class name of the object.
61 * If null, the name of the entry (in the container) will be used as class name.
62 */
63 public function __construct($className = null)
64 {
65 $this->className = $className;
66 }
67
68 /**
69 * Define the entry as lazy.
70 *
71 * A lazy entry is created only when it is used, a proxy is injected instead.
72 *
73 * @return ObjectDefinitionHelper
74 */
75 public function lazy()
76 {
77 $this->lazy = true;
78 return $this;
79 }
80
81 /**
82 * Defines the scope of the entry.
83 *
84 * @param string $scope
85 *
86 * @return ObjectDefinitionHelper
87 */
88 public function scope($scope)
89 {
90 $this->scope = $scope;
91 return $this;
92 }
93
94 /**
95 * Defines the arguments to use to call the constructor.
96 *
97 * This method takes a variable number of arguments, example:
98 * ->constructor($param1, $param2, $param3)
99 *
100 * @param mixed ... Parameters to use for calling the constructor of the class.
101 *
102 * @return ObjectDefinitionHelper
103 */
104 public function constructor()
105 {
106 $this->constructor = func_get_args();
107 return $this;
108 }
109
110 /**
111 * Defines a value for a specific argument of the constructor.
112 *
113 * This method is usually used together with annotations or autowiring, when a parameter
114 * is not (or cannot be) type-hinted. Using this method instead of constructor() allows to
115 * avoid defining all the parameters (letting them being resolved using annotations or autowiring)
116 * and only define one.
117 *
118 * @param string $parameter Parameter for which the value will be given.
119 * @param mixed $value Value to give to this parameter.
120 *
121 * @return ObjectDefinitionHelper
122 */
123 public function constructorParameter($parameter, $value)
124 {
125 $this->constructor[$parameter] = $value;
126 return $this;
127 }
128
129 /**
130 * Defines a value to inject in a property of the object.
131 *
132 * @param string $property Entry in which to inject the value.
133 * @param mixed $value Value to inject in the property.
134 *
135 * @return ObjectDefinitionHelper
136 */
137 public function property($property, $value)
138 {
139 $this->properties[$property] = $value;
140 return $this;
141 }
142
143 /**
144 * Defines a method to call and the arguments to use.
145 *
146 * This method takes a variable number of arguments after the method name, example:
147 *
148 * ->method('myMethod', $param1, $param2)
149 *
150 * Can be used multiple times to declare multiple calls.
151 *
152 * @param string $method Name of the method to call.
153 * @param mixed ... Parameters to use for calling the method.
154 *
155 * @return ObjectDefinitionHelper
156 */
157 public function method($method)
158 {
159 $args = func_get_args();
160 array_shift($args);
161
162 if (! isset($this->methods[$method])) {
163 $this->methods[$method] = [];
164 }
165
166 $this->methods[$method][] = $args;
167
168 return $this;
169 }
170
171 /**
172 * Defines a method to call and a value for a specific argument.
173 *
174 * This method is usually used together with annotations or autowiring, when a parameter
175 * is not (or cannot be) type-hinted. Using this method instead of method() allows to
176 * avoid defining all the parameters (letting them being resolved using annotations or
177 * autowiring) and only define one.
178 *
179 * If multiple calls to the method have been configured already (e.g. in a previous definition)
180 * then this method only overrides the parameter for the *first* call.
181 *
182 * @param string $method Name of the method to call.
183 * @param string $parameter Name or index of the parameter for which the value will be given.
184 * @param mixed $value Value to give to this parameter.
185 *
186 * @return ObjectDefinitionHelper
187 */
188 public function methodParameter($method, $parameter, $value)
189 {
190 // Special case for the constructor
191 if ($method === '__construct') {
192 $this->constructor[$parameter] = $value;
193 return $this;
194 }
195
196 if (! isset($this->methods[$method])) {
197 $this->methods[$method] = [0 => []];
198 }
199
200 $this->methods[$method][0][$parameter] = $value;
201
202 return $this;
203 }
204
205 /**
206 * {@inheritdoc}
207 */
208 public function getDefinition($entryName)
209 {
210 $definition = new ObjectDefinition($entryName, $this->className);
211
212 if ($this->lazy !== null) {
213 $definition->setLazy($this->lazy);
214 }
215 if ($this->scope !== null) {
216 $definition->setScope($this->scope);
217 }
218
219 if (! empty($this->constructor)) {
220 $parameters = $this->fixParameters($definition, '__construct', $this->constructor);
221 $constructorInjection = MethodInjection::constructor($parameters);
222 $definition->setConstructorInjection($constructorInjection);
223 }
224
225 if (! empty($this->properties)) {
226 foreach ($this->properties as $property => $value) {
227 $definition->addPropertyInjection(
228 new PropertyInjection($property, $value)
229 );
230 }
231 }
232
233 if (! empty($this->methods)) {
234 foreach ($this->methods as $method => $calls) {
235 foreach ($calls as $parameters) {
236 $parameters = $this->fixParameters($definition, $method, $parameters);
237 $methodInjection = new MethodInjection($method, $parameters);
238 $definition->addMethodInjection($methodInjection);
239 }
240 }
241 }
242
243 return $definition;
244 }
245
246 /**
247 * Fixes parameters indexed by the parameter name -> reindex by position.
248 *
249 * This is necessary so that merging definitions between sources is possible.
250 *
251 * @param ObjectDefinition $definition
252 * @param string $method
253 * @param array $parameters
254 * @throws DefinitionException
255 * @return array
256 */
257 private function fixParameters(ObjectDefinition $definition, $method, $parameters)
258 {
259 $fixedParameters = [];
260
261 foreach ($parameters as $index => $parameter) {
262 // Parameter indexed by the parameter name, we reindex it with its position
263 if (is_string($index)) {
264 $callable = [$definition->getClassName(), $method];
265 try {
266 $reflectionParameter = new \ReflectionParameter($callable, $index);
267 } catch (\ReflectionException $e) {
268 throw DefinitionException::create($definition, "Parameter with name '$index' could not be found");
269 }
270
271 $index = $reflectionParameter->getPosition();
272 }
273
274 $fixedParameters[$index] = $parameter;
275 }
276
277 return $fixedParameters;
278 }
279 }