Completed
Push — master ( 7147c7...2fc22b )
by Dawid
03:18
created

DataCollector::getRouteCollectionGenerator()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 13
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 4.0312

Importance

Changes 0
Metric Value
cc 4
eloc 7
nc 4
nop 0
dl 0
loc 13
ccs 7
cts 8
cp 0.875
crap 4.0312
rs 9.2
c 0
b 0
f 0
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 17
    public function __construct(
59
        RouterInterface $router,
60
        Reader $reader,
61
        ControllerResolverInterface $controllerResolver,
62
        DataCollectorExtension $dataCollectorExtension
63
    ) {
64 17
        $this->router = $router;
65 17
        $this->reader = $reader;
66 17
        $this->controllerResolver = $controllerResolver;
67 17
        $this->dataCollectorExtension = $dataCollectorExtension;
68 17
    }
69
70
    /**
71
     * {@inheritdoc}
72
     */
73 15
    public function collect(Request $request, Response $response, \Exception $exception = null): void
74
    {
75 15
        $this->data['known_route_response_schemas'] = $request->attributes->has(RequestSchemaValidatorListener::ATTRIBUTE_RESPONSE_SCHEMAS)
76 3
            ? $request->attributes->get(RequestSchemaValidatorListener::ATTRIBUTE_RESPONSE_SCHEMAS)
77 12
            : null;
78
79 15
        $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 15
            : null;
82
83 15
        $this->extractRoutesData();
84 15
    }
85
86
    /**
87
     * {@inheritdoc}
88
     */
89 17
    public function getName(): string
