Issues (21)

EventListener/ResponseSchemaValidatorListener.php (4 issues)

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Spiechu\SymfonyCommonsBundle\EventListener;
6
7
use Spiechu\SymfonyCommonsBundle\Event\ResponseSchemaCheck\CheckRequest;
8
use Spiechu\SymfonyCommonsBundle\Event\ResponseSchemaCheck\CheckResult;
9
use Spiechu\SymfonyCommonsBundle\Event\ResponseSchemaCheck\Events;
10
use Spiechu\SymfonyCommonsBundle\Service\SchemaValidator\ValidationResult;
11
use Spiechu\SymfonyCommonsBundle\Utils\ArrayUtils;
12
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
13
use Symfony\Component\HttpFoundation\Request;
14
use Symfony\Component\HttpFoundation\Response;
15
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
16
17
class ResponseSchemaValidatorListener
18
{
19
    /**
20
     * @var EventDispatcherInterface
21
     */
22
    protected $eventDispatcher;
23
24
    /**
25
     * @var bool
26
     */
27
    protected $throwExceptionWhenFormatNotFound;
28
29
    /**
30
     * @param EventDispatcherInterface $eventDispatcher
31
     * @param bool                     $throwExceptionWhenFormatNotFound
32
     */
33 37
    public function __construct(EventDispatcherInterface $eventDispatcher, bool $throwExceptionWhenFormatNotFound)
34
    {
35 37
        $this->eventDispatcher = $eventDispatcher;
36 37
        $this->throwExceptionWhenFormatNotFound = $throwExceptionWhenFormatNotFound;
37 37
    }
38
39
    /**
40
     * @param FilterResponseEvent $filterResponseEvent
41
     *
42
     * @throws \RuntimeException
43
     */
44 37
    public function onKernelResponse(FilterResponseEvent $filterResponseEvent): void
45
    {
46 37
        if (!$filterResponseEvent->isMasterRequest()) {
47 1
            return;
48
        }
49
50 36
        $request = $filterResponseEvent->getRequest();
51 36
        $responseSchemas = $this->getResponseSchemas($request);
52
53 36
        if (empty($responseSchemas)) {
54 18
            return;
55
        }
56
57 18
        $response = $filterResponseEvent->getResponse();
58 18
        $format = $this->getFormat($request, $response);
59
60 15
        if (null === $format || empty($responseSchemas[$format])) {
61 4
            return;
62
        }
63
64 11
        $responseStatusCode = $response->getStatusCode();
65
66 11
        if (!array_key_exists($responseStatusCode, $responseSchemas[$format])) {
67 2
            return;
68
        }
69
70 9
        $content = $response->getContent();
71 9
        $validationResult = $this->dispatchCheckSchemaRequest($format, $content, $responseSchemas[$format][$responseStatusCode]);
72
73 7
        $this->eventDispatcher->dispatch(
74 7
            Events::CHECK_RESULT,
0 ignored issues
show
Spiechu\SymfonyCommonsBu...ck\Events::CHECK_RESULT of type string is incompatible with the type object expected by parameter $event of Symfony\Contracts\EventD...erInterface::dispatch(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

74
            /** @scrutinizer ignore-type */ Events::CHECK_RESULT,
Loading history...
75 7
            new CheckResult($format, $content, $validationResult)
0 ignored issues
show
The call to Symfony\Contracts\EventD...erInterface::dispatch() has too many arguments starting with new Spiechu\SymfonyCommo...ent, $validationResult). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

75
        $this->eventDispatcher->/** @scrutinizer ignore-call */ 
76
                                dispatch(

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. Please note the @ignore annotation hint above.

Loading history...
76
        );
77 5
    }
78
79
    /**
80
     * @param Request $request
81
     *
82
     * @return array
83
     */
84 36
    protected function getResponseSchemas(Request $request): array
85
    {
86 36
        $responseSchemas = $request->attributes->get(
87 36
            RequestSchemaValidatorListener::ATTRIBUTE_RESPONSE_SCHEMAS,
88 36
            []
89
        );
90
91 36
        if (empty($responseSchemas) || empty(ArrayUtils::flatArrayRecursive($responseSchemas))) {
92 18
            return [];
93
        }
94
95 18
        return $responseSchemas;
96
    }
97
98
    /**
99
     * @param Request  $request
100
     * @param Response $response
101
     *
102
     * @throws \RuntimeException When not able to determine response format on $this->throwExceptionWhenFormatNotFound flag true
103
     *
104
     * @return null|string
105
     */
106 18
    protected function getFormat(Request $request, Response $response): ?string
107
    {
108 18
        $format = $request->getFormat($response->headers->get('content_type'));
109
110 18
        if (null !== $format) {
111 12
            return strtolower($format);
112
        }
113
114 6
        if ($this->throwExceptionWhenFormatNotFound) {
115 3
            throw new \RuntimeException('Not able to determine response format');
116
        }
117
118 3
        return null;
119
    }
120
121
    /**
122
     * @param string $format
123
     * @param string $content
124
     * @param string $responseSchemaLocation
125
     *
126
     * @throws \RuntimeException
127
     *
128
     * @return ValidationResult
129
     */
130 9
    protected function dispatchCheckSchemaRequest(string $format, string $content, string $responseSchemaLocation): ValidationResult
131
    {
132 9
        $checkEventName = Events::getCheckSchemaEventNameFor($format);
133
134 9
        if (!$this->eventDispatcher->hasListeners($checkEventName)) {
135 1
            throw new \RuntimeException(sprintf('No listener listens on "%s" event', $checkEventName));
136
        }
137
138 8
        $checkRequest = new CheckRequest($format, $content, $responseSchemaLocation);
139
140 8
        $this->eventDispatcher->dispatch($checkEventName, $checkRequest);
0 ignored issues
show
$checkEventName of type string is incompatible with the type object expected by parameter $event of Symfony\Contracts\EventD...erInterface::dispatch(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

140
        $this->eventDispatcher->dispatch(/** @scrutinizer ignore-type */ $checkEventName, $checkRequest);
Loading history...
The call to Symfony\Contracts\EventD...erInterface::dispatch() has too many arguments starting with $checkRequest. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

140
        $this->eventDispatcher->/** @scrutinizer ignore-call */ 
141
                                dispatch($checkEventName, $checkRequest);

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. Please note the @ignore annotation hint above.

Loading history...
141
142 8
        $validationResult = $checkRequest->getValidationResult();
143
144 8
        if (!$validationResult instanceof ValidationResult) {
145 1
            throw new \RuntimeException(sprintf('No listener was able to check request for format "%s"', $format));
146
        }
147
148 7
        return $validationResult;
149
    }
150
}
151