Completed
Push — master ( aafb5f...ed91ed )
by Christian
14s queued 10s
created

ViewHandler::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 26

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 26
ccs 12
cts 12
cp 1
rs 9.504
c 0
b 0
f 0
cc 1
nc 1
nop 9
crap 1

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

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\View;
13
14
use FOS\RestBundle\Context\Context;
15
use FOS\RestBundle\Serializer\Serializer;
16
use Symfony\Component\Form\FormInterface;
17
use Symfony\Component\HttpFoundation\RedirectResponse;
18
use Symfony\Component\HttpFoundation\Request;
19
use Symfony\Component\HttpFoundation\RequestStack;
20
use Symfony\Component\HttpFoundation\Response;
21
use Symfony\Component\HttpKernel\Exception\UnsupportedMediaTypeHttpException;
22
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
23
24
/**
25
 * View may be used in controllers to build up a response in a format agnostic way
26
 * The View class takes care of encoding your data in json, xml via the Serializer
27
 * component.
28
 *
29
 * @author Jordi Boggiano <[email protected]>
30
 * @author Lukas K. Smith <[email protected]>
31
 */
32
class ViewHandler implements ConfigurableViewHandlerInterface
33
{
34
    /**
35
     * Key format, value a callable that returns a Response instance.
36
     *
37
     * @var array
38
     */
39
    protected $customHandlers = [];
40
41
    /**
42
     * The supported formats as keys.
43
     *
44
     * @var array
45
     */
46
    protected $formats;
47
48
    /**
49
     *  HTTP response status code for a failed validation.
50
     *
51
     * @var int
52
     */
53
    protected $failedValidationCode;
54
55
    /**
56
     * HTTP response status code when the view data is null.
57
     *
58
     * @var int
59
     */
60
    protected $emptyContentCode;
61
62
    /**
63
     * Whether or not to serialize null view data.
64
     *
65
     * @var bool
66
     */
67
    protected $serializeNull;
68
69
    /**
70
     * If to force a redirect for the given key format,
71
     * with value being the status code to use.
72
     *
73
     * @var array
74
     */
75
    protected $forceRedirects;
76
77
    /**
78
     * @var string
79
     */
80
    protected $defaultEngine;
81
82
    /**
83
     * @var array
84
     */
85
    protected $exclusionStrategyGroups = [];
86
87
    /**
88
     * @var string
89
     */
90
    protected $exclusionStrategyVersion;
91
92
    /**
93
     * @var bool
94
     */
95
    protected $serializeNullStrategy;
96
97
    private $urlGenerator;
98
    private $serializer;
99
    private $requestStack;
100
101
    private $options;
102
103
    /**
104
     * Constructor.
105
     *
106
     * @param UrlGeneratorInterface $urlGenerator         The URL generator
107
     * @param Serializer            $serializer
108
     * @param RequestStack          $requestStack         The request stack
109
     * @param array                 $formats              the supported formats as keys
110
     * @param int                   $failedValidationCode The HTTP response status code for a failed validation
111
     * @param int                   $emptyContentCode     HTTP response status code when the view data is null
112
     * @param bool                  $serializeNull        Whether or not to serialize null view data
113
     * @param array                 $forceRedirects       If to force a redirect for the given key format, with value being the status code to use
114
     * @param array                 $options              config options
115
     */
116
    private function __construct(
117
        UrlGeneratorInterface $urlGenerator,
118
        Serializer $serializer,
119 81
        RequestStack $requestStack,
120
        array $formats = null,
121
        $failedValidationCode = Response::HTTP_BAD_REQUEST,
122
        $emptyContentCode = Response::HTTP_NO_CONTENT,
123
        $serializeNull = false,
124
        array $forceRedirects = null,
125
        array $options = []
126
    ) {
127
        $this->urlGenerator = $urlGenerator;
128
        $this->serializer = $serializer;
129
        $this->requestStack = $requestStack;
130
        $this->formats = (array) $formats;
131 81
        $this->failedValidationCode = $failedValidationCode;
132 81
        $this->emptyContentCode = $emptyContentCode;
133 81
        $this->serializeNull = $serializeNull;
134 81
        $this->forceRedirects = (array) $forceRedirects;
135 81
        $this->options = $options + [
136 81
            'exclusionStrategyGroups' => [],
137 81
            'exclusionStrategyVersion' => null,
138 81
            'serializeNullStrategy' => null,
139 81
            ];
140 81
        $this->reset();
141 81
    }
142
143
    public static function create(
144
        UrlGeneratorInterface $urlGenerator,
145
        Serializer $serializer,
146
        RequestStack $requestStack,
147
        array $formats = null,
148 1
        int $failedValidationCode = Response::HTTP_BAD_REQUEST,
149
        int $emptyContentCode = Response::HTTP_NO_CONTENT,
150 1
        bool $serializeNull = false,
151 1
        array $options = []
152
    ): self
153
    {
154
        return new self($urlGenerator, $serializer, $requestStack, $formats, $failedValidationCode, $emptyContentCode, $serializeNull, [], $options, false);
0 ignored issues
show
Unused Code introduced by
The call to ViewHandler::__construct() has too many arguments starting with false.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
155
    }
156
157
    /**
158 7
     * Sets the default serialization groups.
159
     *
160 7
     * @param array|string $groups
161 7
     */
162
    public function setExclusionStrategyGroups($groups)
163
    {
164
        $this->exclusionStrategyGroups = (array) $groups;
165
    }
166
167
    /**
168 25
     * Sets the default serialization version.
169
     *
170 25
     * @param string $version
171 25
     */
172
    public function setExclusionStrategyVersion($version)
173
    {
174
        $this->exclusionStrategyVersion = $version;
175
    }
176 44
177
    /**
178 44
     * If nulls should be serialized.
179
     *
180
     * @param bool $isEnabled
181
     */
182
    public function setSerializeNullStrategy($isEnabled)
183
    {
184
        $this->serializeNullStrategy = $isEnabled;
185
    }
186
187
    /**
188
     * {@inheritdoc}
189
     */
190
    public function supports($format)
191
    {
192
        return isset($this->customHandlers[$format]) || isset($this->formats[$format]);
193 16
    }
194
195 16
    /**
196 1
     * Registers a custom handler.
197
     *
198
     * The handler must have the following signature: handler(ViewHandler $viewHandler, View $view, Request $request, $format)
199 15
     * It can use the public methods of this class to retrieve the needed data and return a
200 15
     * Response object ready to be sent.
201
     *
202
     * @param string   $format
203
     * @param callable $callable
204
     *
205
     * @throws \InvalidArgumentException
206
     */
207
    public function registerHandler($format, $callable)
208
    {
209
        if (!is_callable($callable)) {
210
            throw new \InvalidArgumentException('Registered view callback must be callable.');
211
        }
212
213
        $this->customHandlers[$format] = $callable;
214 55
    }
215
216 55
    /**
217
     * Gets a response HTTP status code from a View instance.
218 55
     *
219 7
     * By default it will return 200. However if there is a FormInterface stored for
220
     * the key 'form' in the View's data it will return the failed_validation
221
     * configuration if the form instance has errors.
222 48
     *
223 48
     * @param View  $view
224 15
     * @param mixed $content
225
     *
226
     * @return int HTTP status code
227 33
     */
228
    protected function getStatusCode(View $view, $content = null)
229
    {
230
        $form = $this->getFormFromView($view);
231
232
        if ($form && $form->isSubmitted() && !$form->isValid()) {
233
            return $this->failedValidationCode;
234
        }
235
236
        $statusCode = $view->getStatusCode();
237 46
        if (null !== $statusCode) {
238
            return $statusCode;
239 46
        }
240
241
        return null !== $content ? Response::HTTP_OK : $this->emptyContentCode;
242
    }
243
244
    /**
245
     * Gets or creates a JMS\Serializer\SerializationContext and initializes it with
246
     * the view exclusion strategies, groups & versions if a new context is created.
247
     *
248
     * @param View $view
249
     *
250 31
     * @return Context
251
     */
252 31
    protected function getSerializationContext(View $view)
253
    {
254 31
        $context = $view->getContext();
255 31
256 1
        $groups = $context->getGroups();
257 1
        if (empty($groups) && $this->exclusionStrategyGroups) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->exclusionStrategyGroups of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
258
            $context->setGroups($this->exclusionStrategyGroups);
259 31
        }
260 5
261 5
        if (null === $context->getVersion() && $this->exclusionStrategyVersion) {
262
            $context->setVersion($this->exclusionStrategyVersion);
263 31
        }
264 18
265 18
        if (null === $context->getSerializeNull() && null !== $this->serializeNullStrategy) {
266
            $context->setSerializeNull($this->serializeNullStrategy);
267 31
        }
268
269
        if (null !== $view->getStatusCode()) {
270
            $context->setAttribute('status_code', $view->getStatusCode());
271
        }
272
273
        return $context;
274
    }