90
    {
91 17
        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 8
    public static function getSubscribedEvents(): array
114
    {
115
        return [
116 8
            ResponseSchemaCheckEvents::CHECK_RESULT => ['onCheckResult', 100],
117 8
            ApiVersionEvents::API_VERSION_SET => ['onApiVersionSet', 100],
118
        ];
119
    }
120
121
    /**
122
     * @param CheckResult $checkResult
123
     */
124 5
    public function onCheckResult(CheckResult $checkResult): void
125
    {
126 5
        $this->data['validation_result'] = $checkResult->getValidationResult();
127 5
    }
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 10
    public function getKnownRouteResponseSchemas(): array
141
    {
142 10
        return empty($this->data['known_route_response_schemas']) ? [] : $this->data['known_route_response_schemas'];
143
    }
144
145
    /**
146
     * @return int
147
     */
148 10
    public function getKnownRouteResponseSchemaNumber(): int
149
    {
150 10
        $counter = 0;
151
152 10
        foreach ($this->getKnownRouteResponseSchemas() as $format) {
153
            $counter += \count($format);
154
        }
155
156 10
        return $counter;
157
    }
158
159
    /**
160
     * @return int
161
     */
162 10
    public function getAllPotentialErrorsCount(): int
163
    {
164 10
        return \count($this->getValidationErrors()) + $this->getGlobalNonExistingSchemaFiles();
165
    }
166
167
    /**
168
     * @return bool
169
     */
170 12
    public function responseWasChecked(): bool
171
    {
172 12
        return array_key_exists('validation_result', $this->data);
173
    }
174
175
    /**
176
     * @return bool
177
     */
178 10
    public function apiVersionWasSet(): bool
179
    {
180 10
        return array_key_exists('api_version_set', $this->data);
181
    }
182
183
    /**
184
     * @return null|string
185
     */
186 10
    public function getApiVersion(): ?string
187
    {
188 10
        return $this->apiVersionWasSet() ? $this->data['api_version_set'] : null;
189
    }
190
191
    /**
192
     * @return ValidationViolation[]
193
     */
194 10
    public function getValidationErrors(): array
195
    {
196 10
        if (!$this->responseWasChecked()) {
197 10
            return [];
198
        }
199
200
        return $this->data['validation_result']->getViolations();
201
    }
202
203
    /**
204
     * @return bool
205
     */
206 1
    public function isGetMethodWasOverridden(): bool
207
    {
208 1
        return empty($this->data['get_method_override']) ? false : true;
209
    }
210
211
    /**
212
     * @return null|string
213
     */
214 1
    public function getGetMethodOverriddenTo(): ?string
215
    {
216 1
        return $this->isGetMethodWasOverridden() ? $this->data['get_method_override'] : null;
217
    }
218
219 15
    protected function extractRoutesData(): void
220
    {
221 15
        $this->data['global_response_schemas'] = [];
222 15
        $this->data['global_non_existing_schema_files'] = 0;
223
224
        /** @var Route $route */
225
        /** @var string $controllerDefinition */
226
        /** @var ResponseSchemaValidator $responseSchemaValidator */
227 15
        foreach ($this->getRouteCollectionGenerator() as $name => [$route, $controllerDefinition, $responseSchemaValidator]) {
228 13
            $annotationSchemas = $responseSchemaValidator->getSchemas();
229
230 13
            $this->data['global_response_schemas'][] = [
231 13
                'path' => $route->getPath(),
232 13
                'name' => $name,
233 13
                'controller' => $controllerDefinition,
234 13
                'response_schemas' => $annotationSchemas,
235
            ];
236
237 13
            foreach ($annotationSchemas as $schemas) {
238 13
                foreach ($schemas as $schema) {
239 13
                    if (!$this->dataCollectorExtension->schemaFileExists($schema)) {
240 13
                        ++$this->data['global_non_existing_schema_files'];
241
                    }
242
                }
243
            }
244
        }
245 15
    }
246
247
    /**
248
     * @throws \Exception
249
     *
250
     * @return \Generator string $name => [Route $route, string $controllerDefinition, ResponseSchemaValidator $methodAnnotation]
251
     */
252 15
    protected function getRouteCollectionGenerator(): \Generator
253
    {
254 15
        foreach ($this->router->getRouteCollection() as $name => $route) {
255 15
            if (empty($controllerDefinition = $route->getDefault('_controller'))) {
256
                continue;
257
            }
258
259 15
            $methodAnnotation = $this->extractControllerResponseValidator($controllerDefinition);
260 15
            if (!$methodAnnotation instanceof ResponseSchemaValidator) {
261 15
                continue;
262
            }
263
264 13
            yield $name => [$route, $controllerDefinition, $methodAnnotation];
265
        }
266 15
    }
267
268
    /**
269
     * @param string $controllerDefinition
270
     *
271
     * @throws \Exception
272
     *
273
     * @return null|ResponseSchemaValidator
274
     */
275 15
    protected function extractControllerResponseValidator(string $controllerDefinition): ?ResponseSchemaValidator
276
    {
277 15
        $resolvedController = $this->controllerResolver->getController(new Request(
278 15
            [],
279 15
            [],
280
            [
281 15
                '_controller' => $controllerDefinition,
282
            ]
283
        ));
284
285 15
        if (!\is_callable($resolvedController)) {
286
            return null;
287
        }
288
289 15
        return $this->getMethodAnnotationFromController($resolvedController, ResponseSchemaValidator::class);
0 ignored issues
show
Bug introduced by
It seems like $resolvedController can also be of type false; however, parameter $controller of Spiechu\SymfonyCommonsBu...otationFromController() does only seem to accept callable, maybe add an additional type check? ( Ignorable by Annotation )

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

289
        return $this->getMethodAnnotationFromController(/** @scrutinizer ignore-type */ $resolvedController, ResponseSchemaValidator::class);
Loading history...
290
    }
291
292
    /**
293
     * @return int
294
     */
295 10
    protected function getGlobalNonExistingSchemaFiles(): int
296
    {
297 10
        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 15
    protected function getAnnotationReader(): Reader
304
    {
305 15
        return $this->reader;
306
    }
307
}
308