Completed
Push — master ( 93ef1f...d80a95 )
by Christophe
13:13
created

ParamFetcher::resolveParameters()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 14
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 4.128

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 14
ccs 8
cts 10
cp 0.8
rs 9.2
cc 4
eloc 8
nc 4
nop 1
crap 4.128
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()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
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