Passed
Push — master ( 75a0ae...258ca1 )
by Dawid
03:09
created

DataCollector::getRouteCollectionGenerator()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 15
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 4.0218

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 15
ccs 8
cts 9
cp 0.8889
rs 9.2
cc 4
eloc 8
nc 4
nop 0
crap 4.0218
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Spiechu\SymfonyCommonsBundle\Service;
6
7
use Doctrine\Common\Annotations\Reader;
8
use Spiechu\SymfonyCommonsBundle\Annotation\Controller\ControllerAnnotationExtractorTrait;
9
use Spiechu\SymfonyCommonsBundle\Annotation\Controller\ResponseSchemaValidator;
10
use Spiechu\SymfonyCommonsBundle\Event\ApiVersion\ApiVersionSetEvent;
11
use Spiechu\SymfonyCommonsBundle\Event\ApiVersion\Events as ApiVersionEvents;
12
use Spiechu\SymfonyCommonsBundle\Event\ResponseSchemaCheck\CheckResult;
13
use Spiechu\SymfonyCommonsBundle\Event\ResponseSchemaCheck\Events as ResponseSchemaCheckEvents;
14
use Spiechu\SymfonyCommonsBundle\EventListener\GetMethodOverrideListener;
15
use Spiechu\SymfonyCommonsBundle\EventListener\RequestSchemaValidatorListener;
16
use Spiechu\SymfonyCommonsBundle\Service\SchemaValidator\ValidationViolation;
17
use Spiechu\SymfonyCommonsBundle\Twig\DataCollectorExtension;
18
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
19
use Symfony\Component\HttpFoundation\Request;
20
use Symfony\Component\HttpFoundation\Response;
21
use Symfony\Component\HttpKernel\Controller\ControllerResolverInterface;
22
use Symfony\Component\HttpKernel\DataCollector\DataCollector as BaseDataCollector;
23
use Symfony\Component\Routing\Route;
24
use Symfony\Component\Routing\RouterInterface;
25
26
class DataCollector extends BaseDataCollector implements EventSubscriberInterface
27
{
28
    use ControllerAnnotationExtractorTrait;
29
30
    public const COLLECTOR_NAME = 'spiechu_symfony_commons.data_collector';
31
32
    /**
33
     * @var RouterInterface
34
     */
35
    protected $router;
36
37
    /**
38
     * @var Reader
39
     */
40
    protected $reader;
41
42
    /**
43
     * @var ControllerResolverInterface
44
     */
45
    protected $controllerResolver;
46
47
    /**
48
     * @var DataCollectorExtension
49
     */
50
    protected $dataCollectorExtension;
51
52
    /**
53
     * @param RouterInterface             $router
54
     * @param Reader                      $reader
55
     * @param ControllerResolverInterface $controllerResolver
56
     * @param DataCollectorExtension      $dataCollectorExtension
57
     */
58 13
    public function __construct(
59
        RouterInterface $router,
60
        Reader $reader,
61
        ControllerResolverInterface $controllerResolver,
62
        DataCollectorExtension $dataCollectorExtension
63
    ) {
64 13
        $this->router = $router;
65 13
        $this->reader = $reader;
66 13
        $this->controllerResolver = $controllerResolver;
67 13
        $this->dataCollectorExtension = $dataCollectorExtension;
68 13
    }
69
70
    /**
71
     * {@inheritdoc}
72
     */
73 13
    public function collect(Request $request, Response $response, \Exception $exception = null): void
74
    {
75 13
        $this->data['known_route_response_schemas'] = $request->attributes->has(RequestSchemaValidatorListener::ATTRIBUTE_RESPONSE_SCHEMAS)
76 2
            ? $request->attributes->get(RequestSchemaValidatorListener::ATTRIBUTE_RESPONSE_SCHEMAS)
77 11
            : null;
78
79 13
        $this->data['get_method_override'] = $request->attributes->has(GetMethodOverrideListener::ATTRIBUTE_REQUEST_GET_METHOD_OVERRIDE)
80 1
            ? $request->attributes->get(GetMethodOverrideListener::ATTRIBUTE_REQUEST_GET_METHOD_OVERRIDE)
81 13
            : null;
82
83 13
        $this->extractRoutesData();
84 13
    }
85
86
    /**
87
     * {@inheritdoc}
88
     */
89 13
    public function getName(): string
90
    {
91 13
        return static::COLLECTOR_NAME;
92
    }
93
94
    /**
95
     * Forward compatibility with Symfony 3.4.
96
     */
97
    public function reset(): void
98
    {
99
        $this->data = [];
100
    }
101
102
    /**
103
     * @return array
104
     */
105
    public function getGlobalResponseSchemas(): array
106
    {
107
        return $this->data['global_response_schemas'];
108
    }
109
110
    /**
111
     * {@inheritdoc}
112
     */
113 7
    public static function getSubscribedEvents(): array
114
    {
115
        return [
116 7
            ResponseSchemaCheckEvents::CHECK_RESULT => ['onCheckResult', 100],
117 7
            ApiVersionEvents::API_VERSION_SET => ['onApiVersionSet', 100],
118
        ];
119
    }
120
121
    /**
122
     * @param CheckResult $checkResult
123
     */
124 2
    public function onCheckResult(CheckResult $checkResult): void
125
    {
126 2
        $this->data['validation_result'] = $checkResult->getValidationResult();
127 2
    }
128
129
    /**
130
     * @param ApiVersionSetEvent $apiVersionSetEvent
131
     */
132 9
    public function onApiVersionSet(ApiVersionSetEvent $apiVersionSetEvent): void
133
    {
134 9
        $this->data['api_version_set'] = $apiVersionSetEvent->getApiVersion();
135 9
    }
136
137
    /**
138
     * @return array
139
     */
140 9
    public function getKnownRouteResponseSchemas(): array
141
    {
142 9
        return empty($this->data['known_route_response_schemas']) ? [] : $this->data['known_route_response_schemas'];
143
    }
144
145
    /**
146
     * @return int
147
     */
148 9
    public function getKnownRouteResponseSchemaNumber(): int
149
    {
150 9
        $counter = 0;
151
152 9
        foreach ($this->getKnownRouteResponseSchemas() as $format) {
153
            $counter += \count($format);
154
        }
155
156 9
        return $counter;
157
    }
158
159
    /**
160
     * @return int
161
     */
162 9
    public function getAllPotentialErrorsCount(): int
163
    {
164 9
        return \count($this->getValidationErrors()) + $this->getGlobalNonExistingSchemaFiles();
165
    }
166
167
    /**
168
     * @return bool
169
     */
170 11
    public function responseWasChecked(): bool
171
    {
172 11
        return array_key_exists('validation_result', $this->data);
173
    }
174
175
    /**
176
     * @return bool
177
     */
178 9
    public function apiVersionWasSet(): bool
179
    {
180 9
        return array_key_exists('api_version_set', $this->data);
181
    }
182
183
    /**
184
     * @return null|string
185
     */
186 9
    public function getApiVersion(): ?string
187
    {
188 9
        return $this->apiVersionWasSet() ? $this->data['api_version_set'] : null;
189
    }
190
191
    /**
192
     * @return ValidationViolation[]
193
     */
194 9
    public function getValidationErrors(): array
195
    {
196 9
        if (!$this->responseWasChecked()) {
197 9
            return [];
198
        }
199
200
        return $this->data['validation_result']->getViolations();
201
    }
202
203
    /**
204
     * @return bool
205
     */
206
    public function isGetMethodWasOverridden(): bool
207
    {
208
        return empty($this->data['get_method_override']) ? false : true;
209
    }
210
211
    /**
212
     * @return null|string
213
     */
214
    public function getGetMethodOverriddenTo(): ?string
215
    {
216
        return $this->isGetMethodWasOverridden() ? $this->data['get_method_override'] : null;
217
    }
218
219 13
    protected function extractRoutesData(): void
220
    {
221 13
        $this->data['global_response_schemas'] = [];
222 13
        $this->data['global_non_existing_schema_files'] = 0;
223
224
        /** @var Route $route */
225
        /** @var string $controllerDefinition */
226
        /** @var ResponseSchemaValidator $responseSchemaValidator */
227 13
        foreach ($this->getRouteCollectionGenerator() as $name => [$route, $controllerDefinition, $responseSchemaValidator]) {
228 12
            $annotationSchemas = $responseSchemaValidator->getSchemas();
0 ignored issues
show
Bug introduced by
The variable $responseSchemaValidator does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
229
230 12
            $this->data['global_response_schemas'][] = [
231 12
                'path' => $route->getPath(),
0 ignored issues
show
Bug introduced by
The variable $route does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
232 12
                'name' => $name,
233 12
                'controller' => $controllerDefinition,
0 ignored issues
show
Bug introduced by
The variable $controllerDefinition does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
234 12
                'response_schemas' => $annotationSchemas,
235
            ];
236
237 12
            foreach ($annotationSchemas as $schemas) {
238 12
                foreach ($schemas as $schema) {
239 12
                    if (!$this->dataCollectorExtension->schemaFileExists($schema)) {
240 12
                        ++$this->data['global_non_existing_schema_files'];
241
                    }
242
                }
243
            }
244
        }
245 13
    }
246
247
    /**
248
     * @throws \Exception
249
     *
250
     * @return \Generator string $name => [Route $route, string $controllerDefinition, ResponseSchemaValidator $methodAnnotation]
251
     */
252 13
    protected function getRouteCollectionGenerator(): \Generator
253
    {
254 13
        foreach ($this->router->getRouteCollection() as $name => $route) {
255 13
            if (empty($controllerDefinition = $route->getDefault('_controller'))) {
256
                continue;
257
            }
258
259 13
            $methodAnnotation = $this->extractControllerResponseValidator($controllerDefinition);
260 13
            if (!$methodAnnotation instanceof ResponseSchemaValidator) {
261 13
                continue;
262
            }
263
264 12
            yield $name => [$route, $controllerDefinition, $methodAnnotation];
265
        }
266 13
    }
267
268
    /**
269
     * @param string $controllerDefinition
270
     *
271
     * @throws \Exception
272
     *
273
     * @return null|ResponseSchemaValidator
274
     */
275 13
    protected function extractControllerResponseValidator(string $controllerDefinition): ?ResponseSchemaValidator
276
    {
277 13
        $resolvedController = $this->controllerResolver->getController(new Request(
278 13
            [],
279 13
            [],
280
            [
281 13
                '_controller' => $controllerDefinition,
282
            ]
283
        ));
284
285 13
        if (!\is_callable($resolvedController)) {
286
            return null;
287
        }
288
289 13
        return $this->getMethodAnnotationFromController($resolvedController, ResponseSchemaValidator::class);
290
    }
291
292
    /**
293
     * @return int
294
     */
295 9
    protected function getGlobalNonExistingSchemaFiles(): int
296
    {
297 9
        return empty($this->data['global_non_existing_schema_files']) ? 0 : \count($this->data['global_non_existing_schema_files']);
298
    }
299
300
    /**
301
     * {@inheritdoc}
302
     */
303 13
    protected function getAnnotationReader(): Reader
304
    {
305 13
        return $this->reader;
306
    }
307
}
308