Completed
Push — master ( 5a1ced...5e31e4 )
by Lukas Kahwe
18:21 queued 11:48
created

ParamFetcher::all()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 11
ccs 7
cts 7
cp 1
rs 9.4286
cc 2
eloc 6
nc 2
nop 1
crap 2
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
     * @param ValidatorInterface          $validator
59
     * @param ViolationFormatterInterface $violationFormatter
60
     */
61 23
    public function __construct(ParamReaderInterface $paramReader, RequestStack $requestStack, ViolationFormatterInterface $violationFormatter, ValidatorInterface $validator = null)
62
    {
63 23
        $this->paramReader = $paramReader;
64 23
        $this->requestStack = $requestStack;
65 23
        $this->violationFormatter = $violationFormatter;
66 23
        $this->validator = $validator;
67 23
    }
68
69
    /**
70
     * {@inheritdoc}
71
     */
72 11
    public function setController($controller)
73
    {
74 11
        $this->controller = $controller;
75 11
    }
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
     *
82
     * @param ParamInterface $param
83
     */
84 1
    public function addParam(ParamInterface $param)
85
    {
86 1
        $this->getParams(); // init params
87 1
        $this->params[$param->getName()] = $param;
88 1
    }
89
90
    /**
91
     * @return ParamInterface[]
92
     */
93 6
    public function getParams()
94
    {
95 6
        if (null === $this->params) {
96 6
            $this->initParams();
97 6
        }
98
99 6
        return $this->params;
100
    }
101
102
    /**
103
     * {@inheritdoc}
104
     */
105 6
    public function get($name, $strict = null)
106
    {
107 6
        $params = $this->getParams();
108
109 6
        if (!array_key_exists($name, $params)) {
110 1
            throw new \InvalidArgumentException(sprintf("No @ParamInterface configuration for parameter '%s'.", $name));
111
        }
112
113
        /** @var ParamInterface $param */
114 5
        $param = $params[$name];
115 5
        $default = $param->getDefault();
116 5
        $strict = (null !== $strict ? $strict : $param->isStrict());
117
118 5
        $paramValue = $param->getValue($this->getRequest(), $default);
119
120 5
        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
     *
131
     * @return mixed
132
     */
133 10
    protected function cleanParamWithRequirements(ParamInterface $param, $paramValue, $strict)
134
    {
135 10
        if (empty($this->container)) {
136
            throw new \InvalidArgumentException(
137
                'The ParamFetcher has been not initialized correctly. '.
138
                'The container for parameter resolution is missing.'
139
            );
140
        }
141
142 10
        $default = $param->getDefault();
143 10
        $default = $this->resolveValue($this->container, $default);
144
145 10
        $this->checkNotIncompatibleParams($param);
146 10
        if ($default !== null && $default === $paramValue) {
147 3
            return $paramValue;
148
        }
149
150 9
        $constraints = $param->getConstraints();
151 9
        $this->resolveConstraints($constraints);
152 9
        if (empty($constraints)) {
153 1
            return $paramValue;
154
        }
155 8
        if (null === $this->validator) {
156 1
            throw new \RuntimeException(
157
                'The ParamFetcher requirements feature requires the symfony/validator component.'
158 1
            );
159
        }
160
161
        try {
162 7
            $errors = $this->validator->validate($paramValue, $constraints);
163 7
        } catch (ValidatorException $e) {
164 2
            $violation = new ConstraintViolation(
165 2
                $e->getMessage(),
166 2
                $e->getMessage(),
167 2
                array(),
168 2
                $paramValue,
169 2
                '',
170 2
                null,
171 2
                null,
172 2
                $e->getCode()
173 2
            );
174 2
            $errors = new ConstraintViolationList(array($violation));
175
        }
176
177 7
        if (0 < count($errors)) {
178 6
            if ($strict) {
179 1
                throw new BadRequestHttpException(
180 1
                    $this->violationFormatter->formatList($param, $errors)
181 1
                );
182
            }
183
184 5
            return null === $default ? '' : $default;
185
        }
186
187 5
        return $paramValue;
188
    }
189
190
    /**
191
     * {@inheritdoc}
192
     */
193 5
    public function all($strict = null)
194
    {
195 5
        $configuredParams = $this->getParams();
196
197 5
        $params = [];
198 5
        foreach ($configuredParams as $name => $param) {
199 5
            $params[$name] = $this->get($name, $strict);
200 5
        }
201
202 5
        return $params;
203
    }
204
205
    /**
206
     * Initialize the parameters.
207
     *
208
     * @throws \InvalidArgumentException
209
     */
210 10
    private function initParams()
211
    {
212 10
        if (empty($this->controller)) {
213 1
            throw new \InvalidArgumentException('Controller and method needs to be set via setController');
214
        }
215
216 9
        if (!is_array($this->controller) || empty($this->controller[0]) || empty($this->controller[1])) {
217 3
            throw new \InvalidArgumentException(
218
                'Controller needs to be set as a class instance (closures/functions are not supported)'
219 3
            );
220
        }
221
222
        // the controller could be a proxy, e.g. when using the JMSSecuriyExtraBundle or JMSDiExtraBundle
223 6
        $className = ClassUtils::getClass($this->controller[0]);
224
225 6
        $this->params = $this->paramReader->read(
226 6
            new \ReflectionClass($className),
227 6
            $this->controller[1]
228 6
        );
229 6
    }
230
231
    /**
232
     * Check if current param is not in conflict with other parameters
233
     * according to the "incompatibles" field.
234
     *
235
     * @param ParamInterface $param the configuration for the param fetcher
236
     *
237
     * @throws InvalidArgumentException
238
     * @throws BadRequestHttpException
239
     */
240 6
    protected function checkNotIncompatibleParams(ParamInterface $param)
241
    {
242 6
        $params = $this->getParams();
243 6
        foreach ($param->getIncompatibilities() as $incompatibleParamName) {
244 2
            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 1
            if ($incompatibleParam->getValue($this->getRequest(), null) !== null) {
250 1
                $exceptionMessage = sprintf(
251 1
                    "'%s' param is incompatible with %s param.",
252 1
                    $param->getName(),
253 1
                    $incompatibleParam->getName()
254 1
                );
255
256 1
                throw new BadRequestHttpException($exceptionMessage);
257
            }
258 5
        }
259 4
    }
260
261
    /**
262
     * @param Constraint[] $constraints
263
     */
264 9
    private function resolveConstraints(array $constraints)
265
    {
266 9
        foreach ($constraints as $constraint) {
267 8
            if ($constraint instanceof ResolvableConstraintInterface) {
268
                $constraint->resolve($this->container);
269
            }
270 9
        }
271 9
    }
272
273
    /**
274
     * @throws \RuntimeException
275
     *
276
     * @return Request
277
     */
278 6 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 6
        $request = $this->requestStack->getCurrentRequest();
281 6
        if ($request === null) {
282
            throw new \RuntimeException('There is no current request.');
283
        }
284
285 6
        return $request;
286
    }
287
}
288