1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
/* |
4
|
|
|
* This file is part of the FOSRestBundle package. |
5
|
|
|
* |
6
|
|
|
* (c) FriendsOfSymfony <http://friendsofsymfony.github.com/> |
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 FOS\RestBundle\Request; |
13
|
|
|
|
14
|
|
|
use Doctrine\Common\Util\ClassUtils; |
15
|
|
|
use FOS\RestBundle\Controller\Annotations\ParamInterface; |
16
|
|
|
use FOS\RestBundle\Util\ResolverTrait; |
17
|
|
|
use FOS\RestBundle\Validator\Constraints\ResolvableConstraintInterface; |
18
|
|
|
use FOS\RestBundle\Validator\ViolationFormatterInterface; |
19
|
|
|
use Symfony\Component\DependencyInjection\ContainerAwareInterface; |
20
|
|
|
use Symfony\Component\DependencyInjection\ContainerAwareTrait; |
21
|
|
|
use Symfony\Component\HttpFoundation\Request; |
22
|
|
|
use Symfony\Component\HttpFoundation\RequestStack; |
23
|
|
|
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; |
24
|
|
|
use Symfony\Component\Validator\Constraint; |
25
|
|
|
use Symfony\Component\Validator\ConstraintViolationList; |
26
|
|
|
use Symfony\Component\Validator\Validator\ValidatorInterface; |
27
|
|
|
use Symfony\Component\Validator\Exception\ValidatorException; |
28
|
|
|
use Symfony\Component\Validator\ConstraintViolation; |
29
|
|
|
|
30
|
|
|
/** |
31
|
|
|
* Helper to validate parameters of the active request. |
32
|
|
|
* |
33
|
|
|
* @author Alexander <[email protected]> |
34
|
|
|
* @author Lukas Kahwe Smith <[email protected]> |
35
|
|
|
* @author Jordi Boggiano <[email protected]> |
36
|
|
|
* @author Boris Guéry <[email protected]> |
37
|
|
|
*/ |
38
|
|
|
class ParamFetcher implements ParamFetcherInterface, ContainerAwareInterface |
39
|
|
|
{ |
40
|
|
|
use ResolverTrait, ContainerAwareTrait; |
41
|
|
|
|
42
|
|
|
private $paramReader; |
43
|
|
|
private $requestStack; |
44
|
|
|
private $params; |
45
|
|
|
private $validator; |
46
|
|
|
private $violationFormatter; |
47
|
|
|
|
48
|
|
|
/** |
49
|
|
|
* @var callable |
50
|
|
|
*/ |
51
|
|
|
private $controller; |
52
|
|
|
|
53
|
|
|
/** |
54
|
|
|
* Initializes fetcher. |
55
|
|
|
* |
56
|
|
|
* @param ParamReaderInterface $paramReader |
57
|
|
|
* @param RequestStack $requestStack |
58
|
22 |
|
* @param ValidatorInterface $validator |
59
|
|
|
* @param ViolationFormatterInterface $violationFormatter |
60
|
22 |
|
*/ |
61
|
22 |
|
public function __construct(ParamReaderInterface $paramReader, RequestStack $requestStack, ViolationFormatterInterface $violationFormatter, ValidatorInterface $validator = null) |
62
|
22 |
|
{ |
63
|
22 |
|
$this->paramReader = $paramReader; |
64
|
22 |
|
$this->requestStack = $requestStack; |
65
|
|
|
$this->violationFormatter = $violationFormatter; |
66
|
|
|
$this->validator = $validator; |
67
|
|
|
} |
68
|
|
|
|
69
|
10 |
|
/** |
70
|
|
|
* {@inheritdoc} |
71
|
10 |
|
*/ |
72
|
10 |
|
public function setController($controller) |
73
|
|
|
{ |
74
|
|
|
$this->controller = $controller; |
75
|
|
|
} |
76
|
|
|
|
77
|
|
|
/** |
78
|
|
|
* Add additional params to the ParamFetcher during runtime. |
79
|
|
|
* |
80
|
|
|
* Note that adding a param that has the same name as an existing param will override that param. |
81
|
1 |
|
* |
82
|
|
|
* @param ParamInterface $param |
83
|
1 |
|
*/ |
84
|
1 |
|
public function addParam(ParamInterface $param) |
85
|
1 |
|
{ |
86
|
|
|
$this->getParams(); // init params |
87
|
|
|
$this->params[$param->getName()] = $param; |
88
|
|
|
} |
89
|
|
|
|
90
|
5 |
|
/** |
91
|
|
|
* @return ParamInterface[] |
92
|
5 |
|
*/ |
93
|
5 |
|
public function getParams() |
94
|
5 |
|
{ |
95
|
|
|
if (null === $this->params) { |
96
|
5 |
|
$this->initParams(); |
97
|
|
|
} |
98
|
|
|
|
99
|
|
|
return $this->params; |
100
|
|
|
} |
101
|
|
|
|
102
|
5 |
|
/** |
103
|
|
|
* {@inheritdoc} |
104
|
5 |
|
*/ |
105
|
|
|
public function get($name, $strict = null) |
106
|
5 |
|
{ |
107
|
1 |
|
$params = $this->getParams(); |
108
|
|
|
|
109
|
|
|
if (!array_key_exists($name, $params)) { |
110
|
|
|
throw new \InvalidArgumentException(sprintf("No @ParamInterface configuration for parameter '%s'.", $name)); |
111
|
4 |
|
} |
112
|
4 |
|
|
113
|
4 |
|
/** @var ParamInterface $param */ |
114
|
|
|
$param = $params[$name]; |
115
|
4 |
|
$default = $param->getDefault(); |
116
|
|
|
$strict = (null !== $strict ? $strict : $param->isStrict()); |
117
|
4 |
|
|
118
|
|
|
$paramValue = $param->getValue($this->getRequest(), $default); |
119
|
|
|
|
120
|
|
|
return $this->cleanParamWithRequirements($param, $paramValue, $strict); |
121
|
|
|
} |
122
|
|
|
|
123
|
|
|
/** |
124
|
|
|
* @param ParamInterface $param |
125
|
|
|
* @param mixed $paramValue |
126
|
|
|
* @param bool $strict |
127
|
|
|
* |
128
|
|
|
* @throws BadRequestHttpException |
129
|
|
|
* @throws \RuntimeException |
130
|
9 |
|
* |
131
|
|
|
* @return mixed |
132
|
9 |
|
*/ |
133
|
|
|
protected function cleanParamWithRequirements(ParamInterface $param, $paramValue, $strict) |
134
|
9 |
|
{ |
135
|
9 |
|
if (empty($this->container)) { |
136
|
2 |
|
throw new \InvalidArgumentException( |
137
|
|
|
'The ParamFetcher has been not initialized correctly. '. |
138
|
|
|
'The container for parameter resolution is missing.' |
139
|
7 |
|
); |
140
|
7 |
|
} |
141
|
1 |
|
|
142
|
|
|
$default = $param->getDefault(); |
143
|
6 |
|
$default = $this->resolveValue($this->container, $default); |
144
|
1 |
|
|
145
|
|
|
$this->checkNotIncompatibleParams($param); |
146
|
1 |
|
if ($default !== null && $default === $paramValue) { |
147
|
|
|
return $paramValue; |
148
|
|
|
} |
149
|
|
|
|
150
|
5 |
|
$constraints = $param->getConstraints(); |
151
|
5 |
|
$this->resolveConstraints($constraints); |
152
|
|
|
if (empty($constraints)) { |
153
|
|
|
return $paramValue; |
154
|
|
|
} |
155
|
|
|
if (null === $this->validator) { |
156
|
|
|
throw new \RuntimeException( |
157
|
|
|
'The ParamFetcher requirements feature requires the symfony/validator component.' |
158
|
|
|
); |
159
|
|
|
} |
160
|
|
|
|
161
|
|
|
try { |
162
|
|
|
$errors = $this->validator->validate($paramValue, $constraints); |
163
|
|
|
} catch (ValidatorException $e) { |
164
|
|
|
$violation = new ConstraintViolation( |
165
|
5 |
|
$e->getMessage(), |
166
|
4 |
|
$e->getMessage(), |
167
|
1 |
|
array(), |
168
|
1 |
|
$paramValue, |
169
|
1 |
|
'', |
170
|
|
|
null, |
171
|
|
|
null, |
172
|
3 |
|
$e->getCode() |
173
|
|
|
); |
174
|
|
|
$errors = new ConstraintViolationList(array($violation)); |
175
|
3 |
|
} |
176
|
|
|
|
177
|
|
|
if (0 < count($errors)) { |
178
|
|
|
if ($strict) { |
179
|
|
|
throw new BadRequestHttpException( |
180
|
|
|
$this->violationFormatter->formatList($param, $errors) |
181
|
4 |
|
); |
182
|
|
|
} |
183
|
4 |
|
|
184
|
|
|
return null === $default ? '' : $default; |
185
|
4 |
|
} |
186
|
4 |
|
|
187
|
4 |
|
return $paramValue; |
188
|
4 |
|
} |
189
|
|
|
|
190
|
4 |
|
/** |
191
|
|
|
* {@inheritdoc} |
192
|
|
|
*/ |
193
|
|
|
public function all($strict = null) |
194
|
|
|
{ |
195
|
|
|
$configuredParams = $this->getParams(); |
196
|
|
|
|
197
|
|
|
$params = []; |
198
|
9 |
|
foreach ($configuredParams as $name => $param) { |
199
|
|
|
$params[$name] = $this->get($name, $strict); |
200
|
9 |
|
} |
201
|
1 |
|
|
202
|
|
|
return $params; |
203
|
|
|
} |
204
|
8 |
|
|
205
|
3 |
|
/** |
206
|
|
|
* Initialize the parameters. |
207
|
3 |
|
* |
208
|
|
|
* @throws \InvalidArgumentException |
209
|
|
|
*/ |
210
|
|
|
private function initParams() |
211
|
5 |
|
{ |
212
|
|
|
if (empty($this->controller)) { |
213
|
5 |
|
throw new \InvalidArgumentException('Controller and method needs to be set via setController'); |
214
|
5 |
|
} |
215
|
5 |
|
|
216
|
5 |
|
if (!is_array($this->controller) || empty($this->controller[0]) || empty($this->controller[1])) { |
217
|
5 |
|
throw new \InvalidArgumentException( |
218
|
|
|
'Controller needs to be set as a class instance (closures/functions are not supported)' |
219
|
5 |
|
); |
220
|
5 |
|
} |
221
|
|
|
|
222
|
|
|
// the controller could be a proxy, e.g. when using the JMSSecuriyExtraBundle or JMSDiExtraBundle |
223
|
|
|
$className = ClassUtils::getClass($this->controller[0]); |
224
|
|
|
|
225
|
|
|
$this->params = $this->paramReader->read( |
226
|
|
|
new \ReflectionClass($className), |
227
|
|
|
$this->controller[1] |
228
|
|
|
); |
229
|
|
|
} |
230
|
|
|
|
231
|
5 |
|
/** |
232
|
|
|
* Check if current param is not in conflict with other parameters |
233
|
5 |
|
* according to the "incompatibles" field. |
234
|
5 |
|
* |
235
|
2 |
|
* @param ParamInterface $param the configuration for the param fetcher |
236
|
1 |
|
* |
237
|
|
|
* @throws InvalidArgumentException |
238
|
1 |
|
* @throws BadRequestHttpException |
239
|
|
|
*/ |
240
|
1 |
|
protected function checkNotIncompatibleParams(ParamInterface $param) |
241
|
1 |
|
{ |
242
|
1 |
|
$params = $this->getParams(); |
243
|
1 |
|
foreach ($param->getIncompatibilities() as $incompatibleParamName) { |
244
|
1 |
|
if (!array_key_exists($incompatibleParamName, $params)) { |
245
|
1 |
|
throw new \InvalidArgumentException(sprintf("No @ParamInterface configuration for parameter '%s'.", $incompatibleParamName)); |
246
|
|
|
} |
247
|
1 |
|
$incompatibleParam = $params[$incompatibleParamName]; |
248
|
|
|
|
249
|
4 |
|
if ($incompatibleParam->getValue($this->getRequest(), null) !== null) { |
250
|
3 |
|
$exceptionMessage = sprintf( |
251
|
|
|
"'%s' param is incompatible with %s param.", |
252
|
|
|
$param->getName(), |
253
|
|
|
$incompatibleParam->getName() |
254
|
|
|
); |
255
|
5 |
|
|
256
|
|
|
throw new BadRequestHttpException($exceptionMessage); |
257
|
5 |
|
} |
258
|
5 |
|
} |
259
|
4 |
|
} |
260
|
|
|
|
261
|
|
|
/** |
262
|
|
|
* @param Constraint[] $constraints |
263
|
|
|
*/ |
264
|
|
|
private function resolveConstraints(array $constraints) |
265
|
4 |
|
{ |
266
|
4 |
|
foreach ($constraints as $constraint) { |
267
|
5 |
|
if ($constraint instanceof ResolvableConstraintInterface) { |
268
|
5 |
|
$constraint->resolve($this->container); |
269
|
|
|
} |
270
|
|
|
} |
271
|
|
|
} |
272
|
|
|
|
273
|
|
|
/** |
274
|
|
|
* @throws \RuntimeException |
275
|
5 |
|
* |
276
|
|
|
* @return Request |
277
|
5 |
|
*/ |
278
|
5 |
View Code Duplication |
private function getRequest() |
|
|
|
|
279
|
|
|
{ |
280
|
|
|
$request = $this->requestStack->getCurrentRequest(); |
281
|
|
|
if ($request === null) { |
282
|
5 |
|
throw new \RuntimeException('There is no current request.'); |
283
|
|
|
} |
284
|
|
|
|
285
|
|
|
return $request; |
286
|
|
|
} |
287
|
|
|
} |
288
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.