275
276
    /**
277
     * Handles a request with the proper handler.
278
     *
279
     * Decides on which handler to use based on the request format.
280
     *
281
     * @param View    $view
282 40
     * @param Request $request
283
     *
284 40
     * @throws UnsupportedMediaTypeHttpException
285 12
     *
286 12
     * @return Response
287
     */
288 40
    public function handle(View $view, Request $request = null)
289
    {
290 40
        if (null === $request) {
291 1
            $request = $this->requestStack->getCurrentRequest();
292 1
        }
293
294
        $format = $view->getFormat() ?: $request->getRequestFormat();
0 ignored issues
show
Bug introduced by
It seems like $request is not always an object, but can also be of type null. Maybe add an additional type check?

If a variable is not always an object, we recommend to add an additional type check to ensure your method call is safe:

function someFunction(A $objectMaybe = null)
{
    if ($objectMaybe instanceof A) {
        $objectMaybe->doSomething();
    }
}
Loading history...
295 39
296 10
        if (!$this->supports($format)) {
297
            $msg = "Format '$format' not supported, handler must be implemented";
298
299 29
            throw new UnsupportedMediaTypeHttpException($msg);
300
        }
301
302
        if (isset($this->customHandlers[$format])) {
303
            return call_user_func($this->customHandlers[$format], $this, $view, $request, $format);
304
        }
305
306
        return $this->createResponse($view, $request, $format);
0 ignored issues
show
Bug introduced by
It seems like $request can be null; however, createResponse() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
307
    }
308
309
    /**
310
     * Creates the Response from the view.
311 8
     *
312
     * @param View   $view
313 8
     * @param string $location
314 8
     * @param string $format
315 1
     *
316 1
     * @return Response
317 7
     */
318 7
    public function createRedirectResponse(View $view, $location, $format)
319 2
    {
320 2
        $content = null;
321 2
        if ((Response::HTTP_CREATED === $view->getStatusCode() || Response::HTTP_ACCEPTED === $view->getStatusCode()) && null !== $view->getData()) {
322 2
            $response = $this->initResponse($view, $format);
323
        } else {
324
            $response = $view->getResponse();
325 8
            if ('html' === $format && isset($this->forceRedirects[$format])) {
326 8
                $redirect = new RedirectResponse($location);
327
                $content = $redirect->getContent();
328 8
                $response->setContent($content);
329 8
            }
330
        }
331 8
332
        $code = isset($this->forceRedirects[$format])
333
            ? $this->forceRedirects[$format] : $this->getStatusCode($view, $content);
334
335
        $response->setStatusCode($code);
336
        $response->headers->set('Location', $location);
337
338
        return $response;
339
    }
340
341
    /**
342 15
     * Handles creation of a Response using either redirection or the serializer service.
343
     *
344 15
     * @param View    $view
345
     * @param Request $request
346 15
     * @param string  $format
347 15
     *
348 2
     * @return Response
349
     */
350
    public function createResponse(View $view, Request $request, $format)
351
    {
352 2
        $route = $view->getRoute();
353
354
        $location = $route
355
            ? $this->urlGenerator->generate($route, (array) $view->getRouteParameters(), UrlGeneratorInterface::ABSOLUTE_URL)
356 2
            : $view->getLocation();
357
358 15
        if ($location) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $location of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
359
            return $this->createRedirectResponse($view, $location, $format);
360
        }
361
362
        $response = $this->initResponse($view, $format);
363
364
        if (!$response->headers->has('Content-Type')) {
365
            $mimeType = $request->attributes->get('media_type');
366
            if (null === $mimeType) {
367
                $mimeType = $request->getMimeType($format);
368 22
            }
369
370 22
            $response->headers->set('Content-Type', $mimeType);
371
        }
372 22
373 2
        return $response;
374 22
    }
375 12
376 12
    /**
377
     * Initializes a response object that represents the view and holds the view's status code.
378 22
     *
379 2
     * @param View   $view
380 2
     * @param string $format
381
     *
382 22
     * @return Response
383 22
     */
384 2
    private function initResponse(View $view, $format)
385 2
    {
386
        $content = null;
387 22
        if ($this->serializeNull || null !== $view->getData()) {
388
            $data = $this->getDataFromView($view);
389
390
            if ($data instanceof FormInterface && $data->isSubmitted() && !$data->isValid()) {
391
                $view->getContext()->setAttribute('status_code', $this->failedValidationCode);
392
            }
393
394
            $context = $this->getSerializationContext($view);
395
396
            $content = $this->serializer->serialize($data, $format, $context);
397
        }
398
399 51
        $response = $view->getResponse();
400
        $response->setStatusCode($this->getStatusCode($view, $content));
401 51
402
        if (null !== $content) {
403
            $response->setContent($content);
404 51
        }
405 51
406
        return $response;
407 51
    }
408 8
409
    /**
410
     * Returns the form from the given view if present, false otherwise.
411 43
     *
412
     * @param View $view
413 43
     *
414 43
     * @return bool|FormInterface
415 43
     */
416
    protected function getFormFromView(View $view)
417 43
    {
418
        $data = $view->getData();
419
420
        if ($data instanceof FormInterface) {
421
            return $data;
422
        }
423
424
        if (is_array($data) && isset($data['form']) && $data['form'] instanceof FormInterface) {
425
            return $data['form'];
426
        }
427
428 44
        return false;
429
    }
430 44
431 44
    /**
432 15
     * Returns the data from a view.
433 44
     *
434 28
     * @param View $view
435
     *
436 28
     * @return mixed|null
437 6
     */
438 6
    private function getDataFromView(View $view)
439
    {
440 28
        $form = $this->getFormFromView($view);
441 28
442
        if (false === $form) {
443 28
            return $view->getData();
444 28
        }
445
446 44
        return $form;
447 44
    }
448
449 44
    /**
450 39
     * Resets internal object state at the end of the request.
451 39
     */
452
    public function reset()
453 44
    {
454
        $this->exclusionStrategyGroups = $this->options['exclusionStrategyGroups'];
455
        $this->exclusionStrategyVersion = $this->options['exclusionStrategyVersion'];
456
        $this->serializeNullStrategy = $this->options['serializeNullStrategy'];
457
    }
458
}
459